mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 12:16:30 +00:00
Compare commits
98 Commits
v0.7.0-rc7
...
v0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d078b6581 | |||
| 14d9f04e89 | |||
| db28e839e0 | |||
| de30ffb2c3 | |||
| 5c5ee194cb | |||
| b6dd67c707 | |||
| 740958dda7 | |||
| c38386d876 | |||
| 4267fb66ef | |||
| a74b512540 | |||
| 60809a4f72 | |||
| 65fcc81b42 | |||
| 06cf8fee1b | |||
| c92dab0eb4 | |||
| 6ad5b2bcf4 | |||
| 77f1362c64 | |||
| 4049359bee | |||
| 7daefc9d3f | |||
| d4c32b9015 | |||
| 8bd6127ab3 | |||
| 2302293244 | |||
| fd7ff6411d | |||
| 59f76bf1c7 | |||
| 02cb7f45fa | |||
| a937313747 | |||
| fb3d60f27a | |||
| 5ff74e268d | |||
| 09b7b55e2c | |||
| 110c4f2043 | |||
| 0d1b5d7676 | |||
| 5242a49f3f | |||
| 2586c042ae | |||
| 688e86c625 | |||
| 750d2d8d07 | |||
| 19df6c32c0 | |||
| 1d903da6fd | |||
| aaefb8c07c | |||
| b3959e69b5 | |||
| 43c7df946d | |||
| 6acdf68ee1 | |||
| 487b3d8a8c | |||
| 33f70f8978 | |||
| 809239c0af | |||
| 937f52aef9 | |||
| aa48acc5ec | |||
| ac70e296db | |||
| e2c3860ec3 | |||
| 2d715bf3c0 | |||
| d9e54e28e7 | |||
| 78d2e2dc37 | |||
| abfdaca3f8 | |||
| 3a2fbcfdec | |||
| ba2b36e192 | |||
| d47d49a2f9 | |||
| 8b0b10b6f9 | |||
| 399c71de83 | |||
| d8f4b733f2 | |||
| b4eeb6be61 | |||
| 41704d8933 | |||
| 64dd4afed6 | |||
| 5da1ed3291 | |||
| ad23745456 | |||
| cee0a292d0 | |||
| 07180f3aa7 | |||
| a606474825 | |||
| 5d6ef3177b | |||
| 0a89db04fe | |||
| 1cce9f25b2 | |||
| f1b3e278b9 | |||
| e288e7763e | |||
| 9696ec509a | |||
| ba6dd1d8d6 | |||
| c67f9b671d | |||
| 1c8ae47770 | |||
| d55998be81 | |||
| e69bbd239e | |||
| a26f9183bd | |||
| 944a48ec5a | |||
| 79e2b33ede | |||
| 076c0eab70 | |||
| 1f9223a7c2 | |||
| 476559458d | |||
| d4c8fb9ee2 | |||
| ae8c589d35 | |||
| 6130f2531e | |||
| ef14aaf627 | |||
| 1e7c04fcfe | |||
| 8b0cd60019 | |||
| b3f5973f41 | |||
| 3314e005f3 | |||
| a93e40a158 | |||
| cb48ecc9dc | |||
| 53f1bf0f99 | |||
| 9dc59797e0 | |||
| 806abe90ba | |||
| efd0e13ca7 | |||
| 60cb5f1a34 | |||
| 6d420407ca |
@@ -1,5 +1,55 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.0 (2013-11-25)
|
||||
|
||||
#### Notable features since 0.6.0
|
||||
|
||||
* Storage drivers: choose from aufs, device mapper, vfs or btrfs.
|
||||
* Standard Linux support: docker now runs on unmodified linux kernels and all major distributions.
|
||||
* Links: compose complex software stacks by connecting containers to each other.
|
||||
* Container naming: organize your containers by giving them memorable names.
|
||||
* Advanced port redirects: specify port redirects per interface, or keep sensitive ports private.
|
||||
* Offline transfer: push and pull images to the filesystem without losing information.
|
||||
* Quality: numerous bugfixes and small usability improvements. Significant increase in test coverage.
|
||||
|
||||
## 0.6.7 (2013-11-21)
|
||||
|
||||
#### Runtime
|
||||
|
||||
* Improved stability, fixes some race conditons
|
||||
* Skip the volumes mounted when deleting the volumes of container.
|
||||
* Fix layer size computation: handle hard links correctly
|
||||
* Use the work Path for docker cp CONTAINER:PATH
|
||||
* Fix tmp dir never cleanup
|
||||
* Speedup docker ps
|
||||
* More informative error message on name collisions
|
||||
* Fix nameserver regex
|
||||
* Always return long id's
|
||||
* Fix container restart race condition
|
||||
* Keep published ports on docker stop;docker start
|
||||
* Fix container networking on Fedora
|
||||
* Correctly express "any address" to iptables
|
||||
* Fix network setup when reconnecting to ghost container
|
||||
* Prevent deletion if image is used by a running container
|
||||
* Lock around read operations in graph
|
||||
|
||||
#### RemoteAPI
|
||||
|
||||
* Return full ID on docker rmi
|
||||
|
||||
#### Client
|
||||
|
||||
+ Add -tree option to images
|
||||
+ Offline image transfer
|
||||
* Exit with status 2 on usage error and display usage on stderr
|
||||
* Do not forward SIGCHLD to container
|
||||
* Use string timestamp for docker events -since
|
||||
|
||||
#### Other
|
||||
|
||||
* Update to go 1.2rc5
|
||||
+ Add /etc/default/docker support to upstart
|
||||
|
||||
## 0.6.6 (2013-11-06)
|
||||
|
||||
#### Runtime
|
||||
@@ -17,6 +67,7 @@
|
||||
+ 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
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ run apt-get install -y -q lxc
|
||||
run apt-get install -y -q aufs-tools
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
run git clone git://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103
|
||||
run git clone https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
# note: we can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
wf.Flush()
|
||||
if since != 0 {
|
||||
// If since, send previous events that happened after the timestamp
|
||||
for _, event := range srv.events {
|
||||
for _, event := range srv.GetEvents() {
|
||||
if event.Time >= since {
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
|
||||
+18
-11
@@ -71,17 +71,27 @@ func createSampleDir(t *testing.T, root string) {
|
||||
{Symlink, "symlink1", "target1", 0666},
|
||||
{Symlink, "symlink2", "target2", 0666},
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, info := range files {
|
||||
p := path.Join(root, info.path)
|
||||
if info.filetype == Dir {
|
||||
if err := os.MkdirAll(path.Join(root, info.path), info.permissions); err != nil {
|
||||
if err := os.MkdirAll(p, info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Regular {
|
||||
if err := ioutil.WriteFile(path.Join(root, info.path), []byte(info.contents), info.permissions); err != nil {
|
||||
if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Symlink {
|
||||
if err := os.Symlink(info.contents, path.Join(root, info.path)); err != nil {
|
||||
if err := os.Symlink(info.contents, p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if info.filetype != Symlink {
|
||||
// Set a consistent ctime, atime for all files and dirs
|
||||
if err := os.Chtimes(p, now, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -200,6 +210,9 @@ func TestChangesDirsMutated(t *testing.T) {
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(src)
|
||||
defer os.RemoveAll(dst)
|
||||
|
||||
mutateSampleDir(t, dst)
|
||||
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
@@ -225,8 +238,7 @@ func TestChangesDirsMutated(t *testing.T) {
|
||||
{"/symlinknew", ChangeAdd},
|
||||
}
|
||||
|
||||
i := 0
|
||||
for ; i < max(len(changes), len(expectedChanges)); i++ {
|
||||
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
|
||||
if i >= len(expectedChanges) {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
}
|
||||
@@ -240,14 +252,9 @@ func TestChangesDirsMutated(t *testing.T) {
|
||||
} else if changes[i].Path < expectedChanges[i].Path {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
} else {
|
||||
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
||||
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
|
||||
}
|
||||
}
|
||||
for ; i < len(expectedChanges); i++ {
|
||||
}
|
||||
|
||||
os.RemoveAll(src)
|
||||
os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func TestApplyLayer(t *testing.T) {
|
||||
|
||||
+1
-1
@@ -241,7 +241,7 @@ func (b *buildFile) CmdVolume(args string) error {
|
||||
volume = []string{args}
|
||||
}
|
||||
if b.config.Volumes == nil {
|
||||
b.config.Volumes = NewPathOpts()
|
||||
b.config.Volumes = PathOpts{}
|
||||
}
|
||||
for _, v := range volume {
|
||||
b.config.Volumes[v] = struct{}{}
|
||||
|
||||
+159
-148
@@ -851,7 +851,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))))
|
||||
|
||||
if *noTrunc {
|
||||
fmt.Fprintf(w, "%s\t", out.CreatedBy)
|
||||
@@ -1208,7 +1208,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
}
|
||||
|
||||
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))))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().UTC().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 {
|
||||
@@ -1350,7 +1350,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
if !*noTrunc {
|
||||
out.Command = utils.Trunc(out.Command, 20)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
|
||||
if *size {
|
||||
if out.SizeRootFs > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
|
||||
@@ -1457,7 +1457,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
|
||||
cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1638,15 +1638,7 @@ type ports []int
|
||||
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
|
||||
type AttachOpts map[string]bool
|
||||
|
||||
func NewAttachOpts() AttachOpts {
|
||||
return make(AttachOpts)
|
||||
}
|
||||
|
||||
func (opts AttachOpts) String() string {
|
||||
// Cast to underlying map type to avoid infinite recursion
|
||||
return fmt.Sprintf("%v", map[string]bool(opts))
|
||||
}
|
||||
|
||||
func (opts AttachOpts) String() string { return fmt.Sprintf("%v", map[string]bool(opts)) }
|
||||
func (opts AttachOpts) Set(val string) error {
|
||||
if val != "stdin" && val != "stdout" && val != "stderr" {
|
||||
return fmt.Errorf("Unsupported stream name: %s", val)
|
||||
@@ -1655,24 +1647,22 @@ func (opts AttachOpts) Set(val string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts AttachOpts) Get(val string) bool {
|
||||
if res, exists := opts[val]; exists {
|
||||
return res
|
||||
// LinkOpts stores arguments to `docker run -link`
|
||||
type LinkOpts []string
|
||||
|
||||
func (link *LinkOpts) String() string { return fmt.Sprintf("%v", []string(*link)) }
|
||||
func (link *LinkOpts) Set(val string) error {
|
||||
if _, err := parseLink(val); err != nil {
|
||||
return err
|
||||
}
|
||||
return false
|
||||
*link = append(*link, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PathOpts stores a unique set of absolute paths
|
||||
type PathOpts map[string]struct{}
|
||||
|
||||
func NewPathOpts() PathOpts {
|
||||
return make(PathOpts)
|
||||
}
|
||||
|
||||
func (opts PathOpts) String() string {
|
||||
return fmt.Sprintf("%v", map[string]struct{}(opts))
|
||||
}
|
||||
|
||||
func (opts PathOpts) String() string { return fmt.Sprintf("%v", map[string]struct{}(opts)) }
|
||||
func (opts PathOpts) Set(val string) error {
|
||||
var containerPath string
|
||||
|
||||
@@ -1736,60 +1726,60 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
}
|
||||
|
||||
func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
var (
|
||||
// FIXME: use utils.ListOpts for attach and volumes?
|
||||
flAttach = AttachOpts{}
|
||||
flVolumes = PathOpts{}
|
||||
flLinks = LinkOpts{}
|
||||
|
||||
flPublish utils.ListOpts
|
||||
flExpose utils.ListOpts
|
||||
flEnv utils.ListOpts
|
||||
flDns utils.ListOpts
|
||||
flVolumesFrom utils.ListOpts
|
||||
flLxcOpts utils.ListOpts
|
||||
|
||||
flAutoRemove = cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
flDetach = cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||
flNetwork = cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged = cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
flPublishAll = cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
|
||||
flStdin = cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty = cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flContainerIDFile = cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flEntrypoint = cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
flHostname = cmd.String("h", "", "Container host name")
|
||||
flMemoryString = cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
|
||||
flUser = cmd.String("u", "", "Username or UID")
|
||||
flWorkingDir = cmd.String("w", "", "Working directory inside the container")
|
||||
flCpuShares = cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
// For documentation purpose
|
||||
_ = 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")
|
||||
)
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
|
||||
flUser := cmd.String("u", "", "Username or UID")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||
flAttach := NewAttachOpts()
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
|
||||
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
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 && *flMemoryString != "" && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
*flMemoryString = ""
|
||||
}
|
||||
|
||||
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
var flPublish utils.ListOpts
|
||||
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
|
||||
var flExpose utils.ListOpts
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
|
||||
var flEnv utils.ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
|
||||
var flDns utils.ListOpts
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
||||
|
||||
var flVolumesFrom utils.ListOpts
|
||||
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
|
||||
|
||||
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
|
||||
var flLxcOpts utils.ListOpts
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
var flLinks utils.ListOpts
|
||||
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
|
||||
|
||||
cmd.Var(&flPublish, "p", fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat))
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Check if the kernel supports memory limit cgroup.
|
||||
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
|
||||
*flMemoryString = ""
|
||||
}
|
||||
|
||||
// Validate input params
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, nil, cmd, ErrConflictAttachDetach
|
||||
}
|
||||
@@ -1811,8 +1801,7 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
|
||||
}
|
||||
}
|
||||
|
||||
envs := []string{}
|
||||
|
||||
var envs []string
|
||||
for _, env := range flEnv {
|
||||
arr := strings.Split(env, "=")
|
||||
if len(arr) > 1 {
|
||||
@@ -1824,19 +1813,15 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
|
||||
}
|
||||
|
||||
var flMemory int64
|
||||
|
||||
if *flMemoryString != "" {
|
||||
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
flMemory = parsedMemory
|
||||
}
|
||||
|
||||
var binds []string
|
||||
|
||||
// add any bind targets to the list of container volumes
|
||||
for bind := range flVolumes {
|
||||
arr := strings.Split(bind, ":")
|
||||
@@ -1851,10 +1836,12 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
|
||||
}
|
||||
}
|
||||
|
||||
parsedArgs := cmd.Args()
|
||||
runCmd := []string{}
|
||||
entrypoint := []string{}
|
||||
image := ""
|
||||
var (
|
||||
parsedArgs = cmd.Args()
|
||||
runCmd []string
|
||||
entrypoint []string
|
||||
image string
|
||||
)
|
||||
if len(parsedArgs) >= 1 {
|
||||
image = cmd.Arg(0)
|
||||
}
|
||||
@@ -1865,16 +1852,16 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
|
||||
entrypoint = []string{*flEntrypoint}
|
||||
}
|
||||
|
||||
var lxcConf []KeyValuePair
|
||||
lxcConf, err := parseLxcConfOpts(flLxcOpts)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
hostname := *flHostname
|
||||
domainname := ""
|
||||
|
||||
parts := strings.SplitN(hostname, ".", 2)
|
||||
var (
|
||||
domainname string
|
||||
hostname = *flHostname
|
||||
parts = strings.SplitN(hostname, ".", 2)
|
||||
)
|
||||
if len(parts) > 1 {
|
||||
hostname = parts[0]
|
||||
domainname = parts[1]
|
||||
@@ -1907,9 +1894,9 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
|
||||
OpenStdin: *flStdin,
|
||||
Memory: flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
AttachStdin: flAttach["stdin"],
|
||||
AttachStdout: flAttach["stdout"],
|
||||
AttachStderr: flAttach["stderr"],
|
||||
Env: envs,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
@@ -1952,30 +1939,33 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
flRm := cmd.Lookup("rm")
|
||||
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
|
||||
// Retrieve relevant client-side config
|
||||
var (
|
||||
flName = cmd.Lookup("name")
|
||||
flRm = cmd.Lookup("rm")
|
||||
flSigProxy = cmd.Lookup("sig-proxy")
|
||||
autoRemove, _ = strconv.ParseBool(flRm.Value.String())
|
||||
sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String())
|
||||
)
|
||||
|
||||
flSigProxy := cmd.Lookup("sig-proxy")
|
||||
sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
|
||||
flName := cmd.Lookup("name")
|
||||
// Disable sigProxy in case on TTY
|
||||
if config.Tty {
|
||||
sigProxy = false
|
||||
}
|
||||
|
||||
var containerIDFile *os.File
|
||||
var containerIDFile io.WriteCloser
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
||||
if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
|
||||
return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
|
||||
}
|
||||
containerIDFile, err = os.Create(hostConfig.ContainerIDFile)
|
||||
if err != nil {
|
||||
if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
|
||||
return fmt.Errorf("failed to create the container ID file: %s", err)
|
||||
}
|
||||
defer containerIDFile.Close()
|
||||
}
|
||||
|
||||
containerValues := url.Values{}
|
||||
name := flName.Value.String()
|
||||
if name != "" {
|
||||
if name := flName.Value.String(); name != "" {
|
||||
containerValues.Set("name", name)
|
||||
}
|
||||
|
||||
@@ -1996,8 +1986,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
v.Set("tag", tag)
|
||||
|
||||
// Resolve the Repository name from fqn to endpoint + name
|
||||
var endpoint string
|
||||
endpoint, _, err = registry.ResolveRepositoryName(repos)
|
||||
endpoint, _, err := registry.ResolveRepositoryName(repos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2015,32 +2004,27 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
if err != nil {
|
||||
if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
||||
return err
|
||||
}
|
||||
body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
|
||||
if err != nil {
|
||||
if body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runResult := &APIRun{}
|
||||
err = json.Unmarshal(body, runResult)
|
||||
if err != nil {
|
||||
var runResult APIRun
|
||||
if err := json.Unmarshal(body, &runResult); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, warning := range runResult.Warnings {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
|
||||
}
|
||||
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
|
||||
if _, err = containerIDFile.Write([]byte(runResult.ID)); err != nil {
|
||||
return fmt.Errorf("failed to write the container ID to the file: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -2051,27 +2035,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
|
||||
var (
|
||||
wait chan struct{}
|
||||
errCh chan error
|
||||
waitDisplayId chan struct{}
|
||||
errCh chan error
|
||||
)
|
||||
|
||||
if !config.AttachStdout && !config.AttachStderr {
|
||||
// Make this asynchrone in order to let the client write to stdin before having to read the ID
|
||||
wait = make(chan struct{})
|
||||
waitDisplayId = make(chan struct{})
|
||||
go func() {
|
||||
defer close(wait)
|
||||
defer close(waitDisplayId)
|
||||
fmt.Fprintf(cli.out, "%s\n", runResult.ID)
|
||||
}()
|
||||
}
|
||||
|
||||
hijacked := make(chan bool)
|
||||
// We need to instanciate the chan because the select needs it. It can
|
||||
// be closed but can't be uninitialized.
|
||||
hijacked := make(chan io.Closer)
|
||||
|
||||
// Block the return until the chan gets closed
|
||||
defer func() {
|
||||
utils.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
||||
if _, ok := <-hijacked; ok {
|
||||
utils.Errorf("Hijack did not finish (chan still open)")
|
||||
}
|
||||
}()
|
||||
|
||||
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||
|
||||
v := url.Values{}
|
||||
var (
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
v = url.Values{}
|
||||
)
|
||||
v.Set("stream", "1")
|
||||
var out, stderr io.Writer
|
||||
var in io.ReadCloser
|
||||
|
||||
if config.AttachStdin {
|
||||
v.Set("stdin", "1")
|
||||
@@ -2099,7 +2094,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case <-hijacked:
|
||||
case closer := <-hijacked:
|
||||
// Make sure that hijack gets closed when returning. (result
|
||||
// in closing hijack chan and freeing server's goroutines.
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
utils.Debugf("Error hijack: %s", err)
|
||||
@@ -2125,31 +2125,37 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Detached mode: wait for the id to be displayed and return.
|
||||
if !config.AttachStdout && !config.AttachStderr {
|
||||
// Detached mode
|
||||
<-wait
|
||||
} else {
|
||||
running, status, err := getExitCode(cli, runResult.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if autoRemove {
|
||||
if running {
|
||||
return fmt.Errorf("Impossible to auto-remove a detached container")
|
||||
}
|
||||
// Wait for the process to
|
||||
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if status != 0 {
|
||||
return &utils.StatusError{Status: status}
|
||||
}
|
||||
<-waitDisplayId
|
||||
return nil
|
||||
}
|
||||
|
||||
var status int
|
||||
|
||||
// Attached mode
|
||||
if autoRemove {
|
||||
// Autoremove: wait for the container to finish, retrieve
|
||||
// the exit code and remove the container
|
||||
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, status, err = getExitCode(cli, runResult.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// No Autoremove: Simply retrieve the exit code
|
||||
if _, status, err = getExitCode(cli, runResult.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if status != 0 {
|
||||
return &utils.StatusError{Status: status}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2334,7 +2340,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
||||
}
|
||||
|
||||
if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, out)
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, out, cli.isTerminal)
|
||||
}
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
@@ -2342,7 +2348,12 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error {
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
|
||||
defer func() {
|
||||
if started != nil {
|
||||
close(started)
|
||||
}
|
||||
}()
|
||||
// fixme: refactor client to support redirect
|
||||
re := regexp.MustCompile("/+")
|
||||
path = re.ReplaceAllString(path, "/")
|
||||
@@ -2372,7 +2383,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
defer rwc.Close()
|
||||
|
||||
if started != nil {
|
||||
started <- true
|
||||
started <- rwc
|
||||
}
|
||||
|
||||
var receiveStdout chan error
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ func main() {
|
||||
flEnableIptables := flag.Bool("iptables", true, "Disable docker's addition of iptables rules")
|
||||
flDefaultIp := flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports")
|
||||
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
|
||||
flGraphDriver := flag.String("graph-driver", "", "Force docker runtime to use a specific graph driver")
|
||||
flGraphDriver := flag.String("s", "", "Force the docker runtime to use a specific storage driver")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ run apt-get install -y python-setuptools make
|
||||
run easy_install pip
|
||||
#from docs/requirements.txt, but here to increase cacheability
|
||||
run pip install Sphinx==1.1.3
|
||||
run pip install sphinxcontrib-httpdomain==1.1.8
|
||||
run pip install sphinxcontrib-httpdomain==1.1.9
|
||||
add . /docs
|
||||
run cd /docs; make docs
|
||||
|
||||
|
||||
+7
-6
@@ -41,11 +41,12 @@ its dependencies. There are two main ways to install this tool:
|
||||
|
||||
###Native Installation
|
||||
|
||||
* Install sphinx: `pip install sphinx`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinx`
|
||||
* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
Install dependencies from `requirements.txt` file in your `docker/docs`
|
||||
directory:
|
||||
|
||||
* Linux: `pip install -r docs/requirements.txt`
|
||||
|
||||
* Mac OS X: `[sudo] pip-2.7 -r docs/requirements.txt`
|
||||
|
||||
###Alternative Installation: Docker Container
|
||||
|
||||
@@ -136,7 +137,7 @@ Manpages
|
||||
--------
|
||||
|
||||
* To make the manpages, run ``make man``. Please note there is a bug
|
||||
in spinx 1.1.3 which makes this fail. Upgrade to the latest version
|
||||
in Sphinx 1.1.3 which makes this fail. Upgrade to the latest version
|
||||
of Sphinx.
|
||||
* Then preview the manpage by running ``man _build/man/docker.1``,
|
||||
where ``_build/man/docker.1`` is the path to the generated manfile
|
||||
|
||||
@@ -18,6 +18,38 @@ To list available commands, either run ``docker`` with no parameters or execute
|
||||
|
||||
...
|
||||
|
||||
.. _cli_daemon:
|
||||
|
||||
``daemon``
|
||||
----------
|
||||
|
||||
::
|
||||
|
||||
Usage of docker:
|
||||
-D=false: Enable debug mode
|
||||
-H=[unix:///var/run/docker.sock]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise
|
||||
-api-enable-cors=false: Enable CORS headers in the remote API
|
||||
-b="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking
|
||||
-d=false: Enable daemon mode
|
||||
-dns="": Force docker to use specific DNS servers
|
||||
-g="/var/lib/docker": Path to use as the root of the docker runtime
|
||||
-icc=true: Enable inter-container communication
|
||||
-ip="0.0.0.0": Default IP address to use when binding container ports
|
||||
-iptables=true: Disable docker's addition of iptables rules
|
||||
-p="/var/run/docker.pid": Path to use for daemon PID file
|
||||
-r=true: Restart previously running containers
|
||||
-s="": Force the docker runtime to use a specific storage driver
|
||||
-v=false: Print version information and quit
|
||||
|
||||
The docker daemon is the persistent process that manages containers. Docker uses the same binary for both the
|
||||
daemon and client. To run the daemon you provide the ``-d`` flag.
|
||||
|
||||
To force docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``
|
||||
|
||||
To set the dns server for all docker containers, use ``docker -d -dns 8.8.8.8``
|
||||
|
||||
To run the daemon with debug output, use ``docker -d -D``
|
||||
|
||||
.. _cli_attach:
|
||||
|
||||
``attach``
|
||||
@@ -369,7 +401,13 @@ Show events in the past from a specified time
|
||||
|
||||
Usage: docker export CONTAINER
|
||||
|
||||
Export the contents of a filesystem as a tar archive
|
||||
Export the contents of a filesystem as a tar archive to STDOUT
|
||||
|
||||
for example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker export red_panda > latest.tar
|
||||
|
||||
.. _cli_history:
|
||||
|
||||
@@ -591,6 +629,12 @@ might not get preserved.
|
||||
|
||||
Insert a file from URL in the IMAGE at PATH
|
||||
|
||||
Use the specified IMAGE as the parent for a new image which adds a
|
||||
:ref:`layer <layer_def>` containing the new file. ``insert`` does not modify
|
||||
the original image, and the new image has the contents of the parent image,
|
||||
plus the new file.
|
||||
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
@@ -600,6 +644,7 @@ Insert file from github
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker insert 8283e18b24bc https://raw.github.com/metalivedev/django/master/postinstall /tmp/postinstall.sh
|
||||
06fd35556d7b
|
||||
|
||||
.. _cli_inspect:
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
:title: Process Management with CFEngine
|
||||
:description: Managing containerized processes with CFEngine
|
||||
:keywords: cfengine, process, management, usage, docker, documentation
|
||||
|
||||
Process Management with CFEngine
|
||||
================================
|
||||
|
||||
Create Docker containers with managed processes.
|
||||
|
||||
Docker monitors one process in each running container and the container lives or dies with that process.
|
||||
By introducing CFEngine inside Docker containers, we can alleviate a few of the issues that may arise:
|
||||
|
||||
* It is possible to easily start multiple processes within a container, all of which will be managed automatically, with the normal ``docker run`` command.
|
||||
* If a managed process dies or crashes, CFEngine will start it again within 1 minute.
|
||||
* The container itself will live as long as the CFEngine scheduling daemon (cf-execd) lives. With CFEngine, we are able to decouple the life of the container from the uptime of the service it provides.
|
||||
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
||||
CFEngine, together with the cfe-docker integration policies, are installed as part of the Dockerfile. This builds CFEngine into our Docker image.
|
||||
|
||||
The Dockerfile's ``ENTRYPOINT`` takes an arbitrary amount of commands (with any desired arguments) as parameters.
|
||||
When we run the Docker container these parameters get written to CFEngine policies and CFEngine takes over to ensure that the desired processes are running in the container.
|
||||
|
||||
CFEngine scans the process table for the ``basename`` of the commands given to the ``ENTRYPOINT`` and runs the command to start the process if the ``basename`` is not found.
|
||||
For example, if we start the container with ``docker run "/path/to/my/application parameters"``, CFEngine will look for a process named ``application`` and run the command.
|
||||
If an entry for ``application`` is not found in the process table at any point in time, CFEngine will execute ``/path/to/my/application parameters`` to start the application once again.
|
||||
The check on the process table happens every minute.
|
||||
|
||||
Note that it is therefore important that the command to start your application leaves a process with the basename of the command.
|
||||
This can be made more flexible by making some minor adjustments to the CFEngine policies, if desired.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
This example assumes you have Docker installed and working.
|
||||
We will install and manage ``apache2`` and ``sshd`` in a single container.
|
||||
|
||||
There are three steps:
|
||||
|
||||
1. Install CFEngine into the container.
|
||||
2. Copy the CFEngine Docker process management policy into the containerized CFEngine installation.
|
||||
3. Start your application processes as part of the ``docker run`` command.
|
||||
|
||||
|
||||
Building the container image
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The first two steps can be done as part of a Dockerfile, as follows.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu
|
||||
MAINTAINER Eystein Måløy Stenberg <eytein.stenberg@gmail.com>
|
||||
|
||||
RUN apt-get -y install wget lsb-release unzip
|
||||
|
||||
# install latest CFEngine
|
||||
RUN wget -qO- http://cfengine.com/pub/gpg.key | apt-key add -
|
||||
RUN echo "deb http://cfengine.com/pub/apt $(lsb_release -cs) main" > /etc/apt/sources.list.d/cfengine-community.list
|
||||
RUN apt-get update
|
||||
RUN apt-get install cfengine-community
|
||||
|
||||
# install cfe-docker process management policy
|
||||
RUN wget --no-check-certificate https://github.com/estenberg/cfe-docker/archive/master.zip -P /tmp/ && unzip /tmp/master.zip -d /tmp/
|
||||
RUN cp /tmp/cfe-docker-master/cfengine/bin/* /var/cfengine/bin/
|
||||
RUN cp /tmp/cfe-docker-master/cfengine/inputs/* /var/cfengine/inputs/
|
||||
RUN rm -rf /tmp/cfe-docker-master /tmp/master.zip
|
||||
|
||||
# apache2 and openssh are just for testing purposes, install your own apps here
|
||||
RUN apt-get -y install openssh-server apache2
|
||||
RUN mkdir -p /var/run/sshd
|
||||
RUN echo "root:password" | chpasswd # need a password for ssh
|
||||
|
||||
ENTRYPOINT ["/var/cfengine/bin/docker_processes_run.sh"]
|
||||
|
||||
|
||||
By saving this file as ``Dockerfile`` to a working directory, you can then build your container with the docker build command,
|
||||
e.g. ``docker build -t managed_image``.
|
||||
|
||||
Testing the container
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Start the container with ``apache2`` and ``sshd`` running and managed, forwarding a port to our SSH instance:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.1:222:22 -d managed_image "/usr/sbin/sshd" "/etc/init.d/apache2 start"
|
||||
|
||||
We now clearly see one of the benefits of the cfe-docker integration: it allows to start several processes
|
||||
as part of a normal ``docker run`` command.
|
||||
|
||||
We can now log in to our new container and see that both ``apache2`` and ``sshd`` are running. We have set the root password to
|
||||
"password" in the Dockerfile above and can use that to log in with ssh:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ssh -p222 root@127.0.0.1
|
||||
|
||||
ps -ef
|
||||
UID PID PPID C STIME TTY TIME CMD
|
||||
root 1 0 0 07:48 ? 00:00:00 /bin/bash /var/cfengine/bin/docker_processes_run.sh /usr/sbin/sshd /etc/init.d/apache2 start
|
||||
root 18 1 0 07:48 ? 00:00:00 /var/cfengine/bin/cf-execd -F
|
||||
root 20 1 0 07:48 ? 00:00:00 /usr/sbin/sshd
|
||||
root 32 1 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 34 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 35 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 36 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
root 93 20 0 07:48 ? 00:00:00 sshd: root@pts/0
|
||||
root 105 93 0 07:48 pts/0 00:00:00 -bash
|
||||
root 112 105 0 07:49 pts/0 00:00:00 ps -ef
|
||||
|
||||
|
||||
If we stop apache2, it will be started again within a minute by CFEngine.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
service apache2 status
|
||||
Apache2 is running (pid 32).
|
||||
service apache2 stop
|
||||
* Stopping web server apache2 ... waiting [ OK ]
|
||||
service apache2 status
|
||||
Apache2 is NOT running.
|
||||
# ... wait up to 1 minute...
|
||||
service apache2 status
|
||||
Apache2 is running (pid 173).
|
||||
|
||||
|
||||
Adapting to your applications
|
||||
-----------------------------
|
||||
|
||||
To make sure your applications get managed in the same manner, there are just two things you need to adjust from the above example:
|
||||
|
||||
* In the Dockerfile used above, install your applications instead of ``apache2`` and ``sshd``.
|
||||
* When you start the container with ``docker run``, specify the command line arguments to your applications rather than ``apache2`` and ``sshd``.
|
||||
@@ -24,3 +24,5 @@ to more substantial services like those which you might find in production.
|
||||
postgresql_service
|
||||
mongodb
|
||||
running_riak_service
|
||||
using_supervisord
|
||||
cfengine_process_management
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
:title: Using Supervisor with Docker
|
||||
:description: How to use Supervisor process management with Docker
|
||||
:keywords: docker, supervisor, process management
|
||||
|
||||
.. _using_supervisord:
|
||||
|
||||
Using Supervisor with Docker
|
||||
============================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Traditionally a Docker container runs a single process when it is launched, for
|
||||
example an Apache daemon or a SSH server daemon. Often though you want to run
|
||||
more than one process in a container. There are a number of ways you can
|
||||
achieve this ranging from using a simple Bash script as the value of your
|
||||
container's ``CMD`` instruction to installing a process management tool.
|
||||
|
||||
In this example we're going to make use of the process management tool,
|
||||
`Supervisor <http://supervisord.org/>`_, to manage multiple processes in our
|
||||
container. Using Supervisor allows us to better control, manage, and restart the
|
||||
processes we want to run. To demonstrate this we're going to install and manage both an
|
||||
SSH daemon and an Apache daemon.
|
||||
|
||||
Creating a Dockerfile
|
||||
---------------------
|
||||
|
||||
Let's start by creating a basic ``Dockerfile`` for our new image.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu:latest
|
||||
MAINTAINER examples@docker.io
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
|
||||
Installing Supervisor
|
||||
---------------------
|
||||
|
||||
We can now install our SSH and Apache daemons as well as Supervisor in our container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
RUN apt-get install -y openssh-server apache2 supervisor
|
||||
RUN mkdir -p /var/run/sshd
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
|
||||
Here we're installing the ``openssh-server``, ``apache2`` and ``supervisor``
|
||||
(which provides the Supervisor daemon) packages. We're also creating two new
|
||||
directories that are needed to run our SSH daemon and Supervisor.
|
||||
|
||||
Adding Supervisor's configuration file
|
||||
--------------------------------------
|
||||
|
||||
Now let's add a configuration file for Supervisor. The default file is called
|
||||
``supervisord.conf`` and is located in ``/etc/supervisor/conf.d/``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
Let's see what is inside our ``supervisord.conf`` file.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
|
||||
[program:sshd]
|
||||
command=/usr/sbin/sshd -D
|
||||
|
||||
[program:apache2]
|
||||
command=/bin/bash -c "source /etc/apache2/envvars && /usr/sbin/apache2 -DFOREGROUND"
|
||||
|
||||
The ``supervisord.conf`` configuration file contains directives that configure
|
||||
Supervisor and the processes it manages. The first block ``[supervisord]``
|
||||
provides configuration for Supervisor itself. We're using one directive,
|
||||
``nodaemon`` which tells Supervisor to run interactively rather than daemonize.
|
||||
|
||||
The next two blocks manage the services we wish to control. Each block controls
|
||||
a separate process. The blocks contain a single directive, ``command``, which
|
||||
specifies what command to run to start each process.
|
||||
|
||||
Exposing ports and running Supervisor
|
||||
-------------------------------------
|
||||
|
||||
Now let's finish our ``Dockerfile`` by exposing some required ports and
|
||||
specifying the ``CMD`` instruction to start Supervisor when our container
|
||||
launches.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
EXPOSE 22 80
|
||||
CMD ["/usr/bin/supervisord"]
|
||||
|
||||
Here we've exposed ports 22 and 80 on the container and we're running the
|
||||
``/usr/bin/supervisord`` binary when the container launches.
|
||||
|
||||
Building our container
|
||||
----------------------
|
||||
|
||||
We can now build our new container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build -t <yourname>/supervisord .
|
||||
|
||||
Running our Supervisor container
|
||||
--------------------------------
|
||||
|
||||
Once we've got a built image we can launch a container from it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -p 22 -p 80 -t -i <yourname>/supervisor
|
||||
2013-11-25 18:53:22,312 CRIT Supervisor running as root (no user in config file)
|
||||
2013-11-25 18:53:22,312 WARN Included extra file "/etc/supervisor/conf.d/supervisord.conf" during parsing
|
||||
2013-11-25 18:53:22,342 INFO supervisord started with pid 1
|
||||
2013-11-25 18:53:23,346 INFO spawned: 'sshd' with pid 6
|
||||
2013-11-25 18:53:23,349 INFO spawned: 'apache2' with pid 7
|
||||
. . .
|
||||
|
||||
We've launched a new container interactively using the ``docker run`` command.
|
||||
That container has run Supervisor and launched the SSH and Apache daemons with
|
||||
it. We've specified the ``-p`` flag to expose ports 22 and 80. From here we can
|
||||
now identify the exposed ports and connect to one or both of the SSH and Apache
|
||||
daemons.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:title: Installation on Arch Linux
|
||||
:description: Docker installation on Arch Linux.
|
||||
:description: Docker installation on Arch Linux.
|
||||
:keywords: arch linux, virtualization, docker, documentation, installation
|
||||
|
||||
.. _arch_linux:
|
||||
@@ -7,6 +7,10 @@
|
||||
Arch Linux
|
||||
==========
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
.. include:: install_unofficial.inc
|
||||
|
||||
Installing on Arch Linux is not officially supported but can be handled via
|
||||
either of the following AUR packages:
|
||||
|
||||
@@ -32,10 +36,6 @@ either AUR package.
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
.. include:: install_unofficial.inc
|
||||
|
||||
The instructions here assume **yaourt** is installed. See
|
||||
`Arch User Repository <https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages>`_
|
||||
for information on building and installing packages from the AUR if you have not
|
||||
|
||||
@@ -12,17 +12,9 @@ Binaries
|
||||
**This instruction set is meant for hackers who want to try out Docker
|
||||
on a variety of environments.**
|
||||
|
||||
Right now, the officially supported distributions are:
|
||||
|
||||
- :ref:`ubuntu_precise`
|
||||
- :ref:`ubuntu_raring`
|
||||
|
||||
|
||||
But we know people have had success running it under
|
||||
|
||||
- Debian
|
||||
- Suse
|
||||
- :ref:`arch_linux`
|
||||
Before following these directions, you should really check if a packaged version
|
||||
of Docker is already available for your distribution. We have packages for many
|
||||
distributions, and more keep showing up all the time!
|
||||
|
||||
Check Your Kernel
|
||||
-----------------
|
||||
@@ -34,7 +26,7 @@ Get the docker binary:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
wget --output-document=docker https://get.docker.io/builds/Linux/x86_64/docker-latest
|
||||
wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O docker
|
||||
chmod +x docker
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
:title: Requirements and Installation on Fedora
|
||||
:description: Please note this project is currently under heavy development. It should not be used in production.
|
||||
:keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux
|
||||
|
||||
.. _fedora:
|
||||
|
||||
Fedora
|
||||
======
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
.. include:: install_unofficial.inc
|
||||
|
||||
.. warning::
|
||||
|
||||
This is a placeholder for the Fedora installation instructions. Currently there is not an available
|
||||
Docker package in the Fedora distribution. These packages are being built and should be available soon.
|
||||
These instructions will be updated when the package is available.
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
.. _gentoo_linux:
|
||||
|
||||
Gentoo Linux
|
||||
============
|
||||
Gentoo
|
||||
======
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
@@ -22,17 +22,19 @@ provided at https://github.com/tianon/docker-overlay which can be added using
|
||||
properly installing and using the overlay can be found in `the overlay README
|
||||
<https://github.com/tianon/docker-overlay/blob/master/README.md#using-this-overlay>`_.
|
||||
|
||||
Note that sometimes there is a disparity between the latest version and what's
|
||||
in the overlay, and between the latest version in the overlay and what's in the
|
||||
portage tree. Please be patient, and the latest version should propagate
|
||||
shortly.
|
||||
|
||||
Installation
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The package should properly pull in all the necessary dependencies and prompt
|
||||
for all necessary kernel options. For the most straightforward installation
|
||||
experience, use ``sys-kernel/aufs-sources`` as your kernel sources. If you
|
||||
prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains
|
||||
``sys-fs/aufs3``, which includes the patches necessary for adding AUFS support
|
||||
to other kernel source packages such as ``sys-kernel/gentoo-sources`` (and a
|
||||
``kernel-patch`` USE flag to perform the patching to ``/usr/src/linux``
|
||||
automatically).
|
||||
for all necessary kernel options. The ebuilds for 0.7+ include use flags to
|
||||
pull in the proper dependencies of the major storage drivers, with the
|
||||
"device-mapper" use flag being enabled by default, since that is the simplest
|
||||
installation path.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -47,9 +49,9 @@ the #docker IRC channel on the freenode network.
|
||||
Starting Docker
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Ensure that you are running a kernel that includes the necessary AUFS
|
||||
patches/support and includes all the necessary modules and/or configuration for
|
||||
LXC.
|
||||
Ensure that you are running a kernel that includes all the necessary modules
|
||||
and/or configuration for LXC (and optionally for device-mapper and/or AUFS,
|
||||
depending on the storage driver you've decided to use).
|
||||
|
||||
OpenRC
|
||||
------
|
||||
|
||||
@@ -9,7 +9,7 @@ Installation
|
||||
|
||||
There are a number of ways to install Docker, depending on where you
|
||||
want to run the daemon. The :ref:`ubuntu_linux` installation is the
|
||||
officially-tested version, and the community adds more techniques for
|
||||
officially-tested version. The community adds more techniques for
|
||||
installing Docker all the time.
|
||||
|
||||
Contents:
|
||||
@@ -18,13 +18,14 @@ Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
ubuntulinux
|
||||
binaries
|
||||
security
|
||||
upgrading
|
||||
kernel
|
||||
fedora
|
||||
archlinux
|
||||
gentoolinux
|
||||
vagrant
|
||||
windows
|
||||
amazon
|
||||
rackspace
|
||||
archlinux
|
||||
gentoolinux
|
||||
kernel
|
||||
binaries
|
||||
security
|
||||
upgrading
|
||||
|
||||
@@ -11,10 +11,10 @@ In short, Docker has the following kernel requirements:
|
||||
|
||||
- Linux version 3.8 or above.
|
||||
|
||||
- `AUFS support <http://aufs.sourceforge.net/>`_.
|
||||
|
||||
- Cgroups and namespaces must be enabled.
|
||||
|
||||
*Note: as of 0.7 docker no longer requires aufs. AUFS support is still available as an optional driver.*
|
||||
|
||||
The officially supported kernel is the one recommended by the
|
||||
:ref:`ubuntu_linux` installation path. It is the one that most developers
|
||||
will use, and the one that receives the most attention from the core
|
||||
@@ -58,17 +58,6 @@ detects something older than 3.8.
|
||||
See issue `#407 <https://github.com/dotcloud/docker/issues/407>`_ for details.
|
||||
|
||||
|
||||
AUFS support
|
||||
------------
|
||||
|
||||
Docker currently relies on AUFS, an unioning filesystem.
|
||||
While AUFS is included in the kernels built by the Debian and Ubuntu
|
||||
distributions, is not part of the standard kernel. This means that if
|
||||
you decide to roll your own kernel, you will have to patch your
|
||||
kernel tree to add AUFS. The process is documented on
|
||||
`AUFS webpage <http://aufs.sourceforge.net/>`_.
|
||||
|
||||
|
||||
Cgroups and namespaces
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
:description: Installing Docker on Ubuntu proviced by Rackspace
|
||||
:keywords: Rackspace Cloud, installation, docker, linux, ubuntu
|
||||
|
||||
===============
|
||||
Rackspace Cloud
|
||||
===============
|
||||
|
||||
@@ -14,14 +13,14 @@ straightforward, and you should mostly be able to follow the
|
||||
|
||||
**However, there is one caveat:**
|
||||
|
||||
If you are using any linux not already shipping with the 3.8 kernel
|
||||
If you are using any Linux not already shipping with the 3.8 kernel
|
||||
you will need to install it. And this is a little more difficult on
|
||||
Rackspace.
|
||||
|
||||
Rackspace boots their servers using grub's ``menu.lst`` and does not
|
||||
like non 'virtual' packages (e.g. xen compatible) kernels there,
|
||||
although they do work. This makes ``update-grub`` to not have the
|
||||
expected result, and you need to set the kernel manually.
|
||||
like non 'virtual' packages (e.g. Xen compatible) kernels there,
|
||||
although they do work. This results in ``update-grub`` not having the
|
||||
expected result, and you will need to set the kernel manually.
|
||||
|
||||
**Do not attempt this on a production machine!**
|
||||
|
||||
@@ -34,7 +33,7 @@ expected result, and you need to set the kernel manually.
|
||||
apt-get install linux-generic-lts-raring
|
||||
|
||||
|
||||
Great, now you have kernel installed in ``/boot/``, next is to make it
|
||||
Great, now you have the kernel installed in ``/boot/``, next you need to make it
|
||||
boot next time.
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -48,9 +47,9 @@ boot next time.
|
||||
Now you need to manually edit ``/boot/grub/menu.lst``, you will find a
|
||||
section at the bottom with the existing options. Copy the top one and
|
||||
substitute the new kernel into that. Make sure the new kernel is on
|
||||
top, and double check kernel and initrd point to the right files.
|
||||
top, and double check the kernel and initrd lines point to the right files.
|
||||
|
||||
Make special care to double check the kernel and initrd entries.
|
||||
Take special care to double check the kernel and initrd entries.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -79,7 +78,7 @@ It will probably look something like this:
|
||||
initrd /boot/initrd.img-3.2.0-38-virtual
|
||||
|
||||
|
||||
Reboot server (either via command line or console)
|
||||
Reboot the server (either via command line or console)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
.. _ubuntu_linux:
|
||||
|
||||
Ubuntu Linux
|
||||
============
|
||||
Ubuntu
|
||||
======
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -14,16 +14,11 @@ Ubuntu Linux
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
Right now, the officially supported distribution are:
|
||||
Docker is supported on the following versions of Ubuntu:
|
||||
|
||||
- :ref:`ubuntu_precise`
|
||||
- :ref:`ubuntu_raring`
|
||||
|
||||
Docker has the following dependencies
|
||||
|
||||
* Linux kernel 3.8 (read more about :ref:`kernel`)
|
||||
* AUFS file system support (we are working on BTRFS support as an alternative)
|
||||
|
||||
Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated
|
||||
Firewall) <https://help.ubuntu.com/community/UFW>`_
|
||||
|
||||
@@ -70,32 +65,35 @@ Installation
|
||||
|
||||
Docker is available as a Debian package, which makes installation easy.
|
||||
|
||||
First add the Docker repository key to your local keychain. You can use the
|
||||
``apt-key`` command to check the fingerprint matches: ``36A1 D786 9245 C895 0F96
|
||||
6E92 D857 6A8B A88D 21E9``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Add the Docker repository key to your local keychain
|
||||
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
Add the Docker repository to your apt sources list, update and install the
|
||||
``lxc-docker`` package.
|
||||
|
||||
# Update your sources
|
||||
sudo apt-get update
|
||||
|
||||
# Install, you will see another warning that the package cannot be authenticated. Confirm install.
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
Verify it worked
|
||||
*You may receive a warning that the package isn't trusted. Answer yes to
|
||||
continue installation.*
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
Now verify that the installation has worked by downloading the ``ubuntu`` image
|
||||
and launching a container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
# type 'exit' to exit
|
||||
|
||||
Type ``exit`` to exit
|
||||
|
||||
**Done!**, now continue with the :ref:`hello_world` example.
|
||||
|
||||
@@ -107,10 +105,13 @@ Ubuntu Raring 13.04 (64 bit)
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
**AUFS filesystem support**
|
||||
**Optional AUFS filesystem support**
|
||||
|
||||
Ubuntu Raring already comes with the 3.8 kernel, so we don't need to install it. However, not all systems
|
||||
have AUFS filesystem support enabled, so we need to install it.
|
||||
have AUFS filesystem support enabled. AUFS support is optional as of version 0.7, but it's still available as
|
||||
a driver and we recommend using it if you can.
|
||||
|
||||
To make sure AUFS is installed, run the following commands:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -123,36 +124,37 @@ Installation
|
||||
|
||||
Docker is available as a Debian package, which makes installation easy.
|
||||
|
||||
*Please note that these instructions have changed for 0.6. If you are upgrading from an earlier version, you will need
|
||||
to follow them again.*
|
||||
.. warning::
|
||||
|
||||
Please note that these instructions have changed for 0.6. If you are upgrading from an earlier version, you will need
|
||||
to follow them again.
|
||||
|
||||
First add the Docker repository key to your local keychain. You can use the
|
||||
``apt-key`` command to check the fingerprint matches: ``36A1 D786 9245 C895 0F96
|
||||
6E92 D857 6A8B A88D 21E9``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Add the Docker repository key to your local keychain
|
||||
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
|
||||
# update
|
||||
sudo apt-get update
|
||||
|
||||
# install
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
|
||||
Verify it worked
|
||||
Add the Docker repository to your apt sources list, update and install the
|
||||
``lxc-docker`` package.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
Now verify that the installation has worked by downloading the ``ubuntu`` image
|
||||
and launching a container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# download the base 'ubuntu' container
|
||||
# and run bash inside it while setting up an interactive shell
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
# type exit to exit
|
||||
|
||||
Type ``exit`` to exit
|
||||
|
||||
**Done!**, now continue with the :ref:`hello_world` example.
|
||||
|
||||
@@ -162,8 +164,8 @@ Verify it worked
|
||||
Docker and UFW
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Docker uses a bridge to manage container networking. By default, UFW
|
||||
drops all `forwarding`, thus a first step is to enable UFW forwarding:
|
||||
Docker uses a bridge to manage container networking. By default, UFW drops all
|
||||
`forwarding` traffic. As a result will you need to enable UFW forwarding:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -181,9 +183,9 @@ Then reload UFW:
|
||||
sudo ufw reload
|
||||
|
||||
|
||||
UFW's default set of rules denied all `incoming`, so if you want to be
|
||||
able to reach your containers from another host, you should allow
|
||||
incoming connections on the docker port (default 4243):
|
||||
UFW's default set of rules denies all `incoming` traffic. If you want to be
|
||||
able to reach your containers from another host then you should allow
|
||||
incoming connections on the Docker port (default 4243):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
@@ -76,11 +76,11 @@ client commands.
|
||||
# Add the docker group if it doesn't already exist.
|
||||
sudo groupadd docker
|
||||
|
||||
# Add the user "ubuntu" to the docker group.
|
||||
# Add the connected user "${USERNAME}" to the docker group.
|
||||
# Change the user name to match your preferred user.
|
||||
# You may have to logout and log back in again for
|
||||
# this to take effect.
|
||||
sudo gpasswd -a ubuntu docker
|
||||
sudo gpasswd -a ${USERNAME} docker
|
||||
|
||||
# Restart the docker daemon.
|
||||
sudo service docker restart
|
||||
|
||||
Vendored
+1
-1
@@ -35,7 +35,7 @@
|
||||
%}
|
||||
|
||||
{#
|
||||
This part is hopefully complex because things like |cut '/index/' are not available in spinx jinja
|
||||
This part is hopefully complex because things like |cut '/index/' are not available in Sphinx jinja
|
||||
and will make it crash. (and we need index/ out.
|
||||
#}
|
||||
<link rel="canonical" href="http://docs.docker.io/en/latest/
|
||||
|
||||
@@ -112,7 +112,7 @@ func (graph *Graph) Create(layerData archive.Archive, container *Container, comm
|
||||
img := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: comment,
|
||||
Created: time.Now(),
|
||||
Created: time.Now().UTC(),
|
||||
DockerVersion: VERSION,
|
||||
Author: author,
|
||||
Config: config,
|
||||
@@ -131,7 +131,15 @@ func (graph *Graph) Create(layerData archive.Archive, container *Container, comm
|
||||
|
||||
// Register imports a pre-existing image into the graph.
|
||||
// FIXME: pass img as first argument
|
||||
func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Image) error {
|
||||
func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Image) (err error) {
|
||||
defer func() {
|
||||
// If any error occurs, remove the new dir from the driver.
|
||||
// Don't check for errors since the dir might not have been created.
|
||||
// FIXME: this leaves a possible race condition.
|
||||
if err != nil {
|
||||
graph.driver.Remove(img.ID)
|
||||
}
|
||||
}()
|
||||
if err := ValidateID(img.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -147,6 +155,12 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im
|
||||
return err
|
||||
}
|
||||
|
||||
// If the driver has this ID but the graph doesn't, remove it from the driver to start fresh.
|
||||
// (the graph is the source of truth).
|
||||
// Ignore errors, since we don't know if the driver correctly returns ErrNotExist.
|
||||
// (FIXME: make that mandatory for drivers).
|
||||
graph.driver.Remove(img.ID)
|
||||
|
||||
tmp, err := graph.Mktemp("")
|
||||
defer os.RemoveAll(tmp)
|
||||
if err != nil {
|
||||
@@ -363,3 +377,7 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
|
||||
func (graph *Graph) imageRoot(id string) string {
|
||||
return path.Join(graph.Root, id)
|
||||
}
|
||||
|
||||
func (graph *Graph) Driver() graphdriver.Driver {
|
||||
return graph.driver
|
||||
}
|
||||
|
||||
-302
@@ -1,302 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
// Root should exist
|
||||
if _, err := os.Stat(graph.Root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Map() should be empty
|
||||
if l, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(l) != 0 {
|
||||
t.Fatalf("len(Map()) should return %d, not %d", 0, len(l))
|
||||
}
|
||||
}
|
||||
|
||||
// Test that Register can be interrupted cleanly without side effects
|
||||
func TestInterruptedRegister(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
|
||||
image := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
go graph.Register(nil, badArchive, image)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
|
||||
if _, err := graph.Get(image.ID); err == nil {
|
||||
t.Fatal("Image should not exist after Register is interrupted")
|
||||
}
|
||||
// Registering the same image again should succeed if the first register was interrupted
|
||||
goodArchive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Register(nil, goodArchive, image); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
|
||||
// create multiple, check the amount of images and paths, etc..)
|
||||
func TestGraphCreate(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ValidateID(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if image.Comment != "Testing" {
|
||||
t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
|
||||
}
|
||||
if image.DockerVersion != VERSION {
|
||||
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", VERSION, image.DockerVersion)
|
||||
}
|
||||
images, err := graph.Map()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images[image.ID] == nil {
|
||||
t.Fatalf("Could not find image with id %s", image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
err = graph.Register(nil, archive, image)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if resultImg, err := graph.Get(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if resultImg.ID != image.ID {
|
||||
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
|
||||
}
|
||||
if resultImg.Comment != image.Comment {
|
||||
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an image can be deleted by its shorthand prefix
|
||||
func TestDeletePrefix(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
img := createTestImage(graph, t)
|
||||
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
}
|
||||
|
||||
func createTestImage(graph *Graph, t *testing.T) *Image {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := graph.Create(archive, nil, "Test image", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
if err := graph.Delete(img.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 2)
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
// Test delete wrong name
|
||||
if err := graph.Delete("Not_foo"); err == nil {
|
||||
t.Fatalf("Deleting wrong ID should return an error")
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test delete twice (pull -> rm -> pull -> rm)
|
||||
if err := graph.Register(nil, archive, img1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
}
|
||||
|
||||
func TestByParent(t *testing.T) {
|
||||
archive1, _ := fakeTar()
|
||||
archive2, _ := fakeTar()
|
||||
archive3, _ := fakeTar()
|
||||
|
||||
graph := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
parentImage := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "parent",
|
||||
Created: time.Now(),
|
||||
Parent: "",
|
||||
}
|
||||
childImage1 := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "child1",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
childImage2 := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "child2",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
_ = graph.Register(nil, archive1, parentImage)
|
||||
_ = graph.Register(nil, archive2, childImage1)
|
||||
_ = graph.Register(nil, archive3, childImage2)
|
||||
|
||||
byParent, err := graph.ByParent()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
numChildren := len(byParent[parentImage.ID])
|
||||
if numChildren != 2 {
|
||||
t.Fatalf("Expected 2 children, found %d", numChildren)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNImages(graph *Graph, t *testing.T, n int) {
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if actualN := len(images); actualN != n {
|
||||
t.Fatalf("Expected %d images, found %d", n, actualN)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
|
||||
func tempGraph(t *testing.T) *Graph {
|
||||
tmp, err := ioutil.TempDir("", "docker-graph-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backend, err := graphdriver.New(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
graph, err := NewGraph(tmp, backend)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return graph
|
||||
}
|
||||
|
||||
func nukeGraph(graph *Graph) {
|
||||
graph.driver.Cleanup()
|
||||
os.RemoveAll(graph.Root)
|
||||
}
|
||||
|
||||
func testArchive(t *testing.T) archive.Archive {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return archive
|
||||
}
|
||||
|
||||
func fakeTar() (io.Reader, error) {
|
||||
content := []byte("Hello world!\n")
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
||||
hdr := new(tar.Header)
|
||||
hdr.Size = int64(len(content))
|
||||
hdr.Name = name
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tw.Write([]byte(content))
|
||||
}
|
||||
tw.Close()
|
||||
return buf, nil
|
||||
}
|
||||
@@ -650,10 +650,13 @@ func (devices *DeviceSet) waitRemove(hash string) error {
|
||||
// The error might actually be something else, but we can't differentiate.
|
||||
return nil
|
||||
}
|
||||
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
|
||||
if i%100 == 0 {
|
||||
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
|
||||
}
|
||||
if devinfo.Exists == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
if i == 1000 {
|
||||
@@ -676,7 +679,9 @@ func (devices *DeviceSet) waitClose(hash string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
|
||||
if i%100 == 0 {
|
||||
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
|
||||
}
|
||||
if devinfo.OpenCount == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func AttachLoopDevice(filename string) (*osFile, error) {
|
||||
}
|
||||
|
||||
func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) {
|
||||
dev, inode, err := dmGetLoopbackBackingFile(file.Fd())
|
||||
dev, inode, err := DmGetLoopbackBackingFile(file.Fd())
|
||||
if err != 0 {
|
||||
return 0, 0, ErrGetLoopbackBackingFile
|
||||
}
|
||||
@@ -195,8 +195,7 @@ func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) {
|
||||
}
|
||||
|
||||
func LoopbackSetCapacity(file *osFile) error {
|
||||
err := dmLoopbackSetCapacity(file.Fd())
|
||||
if err != 0 {
|
||||
if err := DmLoopbackSetCapacity(file.Fd()); err != 0 {
|
||||
return ErrLoopbackSetCapacity
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -148,26 +148,28 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
DmAttachLoopDevice = dmAttachLoopDeviceFct
|
||||
DmGetBlockSize = dmGetBlockSizeFct
|
||||
DmGetLibraryVersion = dmGetLibraryVersionFct
|
||||
DmGetNextTarget = dmGetNextTargetFct
|
||||
DmLogInitVerbose = dmLogInitVerboseFct
|
||||
DmSetDevDir = dmSetDevDirFct
|
||||
DmTaskAddTarget = dmTaskAddTargetFct
|
||||
DmTaskCreate = dmTaskCreateFct
|
||||
DmTaskDestroy = dmTaskDestroyFct
|
||||
DmTaskGetInfo = dmTaskGetInfoFct
|
||||
DmTaskRun = dmTaskRunFct
|
||||
DmTaskSetAddNode = dmTaskSetAddNodeFct
|
||||
DmTaskSetCookie = dmTaskSetCookieFct
|
||||
DmTaskSetMessage = dmTaskSetMessageFct
|
||||
DmTaskSetName = dmTaskSetNameFct
|
||||
DmTaskSetRo = dmTaskSetRoFct
|
||||
DmTaskSetSector = dmTaskSetSectorFct
|
||||
DmUdevWait = dmUdevWaitFct
|
||||
GetBlockSize = getBlockSizeFct
|
||||
LogWithErrnoInit = logWithErrnoInitFct
|
||||
DmAttachLoopDevice = dmAttachLoopDeviceFct
|
||||
DmGetBlockSize = dmGetBlockSizeFct
|
||||
DmGetLibraryVersion = dmGetLibraryVersionFct
|
||||
DmGetNextTarget = dmGetNextTargetFct
|
||||
DmLogInitVerbose = dmLogInitVerboseFct
|
||||
DmSetDevDir = dmSetDevDirFct
|
||||
DmTaskAddTarget = dmTaskAddTargetFct
|
||||
DmTaskCreate = dmTaskCreateFct
|
||||
DmTaskDestroy = dmTaskDestroyFct
|
||||
DmTaskGetInfo = dmTaskGetInfoFct
|
||||
DmTaskRun = dmTaskRunFct
|
||||
DmTaskSetAddNode = dmTaskSetAddNodeFct
|
||||
DmTaskSetCookie = dmTaskSetCookieFct
|
||||
DmTaskSetMessage = dmTaskSetMessageFct
|
||||
DmTaskSetName = dmTaskSetNameFct
|
||||
DmTaskSetRo = dmTaskSetRoFct
|
||||
DmTaskSetSector = dmTaskSetSectorFct
|
||||
DmUdevWait = dmUdevWaitFct
|
||||
GetBlockSize = getBlockSizeFct
|
||||
LogWithErrnoInit = logWithErrnoInitFct
|
||||
DmGetLoopbackBackingFile = dmGetLoopbackBackingFileFct
|
||||
DmLoopbackSetCapacity = dmLoopbackSetCapacityFct
|
||||
)
|
||||
|
||||
func free(p *C.char) {
|
||||
@@ -238,14 +240,14 @@ func dmTaskAddTargetFct(task *CDmTask,
|
||||
C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
|
||||
}
|
||||
|
||||
func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, sysErrno) {
|
||||
func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) {
|
||||
var lo64 C.struct_loop_info64
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64,
|
||||
uintptr(unsafe.Pointer(&lo64)))
|
||||
return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err)
|
||||
}
|
||||
|
||||
func dmLoopbackSetCapacity(fd uintptr) sysErrno {
|
||||
func dmLoopbackSetCapacityFct(fd uintptr) sysErrno {
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0)
|
||||
return sysErrno(err)
|
||||
}
|
||||
|
||||
+8
-17
@@ -8,8 +8,6 @@ import (
|
||||
"path"
|
||||
)
|
||||
|
||||
var DefaultDriver string
|
||||
|
||||
type InitFunc func(root string) (Driver, error)
|
||||
|
||||
type Driver interface {
|
||||
@@ -34,13 +32,14 @@ type Differ interface {
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultDriver string
|
||||
// All registred drivers
|
||||
drivers map[string]InitFunc
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"aufs",
|
||||
"devicemapper",
|
||||
"dummy",
|
||||
"vfs",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -64,14 +63,8 @@ func GetDriver(name, home string) (Driver, error) {
|
||||
return nil, fmt.Errorf("No such driver: %s", name)
|
||||
}
|
||||
|
||||
func New(root string) (Driver, error) {
|
||||
var driver Driver
|
||||
var lastError error
|
||||
|
||||
for _, name := range []string{
|
||||
os.Getenv("DOCKER_DRIVER"),
|
||||
DefaultDriver,
|
||||
} {
|
||||
func New(root string) (driver Driver, err error) {
|
||||
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
|
||||
if name != "" {
|
||||
return GetDriver(name, root)
|
||||
}
|
||||
@@ -79,9 +72,8 @@ func New(root string) (Driver, error) {
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range priority {
|
||||
driver, lastError = GetDriver(name, root)
|
||||
if lastError != nil {
|
||||
utils.Debugf("Error loading driver %s: %s", name, lastError)
|
||||
if driver, err = GetDriver(name, root); err != nil {
|
||||
utils.Debugf("Error loading driver %s: %s", name, err)
|
||||
continue
|
||||
}
|
||||
return driver, nil
|
||||
@@ -89,11 +81,10 @@ func New(root string) (Driver, error) {
|
||||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for _, initFunc := range drivers {
|
||||
driver, lastError = initFunc(root)
|
||||
if lastError != nil {
|
||||
if driver, err = initFunc(root); err != nil {
|
||||
continue
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
return nil, lastError
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package dummy
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("dummy", Init)
|
||||
graphdriver.Register("vfs", Init)
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
@@ -24,7 +24,7 @@ type Driver struct {
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "dummy"
|
||||
return "vfs"
|
||||
}
|
||||
|
||||
func (d *Driver) Status() [][2]string {
|
||||
+14
-3
@@ -37,13 +37,24 @@ DEFAULT_BUNDLES=(
|
||||
test
|
||||
dynbinary
|
||||
dyntest
|
||||
tgz
|
||||
ubuntu
|
||||
)
|
||||
|
||||
VERSION=$(cat ./VERSION)
|
||||
GITCOMMIT=$(git rev-parse --short HEAD)
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
GITCOMMIT="$GITCOMMIT-dirty"
|
||||
if [ -d .git ] && command -v git &> /dev/null; then
|
||||
GITCOMMIT=$(git rev-parse --short HEAD)
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
GITCOMMIT="$GITCOMMIT-dirty"
|
||||
fi
|
||||
elif [ "$DOCKER_GITCOMMIT" ]; then
|
||||
GITCOMMIT="$DOCKER_GITCOMMIT"
|
||||
else
|
||||
echo >&2 'error: .git directory missing and DOCKER_GITCOMMIT not specified'
|
||||
echo >&2 ' Please either build with the .git directory accessible, or specify the'
|
||||
echo >&2 ' exact (--short) commit hash you are building using DOCKER_GITCOMMIT for'
|
||||
echo >&2 ' future accountability in diagnosing build issues. Thanks!'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use these flags when compiling the tests and final binary
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
DEST="$1"
|
||||
BINARY="$DEST/../binary/docker-$VERSION"
|
||||
TGZ="$DEST/docker-$VERSION.tgz"
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -x "$BINARY" ]; then
|
||||
echo >&2 'error: binary must be run before tgz'
|
||||
false
|
||||
fi
|
||||
|
||||
mkdir -p "$DEST/build"
|
||||
|
||||
mkdir -p "$DEST/build/usr/local/bin"
|
||||
cp -L "$BINARY" "$DEST/build/usr/local/bin/docker"
|
||||
|
||||
tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr
|
||||
|
||||
rm -rf "$DEST/build"
|
||||
|
||||
echo "Created tgz: $TGZ"
|
||||
@@ -47,6 +47,7 @@ cd /go/src/github.com/dotcloud/docker
|
||||
|
||||
RELEASE_BUNDLES=(
|
||||
binary
|
||||
tgz
|
||||
ubuntu
|
||||
)
|
||||
|
||||
@@ -188,6 +189,22 @@ EOF
|
||||
echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu"
|
||||
}
|
||||
|
||||
# Upload a tgz to S3
|
||||
release_tgz() {
|
||||
[ -e bundles/$VERSION/tgz/docker-$VERSION.tgz ] || {
|
||||
echo >&2 './hack/make.sh must be run before release_binary'
|
||||
exit 1
|
||||
}
|
||||
|
||||
S3DIR=s3://$BUCKET/builds/Linux/x86_64
|
||||
s3cmd --acl-public put bundles/$VERSION/tgz/docker-$VERSION.tgz $S3DIR/docker-$VERSION.tgz
|
||||
|
||||
if [ -z "$NOLATEST" ]; then
|
||||
echo "Copying docker-$VERSION.tgz to docker-latest.tgz"
|
||||
s3cmd --acl-public cp $S3DIR/docker-$VERSION.tgz $S3DIR/docker-latest.tgz
|
||||
fi
|
||||
}
|
||||
|
||||
# Upload a static binary to S3
|
||||
release_binary() {
|
||||
[ -e bundles/$VERSION/binary/docker-$VERSION ] || {
|
||||
@@ -230,6 +247,7 @@ release_test() {
|
||||
main() {
|
||||
setup_s3
|
||||
release_binary
|
||||
release_tgz
|
||||
release_ubuntu
|
||||
release_index
|
||||
release_test
|
||||
|
||||
@@ -84,12 +84,12 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, la
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
start := time.Now()
|
||||
start := time.Now().UTC()
|
||||
utils.Debugf("Start untar layer")
|
||||
if err := archive.ApplyLayer(layer, layerData); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
|
||||
utils.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
||||
|
||||
if img.Parent == "" {
|
||||
if size, err = utils.TreeSize(layer); err != nil {
|
||||
|
||||
+268
-11
@@ -1,12 +1,17 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMount(t *testing.T) {
|
||||
@@ -41,19 +46,271 @@ func TestMount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME: duplicate
|
||||
func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
|
||||
tmp, err := ioutil.TempDir("", "docker-graph-")
|
||||
if err != nil {
|
||||
func TestInit(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
// Root should exist
|
||||
if _, err := os.Stat(graph.Root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
driver, err := graphdriver.New(tmp)
|
||||
if err != nil {
|
||||
// Map() should be empty
|
||||
if l, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(l) != 0 {
|
||||
t.Fatalf("len(Map()) should return %d, not %d", 0, len(l))
|
||||
}
|
||||
graph, err := docker.NewGraph(tmp, driver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return graph, driver
|
||||
}
|
||||
|
||||
// Test that Register can be interrupted cleanly without side effects
|
||||
func TestInterruptedRegister(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
|
||||
image := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
|
||||
graph.Register(nil, badArchive, image)
|
||||
if _, err := graph.Get(image.ID); err == nil {
|
||||
t.Fatal("Image should not exist after Register is interrupted")
|
||||
}
|
||||
// Registering the same image again should succeed if the first register was interrupted
|
||||
goodArchive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Register(nil, goodArchive, image); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
|
||||
// create multiple, check the amount of images and paths, etc..)
|
||||
func TestGraphCreate(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := docker.ValidateID(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if image.Comment != "Testing" {
|
||||
t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
|
||||
}
|
||||
if image.DockerVersion != docker.VERSION {
|
||||
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", docker.VERSION, image.DockerVersion)
|
||||
}
|
||||
images, err := graph.Map()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images[image.ID] == nil {
|
||||
t.Fatalf("Could not find image with id %s", image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
err = graph.Register(nil, archive, image)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if resultImg, err := graph.Get(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if resultImg.ID != image.ID {
|
||||
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
|
||||
}
|
||||
if resultImg.Comment != image.Comment {
|
||||
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an image can be deleted by its shorthand prefix
|
||||
func TestDeletePrefix(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
img := createTestImage(graph, t)
|
||||
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
}
|
||||
|
||||
func createTestImage(graph *docker.Graph, t *testing.T) *docker.Image {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := graph.Create(archive, nil, "Test image", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
if err := graph.Delete(img.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 2)
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
// Test delete wrong name
|
||||
if err := graph.Delete("Not_foo"); err == nil {
|
||||
t.Fatalf("Deleting wrong ID should return an error")
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test delete twice (pull -> rm -> pull -> rm)
|
||||
if err := graph.Register(nil, archive, img1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
}
|
||||
|
||||
func TestByParent(t *testing.T) {
|
||||
archive1, _ := fakeTar()
|
||||
archive2, _ := fakeTar()
|
||||
archive3, _ := fakeTar()
|
||||
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
parentImage := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "parent",
|
||||
Created: time.Now(),
|
||||
Parent: "",
|
||||
}
|
||||
childImage1 := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "child1",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
childImage2 := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "child2",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
_ = graph.Register(nil, archive1, parentImage)
|
||||
_ = graph.Register(nil, archive2, childImage1)
|
||||
_ = graph.Register(nil, archive3, childImage2)
|
||||
|
||||
byParent, err := graph.ByParent()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
numChildren := len(byParent[parentImage.ID])
|
||||
if numChildren != 2 {
|
||||
t.Fatalf("Expected 2 children, found %d", numChildren)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
|
||||
func assertNImages(graph *docker.Graph, t *testing.T, n int) {
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if actualN := len(images); actualN != n {
|
||||
t.Fatalf("Expected %d images, found %d", n, actualN)
|
||||
}
|
||||
}
|
||||
|
||||
func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
|
||||
tmp, err := ioutil.TempDir("", "docker-graph-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
driver, err := graphdriver.New(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
graph, err := docker.NewGraph(tmp, driver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return graph, driver
|
||||
}
|
||||
|
||||
func nukeGraph(graph *docker.Graph) {
|
||||
graph.Driver().Cleanup()
|
||||
os.RemoveAll(graph.Root)
|
||||
}
|
||||
|
||||
func testArchive(t *testing.T) archive.Archive {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return archive
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ func layerArchive(tarfile string) (io.Reader, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Always use the same driver (vfs) for all integration tests.
|
||||
// To test other drivers, we need a dedicated driver validation suite.
|
||||
os.Setenv("DOCKER_DRIVER", "vfs")
|
||||
os.Setenv("TEST", "1")
|
||||
|
||||
// Hack to run sys init during unit testing
|
||||
|
||||
@@ -11,14 +11,51 @@ type NameChecker interface {
|
||||
}
|
||||
|
||||
var (
|
||||
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"}
|
||||
left = [...]string{"happy", "jolly", "dreamy", "sad", "angry", "pensive", "focused", "sleepy", "grave", "distracted", "determined", "stoic", "stupefied", "sharp", "agitated", "cocky", "tender", "goofy", "furious", "desperate", "hopeful", "compassionate", "silly", "lonely", "condescending", "naughty", "kickass", "drunk", "boring", "nostalgic", "ecstatic", "insane", "cranky", "mad", "jovial", "sick", "hungry", "thirsty", "elegant", "backstabbing", "clever", "trusting", "loving", "suspicious", "berserk", "high", "romantic", "prickly", "evil"}
|
||||
// Docker 0.7.x generates names from notable scientists and hackers.
|
||||
//
|
||||
// Ada Lovelace invented the first algorithm. http://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
|
||||
// Alan Turing was a founding father of computer science. http://en.wikipedia.org/wiki/Alan_Turing.
|
||||
// Albert Einstein invented the general theory of relativity. http://en.wikipedia.org/wiki/Albert_Einstein
|
||||
// Ambroise Pare invented modern surgery. http://en.wikipedia.org/wiki/Ambroise_Par%C3%A9
|
||||
// Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. http://en.wikipedia.org/wiki/Archimedes
|
||||
// Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod.
|
||||
// Charles Babbage invented the concept of a programmable computer. http://en.wikipedia.org/wiki/Charles_Babbage.
|
||||
// Charles Darwin established the principles of natural evolution. http://en.wikipedia.org/wiki/Charles_Darwin.
|
||||
// Dennis Ritchie and Ken Thompson created UNIX and the C programming language. http://en.wikipedia.org/wiki/Dennis_Ritchie http://en.wikipedia.org/wiki/Ken_Thompson
|
||||
// Douglas Engelbart gave the mother of all demos: http://en.wikipedia.org/wiki/Douglas_Engelbart
|
||||
// Emmett Brown invented time travel. http://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
|
||||
// Enrico Fermi invented the first nuclear reactor. http://en.wikipedia.org/wiki/Enrico_Fermi.
|
||||
// Euclid invented geometry. http://en.wikipedia.org/wiki/Euclid
|
||||
// Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. http://en.wikipedia.org/wiki/Galileo_Galilei
|
||||
// Henry Poincare made fundamental contributions in several fields of mathematics. http://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
|
||||
// Isaac Newton invented classic mechanics and modern optics. http://en.wikipedia.org/wiki/Isaac_Newton
|
||||
// John McCarthy invented LISP: http://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
|
||||
// Leonardo Da Vinci invented too many things to list here. http://en.wikipedia.org/wiki/Leonardo_da_Vinci.
|
||||
// Linus Torvalds invented Linux and Git. http://en.wikipedia.org/wiki/Linus_Torvalds
|
||||
// Louis Pasteur discovered vaccination, fermentation and pasteurization. http://en.wikipedia.org/wiki/Louis_Pasteur.
|
||||
// Malcolm McLean invented the modern shipping container: http://en.wikipedia.org/wiki/Malcom_McLean
|
||||
// Marie Curie discovered radioactivity. http://en.wikipedia.org/wiki/Marie_Curie.
|
||||
// Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. http://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
|
||||
// Niels Bohr is the father of quantum theory. http://en.wikipedia.org/wiki/Niels_Bohr.
|
||||
// Nikola Tesla invented the AC electric system and every gaget ever used by a James Bond villain. http://en.wikipedia.org/wiki/Nikola_Tesla
|
||||
// Pierre de Fermat pioneered several aspects of modern mathematics. http://en.wikipedia.org/wiki/Pierre_de_Fermat
|
||||
// Richard Feynmann was a key contributor to quantum mechanics and particle physics. http://en.wikipedia.org/wiki/Richard_Feynman
|
||||
// Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. http://en.wikipedia.org/wiki/Rob_Pike
|
||||
// Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. http://en.wikipedia.org/wiki/Stephen_Hawking
|
||||
// Steve Wozniak invented the Apple I and Apple II. http://en.wikipedia.org/wiki/Steve_Wozniak
|
||||
// Werner Heisenberg was a founding father of quantum mechanics. http://en.wikipedia.org/wiki/Werner_Heisenberg
|
||||
// William Shockley, Walter Houser Brattain and John Bardeen co-invented the transistor (thanks Brian Goff).
|
||||
// http://en.wikipedia.org/wiki/John_Bardeen
|
||||
// http://en.wikipedia.org/wiki/Walter_Houser_Brattain
|
||||
// http://en.wikipedia.org/wiki/William_Shockley
|
||||
right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclide", "newton", "fermat", "archimede", "poincare", "heisenberg", "feynmann", "hawkings", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley"}
|
||||
)
|
||||
|
||||
func GenerateRandomName(checker NameChecker) (string, error) {
|
||||
retry := 5
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
name := fmt.Sprintf("%s_%s", colors[rand.Intn(len(colors))], animals[rand.Intn(len(animals))])
|
||||
name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))])
|
||||
for checker != nil && checker.Exists(name) && retry > 0 {
|
||||
name = fmt.Sprintf("%s%d", name, rand.Intn(10))
|
||||
retry = retry - 1
|
||||
|
||||
@@ -26,3 +26,24 @@ func TestGenerateRandomName(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Make sure the generated names are awesome
|
||||
func TestGenerateAwesomeNames(t *testing.T) {
|
||||
name, err := GenerateRandomName(&FalseChecker{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !isAwesome(name) {
|
||||
t.Fatalf("Generated name '%s' is not awesome.", name)
|
||||
}
|
||||
}
|
||||
|
||||
// To be awesome, a container name must involve cool inventors, be easy to remember,
|
||||
// be at least mildly funny, and always be politically correct for enterprise adoption.
|
||||
func isAwesome(name string) bool {
|
||||
coolInventorNames := true
|
||||
easyToRemember := true
|
||||
mildlyFunnyOnOccasion := true
|
||||
politicallyCorrect := true
|
||||
return coolInventorNames && easyToRemember && mildlyFunnyOnOccasion && politicallyCorrect
|
||||
}
|
||||
|
||||
@@ -661,6 +661,9 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
||||
}
|
||||
|
||||
func (manager *NetworkManager) Close() error {
|
||||
if manager.disabled {
|
||||
return nil
|
||||
}
|
||||
err1 := manager.tcpPortAllocator.Close()
|
||||
err2 := manager.udpPortAllocator.Close()
|
||||
err3 := manager.ipAllocator.Close()
|
||||
|
||||
+26
-9
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/graphdriver/aufs"
|
||||
_ "github.com/dotcloud/docker/graphdriver/devmapper"
|
||||
_ "github.com/dotcloud/docker/graphdriver/dummy"
|
||||
_ "github.com/dotcloud/docker/graphdriver/vfs"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -38,6 +38,7 @@ type Capabilities struct {
|
||||
|
||||
type Runtime struct {
|
||||
repository string
|
||||
sysInitPath string
|
||||
containers *list.List
|
||||
networkManager *NetworkManager
|
||||
graph *Graph
|
||||
@@ -404,11 +405,6 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
|
||||
return nil, nil, fmt.Errorf("No command specified")
|
||||
}
|
||||
|
||||
sysInitPath := utils.DockerInitPath()
|
||||
if sysInitPath == "" {
|
||||
return nil, nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
|
||||
}
|
||||
|
||||
// Generate id
|
||||
id := GenerateID()
|
||||
|
||||
@@ -451,7 +447,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
|
||||
container := &Container{
|
||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||
ID: id,
|
||||
Created: time.Now(),
|
||||
Created: time.Now().UTC(),
|
||||
Path: entrypoint,
|
||||
Args: args, //FIXME: de-duplicate from config
|
||||
Config: config,
|
||||
@@ -459,7 +455,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
|
||||
Image: img.ID, // Always use the resolved image id
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
// FIXME: do we need to store this in the container?
|
||||
SysInitPath: sysInitPath,
|
||||
SysInitPath: runtime.sysInitPath,
|
||||
Name: name,
|
||||
Driver: runtime.driver.String(),
|
||||
}
|
||||
@@ -663,7 +659,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
|
||||
// We don't want to use a complex driver like aufs or devmapper
|
||||
// for volumes, just a plain filesystem
|
||||
volumesDriver, err := graphdriver.GetDriver("dummy", config.Root)
|
||||
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -701,6 +697,26 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", VERSION))
|
||||
sysInitPath := utils.DockerInitPath(localCopy)
|
||||
if sysInitPath == "" {
|
||||
return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
|
||||
}
|
||||
|
||||
if !utils.IAMSTATIC {
|
||||
if err := os.Mkdir(path.Join(config.Root, fmt.Sprintf("init")), 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := utils.CopyFile(sysInitPath, localCopy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sysInitPath = localCopy
|
||||
if err := os.Chmod(sysInitPath, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
runtime := &Runtime{
|
||||
repository: runtimeRepo,
|
||||
containers: list.New(),
|
||||
@@ -713,6 +729,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
config: config,
|
||||
containerGraph: graph,
|
||||
driver: driver,
|
||||
sysInitPath: sysInitPath,
|
||||
}
|
||||
|
||||
if err := runtime.restore(); err != nil {
|
||||
|
||||
@@ -63,7 +63,10 @@ func jobInitApi(job *engine.Job) string {
|
||||
}()
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime)
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
|
||||
// https://github.com/dotcloud/docker/issues/2768
|
||||
if srv.runtime.networkManager.bridgeNetwork != nil {
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
|
||||
}
|
||||
if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
@@ -958,6 +961,8 @@ func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
|
||||
}
|
||||
|
||||
func (srv *Server) poolRemove(kind, key string) error {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
switch kind {
|
||||
case "pull":
|
||||
if c, exists := srv.pullingPool[key]; exists {
|
||||
@@ -1832,6 +1837,8 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
|
||||
}
|
||||
|
||||
func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
if srv.reqFactory == nil {
|
||||
ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...)
|
||||
md := &utils.HTTPMetaHeadersDecorator{
|
||||
@@ -1844,9 +1851,9 @@ func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HT
|
||||
}
|
||||
|
||||
func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
|
||||
now := time.Now().Unix()
|
||||
now := time.Now().UTC().Unix()
|
||||
jm := utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
|
||||
srv.events = append(srv.events, jm)
|
||||
srv.AddEvent(jm)
|
||||
for _, c := range srv.listeners {
|
||||
select { // non blocking channel
|
||||
case c <- jm:
|
||||
@@ -1856,8 +1863,20 @@ func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
|
||||
return &jm
|
||||
}
|
||||
|
||||
func (srv *Server) AddEvent(jm utils.JSONMessage) {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
srv.events = append(srv.events, jm)
|
||||
}
|
||||
|
||||
func (srv *Server) GetEvents() []utils.JSONMessage {
|
||||
srv.RLock()
|
||||
defer srv.RUnlock()
|
||||
return srv.events
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
sync.Mutex
|
||||
sync.RWMutex
|
||||
runtime *Runtime
|
||||
pullingPool map[string]chan struct{}
|
||||
pushingPool map[string]chan struct{}
|
||||
|
||||
+4
-3
@@ -59,8 +59,9 @@ func TestLogEvent(t *testing.T) {
|
||||
|
||||
srv.LogEvent("fakeaction2", "fakeid", "fakeimage")
|
||||
|
||||
if len(srv.events) != 2 {
|
||||
t.Fatalf("Expected 2 events, found %d", len(srv.events))
|
||||
numEvents := len(srv.GetEvents())
|
||||
if numEvents != 2 {
|
||||
t.Fatalf("Expected 2 events, found %d", numEvents)
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
@@ -72,7 +73,7 @@ func TestLogEvent(t *testing.T) {
|
||||
setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
|
||||
for i := 2; i < 4; i++ {
|
||||
event := <-listener
|
||||
if event != srv.events[i] {
|
||||
if event != srv.GetEvents()[i] {
|
||||
t.Fatalf("Event received it different than expected")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func (s *State) String() string {
|
||||
if s.Ghost {
|
||||
return fmt.Sprintf("Ghost")
|
||||
}
|
||||
return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().Sub(s.StartedAt)))
|
||||
return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
return fmt.Sprintf("Exit %d", s.ExitCode)
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func (s *State) SetRunning(pid int) {
|
||||
s.Ghost = false
|
||||
s.ExitCode = 0
|
||||
s.Pid = pid
|
||||
s.StartedAt = time.Now()
|
||||
s.StartedAt = time.Now().UTC()
|
||||
}
|
||||
|
||||
func (s *State) SetStopped(exitCode int) {
|
||||
@@ -76,6 +76,6 @@ func (s *State) SetStopped(exitCode int) {
|
||||
|
||||
s.Running = false
|
||||
s.Pid = 0
|
||||
s.FinishedAt = time.Now()
|
||||
s.FinishedAt = time.Now().UTC()
|
||||
s.ExitCode = exitCode
|
||||
}
|
||||
|
||||
@@ -235,14 +235,23 @@ func parseLxcOpt(opt string) (string, string, error) {
|
||||
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
|
||||
}
|
||||
|
||||
// FIXME: network related stuff (including parsing) should be grouped in network file
|
||||
const (
|
||||
PortSpecTemplate = "ip:hostPort:containerPort"
|
||||
PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort"
|
||||
)
|
||||
|
||||
// We will receive port specs in the format of ip:public:private/proto and these need to be
|
||||
// parsed in the internal types
|
||||
func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
|
||||
exposedPorts := make(map[Port]struct{}, len(ports))
|
||||
bindings := make(map[Port][]PortBinding)
|
||||
var (
|
||||
exposedPorts = make(map[Port]struct{}, len(ports))
|
||||
bindings = make(map[Port][]PortBinding)
|
||||
)
|
||||
|
||||
for _, rawPort := range ports {
|
||||
proto := "tcp"
|
||||
|
||||
if i := strings.LastIndex(rawPort, "/"); i != -1 {
|
||||
proto = rawPort[i+1:]
|
||||
rawPort = rawPort[:i]
|
||||
@@ -253,13 +262,16 @@ func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
|
||||
rawPort = fmt.Sprintf(":%s", rawPort)
|
||||
}
|
||||
|
||||
parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort)
|
||||
parts, err := utils.PartParser(PortSpecTemplate, rawPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
containerPort := parts["containerPort"]
|
||||
rawIp := parts["ip"]
|
||||
hostPort := parts["hostPort"]
|
||||
|
||||
var (
|
||||
containerPort = parts["containerPort"]
|
||||
rawIp = parts["ip"]
|
||||
hostPort = parts["hostPort"]
|
||||
)
|
||||
|
||||
if containerPort == "" {
|
||||
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
|
||||
|
||||
+47
-13
@@ -270,13 +270,14 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and
|
||||
}
|
||||
|
||||
// Figure out the path of our dockerinit (which may be SelfPath())
|
||||
func DockerInitPath() string {
|
||||
func DockerInitPath(localCopy string) string {
|
||||
selfPath := SelfPath()
|
||||
if isValidDockerInitPath(selfPath, selfPath) {
|
||||
// if we're valid, don't bother checking anything else
|
||||
return selfPath
|
||||
}
|
||||
var possibleInits = []string{
|
||||
localCopy,
|
||||
filepath.Join(filepath.Dir(selfPath), "dockerinit"),
|
||||
// "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec."
|
||||
"/usr/libexec/docker/dockerinit",
|
||||
@@ -411,7 +412,7 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
|
||||
w.buf.Write([]byte(line))
|
||||
break
|
||||
}
|
||||
b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()})
|
||||
b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now().UTC()})
|
||||
if err != nil {
|
||||
// On error, evict the writer
|
||||
delete(w.writers, sw)
|
||||
@@ -779,14 +780,19 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (jm *JSONMessage) Display(out io.Writer) error {
|
||||
func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
|
||||
if jm.Error != nil {
|
||||
if jm.Error.Code == 401 {
|
||||
return fmt.Errorf("Authentication is required.")
|
||||
}
|
||||
return jm.Error
|
||||
}
|
||||
fmt.Fprintf(out, "%c[2K\r", 27)
|
||||
endl := ""
|
||||
if isTerminal {
|
||||
// <ESC>[2K = erase entire current line
|
||||
fmt.Fprintf(out, "%c[2K\r", 27)
|
||||
endl = "\r"
|
||||
}
|
||||
if jm.Time != 0 {
|
||||
fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
|
||||
}
|
||||
@@ -797,14 +803,14 @@ func (jm *JSONMessage) Display(out io.Writer) error {
|
||||
fmt.Fprintf(out, "(from %s) ", jm.From)
|
||||
}
|
||||
if jm.Progress != "" {
|
||||
fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
|
||||
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress, endl)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\r\n", jm.Status)
|
||||
fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
|
||||
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, isTerminal bool) error {
|
||||
dec := json.NewDecoder(in)
|
||||
ids := make(map[string]int)
|
||||
diff := 0
|
||||
@@ -825,11 +831,17 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
|
||||
} else {
|
||||
diff = len(ids) - line
|
||||
}
|
||||
fmt.Fprintf(out, "%c[%dA", 27, diff)
|
||||
if isTerminal {
|
||||
// <ESC>[{diff}A = move cursor up diff rows
|
||||
fmt.Fprintf(out, "%c[%dA", 27, diff)
|
||||
}
|
||||
}
|
||||
err := jm.Display(out)
|
||||
err := jm.Display(out, isTerminal)
|
||||
if jm.ID != "" {
|
||||
fmt.Fprintf(out, "%c[%dB", 27, diff)
|
||||
if isTerminal {
|
||||
// <ESC>[{diff}B = move cursor down diff rows
|
||||
fmt.Fprintf(out, "%c[%dB", 27, diff)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1226,12 +1238,14 @@ func IsClosedError(err error) bool {
|
||||
|
||||
func PartParser(template, data string) (map[string]string, error) {
|
||||
// ip:public:private
|
||||
templateParts := strings.Split(template, ":")
|
||||
parts := strings.Split(data, ":")
|
||||
var (
|
||||
templateParts = strings.Split(template, ":")
|
||||
parts = strings.Split(data, ":")
|
||||
out = make(map[string]string, len(templateParts))
|
||||
)
|
||||
if len(parts) != len(templateParts) {
|
||||
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
|
||||
}
|
||||
out := make(map[string]string, len(templateParts))
|
||||
|
||||
for i, t := range templateParts {
|
||||
value := ""
|
||||
@@ -1279,3 +1293,23 @@ func GetCallerName(depth int) string {
|
||||
callerShortName := parts[len(parts)-1]
|
||||
return callerShortName
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
if src == dst {
|
||||
return 0, nil
|
||||
}
|
||||
sf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer sf.Close()
|
||||
if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
|
||||
return 0, err
|
||||
}
|
||||
df, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer df.Close()
|
||||
return io.Copy(df, sf)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"io"
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
func fakeTar() (io.Reader, error) {
|
||||
content := []byte("Hello world!\n")
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
||||
hdr := new(tar.Header)
|
||||
hdr.Size = int64(len(content))
|
||||
hdr.Name = name
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tw.Write([]byte(content))
|
||||
}
|
||||
tw.Close()
|
||||
return buf, nil
|
||||
}
|
||||
Reference in New Issue
Block a user