Merge branch 'master' into 0.6.5-dm-plugin
Conflicts: container.go runtime.go
This commit is contained in:
commit
165d1bdbc0
74 changed files with 3140 additions and 801 deletions
3
AUTHORS
3
AUTHORS
|
@ -69,12 +69,14 @@ Gabriel Monroy <gabriel@opdemand.com>
|
|||
Gareth Rushgrove <gareth@morethanseven.net>
|
||||
Greg Thornton <xdissent@me.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
|
||||
Gurjeet Singh <gurjeet@singh.im>
|
||||
Guruprasad <lgp171188@gmail.com>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
Hector Castro <hectcastro@gmail.com>
|
||||
Hunter Blanks <hunter@twilio.com>
|
||||
Isao Jonas <isao.jonas@gmail.com>
|
||||
James Carr <james.r.carr@gmail.com>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
Jason McVetta <jason.mcvetta@gmail.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
|
@ -140,6 +142,7 @@ odk- <github@odkurzacz.org>
|
|||
Pascal Borreli <pascal@borreli.com>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Paul Nasrat <pnasrat@gmail.com>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
pysqz <randomq@126.com>
|
||||
|
|
85
CHANGELOG.md
85
CHANGELOG.md
|
@ -1,43 +1,78 @@
|
|||
# Changelog
|
||||
|
||||
## 0.6.6 (2013-11-06)
|
||||
|
||||
#### Runtime
|
||||
|
||||
* Ensure container name on register
|
||||
* Fix regression in /etc/hosts
|
||||
+ Add lock around write operations in graph
|
||||
* Check if port is valid
|
||||
* Fix restart runtime error with ghost container networking
|
||||
+ Added some more colors and animals to increase the pool of generated names
|
||||
* Fix issues in docker inspect
|
||||
+ Escape apparmor confinement
|
||||
+ Set environment variables using a file.
|
||||
* Prevent docker insert to erase something
|
||||
+ Prevent DNS server conflicts in CreateBridgeIface
|
||||
+ Validate bind mounts on the server side
|
||||
+ Use parent image config in docker build
|
||||
* Fix regression in /etc/hosts
|
||||
|
||||
#### Client
|
||||
|
||||
+ Add -P flag to publish all exposed ports
|
||||
+ Add -notrunc and -q flags to docker history
|
||||
* Fix docker commit, tag and import usage
|
||||
+ Add stars, trusted builds and library flags in docker search
|
||||
* Fix docker logs with tty
|
||||
|
||||
#### RemoteAPI
|
||||
|
||||
* Make /events API send headers immediately
|
||||
* Do not split last column docker top
|
||||
+ Add size to history
|
||||
|
||||
#### Other
|
||||
|
||||
+ Contrib: Desktop integration. Firefox usecase.
|
||||
+ Dockerfile: bump to go1.2rc3
|
||||
|
||||
## 0.6.5 (2013-10-29)
|
||||
|
||||
#### Runtime
|
||||
|
||||
+ Runtime: Containers can now be named
|
||||
+ Runtime: Containers can now be linked together for service discovery
|
||||
+ Runtime: 'run -a', 'start -a' and 'attach' can forward signals to the container for better integration with process supervisors
|
||||
+ Runtime: Automatically start crashed containers after a reboot
|
||||
+ Runtime: Expose IP, port, and proto as separate environment vars for container links
|
||||
* Runtime: Allow ports to be published to specific ips
|
||||
* Runtime: Prohibit inter-container communication by default
|
||||
- Runtime: Ignore ErrClosedPipe for stdin in Container.Attach
|
||||
- Runtime: Fix untag during removal of images
|
||||
- Runtime: Remove unused field kernelVersion
|
||||
* Runtime: Fix issue when mounting subdirectories of /mnt in container
|
||||
* Runtime: Check return value of syscall.Chdir when changing working directory inside dockerinit
|
||||
|
||||
#### Documentation
|
||||
|
||||
* Documentation: Fix the flags for nc in example
|
||||
+ Containers can now be named
|
||||
+ Containers can now be linked together for service discovery
|
||||
+ 'run -a', 'start -a' and 'attach' can forward signals to the container for better integration with process supervisors
|
||||
+ Automatically start crashed containers after a reboot
|
||||
+ Expose IP, port, and proto as separate environment vars for container links
|
||||
* Allow ports to be published to specific ips
|
||||
* Prohibit inter-container communication by default
|
||||
- Ignore ErrClosedPipe for stdin in Container.Attach
|
||||
- Remove unused field kernelVersion
|
||||
* Fix issue when mounting subdirectories of /mnt in container
|
||||
- Fix untag during removal of images
|
||||
* Check return value of syscall.Chdir when changing working directory inside dockerinit
|
||||
|
||||
#### Client
|
||||
|
||||
- Client: Only pass stdin to hijack when needed to avoid closed pipe errors
|
||||
* Client: Use less reflection in command-line method invocation
|
||||
- Client: Monitor the tty size after starting the container, not prior
|
||||
- Client: Remove useless os.Exit() calls after log.Fatal
|
||||
- Only pass stdin to hijack when needed to avoid closed pipe errors
|
||||
* Use less reflection in command-line method invocation
|
||||
- Monitor the tty size after starting the container, not prior
|
||||
- Remove useless os.Exit() calls after log.Fatal
|
||||
|
||||
#### Hack
|
||||
|
||||
- Hack: Update install.sh with $sh_c to get sudo/su for modprobe
|
||||
* Hack: Update all the mkimage scripts to use --numeric-owner as a tar argument
|
||||
* Hack: Update hack/release.sh process to automatically invoke hack/make.sh and bail on build and test issues
|
||||
+ Hack: Add initial init scripts library and a safer Ubuntu packaging script that works for Debian
|
||||
* Hack: Add -p option to invoke debootstrap with http_proxy
|
||||
+ Add initial init scripts library and a safer Ubuntu packaging script that works for Debian
|
||||
* Add -p option to invoke debootstrap with http_proxy
|
||||
- Update install.sh with $sh_c to get sudo/su for modprobe
|
||||
* Update all the mkimage scripts to use --numeric-owner as a tar argument
|
||||
* Update hack/release.sh process to automatically invoke hack/make.sh and bail on build and test issues
|
||||
|
||||
#### Other
|
||||
|
||||
* Documentation: Fix the flags for nc in example
|
||||
* Testing: Remove warnings and prevent mount issues
|
||||
- Testing: Change logic for tty resize to avoid warning in tests
|
||||
- Builder: Fix race condition in docker build with verbose output
|
||||
|
|
14
Dockerfile
14
Dockerfile
|
@ -4,24 +4,24 @@
|
|||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker .
|
||||
# # Apparmor messes with privileged mode: disable it
|
||||
# /etc/init.d/apparmor stop ; /etc/init.d/apparmor teardown
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -lxc-conf=lxc.aa_profile=unconfined -i -t docker bash
|
||||
#
|
||||
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -i -t docker bash
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run -privileged -lxc-conf=lxc.aa_profile=unconfined docker hack/make.sh test
|
||||
# docker run -privileged docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run -privileged -lxc-conf=lxc.aa_profile=unconfined \
|
||||
# docker run -privileged \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||
# the case. Therefore, you don't have to disable it anymore.
|
||||
#
|
||||
|
||||
docker-version 0.6.1
|
||||
from ubuntu:12.04
|
||||
|
@ -36,7 +36,7 @@ run apt-get install -y -q mercurial
|
|||
run apt-get install -y -q build-essential libsqlite3-dev
|
||||
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc2.src.tar.gz | tar -v -C /usr/local -xz
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc3.src.tar.gz | tar -v -C /usr/local -xz
|
||||
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
|
||||
run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.6.5-dev
|
||||
0.6.6-dev
|
||||
|
|
19
api.go
19
api.go
|
@ -23,7 +23,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
APIVERSION = 1.6
|
||||
APIVERSION = 1.7
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTHTTPPORT = 4243
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
|
@ -191,10 +191,24 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.
|
|||
return err
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
if version < 1.7 {
|
||||
outs2 := []APIImagesOld{}
|
||||
for _, ctnr := range outs {
|
||||
outs2 = append(outs2, ctnr.ToLegacy()...)
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs2)
|
||||
} else {
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
}
|
||||
|
||||
func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version > 1.6 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return fmt.Errorf("This is now implemented in the client.")
|
||||
}
|
||||
|
||||
if err := srv.ImagesViz(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -236,6 +250,7 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
wf := utils.NewWriteFlusher(w)
|
||||
wf.Flush()
|
||||
if since != 0 {
|
||||
// If since, send previous events that happened after the timestamp
|
||||
for _, event := range srv.events {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package docker
|
||||
|
||||
import "strings"
|
||||
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
|
@ -9,6 +11,15 @@ type APIHistory struct {
|
|||
}
|
||||
|
||||
type APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIImagesOld struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
|
@ -17,6 +28,26 @@ type APIImages struct {
|
|||
VirtualSize int64
|
||||
}
|
||||
|
||||
func (self *APIImages) ToLegacy() []APIImagesOld {
|
||||
|
||||
outs := []APIImagesOld{}
|
||||
for _, repotag := range self.RepoTags {
|
||||
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
|
||||
outs = append(outs, APIImagesOld{
|
||||
ID: self.ID,
|
||||
Repository: components[0],
|
||||
Tag: components[1],
|
||||
Created: self.Created,
|
||||
Size: self.Size,
|
||||
VirtualSize: self.VirtualSize,
|
||||
})
|
||||
}
|
||||
|
||||
return outs
|
||||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
|
@ -78,11 +109,6 @@ type APIContainersOld struct {
|
|||
SizeRootFs int64
|
||||
}
|
||||
|
||||
type APISearch struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
|
52
api_test.go
52
api_test.go
|
@ -184,7 +184,7 @@ func TestGetImagesJSON(t *testing.T) {
|
|||
|
||||
found := false
|
||||
for _, img := range images {
|
||||
if img.Repository == unitTestImageName {
|
||||
if strings.Contains(img.RepoTags[0], unitTestImageName) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
@ -275,31 +275,6 @@ func TestGetImagesJSON(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetImagesViz(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
if err := getImagesViz(srv, APIVERSION, r, nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if r.Code != http.StatusOK {
|
||||
t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(r.Body)
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if line != "digraph docker {\n" {
|
||||
t.Errorf("Expected digraph docker {\n, %s found", line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesHistory(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
@ -499,8 +474,7 @@ func TestGetContainersTop(t *testing.T) {
|
|||
container.WaitTimeout(2 * time.Second)
|
||||
}()
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -704,8 +678,7 @@ func TestPostContainersKill(t *testing.T) {
|
|||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -747,8 +720,7 @@ func TestPostContainersRestart(t *testing.T) {
|
|||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -855,8 +827,7 @@ func TestPostContainersStop(t *testing.T) {
|
|||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -903,8 +874,7 @@ func TestPostContainersWait(t *testing.T) {
|
|||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -947,8 +917,7 @@ func TestPostContainersAttach(t *testing.T) {
|
|||
defer runtime.Destroy(container)
|
||||
|
||||
// Start the process
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -1037,8 +1006,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
|
|||
defer runtime.Destroy(container)
|
||||
|
||||
// Start the process
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -1233,7 +1201,7 @@ func TestDeleteImages(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+1 {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
|
||||
}
|
||||
|
||||
|
@ -1272,7 +1240,7 @@ func TestDeleteImages(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages) {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags) {
|
||||
t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,9 @@ func (b *buildFile) CmdFrom(name string) error {
|
|||
}
|
||||
b.image = image.ID
|
||||
b.config = &Config{}
|
||||
if image.Config != nil {
|
||||
b.config = image.Config
|
||||
}
|
||||
if b.config.Env == nil || len(b.config.Env) == 0 {
|
||||
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
|
||||
}
|
||||
|
@ -388,8 +391,7 @@ func (b *buildFile) run() (string, error) {
|
|||
}
|
||||
|
||||
//start the container
|
||||
hostConfig := &HostConfig{}
|
||||
if err := c.Start(hostConfig); err != nil {
|
||||
if err := c.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
|
|
@ -542,3 +542,39 @@ func TestBuildADDFileNotFound(t *testing.T) {
|
|||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInheritance(t *testing.T) {
|
||||
runtime, err := newTestRuntime("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
expose 4243
|
||||
`,
|
||||
nil, nil}, t, srv, true)
|
||||
|
||||
img2 := buildImage(testContextTemplate{fmt.Sprintf(`
|
||||
from %s
|
||||
entrypoint ["/bin/echo"]
|
||||
`, img.ID),
|
||||
nil, nil}, t, srv, true)
|
||||
|
||||
// from child
|
||||
if img2.Config.Entrypoint[0] != "/bin/echo" {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// from parent
|
||||
if img.Config.PortSpecs[0] != "4243" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
237
commands.go
237
commands.go
|
@ -655,7 +655,11 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||
if err != nil {
|
||||
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "No such image or container: %s\n", name)
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.err, "%s", err)
|
||||
}
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
|
@ -668,9 +672,11 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||
}
|
||||
indented.WriteString(",")
|
||||
}
|
||||
// Remove trailling ','
|
||||
indented.Truncate(indented.Len() - 1)
|
||||
|
||||
if indented.Len() > 0 {
|
||||
// Remove trailing ','
|
||||
indented.Truncate(indented.Len() - 1)
|
||||
}
|
||||
fmt.Fprintf(cli.out, "[")
|
||||
if _, err := io.Copy(cli.out, indented); err != nil {
|
||||
return err
|
||||
|
@ -683,7 +689,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||
}
|
||||
|
||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
cmd := Subcmd("top", "CONTAINER", "Lookup the running processes of a container")
|
||||
cmd := Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -814,7 +820,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
|||
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY\tSIZE")
|
||||
fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE")
|
||||
}
|
||||
|
||||
for _, out := range outs {
|
||||
|
@ -898,7 +904,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
|
|||
}
|
||||
|
||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
|
||||
cmd := Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -907,7 +913,8 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
src, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
|
||||
src := cmd.Arg(0)
|
||||
repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
|
||||
v := url.Values{}
|
||||
v.Set("repo", repository)
|
||||
v.Set("tag", tag)
|
||||
|
@ -1050,6 +1057,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
all := cmd.Bool("a", false, "show all images")
|
||||
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
|
||||
flViz := cmd.Bool("viz", false, "output graph in graphviz format")
|
||||
flTree := cmd.Bool("tree", false, "output graph in tree format")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -1060,11 +1068,77 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
}
|
||||
|
||||
if *flViz {
|
||||
body, _, err := cli.call("GET", "/images/viz", false)
|
||||
body, _, err := cli.call("GET", "/images/json?all=1", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "%s", body)
|
||||
|
||||
var outs []APIImages
|
||||
err = json.Unmarshal(body, &outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "digraph docker {\n")
|
||||
|
||||
for _, image := range outs {
|
||||
if image.ParentId == "" {
|
||||
fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", utils.TruncateID(image.ID))
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", utils.TruncateID(image.ParentId), utils.TruncateID(image.ID))
|
||||
}
|
||||
if image.RepoTags[0] != "<none>:<none>" {
|
||||
fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", utils.TruncateID(image.ID), utils.TruncateID(image.ID), strings.Join(image.RepoTags, "\\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, " base [style=invisible]\n}\n")
|
||||
} else if *flTree {
|
||||
body, _, err := cli.call("GET", "/images/json?all=1", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var outs []APIImages
|
||||
err = json.Unmarshal(body, &outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var startImageArg = cmd.Arg(0)
|
||||
var startImage APIImages
|
||||
|
||||
var roots []APIImages
|
||||
var byParent = make(map[string][]APIImages)
|
||||
for _, image := range outs {
|
||||
if image.ParentId == "" {
|
||||
roots = append(roots, image)
|
||||
} else {
|
||||
if children, exists := byParent[image.ParentId]; exists {
|
||||
byParent[image.ParentId] = append(children, image)
|
||||
} else {
|
||||
byParent[image.ParentId] = []APIImages{image}
|
||||
}
|
||||
}
|
||||
|
||||
if startImageArg != "" {
|
||||
if startImageArg == image.ID || startImageArg == utils.TruncateID(image.ID) {
|
||||
startImage = image
|
||||
}
|
||||
|
||||
for _, repotag := range image.RepoTags {
|
||||
if repotag == startImageArg {
|
||||
startImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if startImageArg != "" {
|
||||
WalkTree(cli, noTrunc, []APIImages{startImage}, byParent, "")
|
||||
} else {
|
||||
WalkTree(cli, noTrunc, roots, byParent, "")
|
||||
}
|
||||
} else {
|
||||
v := url.Values{}
|
||||
if cmd.NArg() == 1 {
|
||||
|
@ -1087,30 +1161,32 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE")
|
||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE")
|
||||
}
|
||||
|
||||
var repo string
|
||||
var tag string
|
||||
for _, out := range outs {
|
||||
if out.Repository == "" {
|
||||
out.Repository = "<none>"
|
||||
}
|
||||
if out.Tag == "" {
|
||||
out.Tag = "<none>"
|
||||
}
|
||||
for _, repotag := range out.RepoTags {
|
||||
|
||||
if !*noTrunc {
|
||||
out.ID = utils.TruncateID(out.ID)
|
||||
}
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
repo = components[0]
|
||||
tag = components[1]
|
||||
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
if out.VirtualSize > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
||||
if !*noTrunc {
|
||||
out.ID = utils.TruncateID(out.ID)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
if out.VirtualSize > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(w, out.ID)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(w, out.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1121,6 +1197,48 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func WalkTree(cli *DockerCli, noTrunc *bool, images []APIImages, byParent map[string][]APIImages, prefix string) {
|
||||
if len(images) > 1 {
|
||||
length := len(images)
|
||||
for index, image := range images {
|
||||
if index+1 == length {
|
||||
PrintTreeNode(cli, noTrunc, image, prefix+"└─")
|
||||
if subimages, exists := byParent[image.ID]; exists {
|
||||
WalkTree(cli, noTrunc, subimages, byParent, prefix+" ")
|
||||
}
|
||||
} else {
|
||||
PrintTreeNode(cli, noTrunc, image, prefix+"|─")
|
||||
if subimages, exists := byParent[image.ID]; exists {
|
||||
WalkTree(cli, noTrunc, subimages, byParent, prefix+"| ")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, image := range images {
|
||||
PrintTreeNode(cli, noTrunc, image, prefix+"└─")
|
||||
if subimages, exists := byParent[image.ID]; exists {
|
||||
WalkTree(cli, noTrunc, subimages, byParent, prefix+" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintTreeNode(cli *DockerCli, noTrunc *bool, image APIImages, prefix string) {
|
||||
var imageID string
|
||||
if *noTrunc {
|
||||
imageID = image.ID
|
||||
} else {
|
||||
imageID = utils.TruncateID(image.ID)
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "%s%s Size: %s (virtual %s)", prefix, imageID, utils.HumanSize(image.Size), utils.HumanSize(image.VirtualSize))
|
||||
if image.RepoTags[0] != "<none>:<none>" {
|
||||
fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ","))
|
||||
} else {
|
||||
fmt.Fprint(cli.out, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func displayablePorts(ports []APIPort) string {
|
||||
result := []string{}
|
||||
for _, port := range ports {
|
||||
|
@ -1180,7 +1298,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||
}
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
||||
fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
||||
if *size {
|
||||
fmt.Fprintln(w, "\tSIZE")
|
||||
} else {
|
||||
|
@ -1224,14 +1342,16 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||
}
|
||||
|
||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
|
||||
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
||||
flComment := cmd.String("m", "", "Commit message")
|
||||
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
|
||||
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
name, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
|
||||
name := cmd.Arg(0)
|
||||
repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
|
||||
|
||||
if name == "" {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
|
@ -1341,8 +1461,18 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
name := cmd.Arg(0)
|
||||
body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil {
|
||||
container := &Container{}
|
||||
err = json.Unmarshal(body, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", container.Config.Tty, nil, cli.out, cli.err, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -1404,8 +1534,10 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|||
}
|
||||
|
||||
func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
cmd := Subcmd("search", "NAME", "Search the docker index for images")
|
||||
cmd := Subcmd("search", "TERM", "Search the docker index for images")
|
||||
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
|
||||
trusted := cmd.Bool("trusted", false, "Only show trusted builds")
|
||||
stars := cmd.Int("stars", 0, "Only displays with at least xxx stars")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -1421,27 +1553,32 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
outs := []APISearch{}
|
||||
outs := []registry.SearchResult{}
|
||||
err = json.Unmarshal(body, &outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0))
|
||||
w := tabwriter.NewWriter(cli.out, 33, 1, 3, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\n")
|
||||
_, width := cli.getTtySize()
|
||||
if width == 0 {
|
||||
width = 45
|
||||
} else {
|
||||
width = width - 33 //remote the first column
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tTRUSTED\n")
|
||||
for _, out := range outs {
|
||||
if (*trusted && !out.IsTrusted) || (*stars > out.StarCount) {
|
||||
continue
|
||||
}
|
||||
desc := strings.Replace(out.Description, "\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\r", " ", -1)
|
||||
if !*noTrunc && len(desc) > width {
|
||||
desc = utils.Trunc(desc, width-3) + "..."
|
||||
if !*noTrunc && len(desc) > 45 {
|
||||
desc = utils.Trunc(desc, 42) + "..."
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\n", out.Name, desc)
|
||||
fmt.Fprintf(w, "%s\t%s\t%d\t", out.Name, desc, out.StarCount)
|
||||
if out.IsOfficial {
|
||||
fmt.Fprint(w, "[OK]")
|
||||
|
||||
}
|
||||
fmt.Fprint(w, "\t")
|
||||
if out.IsTrusted {
|
||||
fmt.Fprint(w, "[OK]")
|
||||
}
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
|
@ -1509,7 +1646,7 @@ func (opts PathOpts) Set(val string) error {
|
|||
}
|
||||
|
||||
func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
|
||||
cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
|
||||
force := cmd.Bool("f", false, "Force")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -1520,10 +1657,10 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
|||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("repo", cmd.Arg(1))
|
||||
if cmd.NArg() == 3 {
|
||||
v.Set("tag", cmd.Arg(2))
|
||||
}
|
||||
repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
|
||||
|
||||
v.Set("repo", repository)
|
||||
v.Set("tag", tag)
|
||||
|
||||
if *force {
|
||||
v.Set("force", "1")
|
||||
|
|
180
commands_test.go
180
commands_test.go
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -645,3 +646,182 @@ func TestRunAutoRemove(t *testing.T) {
|
|||
t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdLogs(t *testing.T) {
|
||||
cli := NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
if err := cli.CmdRun(unitTestImageID, "sh", "-c", "ls -l"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cli.CmdRun("-t", unitTestImageID, "sh", "-c", "ls -l"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cli.CmdLogs(globalRuntime.List()[0].ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour: using / as a bind mount source should throw an error
|
||||
func TestRunErrorBindMountRootSource(t *testing.T) {
|
||||
|
||||
cli := NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-v", "/:/tmp", unitTestImageID, "echo 'should fail'"); err == nil {
|
||||
t.Fatal("should have failed to run when using / as a source for the bind mount")
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
}
|
||||
|
||||
// Expected behaviour: error out when attempting to bind mount non-existing source paths
|
||||
func TestRunErrorBindNonExistingSource(t *testing.T) {
|
||||
|
||||
cli := NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-v", "/i/dont/exist:/tmp", unitTestImageID, "echo 'should fail'"); err == nil {
|
||||
t.Fatal("should have failed to run when using /i/dont/exist as a source for the bind mount")
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
}
|
||||
|
||||
func TestImagesViz(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
srv := &Server{runtime: globalRuntime}
|
||||
image := buildTestImages(t, srv)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdImages("-viz"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmdOutput := string(cmdOutputBytes)
|
||||
|
||||
regexpStrings := []string{
|
||||
"digraph docker {",
|
||||
fmt.Sprintf("base -> \"%s\" \\[style=invis]", unitTestImageIDShort),
|
||||
fmt.Sprintf("label=\"%s\\\\n%s:latest\"", unitTestImageIDShort, unitTestImageName),
|
||||
fmt.Sprintf("label=\"%s\\\\n%s:%s\"", utils.TruncateID(image.ID), "test", "latest"),
|
||||
"base \\[style=invisible]",
|
||||
}
|
||||
|
||||
compiledRegexps := []*regexp.Regexp{}
|
||||
for _, regexpString := range regexpStrings {
|
||||
regexp, err := regexp.Compile(regexpString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in regex string: ", err)
|
||||
return
|
||||
}
|
||||
compiledRegexps = append(compiledRegexps, regexp)
|
||||
}
|
||||
|
||||
for _, regexp := range compiledRegexps {
|
||||
if !regexp.MatchString(cmdOutput) {
|
||||
t.Fatalf("images -viz content '%s' did not match regexp '%s'", cmdOutput, regexp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestImagesTree(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
srv := &Server{runtime: globalRuntime}
|
||||
image := buildTestImages(t, srv)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdImages("-tree"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmdOutput := string(cmdOutputBytes)
|
||||
|
||||
regexpStrings := []string{
|
||||
fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName),
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
fmt.Sprintf(" └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
|
||||
}
|
||||
|
||||
compiledRegexps := []*regexp.Regexp{}
|
||||
for _, regexpString := range regexpStrings {
|
||||
regexp, err := regexp.Compile(regexpString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in regex string: ", err)
|
||||
return
|
||||
}
|
||||
compiledRegexps = append(compiledRegexps, regexp)
|
||||
}
|
||||
|
||||
for _, regexp := range compiledRegexps {
|
||||
if !regexp.MatchString(cmdOutput) {
|
||||
t.Fatalf("images -tree content '%s' did not match regexp '%s'", cmdOutput, regexp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func buildTestImages(t *testing.T, srv *Server) *Image {
|
||||
|
||||
var testBuilder = testContextTemplate{
|
||||
`
|
||||
from {IMAGE}
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
|
||||
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
image := buildImage(testBuilder, t, srv, true)
|
||||
|
||||
err := srv.ContainerTag(image.ID, "test", "latest", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"net"
|
||||
)
|
||||
|
||||
// FIXME: separate runtime configuration from http api configuration
|
||||
|
|
145
container.go
145
container.go
|
@ -62,11 +62,15 @@ type Container struct {
|
|||
Volumes map[string]string
|
||||
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
||||
// Easier than migrating older container configs :)
|
||||
VolumesRW map[string]bool
|
||||
VolumesRW map[string]bool
|
||||
hostConfig *HostConfig
|
||||
|
||||
activeLinks map[string]*Link
|
||||
}
|
||||
|
||||
// Note: the Config structure should hold only portable information about the container.
|
||||
// Here, "portable" means "independent from the host we are running on".
|
||||
// Non-portable information *should* appear in HostConfig.
|
||||
type Config struct {
|
||||
Hostname string
|
||||
Domainname string
|
||||
|
@ -91,15 +95,16 @@ type Config struct {
|
|||
WorkingDir string
|
||||
Entrypoint []string
|
||||
NetworkDisabled bool
|
||||
Privileged bool
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
Privileged bool
|
||||
PortBindings map[Port][]PortBinding
|
||||
Links []string
|
||||
PublishAllPorts bool
|
||||
}
|
||||
|
||||
type BindMap struct {
|
||||
|
@ -171,6 +176,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
||||
cmd.String("name", "", "Assign a name to the container")
|
||||
flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
|
@ -247,6 +253,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
for bind := range flVolumes {
|
||||
arr := strings.Split(bind, ":")
|
||||
if len(arr) > 1 {
|
||||
if arr[0] == "/" {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
|
||||
}
|
||||
dstDir := arr[1]
|
||||
flVolumes[dstDir] = struct{}{}
|
||||
binds = append(binds, bind)
|
||||
|
@ -300,7 +309,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: *flHostname,
|
||||
Hostname: hostname,
|
||||
Domainname: domainname,
|
||||
PortSpecs: nil, // Deprecated
|
||||
ExposedPorts: ports,
|
||||
|
@ -320,7 +329,6 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
Volumes: flVolumes,
|
||||
VolumesFrom: strings.Join(flVolumesFrom, ","),
|
||||
Entrypoint: entrypoint,
|
||||
Privileged: *flPrivileged,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
|
||||
|
@ -328,8 +336,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
Privileged: *flPrivileged,
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks,
|
||||
PublishAllPorts: *flPublishAll,
|
||||
}
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
||||
|
@ -385,7 +395,17 @@ func (container *Container) Inject(file io.Reader, pth string) error {
|
|||
if err := container.EnsureMounted(); err != nil {
|
||||
return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err)
|
||||
}
|
||||
// FIXME: Handle permissions/already existing dest
|
||||
|
||||
// Return error if path exists
|
||||
if _, err := os.Stat(path.Join(container.rwPath(), pth)); err == nil {
|
||||
// Since err is nil, the path could be stat'd and it exists
|
||||
return fmt.Errorf("%s exists", pth)
|
||||
} else if !os.IsNotExist(err) {
|
||||
// Expect err might be that the file doesn't exist, so
|
||||
// if it's some other error, return that.
|
||||
|
||||
return err
|
||||
}
|
||||
dest, err := os.Create(path.Join(container.RootfsPath(), pth))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -414,7 +434,7 @@ func (container *Container) FromDisk() error {
|
|||
if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return container.readHostConfig()
|
||||
}
|
||||
|
||||
func (container *Container) ToDisk() (err error) {
|
||||
|
@ -422,23 +442,31 @@ func (container *Container) ToDisk() (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ioutil.WriteFile(container.jsonPath(), data, 0666)
|
||||
err = ioutil.WriteFile(container.jsonPath(), data, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return container.writeHostConfig()
|
||||
}
|
||||
|
||||
func (container *Container) ReadHostConfig() (*HostConfig, error) {
|
||||
func (container *Container) readHostConfig() error {
|
||||
container.hostConfig = &HostConfig{}
|
||||
// If the hostconfig file does not exist, do not read it.
|
||||
// (We still have to initialize container.hostConfig,
|
||||
// but that's OK, since we just did that above.)
|
||||
_, err := os.Stat(container.hostConfigPath())
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
data, err := ioutil.ReadFile(container.hostConfigPath())
|
||||
if err != nil {
|
||||
return &HostConfig{}, err
|
||||
return err
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := json.Unmarshal(data, hostConfig); err != nil {
|
||||
return &HostConfig{}, err
|
||||
}
|
||||
return hostConfig, nil
|
||||
return json.Unmarshal(data, container.hostConfig)
|
||||
}
|
||||
|
||||
func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) {
|
||||
data, err := json.Marshal(hostConfig)
|
||||
func (container *Container) writeHostConfig() (err error) {
|
||||
data, err := json.Marshal(container.hostConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -454,21 +482,13 @@ func (container *Container) generateEnvConfig(env []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) generateLXCConfig(hostConfig *HostConfig) error {
|
||||
func (container *Container) generateLXCConfig() error {
|
||||
fo, err := os.Create(container.lxcConfigPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fo.Close()
|
||||
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
|
||||
return err
|
||||
}
|
||||
if hostConfig != nil {
|
||||
if err := LxcHostConfigTemplateCompiled.Execute(fo, hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return LxcTemplateCompiled.Execute(fo, container)
|
||||
}
|
||||
|
||||
func (container *Container) startPty() error {
|
||||
|
@ -663,7 +683,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|||
})
|
||||
}
|
||||
|
||||
func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
||||
func (container *Container) Start() (err error) {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
defer func() {
|
||||
|
@ -672,10 +692,6 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|||
}
|
||||
}()
|
||||
|
||||
if hostConfig == nil { // in docker start of docker restart we want to reuse previous HostConfigFile
|
||||
hostConfig, _ = container.ReadHostConfig()
|
||||
}
|
||||
|
||||
if container.State.Running {
|
||||
return fmt.Errorf("The container %s is already running.", container.ID)
|
||||
}
|
||||
|
@ -685,7 +701,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|||
if container.runtime.networkManager.disabled {
|
||||
container.Config.NetworkDisabled = true
|
||||
} else {
|
||||
if err := container.allocateNetwork(hostConfig); err != nil {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -709,7 +725,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|||
// Define illegal container destinations
|
||||
illegalDsts := []string{"/", "."}
|
||||
|
||||
for _, bind := range hostConfig.Binds {
|
||||
for _, bind := range container.hostConfig.Binds {
|
||||
// FIXME: factorize bind parsing in parseBind
|
||||
var src, dst, mode string
|
||||
arr := strings.Split(bind, ":")
|
||||
|
@ -844,7 +860,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := container.generateLXCConfig(hostConfig); err != nil {
|
||||
if err := container.generateLXCConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -941,8 +957,11 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|||
params = append(params, "--", container.Path)
|
||||
params = append(params, container.Args...)
|
||||
|
||||
container.cmd = exec.Command("lxc-start", params...)
|
||||
|
||||
var lxcStart string = "lxc-start"
|
||||
if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor {
|
||||
lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined")
|
||||
}
|
||||
container.cmd = exec.Command(lxcStart, params...)
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
|
||||
return err
|
||||
|
@ -969,8 +988,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|||
container.waitLock = make(chan struct{})
|
||||
|
||||
container.ToDisk()
|
||||
container.SaveHostConfig(hostConfig)
|
||||
go container.monitor(hostConfig)
|
||||
go container.monitor()
|
||||
|
||||
defer utils.Debugf("Container running: %v", container.State.Running)
|
||||
// We wait for the container to be fully running.
|
||||
|
@ -1007,7 +1025,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|||
}
|
||||
|
||||
func (container *Container) Run() error {
|
||||
if err := container.Start(&HostConfig{}); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.Wait()
|
||||
|
@ -1020,8 +1038,7 @@ func (container *Container) Output() (output []byte, err error) {
|
|||
return nil, err
|
||||
}
|
||||
defer pipe.Close()
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output, err = ioutil.ReadAll(pipe)
|
||||
|
@ -1053,19 +1070,14 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
|||
return utils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
||||
func (container *Container) allocateNetwork() error {
|
||||
if container.Config.NetworkDisabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
var iface *NetworkInterface
|
||||
var err error
|
||||
if !container.State.Ghost {
|
||||
iface, err = container.runtime.networkManager.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if container.State.Ghost {
|
||||
manager := container.runtime.networkManager
|
||||
if manager.disabled {
|
||||
iface = &NetworkInterface{disabled: true}
|
||||
|
@ -1075,18 +1087,30 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
|||
Gateway: manager.bridgeNetwork.IP,
|
||||
manager: manager,
|
||||
}
|
||||
ipNum := ipToInt(iface.IPNet.IP)
|
||||
manager.ipAllocator.inUse[ipNum] = struct{}{}
|
||||
if iface != nil && iface.IPNet.IP != nil {
|
||||
ipNum := ipToInt(iface.IPNet.IP)
|
||||
manager.ipAllocator.inUse[ipNum] = struct{}{}
|
||||
} else {
|
||||
iface, err = container.runtime.networkManager.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iface, err = container.runtime.networkManager.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.PortSpecs != nil {
|
||||
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
|
||||
if err := migratePortMappings(container.Config, hostConfig); err != nil {
|
||||
if err := migratePortMappings(container.Config, container.hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
container.Config.PortSpecs = nil
|
||||
if err := container.SaveHostConfig(hostConfig); err != nil {
|
||||
if err := container.writeHostConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -1098,8 +1122,8 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
|||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
if hostConfig.PortBindings != nil {
|
||||
bindings = hostConfig.PortBindings
|
||||
if container.hostConfig.PortBindings != nil {
|
||||
bindings = container.hostConfig.PortBindings
|
||||
}
|
||||
} else {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
|
@ -1114,6 +1138,9 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
|||
|
||||
for port := range portSpecs {
|
||||
binding := bindings[port]
|
||||
if container.hostConfig.PublishAllPorts && len(binding) == 0 {
|
||||
binding = append(binding, PortBinding{})
|
||||
}
|
||||
for i := 0; i < len(binding); i++ {
|
||||
b := binding[i]
|
||||
nat, err := iface.AllocatePort(port, b)
|
||||
|
@ -1126,7 +1153,7 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
|||
}
|
||||
bindings[port] = binding
|
||||
}
|
||||
container.SaveHostConfig(hostConfig)
|
||||
container.writeHostConfig()
|
||||
|
||||
container.NetworkSettings.Ports = bindings
|
||||
container.network = iface
|
||||
|
@ -1162,7 +1189,7 @@ func (container *Container) waitLxc() error {
|
|||
}
|
||||
}
|
||||
|
||||
func (container *Container) monitor(hostConfig *HostConfig) {
|
||||
func (container *Container) monitor() {
|
||||
// Wait for the program to exit
|
||||
|
||||
// If the command does not exist, try to wait via lxc
|
||||
|
@ -1319,11 +1346,7 @@ func (container *Container) Restart(seconds int) error {
|
|||
if err := container.Stop(seconds); err != nil {
|
||||
return err
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return container.Start()
|
||||
}
|
||||
|
||||
// Wait blocks until the container stops running, then returns its exit code.
|
||||
|
|
|
@ -40,7 +40,7 @@ func TestIDFormat(t *testing.T) {
|
|||
func TestMultipleAttachRestart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, hostConfig, _ := mkContainer(
|
||||
container, _ := mkContainer(
|
||||
runtime,
|
||||
[]string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
|
||||
t,
|
||||
|
@ -61,7 +61,7 @@ func TestMultipleAttachRestart(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l1, err := bufio.NewReader(stdout1).ReadString('\n')
|
||||
|
@ -102,7 +102,7 @@ func TestMultipleAttachRestart(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,7 @@ func TestDiff(t *testing.T) {
|
|||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
// Create a container and remove a file
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
|
||||
container1, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
// The changelog should be empty and not fail before run. See #1705
|
||||
|
@ -178,7 +178,7 @@ func TestDiff(t *testing.T) {
|
|||
}
|
||||
|
||||
// Create a new container from the commited image
|
||||
container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
|
||||
container2, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
if err := container2.Run(); err != nil {
|
||||
|
@ -197,7 +197,7 @@ func TestDiff(t *testing.T) {
|
|||
}
|
||||
|
||||
// Create a new container
|
||||
container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
|
||||
container3, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
|
||||
defer runtime.Destroy(container3)
|
||||
|
||||
if err := container3.Run(); err != nil {
|
||||
|
@ -223,7 +223,7 @@ func TestDiff(t *testing.T) {
|
|||
func TestCommitAutoRun(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
container1, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
|
@ -246,7 +246,7 @@ func TestCommitAutoRun(t *testing.T) {
|
|||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t)
|
||||
container2, _ := mkContainer(runtime, []string{img.ID}, t)
|
||||
defer runtime.Destroy(container2)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
|
@ -256,7 +256,7 @@ func TestCommitAutoRun(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.Wait()
|
||||
|
@ -283,7 +283,7 @@ func TestCommitRun(t *testing.T) {
|
|||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
container1, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
|
@ -306,7 +306,7 @@ func TestCommitRun(t *testing.T) {
|
|||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
|
||||
container2, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
|
||||
defer runtime.Destroy(container2)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
|
@ -316,7 +316,7 @@ func TestCommitRun(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.Wait()
|
||||
|
@ -342,7 +342,7 @@ func TestCommitRun(t *testing.T) {
|
|||
func TestStart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
|
||||
container, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
cStdin, err := container.StdinPipe()
|
||||
|
@ -350,7 +350,7 @@ func TestStart(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -360,7 +360,7 @@ func TestStart(t *testing.T) {
|
|||
if !container.State.Running {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Start(hostConfig); err == nil {
|
||||
if err := container.Start(); err == nil {
|
||||
t.Fatalf("A running container should be able to be started")
|
||||
}
|
||||
|
||||
|
@ -372,7 +372,7 @@ func TestStart(t *testing.T) {
|
|||
func TestRun(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
container, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
|
@ -452,7 +452,7 @@ func TestKillDifferentUser(t *testing.T) {
|
|||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Start(&HostConfig{}); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -501,7 +501,8 @@ func TestCreateVolume(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(c)
|
||||
if err := c.Start(hc); err != nil {
|
||||
c.hostConfig = hc
|
||||
if err := c.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.WaitTimeout(500 * time.Millisecond)
|
||||
|
@ -525,8 +526,7 @@ func TestKill(t *testing.T) {
|
|||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -642,8 +642,7 @@ func TestRestartStdin(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := io.WriteString(stdin, "hello world"); err != nil {
|
||||
|
@ -673,7 +672,7 @@ func TestRestartStdin(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
|
||||
|
@ -850,11 +849,10 @@ func TestMultipleContainers(t *testing.T) {
|
|||
defer runtime.Destroy(container2)
|
||||
|
||||
// Start both containers
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container1.Start(hostConfig); err != nil {
|
||||
if err := container1.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -904,8 +902,7 @@ func TestStdin(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stdin.Close()
|
||||
|
@ -950,8 +947,7 @@ func TestTty(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stdin.Close()
|
||||
|
@ -992,8 +988,7 @@ func TestEnv(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer stdout.Close()
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container.Wait()
|
||||
|
@ -1121,7 +1116,7 @@ func TestLXCConfig(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
container.generateLXCConfig(nil)
|
||||
container.generateLXCConfig()
|
||||
grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar")
|
||||
grepFile(t, container.lxcConfigPath(),
|
||||
fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem))
|
||||
|
@ -1144,7 +1139,7 @@ func TestCustomLxcConfig(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
hostConfig := &HostConfig{LxcConf: []KeyValuePair{
|
||||
container.hostConfig = &HostConfig{LxcConf: []KeyValuePair{
|
||||
{
|
||||
Key: "lxc.utsname",
|
||||
Value: "docker",
|
||||
|
@ -1155,7 +1150,7 @@ func TestCustomLxcConfig(t *testing.T) {
|
|||
},
|
||||
}}
|
||||
|
||||
container.generateLXCConfig(hostConfig)
|
||||
container.generateLXCConfig()
|
||||
grepFile(t, container.lxcConfigPath(), "lxc.utsname = docker")
|
||||
grepFile(t, container.lxcConfigPath(), "lxc.cgroup.cpuset.cpus = 0,1")
|
||||
}
|
||||
|
@ -1208,8 +1203,7 @@ func BenchmarkRunParallel(b *testing.B) {
|
|||
return
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
complete <- err
|
||||
return
|
||||
}
|
||||
|
@ -1253,7 +1247,7 @@ func TestCopyVolumeUidGid(t *testing.T) {
|
|||
defer nuke(r)
|
||||
|
||||
// Add directory not owned by root
|
||||
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t)
|
||||
container1, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t)
|
||||
defer r.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
|
@ -1290,7 +1284,7 @@ func TestCopyVolumeContent(t *testing.T) {
|
|||
defer nuke(r)
|
||||
|
||||
// Put some content in a directory of a container and commit it
|
||||
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, t)
|
||||
container1, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, t)
|
||||
defer r.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
|
@ -1527,9 +1521,9 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer runtime.Destroy(c)
|
||||
if err := c.Start(hc); err != nil {
|
||||
c.hostConfig = hc
|
||||
if err := c.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.WaitTimeout(500 * time.Millisecond)
|
||||
|
@ -1658,3 +1652,31 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
|||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartGhost(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
"",
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container.State.Ghost = true
|
||||
_, err = container.Output()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -426,7 +426,7 @@ _docker_run()
|
|||
|
||||
_docker_search()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-notrunc" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "-notrunc" "-stars" "-trusted" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_start()
|
||||
|
|
11
contrib/desktop-integration/README.txt
Normal file
11
contrib/desktop-integration/README.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
Desktop Integration
|
||||
===================
|
||||
|
||||
The ./contrib/desktop-integration contains examples of typical dockerized
|
||||
desktop applications.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
* Data container: ./data/Dockerfile creates a data image sharing /data volume
|
||||
* Firefox: ./firefox/Dockerfile shows a way to dockerize a common multimedia application
|
38
contrib/desktop-integration/data/Dockerfile
Normal file
38
contrib/desktop-integration/data/Dockerfile
Normal file
|
@ -0,0 +1,38 @@
|
|||
# VERSION: 0.1
|
||||
# DESCRIPTION: Create data image sharing /data volume
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This image is used as base for all data containers.
|
||||
# /data volume is owned by sysadmin.
|
||||
# USAGE:
|
||||
# # Download data Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile
|
||||
#
|
||||
# # Build data image
|
||||
# docker build -t data -rm .
|
||||
#
|
||||
# # Create a data container. (eg: firefox-data)
|
||||
# docker run -name firefox-data data true
|
||||
#
|
||||
# # List data from it
|
||||
# docker run -volumes-from firefox-data busybox ls -al /data
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Smallest base image, just to launch a container
|
||||
from busybox
|
||||
maintainer Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Create a regular user
|
||||
run echo 'sysadmin:x:1000:1000::/data:/bin/sh' >> /etc/passwd
|
||||
run echo 'sysadmin:x:1000:' >> /etc/group
|
||||
|
||||
# Create directory for that user
|
||||
run mkdir /data
|
||||
run chown sysadmin.sysadmin /data
|
||||
|
||||
# Add content to /data. This will keep sysadmin ownership
|
||||
run touch /data/init_volume
|
||||
|
||||
# Create /data volume
|
||||
VOLUME /data
|
49
contrib/desktop-integration/firefox/Dockerfile
Normal file
49
contrib/desktop-integration/firefox/Dockerfile
Normal file
|
@ -0,0 +1,49 @@
|
|||
# VERSION: 0.7
|
||||
# DESCRIPTION: Create firefox container with its dependencies
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a Firefox container with all
|
||||
# dependencies installed. It uses native X11 unix socket and alsa
|
||||
# sound devices. Tested on Debian 7.2
|
||||
# USAGE:
|
||||
# # Download Firefox Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/firefox/Dockerfile
|
||||
#
|
||||
# # Build firefox image
|
||||
# docker build -t firefox -rm .
|
||||
#
|
||||
# # Run stateful data-on-host firefox. For ephemeral, remove -v /data/firefox:/data
|
||||
# docker run -v /data/firefox:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY firefox
|
||||
#
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run -volumes-from firefox-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY firefox
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Base docker image
|
||||
from tianon/debian:wheezy
|
||||
maintainer Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Install firefox dependencies
|
||||
run echo "deb http://ftp.debian.org/debian/ wheezy main contrib" > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y libXrender1 libasound2 \
|
||||
libdbus-glib-1-2 libgtk2.0-0 libpango1.0-0 libxt6 wget bzip2 sudo
|
||||
|
||||
# Install Firefox
|
||||
run mkdir /application
|
||||
run cd /application; wget -O - \
|
||||
http://ftp.mozilla.org/pub/mozilla.org/firefox/releases/25.0/linux-x86_64/en-US/firefox-25.0.tar.bz2 | tar jx
|
||||
|
||||
# create sysadmin account
|
||||
run useradd -m -d /data -p saIVpsc0EVTwA sysadmin
|
||||
run sed -Ei 's/sudo:x:27:/sudo:x:27:sysadmin/' /etc/group
|
||||
run sed -Ei 's/(\%sudo\s+ALL=\(ALL\:ALL\) )ALL/\1 NOPASSWD:ALL/' /etc/sudoers
|
||||
|
||||
# Autorun firefox. -no-remote is necessary to create a new container, as firefox
|
||||
# appears to communicate with itself through X11.
|
||||
cmd ["/bin/sh", "-c", "/usr/bin/sudo -u sysadmin -H -E /application/firefox/firefox -no-remote"]
|
|
@ -6,43 +6,52 @@
|
|||
# Required-Stop: $syslog $remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Linux container runtime
|
||||
# Description: Linux container runtime
|
||||
# Short-Description: Create lightweight, portable, self-sufficient containers.
|
||||
# Description:
|
||||
# Docker is an open-source project to easily create lightweight, portable,
|
||||
# self-sufficient containers from any application. The same container that a
|
||||
# developer builds and tests on a laptop can run at scale, in production, on
|
||||
# VMs, bare metal, OpenStack clusters, public clouds and more.
|
||||
### END INIT INFO
|
||||
|
||||
DOCKER=/usr/bin/docker
|
||||
DOCKER_PIDFILE=/var/run/docker.pid
|
||||
BASE=$(basename $0)
|
||||
|
||||
DOCKER=/usr/bin/$BASE
|
||||
DOCKER_PIDFILE=/var/run/$BASE.pid
|
||||
DOCKER_OPTS=
|
||||
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
||||
|
||||
# Check lxc-docker is present
|
||||
[ -x $DOCKER ] || (log_failure_msg "docker not present"; exit 1)
|
||||
|
||||
# Get lsb functions
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
if [ -f /etc/default/lxc ]; then
|
||||
. /etc/default/lxc
|
||||
if [ -f /etc/default/$BASE ]; then
|
||||
. /etc/default/$BASE
|
||||
fi
|
||||
|
||||
if [ "$1" = start ] && which initctl >/dev/null && initctl version | grep -q upstart; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_root_id ()
|
||||
{
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
log_failure_msg "Docker must be run as root"; exit 1
|
||||
# Check docker is present
|
||||
if [ ! -x $DOCKER ]; then
|
||||
log_failure_msg "$DOCKER not present or not executable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fail_unless_root() {
|
||||
if [ "$(id -u)" != '0' ]; then
|
||||
log_failure_msg "Docker must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
check_root_id || exit 1
|
||||
log_begin_msg "Starting Docker"
|
||||
fail_unless_root
|
||||
log_begin_msg "Starting Docker: $BASE"
|
||||
mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup 2>/dev/null
|
||||
start-stop-daemon --start --background $NO_CLOSE \
|
||||
start-stop-daemon --start --background \
|
||||
--exec "$DOCKER" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
-- -d -p "$DOCKER_PIDFILE" \
|
||||
|
@ -51,15 +60,15 @@ case "$1" in
|
|||
;;
|
||||
|
||||
stop)
|
||||
check_root_id || exit 1
|
||||
log_begin_msg "Stopping Docker"
|
||||
fail_unless_root
|
||||
log_begin_msg "Stopping Docker: $BASE"
|
||||
start-stop-daemon --stop \
|
||||
--pidfile "$DOCKER_PIDFILE"
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
restart)
|
||||
check_root_id || exit 1
|
||||
fail_unless_root
|
||||
docker_pid=`cat "$DOCKER_PIDFILE" 2>/dev/null`
|
||||
[ -n "$docker_pid" ] \
|
||||
&& ps -p $docker_pid > /dev/null 2>&1 \
|
||||
|
@ -68,7 +77,7 @@ case "$1" in
|
|||
;;
|
||||
|
||||
force-reload)
|
||||
check_root_id || exit 1
|
||||
fail_unless_root
|
||||
$0 restart
|
||||
;;
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
|
|
@ -26,14 +26,118 @@ Docker Remote API
|
|||
2. Versions
|
||||
===========
|
||||
|
||||
The current version of the API is 1.6
|
||||
The current version of the API is 1.7
|
||||
|
||||
Calling /images/<name>/insert is the same as calling
|
||||
/v1.6/images/<name>/insert
|
||||
/v1.7/images/<name>/insert
|
||||
|
||||
You can still call an old version of the api using
|
||||
/v1.0/images/<name>/insert
|
||||
|
||||
v1.7
|
||||
****
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.7`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
.. http:get:: /images/json
|
||||
|
||||
The format of the json returned from this uri changed. Instead of an entry
|
||||
for each repo/tag on an image, each image is only represented once, with a
|
||||
nested attribute indicating the repo/tags that apply to that image.
|
||||
|
||||
Instead of:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "12.04",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "latest",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "precise",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 180116135,
|
||||
"Size": 24653,
|
||||
"Created": 1364102658,
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Tag": "12.10",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 180116135,
|
||||
"Size": 24653,
|
||||
"Created": 1364102658,
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Tag": "quantal",
|
||||
"Repository": "ubuntu"
|
||||
}
|
||||
]
|
||||
|
||||
The returned json looks like this:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.04",
|
||||
"ubuntu:precise",
|
||||
"ubuntu:latest"
|
||||
],
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Created": 1365714795,
|
||||
"Size": 131506275,
|
||||
"VirtualSize": 131506275
|
||||
},
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.10",
|
||||
"ubuntu:quantal"
|
||||
],
|
||||
"ParentId": "27cf784147099545",
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Created": 1364102658,
|
||||
"Size": 24653,
|
||||
"VirtualSize": 180116135
|
||||
}
|
||||
]
|
||||
|
||||
.. http:get:: /images/viz
|
||||
|
||||
This URI no longer exists. The ``images -viz`` output is now generated in
|
||||
the client, using the ``/images/json`` data.
|
||||
|
||||
v1.6
|
||||
****
|
||||
|
||||
|
|
|
@ -157,6 +157,57 @@ Create a container
|
|||
:statuscode 406: impossible to attach (container not running)
|
||||
:statuscode 500: server error
|
||||
|
||||
**More Complex Example request, in 2 steps.**
|
||||
**First, use create to expose a Private Port, which can be bound back to a Public Port at startup**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /containers/create HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd":[
|
||||
"/usr/sbin/sshd","-D"
|
||||
],
|
||||
"Image":"image-with-sshd",
|
||||
"ExposedPorts":{"22/tcp":{}}
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Id":"e90e34656806"
|
||||
"Warnings":[]
|
||||
}
|
||||
|
||||
**Second, start (using the ID returned above) the image we just created, mapping the ssh port 22 to something on the host**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /containers/e90e34656806/start HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"PortBindings": { "22/tcp": [{ "HostPort": "11022" }]}
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Length: 0
|
||||
|
||||
**Now you can ssh into your new container on port 11022.**
|
||||
|
||||
|
||||
|
||||
|
||||
Inspect a container
|
||||
*******************
|
||||
|
|
1185
docs/sources/api/docker_remote_api_v1.7.rst
Normal file
1185
docs/sources/api/docker_remote_api_v1.7.rst
Normal file
File diff suppressed because it is too large
Load diff
|
@ -41,7 +41,7 @@ To stop a container, use ``docker stop``
|
|||
To kill the container, use ``docker kill``
|
||||
|
||||
.. _cli_attach_examples:
|
||||
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
|
@ -55,8 +55,8 @@ Examples:
|
|||
Mem: 373572k total, 355560k used, 18012k free, 27872k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221740k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
|
||||
|
||||
top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
|
||||
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
|
||||
|
@ -64,8 +64,8 @@ Examples:
|
|||
Mem: 373572k total, 355244k used, 18328k free, 27872k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221776k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
|
||||
|
||||
top - 02:05:58 up 3:06, 0 users, load average: 0.01, 0.02, 0.05
|
||||
|
@ -74,9 +74,9 @@ Examples:
|
|||
Mem: 373572k total, 355780k used, 17792k free, 27880k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221776k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
^C$
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
^C$
|
||||
$ sudo docker stop $ID
|
||||
|
||||
.. _cli_build:
|
||||
|
@ -133,7 +133,6 @@ to the ``docker`` daemon. ``ADD`` doesn't work when running in this
|
|||
mode because the absence of the context provides no source files to
|
||||
copy to the container.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build github.com/creack/docker-firefox
|
||||
|
@ -151,16 +150,35 @@ by using the ``git://`` schema.
|
|||
|
||||
::
|
||||
|
||||
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]]
|
||||
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
||||
|
||||
Create a new image from a container's changes
|
||||
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Configuration to be applied when the image is launched with `docker run`.
|
||||
-run="": Configuration to be applied when the image is launched with `docker run`.
|
||||
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Full -run example (multiline is ok within a single quote ``'``)
|
||||
Simple commit of an existing container
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker ps
|
||||
ID IMAGE COMMAND CREATED STATUS PORTS
|
||||
c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
|
||||
197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
|
||||
$ docker commit c3f279d17e0a SvenDowideit/testimage:version3
|
||||
f5283438590d
|
||||
$ docker images | head
|
||||
REPOSITORY TAG ID CREATED SIZE
|
||||
SvenDowideit/testimage version3 f5283438590d 16 seconds ago 204.2 MB (virtual 335.7 MB)
|
||||
S
|
||||
|
||||
Full -run example
|
||||
.................
|
||||
|
||||
(multiline is ok within a single quote ``'``)
|
||||
|
||||
::
|
||||
|
||||
|
@ -239,7 +257,7 @@ Shell 1: Listening for events
|
|||
.............................
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
$ sudo docker events
|
||||
|
||||
Shell 2: Start and Stop a Container
|
||||
|
@ -297,8 +315,10 @@ Shell 1: (Again .. now showing events)
|
|||
List images
|
||||
|
||||
-a=false: show all images
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: only show numeric IDs
|
||||
-viz=false: output in graphviz format
|
||||
-tree=false: output graph in tree format
|
||||
-viz=false: output graph in graphviz format
|
||||
|
||||
Displaying images visually
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -310,6 +330,36 @@ Displaying images visually
|
|||
.. image:: docker_images.gif
|
||||
:alt: Example inheritance graph of Docker images.
|
||||
|
||||
|
||||
Displaying image hierarchy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
sudo docker images -tree
|
||||
|
||||
|─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise
|
||||
└─27cf78414709 Size: 180.1 MB (virtual 180.1 MB)
|
||||
└─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal
|
||||
|─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB)
|
||||
| └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB)
|
||||
| └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB)
|
||||
| └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB)
|
||||
| └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB)
|
||||
| └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest
|
||||
└─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB)
|
||||
└─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB)
|
||||
└─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB)
|
||||
└─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB)
|
||||
└─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB)
|
||||
└─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB)
|
||||
└─f165104e82ed Size: 12.29 kB (virtual 640.2 MB)
|
||||
└─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB)
|
||||
└─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB)
|
||||
└─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB)
|
||||
└─c96a99614930 Size: 12.29 kB (virtual 642.2 MB)
|
||||
└─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest
|
||||
|
||||
.. _cli_import:
|
||||
|
||||
``import``
|
||||
|
@ -317,7 +367,7 @@ Displaying images visually
|
|||
|
||||
::
|
||||
|
||||
Usage: docker import URL|- [REPOSITORY [TAG]]
|
||||
Usage: docker import URL|- [REPOSITORY[:TAG]]
|
||||
|
||||
Create a new filesystem image from the contents of a tarball
|
||||
|
||||
|
@ -333,14 +383,16 @@ Examples
|
|||
Import from a remote location
|
||||
.............................
|
||||
|
||||
``$ sudo docker import http://example.com/exampleimage.tgz exampleimagerepo``
|
||||
This will create a new untagged image.
|
||||
|
||||
``$ sudo docker import http://example.com/exampleimage.tgz``
|
||||
|
||||
Import from a local file
|
||||
........................
|
||||
|
||||
Import to docker via pipe and standard in
|
||||
|
||||
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal``
|
||||
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new``
|
||||
|
||||
Import from a local directory
|
||||
.............................
|
||||
|
@ -405,7 +457,7 @@ Insert file from github
|
|||
Usage: docker kill CONTAINER [CONTAINER...]
|
||||
|
||||
Kill a running container (Send SIGKILL)
|
||||
|
||||
|
||||
The main process inside the container will be sent SIGKILL.
|
||||
|
||||
.. _cli_login:
|
||||
|
@ -515,7 +567,7 @@ The main process inside the container will be sent SIGKILL.
|
|||
|
||||
Remove one or more containers
|
||||
-link="": Remove the link instead of the actual container
|
||||
|
||||
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
@ -584,6 +636,7 @@ network communication.
|
|||
-expose=[]: Expose a port from the container without publishing it to your host
|
||||
-link="": Add link to another container (name:alias)
|
||||
-name="": Assign the specified name to the container. If no name is specific docker will generate a random name
|
||||
-P=false: Publish all exposed ports to the host interfaces
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
@ -620,58 +673,51 @@ use-cases, like running Docker within Docker.
|
|||
|
||||
docker run -w /path/to/dir/ -i -t ubuntu pwd
|
||||
|
||||
The ``-w`` lets the command being executed inside directory given,
|
||||
here /path/to/dir/. If the path does not exists it is created inside the
|
||||
The ``-w`` lets the command being executed inside directory given,
|
||||
here /path/to/dir/. If the path does not exists it is created inside the
|
||||
container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
|
||||
|
||||
The ``-v`` flag mounts the current working directory into the container.
|
||||
The ``-w`` lets the command being executed inside the current
|
||||
The ``-v`` flag mounts the current working directory into the container.
|
||||
The ``-w`` lets the command being executed inside the current
|
||||
working directory, by changing into the directory to the value
|
||||
returned by ``pwd``. So this combination executes the command
|
||||
using the container, but inside the current working directory.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.0::80 ubuntu bash
|
||||
docker run -p 127.0.0.1:80:8080 ubuntu bash
|
||||
|
||||
The ``-p`` flag now allows you to bind a port to a specific
|
||||
interface of the host machine. In this example port ``80`` of the
|
||||
container will have a dynamically allocated port bound to 127.0.0.1
|
||||
of the host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.1:80:80 ubuntu bash
|
||||
|
||||
This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your
|
||||
host machine.
|
||||
This binds port ``8080`` of the container to port ``80`` on 127.0.0.1 of the
|
||||
host machine. :ref:`port_redirection` explains in detail how to manipulate ports
|
||||
in Docker.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -expose 80 ubuntu bash
|
||||
|
||||
This will expose port ``80`` of the container for use within a link
|
||||
without publishing the port to the host system's interfaces.
|
||||
This exposes port ``80`` of the container for use within a link without
|
||||
publishing the port to the host system's interfaces. :ref:`port_redirection`
|
||||
explains in detail how to manipulate ports in Docker.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -name console -t -i ubuntu bash
|
||||
|
||||
This will create and run a new container with the container name
|
||||
This will create and run a new container with the container name
|
||||
being ``console``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -link /redis:redis -name console ubuntu bash
|
||||
|
||||
The ``-link`` flag will link the container named ``/redis`` into the
|
||||
The ``-link`` flag will link the container named ``/redis`` into the
|
||||
newly created container with the alias ``redis``. The new container
|
||||
can access the network and environment of the redis container via
|
||||
environment variables. The ``-name`` flag will assign the name ``console``
|
||||
environment variables. The ``-name`` flag will assign the name ``console``
|
||||
to the newly created container.
|
||||
|
||||
.. _cli_search:
|
||||
|
@ -683,8 +729,11 @@ to the newly created container.
|
|||
|
||||
Usage: docker search TERM
|
||||
|
||||
Searches for the TERM parameter on the Docker index and prints out
|
||||
a list of repositories that match.
|
||||
Search the docker index for images
|
||||
|
||||
-notrunc=false: Don't truncate output
|
||||
-stars=0: Only displays with at least xxx stars
|
||||
-trusted=false: Only show trusted builds
|
||||
|
||||
.. _cli_start:
|
||||
|
||||
|
@ -712,7 +761,7 @@ to the newly created container.
|
|||
Stop a running container (Send SIGTERM, and then SIGKILL after grace period)
|
||||
|
||||
-t=10: Number of seconds to wait for the container to stop before killing it.
|
||||
|
||||
|
||||
The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL
|
||||
|
||||
.. _cli_tag:
|
||||
|
@ -722,7 +771,7 @@ The main process inside the container will receive SIGTERM, and after a grace pe
|
|||
|
||||
::
|
||||
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY[:TAG]
|
||||
|
||||
Tag an image into a repository
|
||||
|
||||
|
@ -735,7 +784,7 @@ The main process inside the container will receive SIGTERM, and after a grace pe
|
|||
|
||||
::
|
||||
|
||||
Usage: docker top CONTAINER
|
||||
Usage: docker top CONTAINER [ps OPTIONS]
|
||||
|
||||
Lookup the running processes of a container
|
||||
|
||||
|
@ -757,6 +806,3 @@ Show the version of the docker client, daemon, and latest released version.
|
|||
Usage: docker wait [OPTIONS] NAME
|
||||
|
||||
Block until a container stops, then print its exit code.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ To create the Docker binary, run this command:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -lxc-conf=lxc.aa_profile=unconfined -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh binary
|
||||
sudo docker run -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh binary
|
||||
|
||||
This will create the Docker binary in ``./bundles/<version>-dev/binary/``
|
||||
|
||||
|
@ -64,18 +64,11 @@ This will create the Docker binary in ``./bundles/<version>-dev/binary/``
|
|||
Step 5: Run the Tests
|
||||
---------------------
|
||||
|
||||
To run the Docker test cases you first need to disable `AppArmor <https://wiki.ubuntu.com/AppArmor>`_ using the following commands
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo /etc/init.d/apparmor stop
|
||||
sudo /etc/init.d/apparmor teardown
|
||||
|
||||
To execute the test cases, run this command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -lxc-conf=lxc.aa_profile=unconfined -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test
|
||||
sudo docker run -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test
|
||||
|
||||
|
||||
Note: if you're running the tests in vagrant, you need to specify a dns entry in the command: `-dns 8.8.8.8`
|
||||
|
|
|
@ -10,7 +10,7 @@ CouchDB Service
|
|||
.. include:: example_header.inc
|
||||
|
||||
Here's an example of using data volumes to share the same data between
|
||||
2 CouchDB containers. This could be used for hot upgrades, testing
|
||||
two CouchDB containers. This could be used for hot upgrades, testing
|
||||
different versions of CouchDB on the same data, etc.
|
||||
|
||||
Create first database
|
||||
|
@ -25,8 +25,8 @@ Note that we're marking ``/var/lib/couchdb`` as a data volume.
|
|||
Add data to the first database
|
||||
------------------------------
|
||||
|
||||
We're assuming your docker host is reachable at `localhost`. If not,
|
||||
replace `localhost` with the public IP of your docker host.
|
||||
We're assuming your Docker host is reachable at ``localhost``. If not,
|
||||
replace ``localhost`` with the public IP of your Docker host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -37,7 +37,7 @@ replace `localhost` with the public IP of your docker host.
|
|||
Create second database
|
||||
----------------------
|
||||
|
||||
This time, we're requesting shared access to $COUCH1's volumes.
|
||||
This time, we're requesting shared access to ``$COUCH1``'s volumes.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -52,5 +52,5 @@ Browse data on the second database
|
|||
URL="http://$HOST:$(sudo docker port $COUCH2 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
|
||||
|
||||
Congratulations, you are running 2 Couchdb containers, completely
|
||||
Congratulations, you are now running two Couchdb containers, completely
|
||||
isolated from each other *except* for their data.
|
||||
|
|
|
@ -12,16 +12,16 @@ Hello World
|
|||
Running the Examples
|
||||
====================
|
||||
|
||||
All the examples assume your machine is running the docker daemon. To
|
||||
run the docker daemon in the background, simply type:
|
||||
All the examples assume your machine is running the ``docker`` daemon. To
|
||||
run the ``docker`` daemon in the background, simply type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker -d &
|
||||
|
||||
Now you can run docker in client mode: by default all commands will be
|
||||
Now you can run Docker in client mode: by default all commands will be
|
||||
forwarded to the ``docker`` daemon via a protected Unix socket, so you
|
||||
must run as root.
|
||||
must run as the ``root`` or via the ``sudo`` command.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -38,23 +38,24 @@ Hello World
|
|||
|
||||
This is the most basic example available for using Docker.
|
||||
|
||||
Download the base image (named "ubuntu"):
|
||||
Download the base image which is named ``ubuntu``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download an ubuntu image
|
||||
sudo docker pull ubuntu
|
||||
|
||||
Alternatively to the *ubuntu* image, you can select *busybox*, a bare
|
||||
Alternatively to the ``ubuntu`` image, you can select ``busybox``, a bare
|
||||
minimal Linux system. The images are retrieved from the Docker
|
||||
repository.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
#run a simple echo command, that will echo hello world back to the console over standard out.
|
||||
sudo docker run ubuntu /bin/echo hello world
|
||||
|
||||
This command will run a simple ``echo`` command, that will echo ``hello world`` back to the console over standard out.
|
||||
|
||||
**Explanation:**
|
||||
|
||||
- **"sudo"** execute the following commands as user *root*
|
||||
|
@ -100,9 +101,9 @@ we stop it.
|
|||
CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done")
|
||||
|
||||
We are going to run a simple hello world daemon in a new container
|
||||
made from the *ubuntu* image.
|
||||
made from the ``ubuntu`` image.
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d"
|
||||
- **"sudo docker run -d "** run a command in a new container. We pass "-d"
|
||||
so it runs as a daemon.
|
||||
- **"ubuntu"** is the image we want to run the command inside of.
|
||||
- **"/bin/sh -c"** is the command we want to run in the container
|
||||
|
|
|
@ -10,7 +10,7 @@ Examples
|
|||
|
||||
Here are some examples of how to use Docker to create running
|
||||
processes, starting from a very simple *Hello World* and progressing
|
||||
to more substantial services like you might find in production.
|
||||
to more substantial services like those which you might find in production.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
@ -24,4 +24,3 @@ to more substantial services like you might find in production.
|
|||
postgresql_service
|
||||
mongodb
|
||||
running_riak_service
|
||||
linking_into_redis
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
:title: Linking to an Redis container
|
||||
:description: Running redis linked into your web app
|
||||
:keywords: docker, example, networking, redis, link
|
||||
|
||||
.. _linking_redis:
|
||||
|
||||
Linking Redis
|
||||
=============
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Building a redis container to link as a child of our web application.
|
||||
|
||||
Building the redis container
|
||||
----------------------------
|
||||
|
||||
Lets build a redis image with the following Dockerfile.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/antirez/redis.git
|
||||
cd redis
|
||||
git checkout 2.6
|
||||
|
||||
# Save this Dockerfile to the root of the redis repository.
|
||||
|
||||
# Build redis from source
|
||||
# Make sure you have the redis source code checked out in
|
||||
# the same directory as this Dockerfile
|
||||
FROM ubuntu
|
||||
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
|
||||
RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl
|
||||
|
||||
ADD . /redis
|
||||
|
||||
RUN (cd /redis && make)
|
||||
|
||||
RUN mkdir -p /redis-data
|
||||
VOLUME ["/redis-data"]
|
||||
EXPOSE 6379
|
||||
|
||||
ENTRYPOINT ["/redis/src/redis-server"]
|
||||
CMD ["--dir", "/redis-data"]
|
||||
|
||||
# docker build our new redis image from source
|
||||
docker build -t redis-2.6 .
|
||||
|
||||
|
||||
We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports
|
||||
to connect to our redis container on. If you do not expose any ports for the
|
||||
image then docker will not be able to establish the link between containers.
|
||||
|
||||
|
||||
Run the redis container
|
||||
-----------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d -e PASSWORD=docker -name redis redis-2.6 --requirepass docker
|
||||
|
||||
This will run our redis container with the password docker
|
||||
to secure our service. By specifying the ``-name`` flag on run
|
||||
we will assign the name ``redis`` to this container. If we do not specify a name for
|
||||
our container via the ``-name`` flag docker will automatically generate a name for us.
|
||||
We can issue all the commands that you would expect; start, stop, attach, using the name for our container.
|
||||
The name also allows us to link other containers into this one.
|
||||
|
||||
Linking redis as a child
|
||||
------------------------
|
||||
|
||||
Next we can start a new web application that has a dependency on redis and apply a link
|
||||
to connect both containers. If you noticed when running our redis server we did not use
|
||||
the ``-p`` flag to publish the redis port to the host system. Redis exposed port 6379 via the Dockerfile
|
||||
and this is all we need to establish a link.
|
||||
|
||||
Now lets start our web application with a link into redis.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -t -i -link redis:db -name webapp ubuntu bash
|
||||
|
||||
root@4c01db0b339c:/# env
|
||||
|
||||
HOSTNAME=4c01db0b339c
|
||||
DB_NAME=/webapp/db
|
||||
TERM=xterm
|
||||
DB_PORT=tcp://172.17.0.8:6379
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
|
||||
DB_PORT_6379_TCP_PROTO=tcp
|
||||
DB_PORT_6379_TCP_ADDR=172.17.0.8
|
||||
DB_PORT_6379_TCP_PORT=6379
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
PWD=/
|
||||
DB_ENV_PASSWORD=docker
|
||||
SHLVL=1
|
||||
HOME=/
|
||||
container=lxc
|
||||
_=/usr/bin/env
|
||||
root@4c01db0b339c:/#
|
||||
|
||||
|
||||
When we inspect the environment of the linked container we can see a few extra environment
|
||||
variables have been added. When you specified ``-link redis:db`` you are telling docker
|
||||
to link the container named ``redis`` into this new container with the alias ``db``.
|
||||
Environment variables are prefixed with the alias so that the parent container can access
|
||||
network and environment information from the containers that are linked into it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# The name of the child container
|
||||
DB_NAME=/webapp/db
|
||||
|
||||
# The default protocol, ip, and port of the service running in the container
|
||||
DB_PORT=tcp://172.17.0.8:6379
|
||||
|
||||
# A specific protocol, ip, and port of various services
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
|
||||
DB_PORT_6379_TCP_PROTO=tcp
|
||||
DB_PORT_6379_TCP_ADDR=172.17.0.8
|
||||
DB_PORT_6379_TCP_PORT=6379
|
||||
|
||||
# Get environment variables of the container
|
||||
DB_ENV_PASSWORD=docker
|
||||
|
||||
|
||||
Accessing the network information along with the environment of the child container allows
|
||||
us to easily connect to the redis service on the specific ip and port and use the password
|
||||
specified in the environment.
|
|
@ -10,8 +10,8 @@ Building an Image with MongoDB
|
|||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show how you can build your own
|
||||
docker images with MongoDB preinstalled. We will do that by
|
||||
constructing a Dockerfile that downloads a base image, adds an
|
||||
Docker images with MongoDB pre-installed. We will do that by
|
||||
constructing a ``Dockerfile`` that downloads a base image, adds an
|
||||
apt source and installs the database software on Ubuntu.
|
||||
|
||||
Creating a ``Dockerfile``
|
||||
|
@ -41,7 +41,7 @@ Since we want to be running the latest version of MongoDB we'll need to add the
|
|||
RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
|
||||
|
||||
Then, we don't want Ubuntu to complain about init not being available so we'll
|
||||
divert /sbin/initctl to /bin/true so it thinks everything is working.
|
||||
divert ``/sbin/initctl`` to ``/bin/true`` so it thinks everything is working.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -65,8 +65,8 @@ run without needing to provide a special configuration file)
|
|||
# Create the MongoDB data directory
|
||||
RUN mkdir -p /data/db
|
||||
|
||||
Finally, we'll expose the standard port that MongoDB runs on (27107) as well as
|
||||
define an ENTRYPOINT for the container.
|
||||
Finally, we'll expose the standard port that MongoDB runs on, 27107, as well as
|
||||
define an ``ENTRYPOINT`` instruction for the container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -78,7 +78,7 @@ run all of the commands.
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build -t <yourname>/mongodb .
|
||||
sudo docker build -t <yourname>/mongodb .
|
||||
|
||||
Now you should be able to run ``mongod`` as a daemon and be able to connect on
|
||||
the local port!
|
||||
|
@ -86,13 +86,13 @@ the local port!
|
|||
.. code-block:: bash
|
||||
|
||||
# Regular style
|
||||
MONGO_ID=$(docker run -d <yourname>/mongodb)
|
||||
MONGO_ID=$(sudo docker run -d <yourname>/mongodb)
|
||||
|
||||
# Lean and mean
|
||||
MONGO_ID=$(docker run -d <yourname>/mongodb --noprealloc --smallfiles)
|
||||
MONGO_ID=$(sudo docker run -d <yourname>/mongodb --noprealloc --smallfiles)
|
||||
|
||||
# Check the logs out
|
||||
docker logs $MONGO_ID
|
||||
sudo docker logs $MONGO_ID
|
||||
|
||||
# Connect and play around
|
||||
mongo --port <port you get from `docker ps`>
|
||||
|
|
|
@ -10,7 +10,7 @@ Node.js Web App
|
|||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show you how you can build your own
|
||||
docker images from a parent image using a ``Dockerfile`` . We will do
|
||||
Docker images from a parent image using a ``Dockerfile`` . We will do
|
||||
that by making a simple Node.js hello world web application running on
|
||||
CentOS. You can get the full source code at
|
||||
https://github.com/gasi/docker-node-hello.
|
||||
|
@ -55,7 +55,7 @@ Then, create an ``index.js`` file that defines a web app using the
|
|||
|
||||
|
||||
In the next steps, we’ll look at how you can run this app inside a CentOS
|
||||
container using docker. First, you’ll need to build a docker image of your app.
|
||||
container using Docker. First, you’ll need to build a Docker image of your app.
|
||||
|
||||
Creating a ``Dockerfile``
|
||||
+++++++++++++++++++++++++
|
||||
|
@ -67,8 +67,8 @@ Create an empty file called ``Dockerfile``:
|
|||
touch Dockerfile
|
||||
|
||||
Open the ``Dockerfile`` in your favorite text editor and add the following line
|
||||
that defines the version of docker the image requires to build
|
||||
(this example uses docker 0.3.4):
|
||||
that defines the version of Docker the image requires to build
|
||||
(this example uses Docker 0.3.4):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -76,7 +76,7 @@ that defines the version of docker the image requires to build
|
|||
|
||||
Next, define the parent image you want to use to build your own image on top of.
|
||||
Here, we’ll use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``)
|
||||
available on the `docker index`_:
|
||||
available on the `Docker index`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -95,23 +95,23 @@ To install the right package for CentOS, we’ll use the instructions from the
|
|||
# Install Node.js and npm
|
||||
RUN yum install -y npm
|
||||
|
||||
To bundle your app’s source code inside the docker image, use the ``ADD``
|
||||
command:
|
||||
To bundle your app’s source code inside the Docker image, use the ``ADD``
|
||||
instruction:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Bundle app source
|
||||
ADD . /src
|
||||
|
||||
Install your app dependencies using npm:
|
||||
Install your app dependencies using the ``npm`` binary:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Install app dependencies
|
||||
RUN cd /src; npm install
|
||||
|
||||
Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` command
|
||||
to have it mapped by the docker daemon:
|
||||
Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` instruction
|
||||
to have it mapped by the ``docker`` daemon:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -152,7 +152,7 @@ Building your image
|
|||
+++++++++++++++++++
|
||||
|
||||
Go to the directory that has your ``Dockerfile`` and run the following
|
||||
command to build a docker image. The ``-t`` flag let’s you tag your
|
||||
command to build a Docker image. The ``-t`` flag let’s you tag your
|
||||
image so it’s easier to find later using the ``docker images``
|
||||
command:
|
||||
|
||||
|
@ -160,7 +160,7 @@ command:
|
|||
|
||||
sudo docker build -t <your username>/centos-node-hello .
|
||||
|
||||
Your image will now be listed by docker:
|
||||
Your image will now be listed by Docker:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -176,11 +176,11 @@ Run the image
|
|||
+++++++++++++
|
||||
|
||||
Running your image with ``-d`` runs the container in detached mode, leaving the
|
||||
container running in the background. Run the image you previously built:
|
||||
container running in the background. The ``-p`` flag redirects a public port to a private port in the container. Run the image you previously built:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -d <your username>/centos-node-hello
|
||||
sudo docker run -p 49160:8080 -d <your username>/centos-node-hello
|
||||
|
||||
Print the output of your app:
|
||||
|
||||
|
@ -199,17 +199,17 @@ Print the output of your app:
|
|||
Test
|
||||
++++
|
||||
|
||||
To test your app, get the the port of your app that docker mapped:
|
||||
To test your app, get the the port of your app that Docker mapped:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
sudo docker ps
|
||||
|
||||
> # Example
|
||||
> ID IMAGE COMMAND ... PORTS
|
||||
> ecce33b30ebf gasi/centos-node-hello:latest node /src/index.js 49160->8080
|
||||
|
||||
In the example above, docker mapped the ``8080`` port of the container to
|
||||
In the example above, Docker mapped the ``8080`` port of the container to
|
||||
``49160``.
|
||||
|
||||
Now you can call your app using ``curl`` (install if needed via:
|
||||
|
@ -229,7 +229,7 @@ Now you can call your app using ``curl`` (install if needed via:
|
|||
> Hello World
|
||||
|
||||
We hope this tutorial helped you get up and running with Node.js and
|
||||
CentOS on docker. You can get the full source code at
|
||||
CentOS on Docker. You can get the full source code at
|
||||
https://github.com/gasi/docker-node-hello.
|
||||
|
||||
Continue to :ref:`running_redis_service`.
|
||||
|
|
|
@ -13,7 +13,7 @@ PostgreSQL Service
|
|||
|
||||
.. note::
|
||||
|
||||
As of version 0.5.2, docker requires root privileges to run.
|
||||
As of version 0.5.2, Docker requires root privileges to run.
|
||||
You have to either manually adjust your system configuration (permissions on
|
||||
/var/run/docker.sock or sudo config), or prefix `docker` with `sudo`. Check
|
||||
`this thread`_ for details.
|
||||
|
@ -24,8 +24,7 @@ PostgreSQL Service
|
|||
Installing PostgreSQL on Docker
|
||||
-------------------------------
|
||||
|
||||
For clarity I won't be showing commands output.
|
||||
|
||||
For clarity I won't be showing command output.
|
||||
|
||||
Run an interactive shell in Docker container.
|
||||
|
||||
|
@ -62,7 +61,7 @@ Finally, install PostgreSQL 9.2
|
|||
|
||||
Now, create a PostgreSQL superuser role that can create databases and
|
||||
other roles. Following Vagrant's convention the role will be named
|
||||
`docker` with `docker` password assigned to it.
|
||||
``docker`` with ``docker`` password assigned to it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -108,7 +107,7 @@ Bash prompt; you can also locate it using ``docker ps -a``.
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker commit <container_id> <your username>/postgresql
|
||||
sudo docker commit <container_id> <your username>/postgresql
|
||||
|
||||
Finally, run PostgreSQL server via ``docker``.
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ Python Web App
|
|||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show you how you can author your own
|
||||
docker images using a parent image, making changes to it, and then
|
||||
Docker images using a parent image, making changes to it, and then
|
||||
saving the results as a new image. We will do that by making a simple
|
||||
hello flask web application image.
|
||||
hello Flask web application image.
|
||||
|
||||
**Steps:**
|
||||
|
||||
|
@ -20,22 +20,22 @@ hello flask web application image.
|
|||
|
||||
sudo docker pull shykes/pybuilder
|
||||
|
||||
We are downloading the "shykes/pybuilder" docker image
|
||||
We are downloading the ``shykes/pybuilder`` Docker image
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
URL=http://github.com/shykes/helloflask/archive/master.tar.gz
|
||||
|
||||
We set a URL variable that points to a tarball of a simple helloflask web app
|
||||
We set a ``URL`` variable that points to a tarball of a simple helloflask web app
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
BUILD_JOB=$(sudo docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
|
||||
|
||||
Inside of the "shykes/pybuilder" image there is a command called
|
||||
buildapp, we are running that command and passing the $URL variable
|
||||
Inside of the ``shykes/pybuilder`` image there is a command called
|
||||
``buildapp``, we are running that command and passing the ``$URL`` variable
|
||||
from step 2 to it, and running the whole thing inside of a new
|
||||
container. BUILD_JOB will be set with the new container_id.
|
||||
container. The ``BUILD_JOB`` environment variable will be set with the new container ID.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -43,13 +43,13 @@ container. BUILD_JOB will be set with the new container_id.
|
|||
[...]
|
||||
|
||||
While this container is running, we can attach to the new container to
|
||||
see what is going on. Ctrl-C to disconnect.
|
||||
see what is going on. You can use Ctrl-C to disconnect.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker ps -a
|
||||
|
||||
List all docker containers. If this container has already finished
|
||||
|
||||
List all Docker containers. If this container has already finished
|
||||
running, it will still be listed here.
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -57,8 +57,8 @@ running, it will still be listed here.
|
|||
BUILD_IMG=$(sudo docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
|
||||
|
||||
Save the changes we just made in the container to a new image called
|
||||
``_/builds/github.com/hykes/helloflask/master`` and save the image id in
|
||||
the BUILD_IMG variable name.
|
||||
``_/builds/github.com/hykes/helloflask/master`` and save the image ID in
|
||||
the ``BUILD_IMG`` variable name.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -72,24 +72,24 @@ the BUILD_IMG variable name.
|
|||
- **/usr/local/bin/runapp** is the command which starts the web app.
|
||||
|
||||
Use the new image we just created and create a new container with
|
||||
network port 5000, and return the container id and store in the
|
||||
WEB_WORKER variable.
|
||||
network port 5000, and return the container ID and store in the
|
||||
``WEB_WORKER`` variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker logs $WEB_WORKER
|
||||
* Running on http://0.0.0.0:5000/
|
||||
|
||||
View the logs for the new container using the WEB_WORKER variable, and
|
||||
if everything worked as planned you should see the line "Running on
|
||||
http://0.0.0.0:5000/" in the log output.
|
||||
View the logs for the new container using the ``WEB_WORKER`` variable, and
|
||||
if everything worked as planned you should see the line ``Running on
|
||||
http://0.0.0.0:5000/`` in the log output.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_PORT=$(sudo docker port $WEB_WORKER 5000)
|
||||
|
||||
Look up the public-facing port which is NAT-ed. Find the private port
|
||||
used by the container and store it inside of the WEB_PORT variable.
|
||||
used by the container and store it inside of the ``WEB_PORT`` variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -97,8 +97,8 @@ used by the container and store it inside of the WEB_PORT variable.
|
|||
curl http://127.0.0.1:$WEB_PORT
|
||||
Hello world!
|
||||
|
||||
Access the web app using curl. If everything worked as planned you
|
||||
should see the line "Hello world!" inside of your console.
|
||||
Access the web app using the ``curl`` binary. If everything worked as planned you
|
||||
should see the line ``Hello world!`` inside of your console.
|
||||
|
||||
**Video:**
|
||||
|
||||
|
|
|
@ -9,74 +9,93 @@ Redis Service
|
|||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Very simple, no frills, redis service.
|
||||
Very simple, no frills, Redis service attached to a web application using a link.
|
||||
|
||||
Open a docker container
|
||||
-----------------------
|
||||
Create a docker container for Redis
|
||||
-----------------------------------
|
||||
|
||||
Firstly, we create a ``Dockerfile`` for our new Redis image.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
FROM ubuntu:12.10
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install redis-server
|
||||
EXPOSE 6379
|
||||
ENTRYPOINT ["/usr/bin/redis-server"]
|
||||
|
||||
Building your image
|
||||
-------------------
|
||||
|
||||
Update your Docker container, install the Redis server. Once
|
||||
installed, exit out of the Docker container.
|
||||
Next we build an image from our ``Dockerfile``. Replace ``<your username>``
|
||||
with your own user name.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
apt-get update
|
||||
apt-get install redis-server
|
||||
exit
|
||||
|
||||
Snapshot the installation
|
||||
-------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps -a # grab the container id (this will be the first one in the list)
|
||||
docker commit <container_id> <your username>/redis
|
||||
sudo docker build -t <your username>/redis
|
||||
|
||||
Run the service
|
||||
---------------
|
||||
|
||||
Running the service with `-d` runs the container in detached mode, leaving the
|
||||
container running in the background. Use your snapshot.
|
||||
Use the image we've just created and name your container ``redis``.
|
||||
|
||||
Running the service with ``-d`` runs the container in detached mode, leaving the
|
||||
container running in the background.
|
||||
|
||||
Importantly, we're not exposing any ports on our container. Instead we're going to
|
||||
use a container link to provide access to our Redis database.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -d -p 6379 <your username>/redis /usr/bin/redis-server
|
||||
sudo docker run -name redis -d <your username>/redis
|
||||
|
||||
Test 1
|
||||
++++++
|
||||
Create your web application container
|
||||
-------------------------------------
|
||||
|
||||
Connect to the container with the redis-cli.
|
||||
Next we can create a container for our application. We're going to use the ``-link``
|
||||
flag to create a link to the ``redis`` container we've just created with an alias of
|
||||
``db``. This will create a secure tunnel to the ``redis`` container and expose the
|
||||
Redis instance running inside that container to only this container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker ps # grab the new container id
|
||||
sudo docker inspect <container_id> # grab the ipaddress of the container
|
||||
redis-cli -h <ipaddress> -p 6379
|
||||
redis 10.0.3.32:6379> set docker awesome
|
||||
sudo docker run -link redis:db -i -t ubuntu:12.10 /bin/bash
|
||||
|
||||
Once inside our freshly created container we need to install Redis to get the
|
||||
``redis-cli`` binary to test our connection.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
apt-get update
|
||||
apt-get -y install redis-server
|
||||
service redis-server stop
|
||||
|
||||
Now we can test the connection. Firstly, let's look at the available environmental
|
||||
variables in our web application container. We can use these to get the IP and port
|
||||
of our ``redis`` container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env
|
||||
. . .
|
||||
DB_NAME=/violet_wolf/db
|
||||
DB_PORT_6379_TCP_PORT=6379
|
||||
DB_PORT=tcp://172.17.0.33:6379
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.33:6379
|
||||
DB_PORT_6379_TCP_ADDR=172.17.0.33
|
||||
DB_PORT_6379_TCP_PROTO=tcp
|
||||
|
||||
We can see that we've got a small list of environmental varaibles prefixed with ``DB``.
|
||||
The ``DB`` comes from the link alias specified when we launched the container. Let's use
|
||||
the ``DB_PORT_6379_TCP_ADDR`` variable to connect to our Redis container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
redis-cli -h $DB_PORT_6379_TCP_ADDR
|
||||
redis 172.17.0.33:6379>
|
||||
redis 172.17.0.33:6379> set docker awesome
|
||||
OK
|
||||
redis 10.0.3.32:6379> get docker
|
||||
redis 172.17.0.33:6379> get docker
|
||||
"awesome"
|
||||
redis 10.0.3.32:6379> exit
|
||||
redis 172.17.0.33:6379> exit
|
||||
|
||||
Test 2
|
||||
++++++
|
||||
We could easily use this or other environment variables in our web application to make a
|
||||
connection to our ``redis`` container.
|
||||
|
||||
Connect to the host os with the redis-cli.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker ps # grab the new container id
|
||||
sudo docker port <container_id> 6379 # grab the external port
|
||||
ip addr show # grab the host ip address
|
||||
redis-cli -h <host ipaddress> -p <external port>
|
||||
redis 192.168.0.1:49153> set docker awesome
|
||||
OK
|
||||
redis 192.168.0.1:49153> get docker
|
||||
"awesome"
|
||||
redis 192.168.0.1:49153> exit
|
||||
|
|
|
@ -107,7 +107,7 @@ Create a ``supervisord`` configuration file
|
|||
+++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Create an empty file called ``supervisord.conf``. Make sure it's at the same
|
||||
level as your ``Dockerfile``:
|
||||
directory level as your ``Dockerfile``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@ SSH Daemon Service
|
|||
|
||||
**Video:**
|
||||
|
||||
I've create a little screencast to show how to create a sshd service
|
||||
I've create a little screencast to show how to create a SSHd service
|
||||
and connect to it. It is something like 11 minutes and not entirely
|
||||
smooth, but gives you a good idea.
|
||||
|
||||
.. note::
|
||||
This screencast was created before ``docker`` version 0.5.2, so the
|
||||
This screencast was created before Docker version 0.5.2, so the
|
||||
daemon is unprotected and available via a TCP port. When you run
|
||||
through the same steps in a newer version of ``docker``, you will
|
||||
through the same steps in a newer version of Docker, you will
|
||||
need to add ``sudo`` in front of each ``docker`` command in order
|
||||
to reach the daemon over its protected Unix socket.
|
||||
|
||||
|
@ -29,13 +29,14 @@ smooth, but gives you a good idea.
|
|||
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
You can also get this sshd container by using
|
||||
::
|
||||
You can also get this sshd container by using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker pull dhrp/sshd
|
||||
|
||||
|
||||
The password is 'screencast'
|
||||
The password is ``screencast``.
|
||||
|
||||
**Video's Transcription:**
|
||||
|
||||
|
|
|
@ -162,8 +162,8 @@ Verify it worked
|
|||
Docker and UFW
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Docker uses a bridge to manage containers networking, by default UFW
|
||||
drop all `forwarding`, a first step is to enable forwarding:
|
||||
Docker uses a bridge to manage container networking. By default, UFW
|
||||
drops all `forwarding`, thus a first step is to enable UFW forwarding:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ Using Vagrant (Mac, Linux)
|
|||
|
||||
This guide will setup a new virtualbox virtual machine with docker
|
||||
installed on your computer. This works on most operating systems,
|
||||
including MacOX, Windows, Linux, FreeBSD and others. If you can
|
||||
including MacOSX, Windows, Linux, FreeBSD and others. If you can
|
||||
install these and have at least 400MB RAM to spare you should be good.
|
||||
|
||||
Install Vagrant and Virtualbox
|
||||
|
|
|
@ -208,6 +208,10 @@ configuration / Device configuration)
|
|||
|
||||
.. image:: images/win/hp_bios_vm.JPG
|
||||
|
||||
On some machines the BIOS menu can only be accessed before startup.
|
||||
To access BIOS in this scenario you should restart your computer and
|
||||
press ESC/Enter when prompted to access the boot and BIOS controls. Typically
|
||||
the option to allow virtualization is contained within the BIOS/Security menu.
|
||||
|
||||
Docker is not installed
|
||||
```````````````````````
|
||||
|
|
|
@ -138,22 +138,19 @@ Listing all running containers
|
|||
|
||||
sudo docker ps
|
||||
|
||||
Expose a service on a TCP port
|
||||
Bind a service on a TCP port
|
||||
------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
# Bind port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l 4444)
|
||||
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(sudo docker port $JOB 4444 | awk -F: '{ print $2 }')
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
|
||||
# Replace *eth0* according to your local interface name.
|
||||
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
|
||||
echo hello world | nc $IP $PORT
|
||||
# Connect to the public port
|
||||
echo hello world | nc 127.0.0.1 $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(sudo docker logs $JOB)"
|
||||
|
@ -183,4 +180,3 @@ You now have a image state from which you can create new instances.
|
|||
|
||||
Read more about :ref:`working_with_the_repository` or continue to the
|
||||
complete :ref:`cli`
|
||||
|
||||
|
|
|
@ -174,10 +174,10 @@ override the default specified in CMD.
|
|||
|
||||
``EXPOSE <port> [<port>...]``
|
||||
|
||||
The ``EXPOSE`` instruction sets ports to be publicly exposed when
|
||||
running the image. This is functionally equivalent to running ``docker
|
||||
commit -run '{"PortSpecs": ["<port>", "<port2>"]}'`` outside the
|
||||
builder. Take a look at :ref:`port_redirection` for more information.
|
||||
The ``EXPOSE`` instruction exposes ports for use within links. This is
|
||||
functionally equivalent to running ``docker commit -run '{"PortSpecs":
|
||||
["<port>", "<port2>"]}'`` outside the builder. Refer to
|
||||
:ref:`port_redirection` for detailed information.
|
||||
|
||||
3.6 ENV
|
||||
-------
|
||||
|
@ -243,7 +243,7 @@ The copy obeys the following rules:
|
|||
considered a regular file and the contents of ``<src>`` will be
|
||||
written at ``<dest>``.
|
||||
* If ``<dest>`` doesn't exist, it is created along with all missing
|
||||
directories in its path.
|
||||
directories in its path.
|
||||
|
||||
.. _entrypoint_def:
|
||||
|
||||
|
|
|
@ -19,3 +19,4 @@ Contents:
|
|||
port_redirection
|
||||
puppet
|
||||
host_integration
|
||||
working_with_volumes
|
||||
|
|
|
@ -8,29 +8,136 @@
|
|||
Port redirection
|
||||
================
|
||||
|
||||
Docker can redirect public TCP and UDP ports to your container, so it can be
|
||||
reached over the network. Port redirection is done on ``docker run``
|
||||
using the -p flag.
|
||||
|
||||
A port redirect is specified as *PUBLIC:PRIVATE*, where TCP port
|
||||
*PUBLIC* will be redirected to TCP port *PRIVATE*. As a special case,
|
||||
the public port can be omitted, in which case a random public port
|
||||
will be allocated.
|
||||
Interacting with a service is commonly done through a connection to a
|
||||
port. When this service runs inside a container, one can connect to
|
||||
the port after finding the IP address of the container as follows:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# A random PUBLIC port is redirected to PRIVATE port 80 on the container
|
||||
sudo docker run -p 80 <image> <cmd>
|
||||
# Find IP address of container with ID <container_id>
|
||||
docker inspect <container_id> | grep IPAddress | cut -d '"' -f 4
|
||||
|
||||
# PUBLIC port 80 is redirected to PRIVATE port 80
|
||||
sudo docker run -p 80:80 <image> <cmd>
|
||||
However, this IP address is local to the host system and the container
|
||||
port is not reachable by the outside world. Furthermore, even if the
|
||||
port is used locally, e.g. by another container, this method is
|
||||
tedious as the IP address of the container changes every time it
|
||||
starts.
|
||||
|
||||
To redirect a UDP port the redirection must be expressed as *PUBLIC:PRIVATE/udp*:
|
||||
Docker addresses these two problems and give a simple and robust way
|
||||
to access services running inside containers.
|
||||
|
||||
To allow non-local clients to reach the service running inside the
|
||||
container, Docker provide ways to bind the container port to an
|
||||
interface of the host system. To simplify communication between
|
||||
containers, Docker provides the linking mechanism.
|
||||
|
||||
Binding a port to an host interface
|
||||
-----------------------------------
|
||||
|
||||
To bind a port of the container to a specific interface of the host
|
||||
system, use the ``-p`` parameter of the ``docker run`` command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# PUBLIC port 5300 is redirected to the PRIVATE port 53 using UDP
|
||||
sudo docker run -p 5300:53/udp <image> <cmd>
|
||||
# General syntax
|
||||
docker run -p [([<host_interface>:[host_port]])|(<host_port>):]<container_port>[/udp] <image> <cmd>
|
||||
|
||||
Default port redirects can be built into a container with the
|
||||
``EXPOSE`` build command.
|
||||
When no host interface is provided, the port is bound to all available
|
||||
interfaces of the host machine (aka INADDR_ANY, or 0.0.0.0).When no host port is
|
||||
provided, one is dynamically allocated. The possible combinations of options for
|
||||
TCP port are the following:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Bind TCP port 8080 of the container to TCP port 80 on 127.0.0.1 of the host machine.
|
||||
docker run -p 127.0.0.1:80:8080 <image> <cmd>
|
||||
|
||||
# Bind TCP port 8080 of the container to a dynamically allocated TCP port on 127.0.0.1 of the host machine.
|
||||
docker run -p 127.0.0.1::8080 <image> <cmd>
|
||||
|
||||
# Bind TCP port 8080 of the container to TCP port 80 on all available interfaces of the host machine.
|
||||
docker run -p 80:8080 <image> <cmd>
|
||||
|
||||
# Bind TCP port 8080 of the container to a dynamically allocated TCP port on all available interfaces of the host machine.
|
||||
docker run -p 8080 <image> <cmd>
|
||||
|
||||
UDP ports can also be bound by adding a trailing ``/udp``. All the
|
||||
combinations described for TCP work. Here is only one example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Bind UDP port 5353 of the container to UDP port 53 on 127.0.0.1 of the host machine.
|
||||
docker run -p 127.0.0.1:53:5353/udp <image> <cmd>
|
||||
|
||||
The command ``docker port`` lists the interface and port on the host
|
||||
machine bound to a given container port. It is useful when using
|
||||
dynamically allocated ports:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Bind to a dynamically allocated port
|
||||
docker run -p 127.0.0.1::8080 -name dyn-bound <image> <cmd>
|
||||
|
||||
# Lookup the actual port
|
||||
docker port dyn-bound 8080
|
||||
127.0.0.1:49160
|
||||
|
||||
|
||||
Linking a container
|
||||
-------------------
|
||||
|
||||
Communication between two containers can also be established in a
|
||||
docker-specific way called linking.
|
||||
|
||||
To briefly present the concept of linking, let us consider two
|
||||
containers: ``server``, containing the service, and ``client``,
|
||||
accessing the service. Once ``server`` is running, ``client`` is
|
||||
started and links to server. Linking sets environment variables in
|
||||
``client`` giving it some information about ``server``. In this sense,
|
||||
linking is a method of service discovery.
|
||||
|
||||
Let us now get back to our topic of interest; communication between
|
||||
the two containers. We mentioned that the tricky part about this
|
||||
communication was that the IP address of ``server`` was not
|
||||
fixed. Therefore, some of the environment variables are going to be
|
||||
used to inform ``client`` about this IP address. This process called
|
||||
exposure, is possible because ``client`` is started after ``server``
|
||||
has been started.
|
||||
|
||||
Here is a full example. On ``server``, the port of interest is
|
||||
exposed. The exposure is done either through the ``-expose`` parameter
|
||||
to the ``docker run`` command, or the ``EXPOSE`` build command in a
|
||||
Dockerfile:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Expose port 80
|
||||
docker run -expose 80 -name server <image> <cmd>
|
||||
|
||||
The ``client`` then links to the ``server``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Link
|
||||
docker run -name client -link server:linked-server <image> <cmd>
|
||||
|
||||
``client`` locally refers to ``server`` as ``linked-server``. The
|
||||
following environment variables, among others, are available on
|
||||
``client``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# The default protocol, ip, and port of the service running in the container
|
||||
LINKED-SERVER_PORT=tcp://172.17.0.8:80
|
||||
|
||||
# A specific protocol, ip, and port of various services
|
||||
LINKED-SERVER_PORT_80_TCP=tcp://172.17.0.8:80
|
||||
LINKED-SERVER_PORT_80_TCP_PROTO=tcp
|
||||
LINKED-SERVER_PORT_80_TCP_ADDR=172.17.0.8
|
||||
LINKED-SERVER_PORT_80_TCP_PORT=80
|
||||
|
||||
This tells ``client`` that a service is running on port 80 of
|
||||
``server`` and that ``server`` is accessible at the IP address
|
||||
172.17.0.8
|
||||
|
||||
Note: Using the ``-p`` parameter also exposes the port..
|
||||
|
|
73
docs/sources/use/working_with_volumes.rst
Normal file
73
docs/sources/use/working_with_volumes.rst
Normal file
|
@ -0,0 +1,73 @@
|
|||
:title: Working with Volumes
|
||||
:description: How to create and share volumes
|
||||
:keywords: Examples, Usage, volume, docker, documentation, examples
|
||||
|
||||
.. _volume_def:
|
||||
|
||||
Data Volume
|
||||
===========
|
||||
|
||||
.. versionadded:: v0.3.0
|
||||
Data volumes have been available since version 1 of the
|
||||
:doc:`../api/docker_remote_api`
|
||||
|
||||
A *data volume* is a specially-designated directory within one or more
|
||||
containers that bypasses the :ref:`ufs_def` to provide several useful
|
||||
features for persistant or shared data:
|
||||
|
||||
* **Data volumes can be shared and reused between containers.** This
|
||||
is the feature that makes data volumes so powerful. You can use it
|
||||
for anything from hot database upgrades to custom backup or
|
||||
replication tools. See the example below.
|
||||
* **Changes to a data volume are made directly**, without the overhead
|
||||
of a copy-on-write mechanism. This is good for very large files.
|
||||
* **Changes to a data volume will not be included at the next commit**
|
||||
because they are not recorded as regular filesystem changes in the
|
||||
top layer of the :ref:`ufs_def`
|
||||
|
||||
Each container can have zero or more data volumes.
|
||||
|
||||
Getting Started
|
||||
...............
|
||||
|
||||
|
||||
|
||||
Using data volumes is as simple as adding a new flag: ``-v``. The parameter ``-v`` can be used more than once in order to create more volumes within the new container. The example below shows the instruction to create a container with two new volumes::
|
||||
|
||||
docker run -v /var/volume1 -v /var/volume2 shykes/couchdb
|
||||
|
||||
For a Dockerfile, the VOLUME instruction will add one or more new volumes to any container created from the image::
|
||||
|
||||
VOLUME ["/var/volume1", "/var/volume2"]
|
||||
|
||||
|
||||
Create a new container using existing volumes from an existing container:
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
|
||||
The command below creates a new container which is runnning as daemon ``-d`` and with one volume ``/var/lib/couchdb``::
|
||||
|
||||
COUCH1=$(sudo docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
From the container id of that previous container ``$COUCH1`` it's possible to create new container sharing the same volume using the parameter ``-volumes-from container_id``::
|
||||
|
||||
COUCH2=$(sudo docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
|
||||
Now, the second container has the all the information from the first volume.
|
||||
|
||||
|
||||
Create a new container which mounts a host directory into it:
|
||||
-------------------------------------------------------------
|
||||
|
||||
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro].
|
||||
If "host-dir" is missing, then docker creates a new volume.
|
||||
|
||||
This is not available for a Dockerfile due the portability and sharing purpose of it. The [host-dir] volumes is something 100% host dependent and will break on any other machine.
|
||||
|
||||
For example::
|
||||
|
||||
sudo docker run -v /var/logs:/var/host_logs:ro shykes/couchdb:2013-05-03
|
||||
|
||||
The command above mounts the host directory ``/var/logs`` into the container with read only permissions as ``/var/host_logs``.
|
||||
|
||||
.. versionadded:: v0.5.0
|
|
@ -2,13 +2,12 @@ package engine
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"log"
|
||||
"runtime"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
|
||||
type Handler func(*Job) string
|
||||
|
||||
var globalHandlers map[string]Handler
|
||||
|
@ -25,8 +24,8 @@ func Register(name string, handler Handler) error {
|
|||
// It acts as a store for *containers*, and allows manipulation of these
|
||||
// containers by executing *jobs*.
|
||||
type Engine struct {
|
||||
root string
|
||||
handlers map[string]Handler
|
||||
root string
|
||||
handlers map[string]Handler
|
||||
}
|
||||
|
||||
// New initializes a new engine managing the directory specified at `root`.
|
||||
|
@ -56,8 +55,8 @@ func New(root string) (*Engine, error) {
|
|||
return nil, err
|
||||
}
|
||||
eng := &Engine{
|
||||
root: root,
|
||||
handlers: globalHandlers,
|
||||
root: root,
|
||||
handlers: globalHandlers,
|
||||
}
|
||||
return eng, nil
|
||||
}
|
||||
|
@ -66,12 +65,12 @@ func New(root string) (*Engine, error) {
|
|||
// This function mimics `Command` from the standard os/exec package.
|
||||
func (eng *Engine) Job(name string, args ...string) *Job {
|
||||
job := &Job{
|
||||
eng: eng,
|
||||
Name: name,
|
||||
Args: args,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
eng: eng,
|
||||
Name: name,
|
||||
Args: args,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
handler, exists := eng.handlers[name]
|
||||
if exists {
|
||||
|
@ -79,4 +78,3 @@ func (eng *Engine) Job(name string, args ...string) *Job {
|
|||
}
|
||||
return job
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var globalTestID string
|
||||
|
||||
func init() {
|
||||
Register("dummy", func(job *Job) string { return ""; })
|
||||
Register("dummy", func(job *Job) string { return "" })
|
||||
}
|
||||
|
||||
func mkEngine(t *testing.T) *Engine {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"strings"
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
// A job is the fundamental unit of work in the docker engine.
|
||||
|
@ -20,17 +20,17 @@ import (
|
|||
// One slight variation is that jobs report their status as a string. The
|
||||
// string "0" indicates success, and any other strings indicates an error.
|
||||
// This allows for richer error reporting.
|
||||
//
|
||||
//
|
||||
type Job struct {
|
||||
eng *Engine
|
||||
Name string
|
||||
Args []string
|
||||
env []string
|
||||
Stdin io.ReadCloser
|
||||
Stdout io.WriteCloser
|
||||
Stderr io.WriteCloser
|
||||
handler func(*Job) string
|
||||
status string
|
||||
eng *Engine
|
||||
Name string
|
||||
Args []string
|
||||
env []string
|
||||
Stdin io.ReadCloser
|
||||
Stdout io.WriteCloser
|
||||
Stderr io.WriteCloser
|
||||
handler func(*Job) string
|
||||
status string
|
||||
}
|
||||
|
||||
// Run executes the job and blocks until the job completes.
|
||||
|
@ -57,21 +57,21 @@ func (job *Job) String() string {
|
|||
}
|
||||
|
||||
func (job *Job) Getenv(key string) (value string) {
|
||||
for _, kv := range job.env {
|
||||
if strings.Index(kv, "=") == -1 {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if parts[0] != key {
|
||||
continue
|
||||
}
|
||||
if len(parts) < 2 {
|
||||
value = ""
|
||||
} else {
|
||||
value = parts[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
for _, kv := range job.env {
|
||||
if strings.Index(kv, "=") == -1 {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if parts[0] != key {
|
||||
continue
|
||||
}
|
||||
if len(parts) < 2 {
|
||||
value = ""
|
||||
} else {
|
||||
value = parts[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (job *Job) GetenvBool(key string) (value bool) {
|
||||
|
@ -109,5 +109,5 @@ func (job *Job) SetenvList(key string, value []string) error {
|
|||
}
|
||||
|
||||
func (job *Job) Setenv(key, value string) {
|
||||
job.env = append(job.env, key + "=" + value)
|
||||
job.env = append(job.env, key+"="+value)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"path"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -47,6 +48,7 @@ type WalkFunc func(fullPath string, entity *Entity) error
|
|||
// Graph database for storing entities and their relationships
|
||||
type Database struct {
|
||||
conn *sql.DB
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
// Create a new graph database initialized with a root entity
|
||||
|
@ -54,7 +56,7 @@ func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
|
|||
if conn == nil {
|
||||
return nil, fmt.Errorf("Database connection cannot be nil")
|
||||
}
|
||||
db := &Database{conn}
|
||||
db := &Database{conn: conn}
|
||||
|
||||
if init {
|
||||
if _, err := conn.Exec(createEntityTable); err != nil {
|
||||
|
@ -99,7 +101,9 @@ func (db *Database) Close() error {
|
|||
|
||||
// Set the entity id for a given path
|
||||
func (db *Database) Set(fullPath, id string) (*Entity, error) {
|
||||
// FIXME: is rollback implicit when closing the connection?
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
|
||||
rollback := func() {
|
||||
db.conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
@ -256,6 +260,9 @@ func (db *Database) RefPaths(id string) Edges {
|
|||
|
||||
// Delete the reference to an entity at a given path
|
||||
func (db *Database) Delete(name string) error {
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
|
||||
if name == "/" {
|
||||
return fmt.Errorf("Cannot delete root entity")
|
||||
}
|
||||
|
@ -276,6 +283,9 @@ func (db *Database) Delete(name string) error {
|
|||
// Walk the graph to make sure all references to the entity
|
||||
// are removed and return the number of references removed
|
||||
func (db *Database) Purge(id string) (int, error) {
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
|
||||
rollback := func() {
|
||||
db.conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
@ -310,6 +320,9 @@ func (db *Database) Purge(id string) (int, error) {
|
|||
|
||||
// Rename an edge for a given path
|
||||
func (db *Database) Rename(currentName, newName string) error {
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
|
||||
parentPath, name := splitPath(currentName)
|
||||
newParentPath, newEdgeName := splitPath(newName)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package gograph
|
|||
import (
|
||||
_ "code.google.com/p/gosqlite/sqlite3"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
@ -501,3 +502,39 @@ func TestGetNameWithTrailingSlash(t *testing.T) {
|
|||
t.Fatalf("Entity should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentWrites(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
|
||||
save := func(name string, id string) {
|
||||
if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
|
||||
errs <- err
|
||||
}
|
||||
errs <- nil
|
||||
}
|
||||
purge := func(id string) {
|
||||
if _, err := db.Purge(id); err != nil {
|
||||
errs <- err
|
||||
}
|
||||
errs <- nil
|
||||
}
|
||||
|
||||
save("/1", "1")
|
||||
|
||||
go purge("1")
|
||||
go save("/2", "2")
|
||||
|
||||
any := false
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := <-errs; err != nil {
|
||||
any = true
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
if any {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,14 @@ mountpoint -q $CGROUP ||
|
|||
exit 1
|
||||
}
|
||||
|
||||
if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
|
||||
then
|
||||
mount -t securityfs none /sys/kernel/security || {
|
||||
echo "Could not mount /sys/kernel/security."
|
||||
echo "AppArmor detection and -privileged mode might break."
|
||||
}
|
||||
fi
|
||||
|
||||
# Mount the cgroup hierarchies exactly as they are in the parent system.
|
||||
for SUBSYS in $(cut -d: -f2 /proc/1/cgroup)
|
||||
do
|
||||
|
|
|
@ -135,11 +135,6 @@ sudo('curl -s https://phantomjs.googlecode.com/files/'
|
|||
'phantomjs-1.9.1-linux-x86_64.tar.bz2 | tar jx -C /usr/bin'
|
||||
' --strip-components=2 phantomjs-1.9.1-linux-x86_64/bin/phantomjs')
|
||||
|
||||
#### FIXME. Temporarily install docker with proper apparmor handling
|
||||
sudo('stop docker')
|
||||
sudo('wget -q -O /usr/bin/docker http://test.docker.io/test/docker')
|
||||
sudo('start docker')
|
||||
|
||||
# Preventively reboot docker-ci daily
|
||||
sudo('ln -s /sbin/reboot /etc/cron.daily')
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
# "GPG_PASSPHRASE='Test_docker_GPG_passphrase_signature'
|
||||
# "INDEX_AUTH='Encripted_index_authentication' }
|
||||
# TO_BUILD: docker build -t dockerbuilder .
|
||||
# TO_RELEASE: docker run -i -t -privileged -lxc-conf="lxc.aa_profile = unconfined" -e AWS_S3_BUCKET="test.docker.io" dockerbuilder
|
||||
# TO_RELEASE: docker run -i -t -privileged -e AWS_S3_BUCKET="test.docker.io" dockerbuilder
|
||||
|
||||
from docker
|
||||
maintainer Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
|
@ -23,9 +23,6 @@ run apt-get update; apt-get install -y -q wget python2.7
|
|||
# Add production docker binary
|
||||
run wget -q -O /usr/bin/docker http://get.docker.io/builds/Linux/x86_64/docker-latest; chmod +x /usr/bin/docker
|
||||
|
||||
#### FIXME. Temporarily install docker with proper apparmor handling
|
||||
run wget -q -O /usr/bin/docker http://test.docker.io/test/docker; chmod +x /usr/bin/docker
|
||||
|
||||
# Add proto docker builder
|
||||
add ./dockerbuild /usr/bin/dockerbuild
|
||||
run chmod +x /usr/bin/dockerbuild
|
||||
|
|
|
@ -13,9 +13,6 @@ cd /
|
|||
git clone -q http://github.com/dotcloud/docker /go/src/github.com/dotcloud/docker
|
||||
cd /go/src/github.com/dotcloud/docker
|
||||
|
||||
echo FIXME. Temporarily skip TestPrivilegedCanMount until DinD works reliable on AWS
|
||||
git pull -q https://github.com/mzdaniel/docker.git dind-aws || exit 1
|
||||
|
||||
# Launch docker daemon using dind inside the container
|
||||
./hack/dind /usr/bin/docker -d &
|
||||
sleep 5
|
||||
|
@ -27,7 +24,7 @@ date > timestamp
|
|||
docker build -t docker .
|
||||
|
||||
# Run Docker unittests binary and Ubuntu package
|
||||
docker run -privileged -lxc-conf=lxc.aa_profile=unconfined docker hack/make.sh
|
||||
docker run -privileged docker hack/make.sh
|
||||
exit_status=$?
|
||||
|
||||
# Display load if test fails
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -27,8 +28,10 @@ type Chain struct {
|
|||
}
|
||||
|
||||
func NewChain(name, bridge string) (*Chain, error) {
|
||||
if err := Raw("-t", "nat", "-N", name); err != nil {
|
||||
if output, err := Raw("-t", "nat", "-N", name); err != nil {
|
||||
return nil, err
|
||||
} else if len(output) != 0 {
|
||||
return nil, fmt.Errorf("Error creating new iptables chain: %s", output)
|
||||
}
|
||||
chain := &Chain{
|
||||
Name: name,
|
||||
|
@ -52,13 +55,18 @@ func RemoveExistingChain(name string) error {
|
|||
}
|
||||
|
||||
func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error {
|
||||
return Raw("-t", "nat", fmt.Sprint(action), c.Name,
|
||||
if output, err := Raw("-t", "nat", fmt.Sprint(action), c.Name,
|
||||
"-p", proto,
|
||||
"-d", ip.String(),
|
||||
"--dport", strconv.Itoa(port),
|
||||
"!", "-i", c.Bridge,
|
||||
"-j", "DNAT",
|
||||
"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
|
||||
"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))); err != nil {
|
||||
return err
|
||||
} else if len(output) != 0 {
|
||||
return fmt.Errorf("Error iptables forward: %s", output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Chain) Prerouting(action Action, args ...string) error {
|
||||
|
@ -66,7 +74,12 @@ func (c *Chain) Prerouting(action Action, args ...string) error {
|
|||
if len(args) > 0 {
|
||||
a = append(a, args...)
|
||||
}
|
||||
return Raw(append(a, "-j", c.Name)...)
|
||||
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
|
||||
return err
|
||||
} else if len(output) != 0 {
|
||||
return fmt.Errorf("Error iptables prerouting: %s", output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Chain) Output(action Action, args ...string) error {
|
||||
|
@ -74,7 +87,12 @@ func (c *Chain) Output(action Action, args ...string) error {
|
|||
if len(args) > 0 {
|
||||
a = append(a, args...)
|
||||
}
|
||||
return Raw(append(a, "-j", c.Name)...)
|
||||
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
|
||||
return err
|
||||
} else if len(output) != 0 {
|
||||
return fmt.Errorf("Error iptables output: %s", output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Chain) Remove() error {
|
||||
|
@ -94,17 +112,23 @@ func (c *Chain) Remove() error {
|
|||
|
||||
// Check if an existing rule exists
|
||||
func Exists(args ...string) bool {
|
||||
return Raw(append([]string{"-C"}, args...)...) == nil
|
||||
if _, err := Raw(append([]string{"-C"}, args...)...); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Raw(args ...string) error {
|
||||
func Raw(args ...string) ([]byte, error) {
|
||||
path, err := exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return ErrIptablesNotFound
|
||||
return nil, ErrIptablesNotFound
|
||||
}
|
||||
if err := exec.Command(path, args...).Run(); err != nil {
|
||||
return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
fmt.Printf("[DEBUG] [iptables]: %s, %v\n", path, args)
|
||||
}
|
||||
return nil
|
||||
|
||||
output, err := exec.Command(path, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
|
||||
}
|
||||
return output, err
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ import (
|
|||
)
|
||||
|
||||
func TestIptables(t *testing.T) {
|
||||
if err := Raw("-L"); err != nil {
|
||||
if _, err := Raw("-L"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := os.Getenv("PATH")
|
||||
os.Setenv("PATH", "")
|
||||
defer os.Setenv("PATH", path)
|
||||
if err := Raw("-L"); err == nil {
|
||||
if _, err := Raw("-L"); err == nil {
|
||||
t.Fatal("Not finding iptables in the PATH should cause an error")
|
||||
}
|
||||
}
|
||||
|
|
8
links.go
8
links.go
|
@ -120,7 +120,7 @@ func (l *Link) Disable() {
|
|||
|
||||
func (l *Link) toggle(action string, ignoreErrors bool) error {
|
||||
for _, p := range l.Ports {
|
||||
if err := iptables.Raw(action, "FORWARD",
|
||||
if output, err := iptables.Raw(action, "FORWARD",
|
||||
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
|
||||
"-p", p.Proto(),
|
||||
"-s", l.ParentIP,
|
||||
|
@ -128,9 +128,11 @@ func (l *Link) toggle(action string, ignoreErrors bool) error {
|
|||
"-d", l.ChildIP,
|
||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||
return err
|
||||
} else if len(output) != 0 {
|
||||
return fmt.Errorf("Error toggle iptables forward: %s", output)
|
||||
}
|
||||
|
||||
if err := iptables.Raw(action, "FORWARD",
|
||||
if output, err := iptables.Raw(action, "FORWARD",
|
||||
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
|
||||
"-p", p.Proto(),
|
||||
"-s", l.ChildIP,
|
||||
|
@ -138,6 +140,8 @@ func (l *Link) toggle(action string, ignoreErrors bool) error {
|
|||
"-d", l.ParentIP,
|
||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||
return err
|
||||
} else if len(output) != 0 {
|
||||
return fmt.Errorf("Error toggle iptables forward: %s", output)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -11,7 +11,6 @@ lxc.utsname = {{.Config.Hostname}}
|
|||
{{else}}
|
||||
lxc.utsname = {{.Id}}
|
||||
{{end}}
|
||||
#lxc.aa_profile = unconfined
|
||||
|
||||
{{if .Config.NetworkDisabled}}
|
||||
# network is disabled (-n=false)
|
||||
|
@ -46,7 +45,7 @@ lxc.console = none
|
|||
# no controlling tty at all
|
||||
lxc.tty = 1
|
||||
|
||||
{{if .Config.Privileged}}
|
||||
{{if (getHostConfig .).Privileged}}
|
||||
lxc.cgroup.devices.allow = a
|
||||
{{else}}
|
||||
# no implicit access to devices
|
||||
|
@ -66,7 +65,7 @@ lxc.cgroup.devices.allow = c 4:1 rwm
|
|||
lxc.cgroup.devices.allow = c 1:9 rwm
|
||||
lxc.cgroup.devices.allow = c 1:8 rwm
|
||||
|
||||
# /dev/pts/* - pts namespaces are "coming soon"
|
||||
# /dev/pts/ - pts namespaces are "coming soon"
|
||||
lxc.cgroup.devices.allow = c 136:* rwm
|
||||
lxc.cgroup.devices.allow = c 5:2 rwm
|
||||
|
||||
|
@ -109,8 +108,13 @@ lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,{{ if ind
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Config.Privileged}}
|
||||
{{if (getHostConfig .).Privileged}}
|
||||
# retain all capabilities; no lxc.cap.drop line
|
||||
{{if (getCapabilities .).AppArmor}}
|
||||
lxc.aa_profile = unconfined
|
||||
{{else}}
|
||||
#lxc.aa_profile = unconfined
|
||||
{{end}}
|
||||
{{else}}
|
||||
# drop linux capabilities (apply mainly to the user root in the container)
|
||||
# (Note: 'lxc.cap.keep' is coming soon and should replace this under the
|
||||
|
@ -130,18 +134,15 @@ lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}}
|
|||
{{if .Config.CpuShares}}
|
||||
lxc.cgroup.cpu.shares = {{.Config.CpuShares}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
const LxcHostConfigTemplate = `
|
||||
{{if .LxcConf}}
|
||||
{{range $pair := .LxcConf}}
|
||||
{{if (getHostConfig .).LxcConf}}
|
||||
{{range $pair := (getHostConfig .).LxcConf}}
|
||||
{{$pair.Key}} = {{$pair.Value}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
var LxcTemplateCompiled *template.Template
|
||||
var LxcHostConfigTemplateCompiled *template.Template
|
||||
|
||||
func getMemorySwap(config *Config) int64 {
|
||||
// By default, MemorySwap is set to twice the size of RAM.
|
||||
|
@ -152,17 +153,23 @@ func getMemorySwap(config *Config) int64 {
|
|||
return config.Memory * 2
|
||||
}
|
||||
|
||||
func getHostConfig(container *Container) *HostConfig {
|
||||
return container.hostConfig
|
||||
}
|
||||
|
||||
func getCapabilities(container *Container) *Capabilities {
|
||||
return container.runtime.capabilities
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
funcMap := template.FuncMap{
|
||||
"getMemorySwap": getMemorySwap,
|
||||
"getMemorySwap": getMemorySwap,
|
||||
"getHostConfig": getHostConfig,
|
||||
"getCapabilities": getCapabilities,
|
||||
}
|
||||
LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
LxcHostConfigTemplateCompiled, err = template.New("lxc-hostconfig").Funcs(funcMap).Parse(LxcHostConfigTemplate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ type NameChecker interface {
|
|||
}
|
||||
|
||||
var (
|
||||
colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple"}
|
||||
animals = [...]string{"ant", "bird", "cat", "chicken", "cow", "dog", "fish", "fox", "horse", "lion", "monkey", "pig", "sheep", "tiger", "whale", "wolf"}
|
||||
colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple", "maroon", "crimson", "plum", "fuchsia", "lavender", "slate", "navy", "azure", "aqua", "olive", "teal", "lime", "beige", "tan", "sienna"}
|
||||
animals = [...]string{"ant", "bear", "bird", "cat", "chicken", "cow", "deer", "dog", "donkey", "duck", "fish", "fox", "frog", "horse", "kangaroo", "koala", "lemur", "lion", "lizard", "monkey", "octopus", "pig", "shark", "sheep", "sloth", "spider", "squirrel", "tiger", "toad", "weasel", "whale", "wolf"}
|
||||
)
|
||||
|
||||
func GenerateRandomName(checker NameChecker) (string, error) {
|
||||
|
|
|
@ -9,7 +9,6 @@ func NetworkGetRoutes() ([]*net.IPNet, error) {
|
|||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
|
||||
func NetworkLinkAdd(name string, linkType string) error {
|
||||
return fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
@ -18,7 +17,6 @@ func NetworkLinkUp(iface *net.Interface) error {
|
|||
return fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
|
||||
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
||||
return fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
|
39
network.go
39
network.go
|
@ -76,6 +76,21 @@ func checkRouteOverlaps(networks []*net.IPNet, dockerNetwork *net.IPNet) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkNameserverOverlaps(nameservers []string, dockerNetwork *net.IPNet) error {
|
||||
if len(nameservers) > 0 {
|
||||
for _, ns := range nameservers {
|
||||
_, nsNetwork, err := net.ParseCIDR(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if networkOverlaps(dockerNetwork, nsNetwork) {
|
||||
return fmt.Errorf("%s overlaps nameserver %s", dockerNetwork, nsNetwork)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`,
|
||||
// and attempts to configure it with an address which doesn't conflict with any other interface on the host.
|
||||
// If it can't find an address which doesn't conflict, it will return an error.
|
||||
|
@ -100,6 +115,16 @@ func CreateBridgeIface(config *DaemonConfig) error {
|
|||
"192.168.44.1/24",
|
||||
}
|
||||
|
||||
nameservers := []string{}
|
||||
resolvConf, _ := utils.GetResolvConf()
|
||||
// we don't check for an error here, because we don't really care
|
||||
// if we can't read /etc/resolv.conf. So instead we skip the append
|
||||
// if resolvConf is nil. It either doesn't exist, or we can't read it
|
||||
// for some reason.
|
||||
if resolvConf != nil {
|
||||
nameservers = append(nameservers, utils.GetNameserversAsCIDR(resolvConf)...)
|
||||
}
|
||||
|
||||
var ifaceAddr string
|
||||
for _, addr := range addrs {
|
||||
_, dockerNetwork, err := net.ParseCIDR(addr)
|
||||
|
@ -111,8 +136,10 @@ func CreateBridgeIface(config *DaemonConfig) error {
|
|||
return err
|
||||
}
|
||||
if err := checkRouteOverlaps(routes, dockerNetwork); err == nil {
|
||||
ifaceAddr = addr
|
||||
break
|
||||
if err := checkNameserverOverlaps(nameservers, dockerNetwork); err == nil {
|
||||
ifaceAddr = addr
|
||||
break
|
||||
}
|
||||
} else {
|
||||
utils.Debugf("%s: %s", addr, err)
|
||||
}
|
||||
|
@ -141,9 +168,11 @@ func CreateBridgeIface(config *DaemonConfig) error {
|
|||
}
|
||||
|
||||
if config.EnableIptables {
|
||||
if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
|
||||
if output, err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
|
||||
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
|
||||
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
|
||||
} else if len(output) != 0 {
|
||||
return fmt.Errorf("Error iptables postrouting: %s", output)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -656,8 +685,10 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) {
|
|||
if !config.InterContainerCommunication {
|
||||
if !iptables.Exists(args...) {
|
||||
utils.Debugf("Disable inter-container communication")
|
||||
if err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil {
|
||||
if output, err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil {
|
||||
return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
|
||||
} else if len(output) != 0 {
|
||||
return nil, fmt.Errorf("Error enabling iptables: %s", output)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -295,3 +295,19 @@ func TestCheckRouteOverlaps(t *testing.T) {
|
|||
t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckNameserverOverlaps(t *testing.T) {
|
||||
nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"}
|
||||
|
||||
_, netX, _ := net.ParseCIDR("10.0.2.3/32")
|
||||
|
||||
if err := checkNameserverOverlaps(nameservers, netX); err == nil {
|
||||
t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX)
|
||||
}
|
||||
|
||||
_, netX, _ = net.ParseCIDR("192.168.102.2/32")
|
||||
|
||||
if err := checkNameserverOverlaps(nameservers, netX); err != nil {
|
||||
t.Fatalf("%s should not overlap %v but it does", netX, nameservers)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -615,10 +615,18 @@ func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
|
|||
}
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
StarCount int `json:"star_count"`
|
||||
IsOfficial bool `json:"is_official"`
|
||||
Name string `json:"name"`
|
||||
IsTrusted bool `json:"is_trusted"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type SearchResults struct {
|
||||
Query string `json:"query"`
|
||||
NumResults int `json:"num_results"`
|
||||
Results []map[string]string `json:"results"`
|
||||
Query string `json:"query"`
|
||||
NumResults int `json:"num_results"`
|
||||
Results []SearchResult `json:"results"`
|
||||
}
|
||||
|
||||
type RepositoryData struct {
|
||||
|
|
86
runtime.go
86
runtime.go
|
@ -26,6 +26,7 @@ type Capabilities struct {
|
|||
MemoryLimit bool
|
||||
SwapLimit bool
|
||||
IPv4ForwardingDisabled bool
|
||||
AppArmor bool
|
||||
}
|
||||
|
||||
type Runtime struct {
|
||||
|
@ -115,6 +116,9 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
if err := validateID(container.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runtime.ensureName(container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the root filesystem from the driver
|
||||
rootfs, err := runtime.driver.Get(container.ID)
|
||||
|
@ -159,8 +163,7 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
utils.Debugf("Restarting")
|
||||
container.State.Ghost = false
|
||||
container.State.setStopped(0)
|
||||
hostConfig, _ := container.ReadHostConfig()
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
nomonitor = true
|
||||
|
@ -179,9 +182,27 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
if !container.State.Running {
|
||||
close(container.waitLock)
|
||||
} else if !nomonitor {
|
||||
hostConfig, _ := container.ReadHostConfig()
|
||||
container.allocateNetwork(hostConfig)
|
||||
go container.monitor(hostConfig)
|
||||
go container.monitor()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) ensureName(container *Container) error {
|
||||
if container.Name == "" {
|
||||
name, err := generateRandomName(runtime)
|
||||
if err != nil {
|
||||
name = container.ShortID()
|
||||
}
|
||||
container.Name = name
|
||||
|
||||
if err := container.ToDisk(); err != nil {
|
||||
utils.Debugf("Error saving container name %s", err)
|
||||
}
|
||||
if !runtime.containerGraph.Exists(name) {
|
||||
if _, err := runtime.containerGraph.Set(name, container.ID); err != nil {
|
||||
utils.Debugf("Setting default id - %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -271,13 +292,12 @@ func (runtime *Runtime) restore() error {
|
|||
// Any containers that are left over do not exist in the graph
|
||||
for _, container := range containers {
|
||||
// Try to set the default name for a container if it exists prior to links
|
||||
name, err := generateRandomName(runtime)
|
||||
container.Name, err = generateRandomName(runtime)
|
||||
if err != nil {
|
||||
container.Name = container.ShortID()
|
||||
}
|
||||
container.Name = name
|
||||
|
||||
if _, err := runtime.containerGraph.Set(name, container.ID); err != nil {
|
||||
if _, err := runtime.containerGraph.Set(container.Name, container.ID); err != nil {
|
||||
utils.Debugf("Setting default id - %s", err)
|
||||
}
|
||||
register(container)
|
||||
|
@ -316,6 +336,15 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
|
|||
if runtime.capabilities.IPv4ForwardingDisabled && !quiet {
|
||||
log.Printf("WARNING: IPv4 forwarding is disabled.")
|
||||
}
|
||||
|
||||
// Check if AppArmor seems to be enabled on this system.
|
||||
if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) {
|
||||
utils.Debugf("/sys/kernel/security/apparmor not found; assuming AppArmor is not enabled.")
|
||||
runtime.capabilities.AppArmor = false
|
||||
} else {
|
||||
utils.Debugf("/sys/kernel/security/apparmor found; assuming AppArmor is enabled.")
|
||||
runtime.capabilities.AppArmor = true
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new container from the given configuration with a given name.
|
||||
|
@ -406,6 +435,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
|
|||
Path: entrypoint,
|
||||
Args: args, //FIXME: de-duplicate from config
|
||||
Config: config,
|
||||
hostConfig: &HostConfig{},
|
||||
Image: img.ID, // Always use the resolved image id
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
// FIXME: do we need to store this in the container?
|
||||
|
@ -531,15 +561,22 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a
|
|||
return img, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) getFullName(name string) string {
|
||||
func (runtime *Runtime) getFullName(name string) (string, error) {
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("Container name cannot be empty")
|
||||
}
|
||||
if name[0] != '/' {
|
||||
name = "/" + name
|
||||
}
|
||||
return name
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) GetByName(name string) (*Container, error) {
|
||||
entity := runtime.containerGraph.Get(runtime.getFullName(name))
|
||||
fullName, err := runtime.getFullName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entity := runtime.containerGraph.Get(fullName)
|
||||
if entity == nil {
|
||||
return nil, fmt.Errorf("Could not find entity for %s", name)
|
||||
}
|
||||
|
@ -551,10 +588,13 @@ func (runtime *Runtime) GetByName(name string) (*Container, error) {
|
|||
}
|
||||
|
||||
func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
|
||||
name = runtime.getFullName(name)
|
||||
name, err := runtime.getFullName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
children := make(map[string]*Container)
|
||||
|
||||
err := runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error {
|
||||
err = runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error {
|
||||
c := runtime.Get(e.ID())
|
||||
if c == nil {
|
||||
return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p)
|
||||
|
@ -601,6 +641,9 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := linkLxcStart(config.Root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g, err := NewGraph(path.Join(config.Root, "graph"), driver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -690,6 +733,23 @@ func (runtime *Runtime) Changes(container *Container) ([]graphdriver.Change, err
|
|||
return runtime.driver.Changes(container.ID)
|
||||
}
|
||||
|
||||
func linkLxcStart(root string) error {
|
||||
sourcePath, err := exec.LookPath("lxc-start")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetPath := path.Join(root, "lxc-start-unconfined")
|
||||
|
||||
if _, err := os.Stat(targetPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
} else if err == nil {
|
||||
if err := os.Remove(targetPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Symlink(sourcePath, targetPath)
|
||||
}
|
||||
|
||||
// History is a convenience type for storing a list of containers,
|
||||
// ordered by creation date.
|
||||
type History []*Container
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
const (
|
||||
unitTestImageName = "docker-test-image"
|
||||
unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
|
||||
unitTestImageIDShort = "83599e29c455"
|
||||
unitTestNetworkBridge = "testdockbr0"
|
||||
unitTestStoreBase = "/var/lib/docker/unit-tests"
|
||||
testDaemonAddr = "127.0.0.1:4270"
|
||||
|
@ -119,7 +120,7 @@ func init() {
|
|||
|
||||
func setupBaseImage() {
|
||||
config := &DaemonConfig{
|
||||
Root: unitTestStoreBase,
|
||||
Root: unitTestStoreBase,
|
||||
AutoRestart: false,
|
||||
BridgeIface: unitTestNetworkBridge,
|
||||
}
|
||||
|
@ -325,13 +326,13 @@ func TestGet(t *testing.T) {
|
|||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
container1, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
container2, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
container2, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
container3, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
container3, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime.Destroy(container3)
|
||||
|
||||
if runtime.Get(container1.ID) != container1 {
|
||||
|
@ -390,13 +391,13 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
|
|||
t.Logf("Port %v already in use, trying another one", strPort)
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{
|
||||
container.hostConfig = &HostConfig{
|
||||
PortBindings: make(map[Port][]PortBinding),
|
||||
}
|
||||
hostConfig.PortBindings[p] = []PortBinding{
|
||||
container.hostConfig.PortBindings[p] = []PortBinding{
|
||||
{},
|
||||
}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
nuke(runtime)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -503,16 +504,15 @@ func TestRestore(t *testing.T) {
|
|||
runtime1 := mkRuntime(t)
|
||||
defer nuke(runtime1)
|
||||
// Create a container with one instance of docker
|
||||
container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
|
||||
container1, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime1.Destroy(container1)
|
||||
|
||||
// Create a second container meant to be killed
|
||||
container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
|
||||
container2, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
|
||||
defer runtime1.Destroy(container2)
|
||||
|
||||
// Start the container non blocking
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -575,25 +575,23 @@ func TestReloadContainerLinks(t *testing.T) {
|
|||
runtime1 := mkRuntime(t)
|
||||
defer nuke(runtime1)
|
||||
// Create a container with one instance of docker
|
||||
container1, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/sh"}, t)
|
||||
container1, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/sh"}, t)
|
||||
defer runtime1.Destroy(container1)
|
||||
|
||||
// Create a second container meant to be killed
|
||||
container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
|
||||
container2, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
|
||||
defer runtime1.Destroy(container2)
|
||||
|
||||
// Start the container non blocking
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h1 := &HostConfig{}
|
||||
// Add a link to container 2
|
||||
h1.Links = []string{"/" + container2.ID + ":first"}
|
||||
container1.hostConfig.Links = []string{"/" + container2.ID + ":first"}
|
||||
if err := runtime1.RegisterLink(container1, container2, "first"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container1.Start(h1); err != nil {
|
||||
if err := container1.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -831,3 +829,19 @@ func TestGetAllChildren(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFullName(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
name, err := runtime.getFullName("testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "/testing" {
|
||||
t.Fatalf("Expected /testing got %s", name)
|
||||
}
|
||||
if _, err := runtime.getFullName(""); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
|
105
server.go
105
server.go
|
@ -72,13 +72,16 @@ func (srv *Server) Daemon() error {
|
|||
chErrors := make(chan error, len(protoAddrs))
|
||||
for _, protoAddr := range protoAddrs {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if protoAddrParts[0] == "unix" {
|
||||
syscall.Unlink(protoAddrParts[1])
|
||||
} else if protoAddrParts[0] == "tcp" {
|
||||
switch protoAddrParts[0] {
|
||||
case "unix":
|
||||
if err := syscall.Unlink(protoAddrParts[1]); err != nil && !os.IsNotExist(err) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "tcp":
|
||||
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
return fmt.Errorf("Invalid protocol format.")
|
||||
}
|
||||
go func() {
|
||||
|
@ -184,7 +187,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
|
|||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
|
||||
func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
|
||||
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -193,15 +196,7 @@ func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var outs []APISearch
|
||||
for _, repo := range results.Results {
|
||||
var out APISearch
|
||||
out.Description = repo["description"]
|
||||
out.Name = repo["name"]
|
||||
outs = append(outs, out)
|
||||
}
|
||||
return outs, nil
|
||||
return results.Results, nil
|
||||
}
|
||||
|
||||
func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
|
||||
|
@ -253,7 +248,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
|
|||
for _, image := range images {
|
||||
parentImage, err = image.GetParent()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Error while getting parent image: %v", err)
|
||||
}
|
||||
if parentImage != nil {
|
||||
out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n"))
|
||||
|
@ -290,7 +285,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outs := []APIImages{} //produce [] when empty instead of 'null'
|
||||
lookup := make(map[string]APIImages)
|
||||
for name, repository := range srv.runtime.repositories.Repositories {
|
||||
if filter != "" {
|
||||
if match, _ := path.Match(filter, name); !match {
|
||||
|
@ -298,27 +293,46 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
|
|||
}
|
||||
}
|
||||
for tag, id := range repository {
|
||||
var out APIImages
|
||||
image, err := srv.runtime.graph.Get(id)
|
||||
if err != nil {
|
||||
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
|
||||
continue
|
||||
}
|
||||
delete(allImages, id)
|
||||
out.Repository = name
|
||||
out.Tag = tag
|
||||
out.ID = image.ID
|
||||
out.Created = image.Created.Unix()
|
||||
out.Size = image.Size
|
||||
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||
outs = append(outs, out)
|
||||
|
||||
if out, exists := lookup[id]; exists {
|
||||
out.RepoTags = append(out.RepoTags, fmt.Sprintf("%s:%s", name, tag))
|
||||
|
||||
lookup[id] = out
|
||||
} else {
|
||||
var out APIImages
|
||||
|
||||
delete(allImages, id)
|
||||
|
||||
out.ParentId = image.Parent
|
||||
out.RepoTags = []string{fmt.Sprintf("%s:%s", name, tag)}
|
||||
out.ID = image.ID
|
||||
out.Created = image.Created.Unix()
|
||||
out.Size = image.Size
|
||||
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||
|
||||
lookup[id] = out
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// Display images which aren't part of a
|
||||
|
||||
outs := make([]APIImages, 0, len(lookup))
|
||||
for _, value := range lookup {
|
||||
outs = append(outs, value)
|
||||
}
|
||||
|
||||
// Display images which aren't part of a repository/tag
|
||||
if filter == "" {
|
||||
for _, image := range allImages {
|
||||
var out APIImages
|
||||
out.ID = image.ID
|
||||
out.ParentId = image.Parent
|
||||
out.RepoTags = []string{"<none>:<none>"}
|
||||
out.Created = image.Created.Unix()
|
||||
out.Size = image.Size
|
||||
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||
|
@ -417,7 +431,11 @@ func (srv *Server) ContainerTop(name, ps_args string) (*APITop, error) {
|
|||
}
|
||||
// no scanner.Text because we skip container id
|
||||
for scanner.Scan() {
|
||||
words = append(words, scanner.Text())
|
||||
if i != 0 && len(words) == len(procs.Titles) {
|
||||
words[len(words)-1] = fmt.Sprintf("%s %s", words[len(words)-1], scanner.Text())
|
||||
} else {
|
||||
words = append(words, scanner.Text())
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
procs.Titles = words
|
||||
|
@ -1052,7 +1070,10 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool)
|
|||
if container == nil {
|
||||
return fmt.Errorf("No such link: %s", name)
|
||||
}
|
||||
name = srv.runtime.getFullName(name)
|
||||
name, err := srv.runtime.getFullName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parent, n := path.Split(name)
|
||||
if parent == "/" {
|
||||
return fmt.Errorf("Conflict, cannot remove the default name of the container")
|
||||
|
@ -1305,7 +1326,7 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
|
|||
// After we load all the links into the runtime
|
||||
// set them to nil on the hostconfig
|
||||
hostConfig.Links = nil
|
||||
if err := container.SaveHostConfig(hostConfig); err != nil {
|
||||
if err := container.writeHostConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -1315,11 +1336,33 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
|
|||
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
|
||||
runtime := srv.runtime
|
||||
container := runtime.Get(name)
|
||||
|
||||
if hostConfig != nil {
|
||||
for _, bind := range hostConfig.Binds {
|
||||
splitBind := strings.Split(bind, ":")
|
||||
source := splitBind[0]
|
||||
|
||||
// refuse to bind mount "/" to the container
|
||||
if source == "/" {
|
||||
return fmt.Errorf("Invalid bind mount '%s' : source can't be '/'", bind)
|
||||
}
|
||||
|
||||
// ensure the source exists on the host
|
||||
_, err := os.Stat(source)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return fmt.Errorf("Invalid bind mount '%s' : source doesn't exist", bind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if container == nil {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if hostConfig != nil {
|
||||
container.hostConfig = hostConfig
|
||||
container.ToDisk()
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
return fmt.Errorf("Cannot start container %s: %s", name, err)
|
||||
}
|
||||
srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image))
|
||||
|
|
|
@ -34,8 +34,8 @@ func TestContainerTagImageDelete(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+3 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+3 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+3, len(images))
|
||||
}
|
||||
|
||||
if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
|
||||
|
@ -47,7 +47,7 @@ func TestContainerTagImageDelete(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+2 {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+2 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ func TestContainerTagImageDelete(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+1 {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
|
||||
}
|
||||
|
||||
|
@ -246,14 +246,14 @@ func TestContainerTop(t *testing.T) {
|
|||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
c, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t)
|
||||
c, hostConfig, err := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t)
|
||||
c, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t)
|
||||
c, err := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer runtime.Destroy(c)
|
||||
if err := c.Start(hostConfig); err != nil {
|
||||
if err := c.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -462,7 +462,7 @@ func TestRmi(t *testing.T) {
|
|||
if strings.Contains(unitTestImageID, image.ID) {
|
||||
continue
|
||||
}
|
||||
if image.Repository == "" {
|
||||
if image.RepoTags[0] == "<none>:<none>" {
|
||||
t.Fatalf("Expected tagged image, got untagged one.")
|
||||
}
|
||||
}
|
||||
|
@ -490,7 +490,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 2 {
|
||||
if len(images[0].RepoTags) != 2 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
|
||||
|
@ -499,7 +499,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 1 {
|
||||
if len(images[0].RepoTags) != 1 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
|
||||
|
@ -508,7 +508,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 1 {
|
||||
if len(images[0].RepoTags) != 1 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
|
||||
|
@ -517,7 +517,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 1 {
|
||||
if len(images[0].RepoTags) != 1 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func (s *imageSorter) Less(i, j int) bool {
|
|||
// Sort []ApiImages by most recent creation date and tag name.
|
||||
func sortImagesByCreationAndTag(images []APIImages) {
|
||||
creationAndTag := func(i1, i2 *APIImages) bool {
|
||||
return i1.Created > i2.Created || (i1.Created == i2.Created && i2.Tag > i1.Tag)
|
||||
return i1.Created > i2.Created
|
||||
}
|
||||
|
||||
sorter := &imageSorter{
|
||||
|
|
|
@ -3,6 +3,7 @@ package docker
|
|||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestServerListOrderedImagesByCreationDate(t *testing.T) {
|
||||
|
@ -34,29 +35,46 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) {
|
|||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
archive, err := fakeTar()
|
||||
err := generateImage("bar", runtime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := runtime.graph.Create(archive, nil, "Testing", "", nil)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err = generateImage("zed", runtime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
srv.ContainerTag(image.ID, "repo", "foo", false)
|
||||
srv.ContainerTag(image.ID, "repo", "bar", false)
|
||||
|
||||
images, err := srv.Images(true, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if images[0].Created != images[1].Created || images[0].Tag >= images[1].Tag {
|
||||
t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.")
|
||||
if images[0].RepoTags[0] != "repo:zed" && images[0].RepoTags[0] != "repo:bar" {
|
||||
t.Errorf("Expected []APIImges to be ordered by most recent creation date. %s", images)
|
||||
}
|
||||
}
|
||||
|
||||
func generateImage(name string, runtime *Runtime) error {
|
||||
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
image, err := runtime.graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
srv.ContainerTag(image.ID, "repo", name, false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSortUniquePorts(t *testing.T) {
|
||||
ports := []Port{
|
||||
Port("6379/tcp"),
|
||||
|
|
6
utils.go
6
utils.go
|
@ -250,6 +250,12 @@ func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
|
|||
if containerPort == "" {
|
||||
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
|
||||
}
|
||||
if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil {
|
||||
return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
|
||||
}
|
||||
if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil {
|
||||
return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
|
||||
}
|
||||
|
||||
port := NewPort(proto, containerPort)
|
||||
if _, exists := exposedPorts[port]; !exists {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
)
|
||||
|
||||
func RandomString() string {
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -695,6 +696,13 @@ func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
// Flush the stream immediately.
|
||||
func (wf *WriteFlusher) Flush() {
|
||||
wf.Lock()
|
||||
defer wf.Unlock()
|
||||
wf.flusher.Flush()
|
||||
}
|
||||
|
||||
func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
||||
var flusher http.Flusher
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
|
@ -896,6 +904,23 @@ func StripComments(input []byte, commentMarker []byte) []byte {
|
|||
return output
|
||||
}
|
||||
|
||||
// GetNameserversAsCIDR returns nameservers (if any) listed in
|
||||
// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
|
||||
// This function's output is intended for net.ParseCIDR
|
||||
func GetNameserversAsCIDR(resolvConf []byte) []string {
|
||||
var parsedResolvConf = StripComments(resolvConf, []byte("#"))
|
||||
nameservers := []string{}
|
||||
re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]\.){3}([0-9]))\s*$`)
|
||||
for _, line := range bytes.Split(parsedResolvConf, []byte("\n")) {
|
||||
var ns = re.FindSubmatch(line)
|
||||
if len(ns) > 0 {
|
||||
nameservers = append(nameservers, string(ns[1])+"/32")
|
||||
}
|
||||
}
|
||||
|
||||
return nameservers
|
||||
}
|
||||
|
||||
func ParseHost(host string, port int, addr string) (string, error) {
|
||||
var proto string
|
||||
switch {
|
||||
|
|
|
@ -444,3 +444,41 @@ func TestParsePortMapping(t *testing.T) {
|
|||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNameserversAsCIDR(t *testing.T) {
|
||||
for resolv, result := range map[string][]string{`
|
||||
nameserver 1.2.3.4
|
||||
nameserver 4.3.2.1
|
||||
search example.com`: {"1.2.3.4/32", "4.3.2.1/32"},
|
||||
`search example.com`: {},
|
||||
`nameserver 1.2.3.4
|
||||
search example.com
|
||||
nameserver 4.3.2.1`: {"1.2.3.4/32", "4.3.2.1/32"},
|
||||
``: {},
|
||||
` nameserver 1.2.3.4 `: {"1.2.3.4/32"},
|
||||
`search example.com
|
||||
nameserver 1.2.3.4
|
||||
#nameserver 4.3.2.1`: {"1.2.3.4/32"},
|
||||
`search example.com
|
||||
nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
|
||||
} {
|
||||
test := GetNameserversAsCIDR([]byte(resolv))
|
||||
if !StrSlicesEqual(test, result) {
|
||||
t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StrSlicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) {
|
|||
}
|
||||
|
||||
config := &DaemonConfig{
|
||||
Root: root,
|
||||
Root: root,
|
||||
AutoRestart: false,
|
||||
}
|
||||
runtime, err = NewRuntimeFromDirectory(config)
|
||||
|
@ -118,7 +118,7 @@ func readFile(src string, t *testing.T) (content string) {
|
|||
// dynamically replaced by the current test image.
|
||||
// The caller is responsible for destroying the container.
|
||||
// Call t.Fatal() at the first error.
|
||||
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
|
||||
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, error) {
|
||||
config, hostConfig, _, err := ParseRun(args, nil)
|
||||
defer func() {
|
||||
if err != nil && t != nil {
|
||||
|
@ -126,16 +126,17 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf
|
|||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
if config.Image == "_" {
|
||||
config.Image = GetTestImage(r).ID
|
||||
}
|
||||
c, _, err := r.Create(config, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
return c, hostConfig, nil
|
||||
c.hostConfig = hostConfig
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Create a test container, start it, wait for it to complete, destroy it,
|
||||
|
@ -148,7 +149,7 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e
|
|||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
container, hostConfig, err := mkContainer(r, args, t)
|
||||
container, err := mkContainer(r, args, t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -158,7 +159,7 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e
|
|||
return "", err
|
||||
}
|
||||
defer stdout.Close()
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
container.Wait()
|
||||
|
|
Loading…
Add table
Reference in a new issue