Browse Source

Merge branch 'master' into maxamillion/add_redhat_sysvinit

* master: (64 commits)
  Move the canonical run configuration objects to a sub-package
  Remove useless code in client implementation of 'run'.
  pkg/opts: a collection of custom value parsers implementing flag.Value
  Move api-specific code to the api package
  Fix the tests, too
  Fix the one spot I missed dockerversion
  fix underline/heading
  New package `nat`: utilities for manipulating the text description of network ports.
  rewrite the PostgreSQL example using a Dockerfile, and add details to it
  Move even more stuff into dockerversion
  fix underline/heading
  Move docker version introspection to a sub-package.
  add port forwarding notes for mac/boot2docker docs
  Update remote_api_client_libraries.rst
  Avoid extra mount/unmount during container registration
  add a little more information about the docker run -P option
  lxc: Drop NET_ADMIN capability in non-privileged containers
  devmapper: Remove directory when removing devicemapper device
  add a little info on upgrading
  point out that ENV DEBIAN_FRONTEND will persist, so its not recommended
  ...
Adam Miller 11 years ago
parent
commit
e36d4d8821
86 changed files with 2126 additions and 1274 deletions
  1. 19 3
      CONTRIBUTING.md
  2. 12 2
      api/api.go
  3. 15 10
      archive/archive.go
  4. 7 0
      archive/stat_linux.go
  5. 6 2
      archive/stat_unsupported.go
  6. 8 5
      auth/auth.go
  7. 6 5
      buildfile.go
  8. 18 231
      commands.go
  9. 4 3
      commands_unit_test.go
  10. 2 1
      config.go
  11. 15 150
      container.go
  12. 4 20
      container_unit_test.go
  13. 24 0
      contrib/syntax/textmate/Docker.tmbundle/Preferences/Dockerfile.tmPreferences
  14. 45 6
      contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage
  15. 16 0
      contrib/syntax/textmate/Docker.tmbundle/info.plist
  16. 0 23
      contrib/syntax/textmate/Dockerfile.YAML-tmLanguage
  17. 11 4
      contrib/syntax/textmate/README.md
  18. 1 2
      contrib/syntax/vim/syntax/dockerfile.vim
  19. 6 11
      docker/docker.go
  20. 0 5
      dockerinit/dockerinit.go
  21. 15 0
      dockerversion/dockerversion.go
  22. 53 0
      docs/sources/examples/postgresql_service.Dockerfile
  23. 62 105
      docs/sources/examples/postgresql_service.rst
  24. 7 0
      docs/sources/faq.rst
  25. 1 1
      docs/sources/installation/amazon.rst
  26. 1 1
      docs/sources/installation/archlinux.rst
  27. 98 0
      docs/sources/installation/cruxlinux.rst
  28. 1 1
      docs/sources/installation/fedora.rst
  29. 1 1
      docs/sources/installation/frugalware.rst
  30. 2 2
      docs/sources/installation/gentoolinux.rst
  31. 1 0
      docs/sources/installation/index.rst
  32. 34 7
      docs/sources/installation/mac.rst
  33. 1 1
      docs/sources/installation/openSUSE.rst
  34. 2 2
      docs/sources/installation/rackspace.rst
  35. 1 1
      docs/sources/installation/rhel.rst
  36. 1 1
      docs/sources/installation/ubuntulinux.rst
  37. 4 4
      docs/sources/installation/windows.rst
  38. 3 0
      docs/sources/reference/api/remote_api_client_libraries.rst
  39. 12 5
      docs/sources/reference/builder.rst
  40. 10 1
      docs/sources/reference/commandline/cli.rst
  41. 1 0
      docs/sources/reference/run.rst
  42. 9 0
      docs/sources/use/port_redirection.rst
  43. 2 1
      execdriver/lxc/driver.go
  44. 11 17
      execdriver/lxc/init.go
  45. 1 0
      execdriver/lxc/lxc_template.go
  46. 4 2
      graph.go
  47. 126 44
      graphdriver/devmapper/deviceset.go
  48. 1 1
      graphdriver/devmapper/devmapper.go
  49. 23 53
      graphdriver/devmapper/driver.go
  50. 0 3
      graphdriver/devmapper/driver_test.go
  51. 52 0
      hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh
  52. 15 2
      hack/make.sh
  53. 1 1
      hack/make/dynbinary
  54. 12 11
      image.go
  55. 22 20
      integration/api_test.go
  56. 59 0
      integration/buildfile_test.go
  57. 39 39
      integration/container_test.go
  58. 3 2
      integration/graph_test.go
  59. 23 21
      integration/runtime_test.go
  60. 11 10
      integration/server_test.go
  61. 5 4
      integration/utils_test.go
  62. 6 5
      links.go
  63. 8 6
      links_test.go
  64. 133 0
      nat/nat.go
  65. 28 0
      nat/sort.go
  66. 3 3
      nat/sort_test.go
  67. 18 1
      networkdriver/lxc/driver.go
  68. 11 10
      pkg/opts/opts.go
  69. 1 1
      pkg/opts/opts_test.go
  70. 1 0
      pkg/systemd/MAINTAINERS
  71. 1 0
      pkg/user/MAINTAINERS
  72. 241 0
      pkg/user/user.go
  73. 94 0
      pkg/user/user_test.go
  74. 67 0
      runconfig/compare.go
  75. 76 0
      runconfig/config.go
  76. 17 16
      runconfig/config_test.go
  77. 39 0
      runconfig/hostconfig.go
  78. 119 0
      runconfig/merge.go
  79. 246 0
      runconfig/parse.go
  80. 22 0
      runconfig/parse_test.go
  81. 9 17
      runtime.go
  82. 24 10
      server.go
  83. 0 25
      sorter.go
  84. 6 291
      utils.go
  85. 4 40
      utils/utils.go
  86. 3 2
      version.go

+ 19 - 3
CONTRIBUTING.md

@@ -7,8 +7,10 @@ feels wrong or incomplete.
 ## Reporting Issues
 
 When reporting [issues](https://github.com/dotcloud/docker/issues) 
-on GitHub please include your host OS ( Ubuntu 12.04, Fedora 19, etc... )
-and the output of `docker version` along with the output of `docker info` if possible.  
+on GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc),
+the output of `uname -a` and the output of `docker version` along with
+the output of `docker info`. Please include the steps required to reproduce
+the problem if possible and applicable.
 This information will help us review and fix your issue faster.
 
 ## Build Environment
@@ -86,6 +88,8 @@ curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/maste
 Pull requests descriptions should be as clear as possible and include a
 reference to all the issues that they address.
 
+Pull requests mustn't contain commits from other users or branches.
+
 Code review comments may be added to your pull request. Discuss, then make the
 suggested modifications and push additional commits to your feature branch. Be
 sure to post a comment after pushing. The new commits will show up in the pull
@@ -105,6 +109,18 @@ name and email address match your git configuration. The AUTHORS file is
 regenerated occasionally from the git commit history, so a mismatch may result
 in your changes being overwritten.
 
+### Merge approval
+
+Docker maintainers use LGTM (looks good to me) in comments on the code review
+to indicate acceptance.
+
+A change requires LGTMs from an absolute majority of the maintainers of each
+component affected. For example, if a change affects docs/ and registry/, it
+needs an absolute majority from the maintainers of docs/ AND, separately, an
+absolute majority of the maintainers of registry
+
+For more details see [MAINTAINERS.md](hack/MAINTAINERS.md)
+
 ### Sign your work
 
 The sign-off is a simple line at the end of the explanation for the
@@ -163,7 +179,7 @@ If you have any questions, please refer to the FAQ in the [docs](http://docs.doc
 * Step 1: learn the component inside out
 * Step 2: make yourself useful by contributing code, bugfixes, support etc.
 * Step 3: volunteer on the irc channel (#docker@freenode)
-* Step 4: propose yourself at a scheduled #docker-meeting
+* Step 4: propose yourself at a scheduled docker meeting in #docker-dev
 
 Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available.
 You don't have to be a maintainer to make a difference on the project!

+ 12 - 2
api/api.go

@@ -27,6 +27,7 @@ import (
 	"syscall"
 )
 
+// FIXME: move code common to client and server to common.go
 const (
 	APIVERSION        = 1.9
 	DEFAULTHTTPHOST   = "127.0.0.1"
@@ -34,6 +35,14 @@ const (
 	DEFAULTUNIXSOCKET = "/var/run/docker.sock"
 )
 
+func ValidateHost(val string) (string, error) {
+	host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTHTTPPORT, DEFAULTUNIXSOCKET, val)
+	if err != nil {
+		return val, err
+	}
+	return host, nil
+}
+
 type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
 
 func init() {
@@ -222,7 +231,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r
 				outLegacy := &engine.Env{}
 				outLegacy.Set("Repository", parts[0])
 				outLegacy.Set("Tag", parts[1])
-				outLegacy.Set("ID", out.Get("ID"))
+				outLegacy.Set("Id", out.Get("Id"))
 				outLegacy.SetInt64("Created", out.GetInt64("Created"))
 				outLegacy.SetInt64("Size", out.GetInt64("Size"))
 				outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize"))
@@ -320,6 +329,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite
 	job.Setenv("limit", r.Form.Get("limit"))
 
 	if version >= 1.5 {
+		w.Header().Set("Content-Type", "application/json")
 		job.Stdout.Add(w)
 	} else if outs, err = job.Stdout.AddTable(); err != nil {
 		return err
@@ -366,7 +376,7 @@ func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *h
 		env    engine.Env
 		job    = eng.Job("commit", r.Form.Get("container"))
 	)
-	if err := config.Import(r.Body); err != nil {
+	if err := config.Decode(r.Body); err != nil {
 		utils.Errorf("%s", err)
 	}
 

+ 15 - 10
archive/archive.go

@@ -5,6 +5,7 @@ import (
 	"bytes"
 	"compress/bzip2"
 	"compress/gzip"
+	"errors"
 	"fmt"
 	"github.com/dotcloud/docker/utils"
 	"io"
@@ -17,14 +18,18 @@ import (
 	"syscall"
 )
 
-type Archive io.Reader
-
-type Compression int
+type (
+	Archive     io.Reader
+	Compression int
+	TarOptions  struct {
+		Includes    []string
+		Compression Compression
+	}
+)
 
-type TarOptions struct {
-	Includes    []string
-	Compression Compression
-}
+var (
+	ErrNotImplemented = errors.New("Function not implemented")
+)
 
 const (
 	Uncompressed Compression = iota
@@ -236,14 +241,14 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader)
 		return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
 	}
 
-	if err := syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
+	if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
 		return err
 	}
 
 	// There is no LChmod, so ignore mode for symlink. Also, this
 	// must happen after chown, as that can modify the file mode
 	if hdr.Typeflag != tar.TypeSymlink {
-		if err := syscall.Chmod(path, uint32(hdr.Mode&07777)); err != nil {
+		if err := os.Chmod(path, os.FileMode(hdr.Mode&07777)); err != nil {
 			return err
 		}
 	}
@@ -251,7 +256,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader)
 	ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
 	// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
 	if hdr.Typeflag != tar.TypeSymlink {
-		if err := syscall.UtimesNano(path, ts); err != nil {
+		if err := UtimesNano(path, ts); err != nil {
 			return err
 		}
 	} else {

+ 7 - 0
archive/stat_linux.go

@@ -30,3 +30,10 @@ func LUtimesNano(path string, ts []syscall.Timespec) error {
 
 	return nil
 }
+
+func UtimesNano(path string, ts []syscall.Timespec) error {
+	if err := syscall.UtimesNano(path, ts); err != nil {
+		return err
+	}
+	return nil
+}

+ 6 - 2
archive/stat_darwin.go → archive/stat_unsupported.go

@@ -1,4 +1,4 @@
-// +build !linux !amd64
+// +build !linux
 
 package archive
 
@@ -13,5 +13,9 @@ func getLastModification(stat *syscall.Stat_t) syscall.Timespec {
 }
 
 func LUtimesNano(path string, ts []syscall.Timespec) error {
-	return nil
+	return ErrNotImplemented
+}
+
+func UtimesNano(path string, ts []syscall.Timespec) error {
+	return ErrNotImplemented
 }

+ 8 - 5
auth/auth.go

@@ -151,12 +151,15 @@ func SaveConfig(configFile *ConfigFile) error {
 
 // try to register/login to the registry server
 func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
-	client := &http.Client{}
-	reqStatusCode := 0
-	var status string
-	var reqBody []byte
+	var (
+		status        string
+		reqBody       []byte
+		err           error
+		client        = &http.Client{}
+		reqStatusCode = 0
+		serverAddress = authConfig.ServerAddress
+	)
 
-	serverAddress := authConfig.ServerAddress
 	if serverAddress == "" {
 		serverAddress = IndexServerAddress()
 	}

+ 6 - 5
buildfile.go

@@ -9,6 +9,7 @@ import (
 	"github.com/dotcloud/docker/archive"
 	"github.com/dotcloud/docker/auth"
 	"github.com/dotcloud/docker/registry"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -38,7 +39,7 @@ type buildFile struct {
 
 	image      string
 	maintainer string
-	config     *Config
+	config     *runconfig.Config
 
 	contextPath string
 	context     *utils.TarSum
@@ -101,7 +102,7 @@ func (b *buildFile) CmdFrom(name string) error {
 		}
 	}
 	b.image = image.ID
-	b.config = &Config{}
+	b.config = &runconfig.Config{}
 	if image.Config != nil {
 		b.config = image.Config
 	}
@@ -158,14 +159,14 @@ func (b *buildFile) CmdRun(args string) error {
 	if b.image == "" {
 		return fmt.Errorf("Please provide a source image with `from` prior to run")
 	}
-	config, _, _, err := ParseRun(append([]string{b.image}, b.buildCmdFromJson(args)...), nil)
+	config, _, _, err := runconfig.Parse(append([]string{b.image}, b.buildCmdFromJson(args)...), nil)
 	if err != nil {
 		return err
 	}
 
 	cmd := b.config.Cmd
 	b.config.Cmd = nil
-	MergeConfig(b.config, config)
+	runconfig.Merge(b.config, config)
 
 	defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
 
@@ -742,7 +743,7 @@ func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeC
 	return &buildFile{
 		runtime:       srv.runtime,
 		srv:           srv,
-		config:        &Config{},
+		config:        &runconfig.Config{},
 		outStream:     outStream,
 		errStream:     errStream,
 		tmpContainers: make(map[string]struct{}),

+ 18 - 231
commands.go

@@ -11,11 +11,13 @@ import (
 	"github.com/dotcloud/docker/api"
 	"github.com/dotcloud/docker/archive"
 	"github.com/dotcloud/docker/auth"
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/nat"
 	flag "github.com/dotcloud/docker/pkg/mflag"
-	"github.com/dotcloud/docker/pkg/sysinfo"
 	"github.com/dotcloud/docker/pkg/term"
 	"github.com/dotcloud/docker/registry"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -38,11 +40,6 @@ import (
 	"time"
 )
 
-var (
-	GITCOMMIT string
-	VERSION   string
-)
-
 var (
 	ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?")
 )
@@ -266,11 +263,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 	}
 	serverAddress := auth.IndexServerAddress()
 	if len(cmd.Args()) > 0 {
-		serverAddress, err = registry.ExpandAndVerifyRegistryUrl(cmd.Arg(0))
-		if err != nil {
-			return err
-		}
-		fmt.Fprintf(cli.out, "Login against server at %s\n", serverAddress)
+		serverAddress = cmd.Arg(0)
 	}
 
 	promptDefault := func(prompt string, configDefault string) {
@@ -392,12 +385,12 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
 		cmd.Usage()
 		return nil
 	}
-	if VERSION != "" {
-		fmt.Fprintf(cli.out, "Client version: %s\n", VERSION)
+	if dockerversion.VERSION != "" {
+		fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
 	}
 	fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version())
-	if GITCOMMIT != "" {
-		fmt.Fprintf(cli.out, "Git commit (client): %s\n", GITCOMMIT)
+	if dockerversion.GITCOMMIT != "" {
+		fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT)
 	}
 
 	body, _, err := readBody(cli.call("GET", "/version", nil, false))
@@ -422,7 +415,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
 	release := utils.GetReleaseVersion()
 	if release != "" {
 		fmt.Fprintf(cli.out, "Last stable version: %s", release)
-		if (VERSION != "" || remoteVersion.Exists("Version")) && (strings.Trim(VERSION, "-dev") != release || strings.Trim(remoteVersion.Get("Version"), "-dev") != release) {
+		if (dockerversion.VERSION != "" || remoteVersion.Exists("Version")) && (strings.Trim(dockerversion.VERSION, "-dev") != release || strings.Trim(remoteVersion.Get("Version"), "-dev") != release) {
 			fmt.Fprintf(cli.out, ", please update docker")
 		}
 		fmt.Fprintf(cli.out, "\n")
@@ -803,7 +796,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
 		return err
 	}
 
-	if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists && frontends != nil {
+	if frontends, exists := out.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
 		for _, frontend := range frontends {
 			fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
 		}
@@ -1455,11 +1448,11 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
 	v.Set("comment", *flComment)
 	v.Set("author", *flAuthor)
 	var (
-		config *Config
+		config *runconfig.Config
 		env    engine.Env
 	)
 	if *flConfig != "" {
-		config = &Config{}
+		config = &runconfig.Config{}
 		if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
 			return err
 		}
@@ -1749,210 +1742,9 @@ func (cli *DockerCli) CmdTag(args ...string) error {
 	return nil
 }
 
-//FIXME Only used in tests
-func ParseRun(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
-	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
-	cmd.SetOutput(ioutil.Discard)
-	cmd.Usage = nil
-	return parseRun(cmd, args, sysInfo)
-}
-
-func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
-	var (
-		// FIXME: use utils.ListOpts for attach and volumes?
-		flAttach  = NewListOpts(ValidateAttach)
-		flVolumes = NewListOpts(ValidatePath)
-		flLinks   = NewListOpts(ValidateLink)
-		flEnv     = NewListOpts(ValidateEnv)
-
-		flPublish     ListOpts
-		flExpose      ListOpts
-		flDns         ListOpts
-		flVolumesFrom ListOpts
-		flLxcOpts     ListOpts
-
-		flAutoRemove      = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
-		flDetach          = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id")
-		flNetwork         = cmd.Bool([]string{"n", "-networking"}, true, "Enable networking for this container")
-		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
-		flPublishAll      = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces")
-		flStdin           = cmd.Bool([]string{"i", "-interactive"}, false, "Keep stdin open even if not attached")
-		flTty             = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-tty")
-		flContainerIDFile = cmd.String([]string{"#cidfile", "-cidfile"}, "", "Write the container ID to the file")
-		flEntrypoint      = cmd.String([]string{"#entrypoint", "-entrypoint"}, "", "Overwrite the default entrypoint of the image")
-		flHostname        = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
-		flMemoryString    = cmd.String([]string{"m", "-memory"}, "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
-		flUser            = cmd.String([]string{"u", "-user"}, "", "Username or UID")
-		flWorkingDir      = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
-		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
-
-		// For documentation purpose
-		_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
-		_ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
-	)
-
-	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to stdin, stdout or stderr.")
-	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
-	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)")
-	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
-
-	cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat))
-	cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host")
-	cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers")
-	cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)")
-	cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-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 sysInfo != nil && *flMemoryString != "" && !sysInfo.MemoryLimit {
-		*flMemoryString = ""
-	}
-
-	// Validate input params
-	if *flDetach && flAttach.Len() > 0 {
-		return nil, nil, cmd, ErrConflictAttachDetach
-	}
-	if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
-		return nil, nil, cmd, ErrInvalidWorikingDirectory
-	}
-	if *flDetach && *flAutoRemove {
-		return nil, nil, cmd, ErrConflictDetachAutoRemove
-	}
-
-	// If neither -d or -a are set, attach to everything by default
-	if flAttach.Len() == 0 && !*flDetach {
-		if !*flDetach {
-			flAttach.Set("stdout")
-			flAttach.Set("stderr")
-			if *flStdin {
-				flAttach.Set("stdin")
-			}
-		}
-	}
-
-	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.GetMap() {
-		if arr := strings.Split(bind, ":"); len(arr) > 1 {
-			if arr[0] == "/" {
-				return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
-			}
-			dstDir := arr[1]
-			flVolumes.Set(dstDir)
-			binds = append(binds, bind)
-			flVolumes.Delete(bind)
-		} else if bind == "/" {
-			return nil, nil, cmd, fmt.Errorf("Invalid volume: path can't be '/'")
-		}
-	}
-
-	var (
-		parsedArgs = cmd.Args()
-		runCmd     []string
-		entrypoint []string
-		image      string
-	)
-	if len(parsedArgs) >= 1 {
-		image = cmd.Arg(0)
-	}
-	if len(parsedArgs) > 1 {
-		runCmd = parsedArgs[1:]
-	}
-	if *flEntrypoint != "" {
-		entrypoint = []string{*flEntrypoint}
-	}
-
-	lxcConf, err := parseLxcConfOpts(flLxcOpts)
-	if err != nil {
-		return nil, nil, cmd, err
-	}
-
-	var (
-		domainname string
-		hostname   = *flHostname
-		parts      = strings.SplitN(hostname, ".", 2)
-	)
-	if len(parts) > 1 {
-		hostname = parts[0]
-		domainname = parts[1]
-	}
-
-	ports, portBindings, err := parsePortSpecs(flPublish.GetAll())
-	if err != nil {
-		return nil, nil, cmd, err
-	}
-
-	// Merge in exposed ports to the map of published ports
-	for _, e := range flExpose.GetAll() {
-		if strings.Contains(e, ":") {
-			return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
-		}
-		p := NewPort(splitProtoPort(e))
-		if _, exists := ports[p]; !exists {
-			ports[p] = struct{}{}
-		}
-	}
-
-	config := &Config{
-		Hostname:        hostname,
-		Domainname:      domainname,
-		PortSpecs:       nil, // Deprecated
-		ExposedPorts:    ports,
-		User:            *flUser,
-		Tty:             *flTty,
-		NetworkDisabled: !*flNetwork,
-		OpenStdin:       *flStdin,
-		Memory:          flMemory,
-		CpuShares:       *flCpuShares,
-		AttachStdin:     flAttach.Get("stdin"),
-		AttachStdout:    flAttach.Get("stdout"),
-		AttachStderr:    flAttach.Get("stderr"),
-		Env:             flEnv.GetAll(),
-		Cmd:             runCmd,
-		Dns:             flDns.GetAll(),
-		Image:           image,
-		Volumes:         flVolumes.GetMap(),
-		VolumesFrom:     strings.Join(flVolumesFrom.GetAll(), ","),
-		Entrypoint:      entrypoint,
-		WorkingDir:      *flWorkingDir,
-	}
-
-	hostConfig := &HostConfig{
-		Binds:           binds,
-		ContainerIDFile: *flContainerIDFile,
-		LxcConf:         lxcConf,
-		Privileged:      *flPrivileged,
-		PortBindings:    portBindings,
-		Links:           flLinks.GetAll(),
-		PublishAllPorts: *flPublishAll,
-	}
-
-	if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
-		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
-		config.MemorySwap = -1
-	}
-
-	// When allocating stdin in attached mode, close stdin at client disconnect
-	if config.OpenStdin && config.AttachStdin {
-		config.StdinOnce = true
-	}
-	return config, hostConfig, cmd, nil
-}
-
 func (cli *DockerCli) CmdRun(args ...string) error {
-	config, hostConfig, cmd, err := parseRun(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
+	// FIXME: just use runconfig.Parse already
+	config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
 	if err != nil {
 		return err
 	}
@@ -1995,12 +1787,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
 	//if image not found try to pull it
 	if statusCode == 404 {
-		_, tag := utils.ParseRepositoryTag(config.Image)
-		if tag == "" {
-			tag = DEFAULTTAG
-		}
-
-		fmt.Fprintf(cli.err, "Unable to find image '%s' (tag: %s) locally\n", config.Image, tag)
+		fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
 
 		v := url.Values{}
 		repos, tag := utils.ParseRepositoryTag(config.Image)
@@ -2307,7 +2094,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
 			}
 		}
 	}
-	req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
+	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
 	req.Host = cli.addr
 	if data != nil {
 		req.Header.Set("Content-Type", "application/json")
@@ -2364,7 +2151,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
 	if err != nil {
 		return err
 	}
-	req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
+	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
 	req.Host = cli.addr
 	if method == "POST" {
 		req.Header.Set("Content-Type", "plain/text")
@@ -2428,7 +2215,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
 	if err != nil {
 		return err
 	}
-	req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
+	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
 	req.Header.Set("Content-Type", "plain/text")
 	req.Host = cli.addr
 

+ 4 - 3
commands_unit_test.go

@@ -1,16 +1,17 @@
 package docker
 
 import (
+	"github.com/dotcloud/docker/runconfig"
 	"strings"
 	"testing"
 )
 
-func parse(t *testing.T, args string) (*Config, *HostConfig, error) {
-	config, hostConfig, _, err := ParseRun(strings.Split(args+" ubuntu bash", " "), nil)
+func parse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig, error) {
+	config, hostConfig, _, err := runconfig.Parse(strings.Split(args+" ubuntu bash", " "), nil)
 	return config, hostConfig, err
 }
 
-func mustParse(t *testing.T, args string) (*Config, *HostConfig) {
+func mustParse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig) {
 	config, hostConfig, err := parse(t, args)
 	if err != nil {
 		t.Fatal(err)

+ 2 - 1
config.go

@@ -39,6 +39,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig {
 		EnableIptables:              job.GetenvBool("EnableIptables"),
 		EnableIpForward:             job.GetenvBool("EnableIpForward"),
 		BridgeIP:                    job.Getenv("BridgeIP"),
+		BridgeIface:                 job.Getenv("BridgeIface"),
 		DefaultIp:                   net.ParseIP(job.Getenv("DefaultIp")),
 		InterContainerCommunication: job.GetenvBool("InterContainerCommunication"),
 		GraphDriver:                 job.Getenv("GraphDriver"),
@@ -51,7 +52,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig {
 	} else {
 		config.Mtu = GetDefaultNetworkMtu()
 	}
-	config.DisableNetwork = job.Getenv("BridgeIface") == DisableNetworkBridge
+	config.DisableNetwork = config.BridgeIface == DisableNetworkBridge
 
 	return config
 }

+ 15 - 150
container.go

@@ -8,8 +8,10 @@ import (
 	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/execdriver"
 	"github.com/dotcloud/docker/graphdriver"
+	"github.com/dotcloud/docker/nat"
 	"github.com/dotcloud/docker/pkg/mount"
 	"github.com/dotcloud/docker/pkg/term"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"github.com/kr/pty"
 	"io"
@@ -41,7 +43,7 @@ type Container struct {
 	Path string
 	Args []string
 
-	Config *Config
+	Config *runconfig.Config
 	State  State
 	Image  string
 
@@ -67,109 +69,11 @@ type Container struct {
 	// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
 	// Easier than migrating older container configs :)
 	VolumesRW  map[string]bool
-	hostConfig *HostConfig
+	hostConfig *runconfig.HostConfig
 
 	activeLinks map[string]*Link
 }
 
-// Note: the Config structure should hold only portable information about the container.
-// Here, "portable" means "independent from the host we are running on".
-// Non-portable information *should* appear in HostConfig.
-type Config struct {
-	Hostname        string
-	Domainname      string
-	User            string
-	Memory          int64 // Memory limit (in bytes)
-	MemorySwap      int64 // Total memory usage (memory + swap); set `-1' to disable swap
-	CpuShares       int64 // CPU shares (relative weight vs. other containers)
-	AttachStdin     bool
-	AttachStdout    bool
-	AttachStderr    bool
-	PortSpecs       []string // Deprecated - Can be in the format of 8080/tcp
-	ExposedPorts    map[Port]struct{}
-	Tty             bool // Attach standard streams to a tty, including stdin if it is not closed.
-	OpenStdin       bool // Open stdin
-	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.
-	Env             []string
-	Cmd             []string
-	Dns             []string
-	Image           string // Name of the image as it was passed by the operator (eg. could be symbolic)
-	Volumes         map[string]struct{}
-	VolumesFrom     string
-	WorkingDir      string
-	Entrypoint      []string
-	NetworkDisabled bool
-	OnBuild         []string
-}
-
-func ContainerConfigFromJob(job *engine.Job) *Config {
-	config := &Config{
-		Hostname:        job.Getenv("Hostname"),
-		Domainname:      job.Getenv("Domainname"),
-		User:            job.Getenv("User"),
-		Memory:          job.GetenvInt64("Memory"),
-		MemorySwap:      job.GetenvInt64("MemorySwap"),
-		CpuShares:       job.GetenvInt64("CpuShares"),
-		AttachStdin:     job.GetenvBool("AttachStdin"),
-		AttachStdout:    job.GetenvBool("AttachStdout"),
-		AttachStderr:    job.GetenvBool("AttachStderr"),
-		Tty:             job.GetenvBool("Tty"),
-		OpenStdin:       job.GetenvBool("OpenStdin"),
-		StdinOnce:       job.GetenvBool("StdinOnce"),
-		Image:           job.Getenv("Image"),
-		VolumesFrom:     job.Getenv("VolumesFrom"),
-		WorkingDir:      job.Getenv("WorkingDir"),
-		NetworkDisabled: job.GetenvBool("NetworkDisabled"),
-	}
-	job.GetenvJson("ExposedPorts", &config.ExposedPorts)
-	job.GetenvJson("Volumes", &config.Volumes)
-	if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
-		config.PortSpecs = PortSpecs
-	}
-	if Env := job.GetenvList("Env"); Env != nil {
-		config.Env = Env
-	}
-	if Cmd := job.GetenvList("Cmd"); Cmd != nil {
-		config.Cmd = Cmd
-	}
-	if Dns := job.GetenvList("Dns"); Dns != nil {
-		config.Dns = Dns
-	}
-	if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
-		config.Entrypoint = Entrypoint
-	}
-
-	return config
-}
-
-type HostConfig struct {
-	Binds           []string
-	ContainerIDFile string
-	LxcConf         []KeyValuePair
-	Privileged      bool
-	PortBindings    map[Port][]PortBinding
-	Links           []string
-	PublishAllPorts bool
-}
-
-func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
-	hostConfig := &HostConfig{
-		ContainerIDFile: job.Getenv("ContainerIDFile"),
-		Privileged:      job.GetenvBool("Privileged"),
-		PublishAllPorts: job.GetenvBool("PublishAllPorts"),
-	}
-	job.GetenvJson("LxcConf", &hostConfig.LxcConf)
-	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
-	if Binds := job.GetenvList("Binds"); Binds != nil {
-		hostConfig.Binds = Binds
-	}
-	if Links := job.GetenvList("Links"); Links != nil {
-		hostConfig.Links = Links
-	}
-
-	return hostConfig
-}
-
 type BindMap struct {
 	SrcPath string
 	DstPath string
@@ -177,50 +81,11 @@ type BindMap struct {
 }
 
 var (
-	ErrContainerStart           = errors.New("The container failed to start. Unknown error")
-	ErrContainerStartTimeout    = errors.New("The container failed to start due to timed out.")
-	ErrInvalidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
-	ErrConflictAttachDetach     = errors.New("Conflicting options: -a and -d")
-	ErrConflictDetachAutoRemove = errors.New("Conflicting options: -rm and -d")
+	ErrContainerStart        = errors.New("The container failed to start. Unknown error")
+	ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
 )
 
-type KeyValuePair struct {
-	Key   string
-	Value string
-}
-
-type PortBinding struct {
-	HostIp   string
-	HostPort string
-}
-
-// 80/tcp
-type Port string
-
-func (p Port) Proto() string {
-	parts := strings.Split(string(p), "/")
-	if len(parts) == 1 {
-		return "tcp"
-	}
-	return parts[1]
-}
-
-func (p Port) Port() string {
-	return strings.Split(string(p), "/")[0]
-}
-
-func (p Port) Int() int {
-	i, err := parsePort(p.Port())
-	if err != nil {
-		panic(err)
-	}
-	return i
-}
-
-func NewPort(proto, port string) Port {
-	return Port(fmt.Sprintf("%s/%s", port, proto))
-}
-
+// FIXME: move deprecated port stuff to nat to clean up the core.
 type PortMapping map[string]string // Deprecated
 
 type NetworkSettings struct {
@@ -229,13 +94,13 @@ type NetworkSettings struct {
 	Gateway     string
 	Bridge      string
 	PortMapping map[string]PortMapping // Deprecated
-	Ports       map[Port][]PortBinding
+	Ports       nat.PortMap
 }
 
 func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
 	var outs = engine.NewTable("", 0)
 	for port, bindings := range settings.Ports {
-		p, _ := parsePort(port.Port())
+		p, _ := nat.ParsePort(port.Port())
 		if len(bindings) == 0 {
 			out := &engine.Env{}
 			out.SetInt("PublicPort", p)
@@ -245,7 +110,7 @@ func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
 		}
 		for _, binding := range bindings {
 			out := &engine.Env{}
-			h, _ := parsePort(binding.HostPort)
+			h, _ := nat.ParsePort(binding.HostPort)
 			out.SetInt("PrivatePort", p)
 			out.SetInt("PublicPort", h)
 			out.Set("Type", port.Proto())
@@ -322,7 +187,7 @@ func (container *Container) ToDisk() (err error) {
 }
 
 func (container *Container) readHostConfig() error {
-	container.hostConfig = &HostConfig{}
+	container.hostConfig = &runconfig.HostConfig{}
 	// If the hostconfig file does not exist, do not read it.
 	// (We still have to initialize container.hostConfig,
 	// but that's OK, since we just did that above.)
@@ -1152,8 +1017,8 @@ func (container *Container) allocateNetwork() error {
 	}
 
 	var (
-		portSpecs = make(map[Port]struct{})
-		bindings  = make(map[Port][]PortBinding)
+		portSpecs = make(nat.PortSet)
+		bindings  = make(nat.PortMap)
 	)
 
 	if !container.State.IsGhost() {
@@ -1177,7 +1042,7 @@ func (container *Container) allocateNetwork() error {
 	for port := range portSpecs {
 		binding := bindings[port]
 		if container.hostConfig.PublishAllPorts && len(binding) == 0 {
-			binding = append(binding, PortBinding{})
+			binding = append(binding, nat.PortBinding{})
 		}
 
 		for i := 0; i < len(binding); i++ {
@@ -1593,7 +1458,7 @@ func (container *Container) Copy(resource string) (archive.Archive, error) {
 }
 
 // Returns true if the container exposes a certain port
-func (container *Container) Exposes(p Port) bool {
+func (container *Container) Exposes(p nat.Port) bool {
 	_, exists := container.Config.ExposedPorts[p]
 	return exists
 }

+ 4 - 20
container_unit_test.go

@@ -1,28 +1,12 @@
 package docker
 
 import (
+	"github.com/dotcloud/docker/nat"
 	"testing"
 )
 
-func TestParseLxcConfOpt(t *testing.T) {
-	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
-
-	for _, o := range opts {
-		k, v, err := parseLxcOpt(o)
-		if err != nil {
-			t.FailNow()
-		}
-		if k != "lxc.utsname" {
-			t.Fail()
-		}
-		if v != "docker" {
-			t.Fail()
-		}
-	}
-}
-
 func TestParseNetworkOptsPrivateOnly(t *testing.T) {
-	ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"})
+	ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::80"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -64,7 +48,7 @@ func TestParseNetworkOptsPrivateOnly(t *testing.T) {
 }
 
 func TestParseNetworkOptsPublic(t *testing.T) {
-	ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"})
+	ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100:8080:80"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -106,7 +90,7 @@ func TestParseNetworkOptsPublic(t *testing.T) {
 }
 
 func TestParseNetworkOptsUdp(t *testing.T) {
-	ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"})
+	ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::6000/udp"})
 	if err != nil {
 		t.Fatal(err)
 	}

+ 24 - 0
contrib/syntax/textmate/Docker.tmbundle/Preferences/Dockerfile.tmPreferences

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>name</key>
+	<string>Comments</string>
+	<key>scope</key>
+	<string>source.dockerfile</string>
+	<key>settings</key>
+	<dict>
+		<key>shellVariables</key>
+		<array>
+			<dict>
+				<key>name</key>
+				<string>TM_COMMENT_START</string>
+				<key>value</key>
+				<string># </string>
+			</dict>
+		</array>
+	</dict>
+	<key>uuid</key>
+	<string>2B215AC0-A7F3-4090-9FF6-F4842BD56CA7</string>
+</dict>
+</plist>

+ 45 - 6
contrib/syntax/textmate/Dockerfile.tmLanguage → contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage

@@ -12,15 +12,37 @@
 	<array>
 		<dict>
 			<key>match</key>
-			<string>^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s</string>
-			<key>name</key>
-			<string>keyword.control.dockerfile</string>
+			<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR)\s</string>
+			<key>captures</key>
+			<dict>
+				<key>0</key>
+				<dict>
+					<key>name</key>
+					<string>keyword.control.dockerfile</string>					
+				</dict>
+				<key>1</key>
+				<dict>
+					<key>name</key>
+					<string>keyword.other.special-method.dockerfile</string>					
+				</dict>
+			</dict>
 		</dict>
 		<dict>
 			<key>match</key>
-			<string>^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s</string>
-			<key>name</key>
-			<string>keyword.operator.dockerfile</string>
+			<string>^\s*(ONBUILD\s+)?(CMD|ENTRYPOINT)\s</string>
+			<key>captures</key>
+			<dict>
+				<key>0</key>
+				<dict>
+					<key>name</key>
+					<string>keyword.operator.dockerfile</string>					
+				</dict>
+				<key>1</key>
+				<dict>
+					<key>name</key>
+					<string>keyword.other.special-method.dockerfile</string>					
+				</dict>
+			</dict>
 		</dict>
 		<dict>
 			<key>begin</key>
@@ -39,6 +61,23 @@
 				</dict>
 			</array>
 		</dict>
+		<dict>
+			<key>begin</key>
+			<string>'</string>
+			<key>end</key>
+			<string>'</string>
+			<key>name</key>
+			<string>string.quoted.single.dockerfile</string>
+			<key>patterns</key>
+			<array>
+				<dict>
+					<key>match</key>
+					<string>\\.</string>
+					<key>name</key>
+					<string>constant.character.escaped.dockerfile</string>
+				</dict>
+			</array>
+		</dict>
 		<dict>
 			<key>match</key>
 			<string>^\s*#.*$</string>

+ 16 - 0
contrib/syntax/textmate/Docker.tmbundle/info.plist

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>contactEmailRot13</key>
+	<string>germ@andz.com.ar</string>
+	<key>contactName</key>
+	<string>GermanDZ</string>
+	<key>description</key>
+	<string>Helpers for Docker.</string>
+	<key>name</key>
+	<string>Docker</string>
+	<key>uuid</key>
+	<string>8B9DDBAF-E65C-4E12-FFA7-467D4AA535B1</string>
+</dict>
+</plist>

+ 0 - 23
contrib/syntax/textmate/Dockerfile.YAML-tmLanguage

@@ -1,23 +0,0 @@
-# [PackageDev] target_format: plist, ext: tmLanguage
----
-name: Dockerfile
-scopeName: source.dockerfile
-uuid: a39d8795-59d2-49af-aa00-fe74ee29576e
-
-patterns:
-# Keywords
-- name: keyword.control.dockerfile
-  match: ^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s
-- name: keyword.operator.dockerfile
-  match: ^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s
-# String
-- name: string.quoted.double.dockerfile
-  begin: "\""
-  end: "\""
-  patterns:
-  - name: constant.character.escaped.dockerfile
-    match: \\.
-# Comment
-- name: comment.block.dockerfile
-  match: ^\s*#.*$
-...

+ 11 - 4
contrib/syntax/textmate/README.md

@@ -1,9 +1,16 @@
-# Dockerfile.tmLanguage
+# Docker.tmbundle
 
-Pretty basic Dockerfile.tmLanguage for Sublime Text syntax highlighting.
+Dockerfile syntaxt highlighting for TextMate and Sublime Text.
 
-PR's with syntax updates, suggestions etc. are all very much appreciated!
+## Install
 
-I'll get to making this installable via Package Control soon!
+### Sublime Text
+
+Available for Sublime Text under [package control](https://sublime.wbond.net/packages/Dockerfile%20Syntax%20Highlighting).
+Search for *Dockerfile Syntax Highlighting*
+
+### TextMate 2
+
+Copy the directory `Docker.tmbundle` (showed as a Package in OSX) to `~/Library/Application Support/TextMate/Managed/Bundles`
 
 enjoy.

+ 1 - 2
contrib/syntax/vim/syntax/dockerfile.vim

@@ -11,8 +11,7 @@ let b:current_syntax = "dockerfile"
 
 syntax case ignore
 
-syntax match dockerfileKeyword /\v^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s/
-syntax match dockerfileKeyword /\v^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s/
+syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|VOLUME|WORKDIR)\s/
 highlight link dockerfileKeyword Keyword
 
 syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/

+ 6 - 11
docker/docker.go

@@ -8,17 +8,14 @@ import (
 
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/api"
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/engine"
 	flag "github.com/dotcloud/docker/pkg/mflag"
+	"github.com/dotcloud/docker/pkg/opts"
 	"github.com/dotcloud/docker/sysinit"
 	"github.com/dotcloud/docker/utils"
 )
 
-var (
-	GITCOMMIT string
-	VERSION   string
-)
-
 func main() {
 	if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
 		// Running in init mode
@@ -36,13 +33,13 @@ func main() {
 		pidfile              = flag.String([]string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file")
 		flRoot               = flag.String([]string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the docker runtime")
 		flEnableCors         = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API")
-		flDns                = docker.NewListOpts(docker.ValidateIp4Address)
+		flDns                = opts.NewListOpts(opts.ValidateIp4Address)
 		flEnableIptables     = flag.Bool([]string{"#iptables", "-iptables"}, true, "Disable docker's addition of iptables rules")
 		flEnableIpForward    = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Disable enabling of net.ipv4.ip_forward")
 		flDefaultIp          = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")
 		flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication")
 		flGraphDriver        = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver")
-		flHosts              = docker.NewListOpts(docker.ValidateHost)
+		flHosts              = opts.NewListOpts(api.ValidateHost)
 		flMtu                = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available")
 	)
 	flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
@@ -71,8 +68,6 @@ func main() {
 	if *flDebug {
 		os.Setenv("DEBUG", "1")
 	}
-	docker.GITCOMMIT = GITCOMMIT
-	docker.VERSION = VERSION
 	if *flDaemon {
 		if flag.NArg() != 0 {
 			flag.Usage()
@@ -104,7 +99,7 @@ func main() {
 		job = eng.Job("serveapi", flHosts.GetAll()...)
 		job.SetenvBool("Logging", true)
 		job.SetenvBool("EnableCors", *flEnableCors)
-		job.Setenv("Version", VERSION)
+		job.Setenv("Version", dockerversion.VERSION)
 		if err := job.Run(); err != nil {
 			log.Fatal(err)
 		}
@@ -126,5 +121,5 @@ func main() {
 }
 
 func showVersion() {
-	fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
+	fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT)
 }

+ 0 - 5
dockerinit/dockerinit.go

@@ -4,11 +4,6 @@ import (
 	"github.com/dotcloud/docker/sysinit"
 )
 
-var (
-	GITCOMMIT string
-	VERSION   string
-)
-
 func main() {
 	// Running in init mode
 	sysinit.SysInit()

+ 15 - 0
dockerversion/dockerversion.go

@@ -0,0 +1,15 @@
+package dockerversion
+
+// FIXME: this should be embedded in the docker/docker.go,
+// but we can't because distro policy requires us to
+// package a separate dockerinit binary, and that binary needs
+// to know its version too.
+
+var (
+	GITCOMMIT string
+	VERSION   string
+
+	IAMSTATIC bool   // whether or not Docker itself was compiled statically via ./hack/make.sh binary
+	INITSHA1  string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary
+	INITPATH  string // custom location to search for a valid dockerinit binary (available for packagers as a last resort escape hatch)
+)

+ 53 - 0
docs/sources/examples/postgresql_service.Dockerfile

@@ -0,0 +1,53 @@
+#
+# example Dockerfile for http://docs.docker.io/en/latest/examples/postgresql_service/
+#
+
+FROM ubuntu
+MAINTAINER SvenDowideit@docker.com
+
+# Add the PostgreSQL PGP key to verify their Debian packages.
+# It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc 
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
+
+# Add PostgreSQL's repository. It contains the most recent stable release
+#     of PostgreSQL, ``9.3``.
+RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
+
+# Update the Ubuntu and PostgreSQL repository indexes
+RUN apt-get update
+
+# Install ``python-software-properties``, ``software-properties-common`` and PostgreSQL 9.3
+#  There are some warnings (in red) that show up during the build. You can hide
+#  them by prefixing each apt-get statement with DEBIAN_FRONTEND=noninteractive
+RUN apt-get -y -q install python-software-properties software-properties-common
+RUN apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
+
+# Note: The official Debian and Ubuntu images automatically ``apt-get clean``
+# after each ``apt-get`` 
+
+# Run the rest of the commands as the ``postgres`` user created by the ``postgres-9.3`` package when it was ``apt-get installed``
+USER postgres
+
+# Create a PostgreSQL role named ``docker`` with ``docker`` as the password and
+# then create a database `docker` owned by the ``docker`` role.
+# Note: here we use ``&&\`` to run commands one after the other - the ``\``
+#       allows the RUN command to span multiple lines.
+RUN    /etc/init.d/postgresql start &&\
+    psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" &&\
+    createdb -O docker docker
+
+# Adjust PostgreSQL configuration so that remote connections to the
+# database are possible. 
+RUN echo "host all  all    0.0.0.0/0  md5" >> /etc/postgresql/9.3/main/pg_hba.conf
+
+# And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf``
+RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf
+
+# Expose the PostgreSQL port
+EXPOSE 5432
+
+# Add VOLUMEs to allow backup of config, logs and databases
+VOLUME	["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"]
+
+# Set the default command to run when starting the container
+CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"]

+ 62 - 105
docs/sources/examples/postgresql_service.rst

@@ -9,152 +9,109 @@ PostgreSQL Service
 
 .. include:: example_header.inc
 
-.. note::
-
-    A shorter version of `this blog post`_.
-
-.. _this blog post: http://zaiste.net/2013/08/docker_postgresql_how_to/
-
 Installing PostgreSQL on Docker
 -------------------------------
 
-Run an interactive shell in a Docker container.
-
-.. code-block:: bash
-
-    sudo docker run -i -t ubuntu /bin/bash
-
-Update its dependencies.
-
-.. code-block:: bash
-
-    apt-get update
-
-Install ``python-software-properties``, ``software-properties-common``, ``wget`` and ``vim``.
-
-.. code-block:: bash
-
-    apt-get -y install python-software-properties software-properties-common wget vim
-
-Add PostgreSQL's repository. It contains the most recent stable release
-of PostgreSQL, ``9.3``.
-
-.. code-block:: bash
-
-    wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
-    echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
-    apt-get update
-
-Finally, install PostgreSQL 9.3
+Assuming there is no Docker image that suits your needs in `the index`_, you 
+can create one yourself.
 
-.. code-block:: bash
+.. _the index: http://index.docker.io
 
-    apt-get -y install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
+Start by creating a new Dockerfile:
 
-Now, create a PostgreSQL superuser role that can create databases and
-other roles.  Following Vagrant's convention the role will be named
-``docker`` with ``docker`` password assigned to it.
+.. note::
 
-.. code-block:: bash
+    This PostgreSQL setup is for development only purposes. Refer
+    to the PostgreSQL documentation to fine-tune these settings so that it
+    is suitably secure.
 
-    su postgres -c "createuser -P -d -r -s docker"
+.. literalinclude:: postgresql_service.Dockerfile
 
-Create a test database also named ``docker`` owned by previously created ``docker``
-role.
+Build an image from the Dockerfile assign it a name.
 
 .. code-block:: bash
 
-    su postgres -c "createdb -O docker docker"
+    $ sudo docker build -t eg_postgresql .
 
-Adjust PostgreSQL configuration so that remote connections to the
-database are possible. Make sure that inside
-``/etc/postgresql/9.3/main/pg_hba.conf`` you have following line:
+And run the PostgreSQL server container (in the foreground):
 
 .. code-block:: bash
 
-    host    all             all             0.0.0.0/0               md5
-
-Additionaly, inside ``/etc/postgresql/9.3/main/postgresql.conf``
-uncomment ``listen_addresses`` like so:
+    $ sudo docker run -rm -P -name pg_test eg_postgresql
 
-.. code-block:: bash
+There are  2 ways to connect to the PostgreSQL server. We can use 
+:ref:`working_with_links_names`, or we can access it from our host (or the network).
 
-    listen_addresses='*'
+.. note:: The ``-rm`` removes the container and its image when the container 
+          exists successfully.
 
-.. note::
+Using container linking
+^^^^^^^^^^^^^^^^^^^^^^^
 
-    This PostgreSQL setup is for development only purposes. Refer
-    to PostgreSQL documentation how to fine-tune these settings so that it
-    is secure enough.
-
-Exit.
+Containers can be linked to another container's ports directly using 
+``-link remote_name:local_alias`` in the client's ``docker run``. This will
+set a number of environment variables that can then be used to connect:
 
 .. code-block:: bash
 
-    exit
-
-Create an image from our container and assign it a name. The ``<container_id>``
-is in the Bash prompt; you can also locate it using ``docker ps -a``.
+    $ sudo docker run -rm -t -i -link pg_test:pg eg_postgresql bash
 
-.. code-block:: bash
+    postgres@7ef98b1b7243:/$ psql -h $PG_PORT_5432_TCP_ADDR -p $PG_PORT_5432_TCP_PORT -d docker -U docker --password
 
-    sudo docker commit <container_id> <your username>/postgresql
+Connecting from your host system
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Finally, run the PostgreSQL server via ``docker``.
+Assuming you have the postgresql-client installed, you can use the host-mapped port
+to test as well. You need to use ``docker ps`` to find out what local host port the 
+container is mapped to first:
 
 .. code-block:: bash
 
-    CONTAINER=$(sudo docker run -d -p 5432 \
-      -t <your username>/postgresql \
-      /bin/su postgres -c '/usr/lib/postgresql/9.3/bin/postgres \
-        -D /var/lib/postgresql/9.3/main \
-        -c config_file=/etc/postgresql/9.3/main/postgresql.conf')
-
-Connect the PostgreSQL server using ``psql`` (You will need the
-postgresql client installed on the machine.  For ubuntu, use something
-like ``sudo apt-get install postgresql-client``).
+    $ docker ps
+    CONTAINER ID        IMAGE                  COMMAND                CREATED             STATUS              PORTS                                      NAMES
+    5e24362f27f6        eg_postgresql:latest   /usr/lib/postgresql/   About an hour ago   Up About an hour    0.0.0.0:49153->5432/tcp                    pg_test
+    $ psql -h localhost -p 49153 -d docker -U docker --password
 
-.. code-block:: bash
+Testing the database
+^^^^^^^^^^^^^^^^^^^^
 
-    CONTAINER_IP=$(sudo docker inspect -format='{{.NetworkSettings.IPAddress}}' $CONTAINER)
-    psql -h $CONTAINER_IP -p 5432 -d docker -U docker -W
-
-As before, create roles or databases if needed.
+Once you have authenticated and have a ``docker =#`` prompt, you can
+create a table and populate it.
 
 .. code-block:: bash
 
     psql (9.3.1)
     Type "help" for help.
 
-    docker=# CREATE DATABASE foo OWNER=docker;
-    CREATE DATABASE
-
-Additionally, publish your newly created image on the Docker Index.
+    docker=# CREATE TABLE cities (
+    docker(#     name            varchar(80),
+    docker(#     location        point
+    docker(# );
+    CREATE TABLE
+    docker=# INSERT INTO cities VALUES ('San Francisco', '(-194.0, 53.0)');
+    INSERT 0 1
+    docker=# select * from cities;
+         name      | location  
+    ---------------+-----------
+     San Francisco | (-194,53)
+    (1 row)
 
-.. code-block:: bash
+Using the container volumes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-    sudo docker login
-    Username: <your username>
-    [...]
+You can use the defined volumes to inspect the PostgreSQL log files and to backup your
+configuration and data:
 
 .. code-block:: bash
 
-    sudo docker push <your username>/postgresql
-
-PostgreSQL service auto-launch
-------------------------------
-
-Running our image seems complicated. We have to specify the whole command with
-``docker run``. Let's simplify it so the service starts automatically when the
-container starts.
-
-.. code-block:: bash
+    docker run -rm --volumes-from pg_test -t -i busybox sh
 
-    sudo docker commit -run='{"Cmd": \
-      ["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.3/bin/postgres -D \
-      /var/lib/postgresql/9.3/main -c \
-      config_file=/etc/postgresql/9.3/main/postgresql.conf"], "PortSpecs": ["5432"]}' \
-      <container_id> <your username>/postgresql
+    / # ls
+    bin      etc      lib      linuxrc  mnt      proc     run      sys      usr
+    dev      home     lib64    media    opt      root     sbin     tmp      var
+    / # ls /etc/postgresql/9.3/main/
+    environment      pg_hba.conf      postgresql.conf
+    pg_ctl.conf      pg_ident.conf    start.conf
+    /tmp # ls /var/log
+    ldconfig    postgresql
 
-From now on, just type ``docker run <your username>/postgresql`` and
-PostgreSQL should automatically start.

+ 7 - 0
docs/sources/faq.rst

@@ -175,6 +175,7 @@ Linux:
 - Gentoo
 - ArchLinux
 - openSUSE 12.3+
+- CRUX 3.0+
 
 Cloud:
 
@@ -182,6 +183,12 @@ Cloud:
 - Google Compute Engine
 - Rackspace
 
+How do I report a security issue with Docker?
+.............................................
+
+You can learn about the project's security policy `here <http://www.docker.io/security/>`_
+and report security issues to this `mailbox <mailto:security@docker.com>`_.
+
 Can I help by adding some questions and answers?
 ................................................
 

+ 1 - 1
docs/sources/installation/amazon.rst

@@ -1,5 +1,5 @@
 :title: Installation on Amazon EC2
-:description: Docker installation on Amazon EC2 
+:description: Please note this project is currently under heavy development. It should not be used in production. 
 :keywords: amazon ec2, virtualization, cloud, docker, documentation, installation
 
 Amazon EC2

+ 1 - 1
docs/sources/installation/archlinux.rst

@@ -1,5 +1,5 @@
 :title: Installation on Arch Linux
-:description: Docker installation on Arch Linux.
+:description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: arch linux, virtualization, docker, documentation, installation
 
 .. _arch_linux:

+ 98 - 0
docs/sources/installation/cruxlinux.rst

@@ -0,0 +1,98 @@
+:title: Installation on CRUX Linux
+:description: Docker installation on CRUX Linux.
+:keywords: crux linux, virtualization, Docker, documentation, installation
+
+.. _crux_linux:
+
+
+CRUX Linux
+==========
+
+.. include:: install_header.inc
+
+.. include:: install_unofficial.inc
+
+Installing on CRUX Linux can be handled via the ports from `James Mills <http://prologic.shortcircuit.net.au/>`_:
+
+* `docker <https://bitbucket.org/prologic/ports/src/tip/docker/>`_
+
+* `docker-bin <https://bitbucket.org/prologic/ports/src/tip/docker-bin/>`_
+
+* `docker-git <https://bitbucket.org/prologic/ports/src/tip/docker-git/>`_
+
+The ``docker`` port will install the latest tagged version of Docker. 
+The ``docker-bin`` port will install the latest tagged versin of Docker from upstream built binaries.
+The ``docker-git`` package will build from the current master branch.
+
+
+Installation
+------------
+
+For the time being (*until the CRUX Docker port(s) get into the official contrib repository*) you will need to install
+`James Mills' <https://bitbucket.org/prologic/ports>`_ ports repository. You can do so via:
+
+Download the ``httpup`` file to ``/etc/ports/``:
+::
+    
+    curl -q -o - http://crux.nu/portdb/?a=getup&q=prologic > /etc/ports/prologic.httpup
+    
+
+Add ``prtdir /usr/ports/prologic`` to ``/etc/prt-get.conf``:
+::
+    
+    vim /etc/prt-get.conf
+
+    # or:
+    echo "prtdir /usr/ports/prologic" >> /etc/prt-get.conf
+    
+
+Update ports and prt-get cache:
+::
+    
+    ports -u
+    prt-get cache
+        
+
+To install (*and its dependencies*):
+::
+
+    prt-get depinst docker
+    
+
+Use ``docker-bin`` for the upstream binary or ``docker-git`` to build and install from the master branch from git.
+
+
+Kernel Requirements
+-------------------
+
+To have a working **CRUX+Docker** Host you must ensure your Kernel
+has the necessary modules enabled for LXC containers to function
+correctly and Docker Daemon to work properly.
+
+Please read the ``README.rst``:
+::
+    
+    prt-get readme docker
+    
+There is a ``test_kernel_config.sh`` script in the above ports which you can use to test your Kernel configuration:
+
+::
+    
+    cd /usr/ports/prologic/docker
+    ./test_kernel_config.sh /usr/src/linux/.config
+    
+
+Starting Docker
+---------------
+
+There is a rc script created for Docker. To start the Docker service:
+
+::
+    
+    sudo su -
+    /etc/rc.d/docker start
+    
+To start on system boot:
+
+- Edit ``/etc/rc.conf``
+- Put ``docker`` into the ``SERVICES=(...)`` array after ``net``.

+ 1 - 1
docs/sources/installation/fedora.rst

@@ -1,4 +1,4 @@
-:title: Requirements and Installation on Fedora
+:title: Installation on Fedora
 :description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: Docker, Docker documentation, Fedora, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux
 

+ 1 - 1
docs/sources/installation/frugalware.rst

@@ -1,5 +1,5 @@
 :title: Installation on FrugalWare
-:description: Docker installation on FrugalWare.
+:description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: frugalware linux, virtualization, docker, documentation, installation
 
 .. _frugalware:

+ 2 - 2
docs/sources/installation/gentoolinux.rst

@@ -1,5 +1,5 @@
-:title: Installation on Gentoo Linux
-:description: Docker installation instructions and nuances for Gentoo Linux.
+:title: Installation on Gentoo
+:description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: gentoo linux, virtualization, docker, documentation, installation
 
 .. _gentoo_linux:

+ 1 - 0
docs/sources/installation/index.rst

@@ -21,6 +21,7 @@ Contents:
    rhel
    fedora
    archlinux
+   cruxlinux
    gentoolinux
    openSUSE
    frugalware

+ 34 - 7
docs/sources/installation/mac.rst

@@ -1,4 +1,4 @@
-:title: Requirements and Installation on Mac OS X 10.6 Snow Leopard
+:title: Installation on Mac OS X 10.6 Snow Leopard
 :description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: Docker, Docker documentation, requirements, virtualbox, ssh, linux, os x, osx, mac
 
@@ -49,10 +49,10 @@ Run the following commands to get boot2docker:
 
     # Enter the installation directory
     cd ~/bin
-    
+
     # Get the file
     curl https://raw.github.com/steeve/boot2docker/master/boot2docker > boot2docker
-    
+
     # Mark it executable
     chmod +x boot2docker
 
@@ -66,14 +66,14 @@ Run the following commands to get it downloaded and set up:
 .. code-block:: bash
 
     # Get the file
-    curl -o docker http://get.docker.io/builds/Darwin/x86_64/docker-latest
-    
+    curl -o docker https://get.docker.io/builds/Darwin/x86_64/docker-latest
+
     # Mark it executable
     chmod +x docker
 
     # Set the environment variable for the docker daemon
     export DOCKER_HOST=tcp://
-    
+
     # Copy the executable file
     sudo cp docker /usr/local/bin/
 
@@ -94,7 +94,7 @@ Inside the ``~/bin`` directory, run the following commands:
 
     # Run the VM (the docker daemon)
     ./boot2docker up
-    
+
     # To see all available commands:
     ./boot2docker
 
@@ -116,6 +116,21 @@ client just like any other application.
     # Git commit (server): c348c04
     # Go version (server): go1.2
 
+Forwarding VM Port Range to Host
+--------------------------------
+
+If we take the port range that docker uses by default with the -P option
+(49000-49900), and forward same range from host to vm, we'll be able to interact
+with our containers as if they were running locally:
+
+.. code-block:: bash
+
+   # vm must be powered off
+   for i in {4900..49900}; do
+    VBoxManage modifyvm "boot2docker-vm" --natpf1 "tcp-port$i,tcp,,$i,,$i";
+    VBoxManage modifyvm "boot2docker-vm" --natpf1 "udp-port$i,udp,,$i,,$i";
+   done
+
 SSH-ing The VM
 --------------
 
@@ -147,6 +162,18 @@ If SSH complains about keys:
 
     ssh-keygen -R '[localhost]:2022'
 
+Upgrading to a newer release of boot2docker
+-------------------------------------------
+
+To upgrade an initialised VM, you can use the following 3 commands. Your persistence
+disk will not be changed, so you won't lose your images and containers:
+
+.. code-block:: bash
+
+    ./boot2docker stop
+    ./boot2docker download
+    ./boot2docker start
+
 About the way Docker works on Mac OS X:
 ---------------------------------------
 

+ 1 - 1
docs/sources/installation/openSUSE.rst

@@ -1,5 +1,5 @@
 :title: Installation on openSUSE
-:description: Docker installation on openSUSE.
+:description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: openSUSE, virtualbox, docker, documentation, installation
 
 .. _openSUSE:

+ 2 - 2
docs/sources/installation/rackspace.rst

@@ -1,5 +1,5 @@
-:title: Rackspace Cloud Installation
-:description: Installing Docker on Ubuntu proviced by Rackspace
+:title: Installation on Rackspace Cloud
+:description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: Rackspace Cloud, installation, docker, linux, ubuntu
 
 Rackspace Cloud

+ 1 - 1
docs/sources/installation/rhel.rst

@@ -1,4 +1,4 @@
-:title: Requirements and Installation on Red Hat Enterprise Linux
+:title: Installation on Red Hat Enterprise Linux
 :description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: Docker, Docker documentation, requirements, linux, rhel, centos
 

+ 1 - 1
docs/sources/installation/ubuntulinux.rst

@@ -1,4 +1,4 @@
-:title: Requirements and Installation on Ubuntu Linux
+:title: Installation on Ubuntu
 :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
 

+ 4 - 4
docs/sources/installation/windows.rst

@@ -1,11 +1,11 @@
-:title: Requirements and Installation on Windows
-:description: Docker's tutorial to run docker on Windows
+:title: Installation on Windows
+:description: Please note this project is currently under heavy development. It should not be used in production.
 :keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
 
 .. _windows:
 
-Installing Docker on Windows
-============================
+Windows
+=======
 
 Docker can run on Windows using a VM like VirtualBox. You then run
 Linux within the VM.

+ 3 - 0
docs/sources/reference/api/remote_api_client_libraries.rst

@@ -26,6 +26,9 @@ and we will add the libraries here.
 +----------------------+----------------+--------------------------------------------+----------+
 | Javascript           | docker-js      | https://github.com/dgoujard/docker-js      | Active   |
 +----------------------+----------------+--------------------------------------------+----------+
+| Javascript (Angular) | docker-cp      | https://github.com/13W/docker-cp           | Active   |
+| **WebUI**            |                |                                            |          |
++----------------------+----------------+--------------------------------------------+----------+
 | Javascript (Angular) | dockerui       | https://github.com/crosbymichael/dockerui  | Active   |
 | **WebUI**            |                |                                            |          |
 +----------------------+----------------+--------------------------------------------+----------+

+ 12 - 5
docs/sources/reference/builder.rst

@@ -251,9 +251,14 @@ value ``<value>``. This value will be passed to all future ``RUN``
 instructions. This is functionally equivalent to prefixing the command
 with ``<key>=<value>``
 
+The environment variables set using ``ENV`` will persist when a container is run
+from the resulting image. You can view the values using ``docker inspect``, and change them using ``docker run --env <key>=<value>``.
+
 .. note::
-    The environment variables will persist when a container is run
-    from the resulting image.
+    One example where this can cause unexpected consequenses, is setting 
+    ``ENV DEBIAN_FRONTEND noninteractive``.
+    Which will persist when the container is run interactively; for example: 
+    ``docker run -t -i image bash``
 
 .. _dockerfile_add:
 
@@ -269,7 +274,7 @@ the container's filesystem at path ``<dest>``.
 source directory being built (also called the *context* of the build) or
 a remote file URL.
 
-``<dest>`` is the path at which the source will be copied in the
+``<dest>`` is the absolute path to which the source will be copied inside the
 destination container.
 
 All new files and directories are created with mode 0755, uid and gid
@@ -399,8 +404,10 @@ the image.
 
     ``WORKDIR /path/to/workdir``
 
-The ``WORKDIR`` instruction sets the working directory in which
-the command given by ``CMD`` is executed.
+The ``WORKDIR`` instruction sets the working directory for the ``RUN``, ``CMD`` and
+``ENTRYPOINT``  Dockerfile commands that follow it.
+
+It can be used multiple times in the one Dockerfile.
 
 3.11 ONBUILD
 ------------

+ 10 - 1
docs/sources/reference/commandline/cli.rst

@@ -102,12 +102,17 @@ the ``-H`` flag for the client.
         docker ps
         # both are equal
 
-
 To run the daemon with `systemd socket activation <http://0pointer.de/blog/projects/socket-activation.html>`_, use ``docker -d -H fd://``.
 Using ``fd://`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``.
 If the specified socket activated files aren't found then docker will exit.
 You can find examples of using systemd socket activation with docker and systemd in the `docker source tree <https://github.com/dotcloud/docker/blob/master/contrib/init/systemd/socket-activation/>`_.
 
+.. warning::
+  Docker and LXC do not support the use of softlinks for either the Docker data directory (``/var/lib/docker``) or for ``/tmp``.
+  If your system is likely to be set up in that way, you can use ``readlink -f`` to canonicalise the links:
+
+  ``TMPDIR=$(readlink -f /tmp) /usr/local/bin/docker -d -D -g $(readlink -f /var/lib/docker) -H unix:// $EXPOSE_ALL > /var/lib/boot2docker/docker.log 2>&1``
+
 .. _cli_attach:
 
 ``attach``
@@ -1083,6 +1088,10 @@ is, ``docker run`` is equivalent to the API ``/containers/create`` then
 The ``docker run`` command can be used in combination with ``docker commit`` to
 :ref:`change the command that a container runs <cli_commit_examples>`.
 
+See :ref:`port_redirection` for more detailed information about the ``--expose``, 
+``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for 
+specific examples using ``--link``.
+
 Known Issues (run -volumes-from)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 1 - 0
docs/sources/reference/run.rst

@@ -143,6 +143,7 @@ Network Settings
 ----------------
 
 ::
+
    -n=true   : Enable networking for this container
    -dns=[]   : Set custom dns servers for the container
 

+ 9 - 0
docs/sources/use/port_redirection.rst

@@ -31,6 +31,15 @@ container, Docker provide ways to bind the container port to an
 interface of the host system. To simplify communication between
 containers, Docker provides the linking mechanism.
 
+Auto map all exposed ports on the host
+--------------------------------------
+
+To bind all the exposed container ports to the host automatically, use 
+``docker run -P <imageid>``.  The mapped host ports will be auto-selected 
+from a pool of unused ports (49000..49900), and you will need to use 
+``docker ps``, ``docker inspect <container_id>``  or 
+``docker port <container_id> <port>`` to determine what they are.
+
 Binding a port to a host interface
 -----------------------------------
 

+ 2 - 1
execdriver/lxc/driver.go

@@ -279,7 +279,8 @@ func (i *info) IsRunning() bool {
 
 	output, err := i.driver.getInfo(i.ID)
 	if err != nil {
-		panic(err)
+		utils.Errorf("Error getting info for lxc container %s: %s (%s)", i.ID, err, output)
+		return false
 	}
 	if strings.Contains(string(output), "RUNNING") {
 		running = true

+ 11 - 17
execdriver/lxc/init.go

@@ -4,11 +4,10 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker/execdriver"
 	"github.com/dotcloud/docker/pkg/netlink"
-	"github.com/dotcloud/docker/utils"
+	"github.com/dotcloud/docker/pkg/user"
 	"github.com/syndtr/gocapability/capability"
 	"net"
 	"os"
-	"strconv"
 	"strings"
 	"syscall"
 )
@@ -79,28 +78,22 @@ func setupWorkingDirectory(args *execdriver.InitArgs) error {
 
 // Takes care of dropping privileges to the desired user
 func changeUser(args *execdriver.InitArgs) error {
-	if args.User == "" {
-		return nil
-	}
-	userent, err := utils.UserLookup(args.User)
+	uid, gid, suppGids, err := user.GetUserGroupSupplementary(
+		args.User,
+		syscall.Getuid(), syscall.Getgid(),
+	)
 	if err != nil {
-		return fmt.Errorf("Unable to find user %v: %v", args.User, err)
+		return err
 	}
 
-	uid, err := strconv.Atoi(userent.Uid)
-	if err != nil {
-		return fmt.Errorf("Invalid uid: %v", userent.Uid)
+	if err := syscall.Setgroups(suppGids); err != nil {
+		return fmt.Errorf("Setgroups failed: %v", err)
 	}
-	gid, err := strconv.Atoi(userent.Gid)
-	if err != nil {
-		return fmt.Errorf("Invalid gid: %v", userent.Gid)
-	}
-
 	if err := syscall.Setgid(gid); err != nil {
-		return fmt.Errorf("setgid failed: %v", err)
+		return fmt.Errorf("Setgid failed: %v", err)
 	}
 	if err := syscall.Setuid(uid); err != nil {
-		return fmt.Errorf("setuid failed: %v", err)
+		return fmt.Errorf("Setuid failed: %v", err)
 	}
 
 	return nil
@@ -127,6 +120,7 @@ func setupCapabilities(args *execdriver.InitArgs) error {
 		capability.CAP_AUDIT_CONTROL,
 		capability.CAP_MAC_OVERRIDE,
 		capability.CAP_MAC_ADMIN,
+		capability.CAP_NET_ADMIN,
 	}
 
 	c, err := capability.NewPid(os.Getpid())

+ 1 - 0
execdriver/lxc/lxc_template.go

@@ -15,6 +15,7 @@ lxc.network.name = eth0
 {{else}}
 # network is disabled (-n=false)
 lxc.network.type = empty
+lxc.network.flags = up
 {{end}}
 
 # root filesystem

+ 4 - 2
graph.go

@@ -3,7 +3,9 @@ package docker
 import (
 	"fmt"
 	"github.com/dotcloud/docker/archive"
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/graphdriver"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -125,12 +127,12 @@ func (graph *Graph) Get(name string) (*Image, error) {
 }
 
 // Create creates a new image and registers it in the graph.
-func (graph *Graph) Create(layerData archive.Archive, container *Container, comment, author string, config *Config) (*Image, error) {
+func (graph *Graph) Create(layerData archive.Archive, container *Container, comment, author string, config *runconfig.Config) (*Image, error) {
 	img := &Image{
 		ID:            GenerateID(),
 		Comment:       comment,
 		Created:       time.Now().UTC(),
-		DockerVersion: VERSION,
+		DockerVersion: dockerversion.VERSION,
 		Author:        author,
 		Config:        config,
 		Architecture:  runtime.GOARCH,

+ 126 - 44
graphdriver/devmapper/deviceset.go

@@ -12,6 +12,7 @@ import (
 	"path"
 	"path/filepath"
 	"strconv"
+	"strings"
 	"sync"
 	"time"
 )
@@ -29,6 +30,15 @@ type DevInfo struct {
 	TransactionId uint64     `json:"transaction_id"`
 	Initialized   bool       `json:"initialized"`
 	devices       *DeviceSet `json:"-"`
+
+	mountCount int    `json:"-"`
+	mountPath  string `json:"-"`
+	// A floating mount means one reference is not owned and
+	// will be stolen by the next mount. This allows us to
+	// avoid unmounting directly after creation before the
+	// first get (since we need to mount to set up the device
+	// a bit first).
+	floating bool `json:"-"`
 }
 
 type MetaData struct {
@@ -43,7 +53,7 @@ type DeviceSet struct {
 	TransactionId    uint64
 	NewTransactionId uint64
 	nextFreeDevice   int
-	activeMounts     map[string]int
+	sawBusy          bool
 }
 
 type DiskUsage struct {
@@ -69,6 +79,14 @@ type DevStatus struct {
 	HighestMappedSector uint64
 }
 
+type UnmountMode int
+
+const (
+	UnmountRegular UnmountMode = iota
+	UnmountFloat
+	UnmountSink
+)
+
 func getDevName(name string) string {
 	return "/dev/mapper/" + name
 }
@@ -290,7 +308,7 @@ func (devices *DeviceSet) setupBaseImage() error {
 
 	if oldInfo != nil && !oldInfo.Initialized {
 		utils.Debugf("Removing uninitialized base image")
-		if err := devices.removeDevice(""); err != nil {
+		if err := devices.deleteDevice(""); err != nil {
 			utils.Debugf("\n--->Err: %s\n", err)
 			return err
 		}
@@ -355,6 +373,10 @@ func (devices *DeviceSet) log(level int, file string, line int, dmError int, mes
 		return // Ignore _LOG_DEBUG
 	}
 
+	if strings.Contains(message, "busy") {
+		devices.sawBusy = true
+	}
+
 	utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
 }
 
@@ -562,7 +584,7 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
 	return nil
 }
 
-func (devices *DeviceSet) removeDevice(hash string) error {
+func (devices *DeviceSet) deleteDevice(hash string) error {
 	info := devices.Devices[hash]
 	if info == nil {
 		return fmt.Errorf("hash %s doesn't exists", hash)
@@ -579,7 +601,7 @@ func (devices *DeviceSet) removeDevice(hash string) error {
 
 	devinfo, _ := getInfo(info.Name())
 	if devinfo != nil && devinfo.Exists != 0 {
-		if err := removeDevice(info.Name()); err != nil {
+		if err := devices.removeDeviceAndWait(info.Name()); err != nil {
 			utils.Debugf("Error removing device: %s\n", err)
 			return err
 		}
@@ -610,11 +632,11 @@ func (devices *DeviceSet) removeDevice(hash string) error {
 	return nil
 }
 
-func (devices *DeviceSet) RemoveDevice(hash string) error {
+func (devices *DeviceSet) DeleteDevice(hash string) error {
 	devices.Lock()
 	defer devices.Unlock()
 
-	return devices.removeDevice(hash)
+	return devices.deleteDevice(hash)
 }
 
 func (devices *DeviceSet) deactivateDevice(hash string) error {
@@ -632,28 +654,50 @@ func (devices *DeviceSet) deactivateDevice(hash string) error {
 		return err
 	}
 	if devinfo.Exists != 0 {
-		if err := removeDevice(devname); err != nil {
+		if err := devices.removeDeviceAndWait(devname); err != nil {
 			utils.Debugf("\n--->Err: %s\n", err)
 			return err
 		}
-		if err := devices.waitRemove(hash); err != nil {
+	}
+
+	return nil
+}
+
+// Issues the underlying dm remove operation and then waits
+// for it to finish.
+func (devices *DeviceSet) removeDeviceAndWait(devname string) error {
+	var err error
+
+	for i := 0; i < 10; i++ {
+		devices.sawBusy = false
+		err = removeDevice(devname)
+		if err == nil {
+			break
+		}
+		if !devices.sawBusy {
 			return err
 		}
+
+		// If we see EBUSY it may be a transient error,
+		// sleep a bit a retry a few times.
+		time.Sleep(5 * time.Millisecond)
+	}
+	if err != nil {
+		return err
 	}
 
+	if err := devices.waitRemove(devname); err != nil {
+		return err
+	}
 	return nil
 }
 
 // waitRemove blocks until either:
 // a) the device registered at <device_set_prefix>-<hash> is removed,
 // or b) the 1 second timeout expires.
-func (devices *DeviceSet) waitRemove(hash string) error {
-	utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
-	defer utils.Debugf("[deviceset %s] waitRemove(%) END", devices.devicePrefix, hash)
-	devname, err := devices.byHash(hash)
-	if err != nil {
-		return err
-	}
+func (devices *DeviceSet) waitRemove(devname string) error {
+	utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname)
+	defer utils.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname)
 	i := 0
 	for ; i < 1000; i += 1 {
 		devinfo, err := getInfo(devname)
@@ -728,13 +772,12 @@ func (devices *DeviceSet) Shutdown() error {
 	utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
 	defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
 
-	for path, count := range devices.activeMounts {
-		for i := count; i > 0; i-- {
-			if err := sysUnmount(path, 0); err != nil {
-				utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
+	for _, info := range devices.Devices {
+		if info.mountCount > 0 {
+			if err := sysUnmount(info.mountPath, 0); err != nil {
+				utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err)
 			}
 		}
-		delete(devices.activeMounts, path)
 	}
 
 	for _, d := range devices.Devices {
@@ -756,22 +799,35 @@ func (devices *DeviceSet) Shutdown() error {
 	return nil
 }
 
-func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
+func (devices *DeviceSet) MountDevice(hash, path string) error {
 	devices.Lock()
 	defer devices.Unlock()
 
-	if err := devices.activateDeviceIfNeeded(hash); err != nil {
-		return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
+	info := devices.Devices[hash]
+	if info == nil {
+		return fmt.Errorf("Unknown device %s", hash)
 	}
 
-	info := devices.Devices[hash]
+	if info.mountCount > 0 {
+		if path != info.mountPath {
+			return fmt.Errorf("Trying to mount devmapper device in multple places (%s, %s)", info.mountPath, path)
+		}
 
-	var flags uintptr = sysMsMgcVal
+		if info.floating {
+			// Steal floating ref
+			info.floating = false
+		} else {
+			info.mountCount++
+		}
+		return nil
+	}
 
-	if readOnly {
-		flags = flags | sysMsRdOnly
+	if err := devices.activateDeviceIfNeeded(hash); err != nil {
+		return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
 	}
 
+	var flags uintptr = sysMsMgcVal
+
 	err := sysMount(info.DevName(), path, "ext4", flags, "discard")
 	if err != nil && err == sysEInval {
 		err = sysMount(info.DevName(), path, "ext4", flags, "")
@@ -780,20 +836,53 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
 		return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
 	}
 
-	count := devices.activeMounts[path]
-	devices.activeMounts[path] = count + 1
+	info.mountCount = 1
+	info.mountPath = path
+	info.floating = false
 
 	return devices.setInitialized(hash)
 }
 
-func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error {
-	utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path)
+func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error {
+	utils.Debugf("[devmapper] UnmountDevice(hash=%s, mode=%d)", hash, mode)
 	defer utils.Debugf("[devmapper] UnmountDevice END")
 	devices.Lock()
 	defer devices.Unlock()
 
-	utils.Debugf("[devmapper] Unmount(%s)", path)
-	if err := sysUnmount(path, 0); err != nil {
+	info := devices.Devices[hash]
+	if info == nil {
+		return fmt.Errorf("UnmountDevice: no such device %s\n", hash)
+	}
+
+	if mode == UnmountFloat {
+		if info.floating {
+			return fmt.Errorf("UnmountDevice: can't float floating reference %s\n", hash)
+		}
+
+		// Leave this reference floating
+		info.floating = true
+		return nil
+	}
+
+	if mode == UnmountSink {
+		if !info.floating {
+			// Someone already sunk this
+			return nil
+		}
+		// Otherwise, treat this as a regular unmount
+	}
+
+	if info.mountCount == 0 {
+		return fmt.Errorf("UnmountDevice: device not-mounted id %s\n", hash)
+	}
+
+	info.mountCount--
+	if info.mountCount > 0 {
+		return nil
+	}
+
+	utils.Debugf("[devmapper] Unmount(%s)", info.mountPath)
+	if err := sysUnmount(info.mountPath, 0); err != nil {
 		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
@@ -804,15 +893,9 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro
 		return err
 	}
 
-	if count := devices.activeMounts[path]; count > 1 {
-		devices.activeMounts[path] = count - 1
-	} else {
-		delete(devices.activeMounts, path)
-	}
+	devices.deactivateDevice(hash)
 
-	if deactivate {
-		devices.deactivateDevice(hash)
-	}
+	info.mountPath = ""
 
 	return nil
 }
@@ -955,9 +1038,8 @@ func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
 	SetDevDir("/dev")
 
 	devices := &DeviceSet{
-		root:         root,
-		MetaData:     MetaData{Devices: make(map[string]*DevInfo)},
-		activeMounts: make(map[string]int),
+		root:     root,
+		MetaData: MetaData{Devices: make(map[string]*DevInfo)},
 	}
 
 	if err := devices.initDevmapper(doInit); err != nil {

+ 1 - 1
graphdriver/devmapper/devmapper.go

@@ -324,7 +324,7 @@ func createPool(poolName string, dataFile, metadataFile *osFile) error {
 		return fmt.Errorf("Can't get data size")
 	}
 
-	params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
+	params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768 1 skip_block_zeroing"
 	if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
 		return fmt.Errorf("Can't add target")
 	}

+ 23 - 53
graphdriver/devmapper/driver.go

@@ -7,8 +7,8 @@ import (
 	"github.com/dotcloud/docker/graphdriver"
 	"github.com/dotcloud/docker/utils"
 	"io/ioutil"
+	"os"
 	"path"
-	"sync"
 )
 
 func init() {
@@ -22,9 +22,7 @@ func init() {
 
 type Driver struct {
 	*DeviceSet
-	home       string
-	sync.Mutex // Protects concurrent modification to active
-	active     map[string]int
+	home string
 }
 
 var Init = func(home string) (graphdriver.Driver, error) {
@@ -35,7 +33,6 @@ var Init = func(home string) (graphdriver.Driver, error) {
 	d := &Driver{
 		DeviceSet: deviceSet,
 		home:      home,
-		active:    make(map[string]int),
 	}
 	return d, nil
 }
@@ -83,55 +80,45 @@ func (d *Driver) Create(id, parent string) error {
 		return err
 	}
 
+	// We float this reference so that the next Get call can
+	// steal it, so we don't have to unmount
+	if err := d.DeviceSet.UnmountDevice(id, UnmountFloat); err != nil {
+		return err
+	}
+
 	return nil
 }
 
 func (d *Driver) Remove(id string) error {
-	// Protect the d.active from concurrent access
-	d.Lock()
-	defer d.Unlock()
-
-	if d.active[id] != 0 {
-		utils.Errorf("Warning: removing active id %s\n", id)
+	// Sink the float from create in case no Get() call was made
+	if err := d.DeviceSet.UnmountDevice(id, UnmountSink); err != nil {
+		return err
+	}
+	// This assumes the device has been properly Get/Put:ed and thus is unmounted
+	if err := d.DeviceSet.DeleteDevice(id); err != nil {
+		return err
 	}
 
 	mp := path.Join(d.home, "mnt", id)
-	if err := d.unmount(id, mp); err != nil {
+	if err := os.RemoveAll(mp); err != nil && !os.IsNotExist(err) {
 		return err
 	}
-	return d.DeviceSet.RemoveDevice(id)
+
+	return nil
 }
 
 func (d *Driver) Get(id string) (string, error) {
-	// Protect the d.active from concurrent access
-	d.Lock()
-	defer d.Unlock()
-
-	count := d.active[id]
-
 	mp := path.Join(d.home, "mnt", id)
-	if count == 0 {
-		if err := d.mount(id, mp); err != nil {
-			return "", err
-		}
+	if err := d.mount(id, mp); err != nil {
+		return "", err
 	}
 
-	d.active[id] = count + 1
-
 	return path.Join(mp, "rootfs"), nil
 }
 
 func (d *Driver) Put(id string) {
-	// Protect the d.active from concurrent access
-	d.Lock()
-	defer d.Unlock()
-
-	if count := d.active[id]; count > 1 {
-		d.active[id] = count - 1
-	} else {
-		mp := path.Join(d.home, "mnt", id)
-		d.unmount(id, mp)
-		delete(d.active, id)
+	if err := d.DeviceSet.UnmountDevice(id, UnmountRegular); err != nil {
+		utils.Errorf("Warning: error unmounting device %s: %s\n", id, err)
 	}
 }
 
@@ -140,25 +127,8 @@ func (d *Driver) mount(id, mountPoint string) error {
 	if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
 		return err
 	}
-	// If mountpoint is already mounted, do nothing
-	if mounted, err := Mounted(mountPoint); err != nil {
-		return fmt.Errorf("Error checking mountpoint: %s", err)
-	} else if mounted {
-		return nil
-	}
 	// Mount the device
-	return d.DeviceSet.MountDevice(id, mountPoint, false)
-}
-
-func (d *Driver) unmount(id, mountPoint string) error {
-	// If mountpoint is not mounted, do nothing
-	if mounted, err := Mounted(mountPoint); err != nil {
-		return fmt.Errorf("Error checking mountpoint: %s", err)
-	} else if !mounted {
-		return nil
-	}
-	// Unmount the device
-	return d.DeviceSet.UnmountDevice(id, mountPoint, true)
+	return d.DeviceSet.MountDevice(id, mountPoint)
 }
 
 func (d *Driver) Exists(id string) bool {

+ 0 - 3
graphdriver/devmapper/driver_test.go

@@ -495,7 +495,6 @@ func TestDriverCreate(t *testing.T) {
 			"DmTaskCreate",
 			"DmTaskGetInfo",
 			"sysMount",
-			"Mounted",
 			"DmTaskRun",
 			"DmTaskSetTarget",
 			"DmTaskSetSector",
@@ -614,7 +613,6 @@ func TestDriverRemove(t *testing.T) {
 			"DmTaskCreate",
 			"DmTaskGetInfo",
 			"sysMount",
-			"Mounted",
 			"DmTaskRun",
 			"DmTaskSetTarget",
 			"DmTaskSetSector",
@@ -645,7 +643,6 @@ func TestDriverRemove(t *testing.T) {
 			"DmTaskSetTarget",
 			"DmTaskSetAddNode",
 			"DmUdevWait",
-			"Mounted",
 			"sysUnmount",
 		)
 	}()

+ 52 - 0
hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh

@@ -0,0 +1,52 @@
+#!/bin/bash
+
+export PATH='/go/bin':$PATH
+export DOCKER_PATH='/go/src/github.com/dotcloud/docker'
+
+# Signal coverage report name, parsed by docker-ci
+set -x
+COVERAGE_PATH=$(date +"docker-%Y%m%d%H%M%S")
+set +x
+
+REPORTS="/data/$COVERAGE_PATH"
+INDEX="$REPORTS/index.html"
+
+# Test docker
+cd $DOCKER_PATH
+./hack/make.sh test; exit_status=$?
+PROFILE_PATH="$(ls -d $DOCKER_PATH/bundles/* | sed -n '$ p')/test/coverprofiles"
+
+if [ "$exit_status" -eq "0" ]; then
+    # Download coverage dependencies
+    go get github.com/axw/gocov/gocov
+    go get -u github.com/matm/gocov-html
+
+    # Create coverage report
+    mkdir -p $REPORTS
+    cd $PROFILE_PATH
+    cat > $INDEX << "EOF"
+<!DOCTYPE html><head><meta charset="utf-8">
+<script type="text/javascript" src="//tablesorter.com/jquery-latest.js"></script>
+<script type="text/javascript" src="//tablesorter.com/__jquery.tablesorter.min.js"></script>
+<script type="text/javascript">$(document).ready(function() {
+$("table").tablesorter({ sortForce: [[1,0]] }); });</script>
+<style>table,th,td{border:1px solid black;}</style>
+<title>Docker Coverage Report</title>
+</head><body>
+<h1><strong>Docker Coverage Report</strong></h1>
+<table class="tablesorter">
+<thead><tr><th>package</th><th>pct</th></tr></thead><tbody>
+EOF
+    for profile in *; do
+        gocov convert $profile | gocov-html >$REPORTS/$profile.html
+        echo "<tr><td><a href=\"${profile}.html\">$profile</a></td><td>" >> $INDEX
+        go tool cover -func=$profile | sed -En '$ s/.+\t(.+)/\1/p' >> $INDEX
+        echo "</td></tr>" >> $INDEX
+    done
+    echo "</tbody></table></body></html>" >> $INDEX
+fi
+
+# Signal test and coverage result, parsed by docker-ci
+set -x
+exit $exit_status
+

+ 15 - 2
hack/make.sh

@@ -68,9 +68,22 @@ else
 	exit 1
 fi
 
+if [ "$AUTO_GOPATH" ]; then
+	rm -rf .gopath
+	mkdir -p .gopath/src/github.com/dotcloud
+	ln -sf ../../../.. .gopath/src/github.com/dotcloud/docker
+	export GOPATH="$(pwd)/.gopath:$(pwd)/vendor"
+fi
+
+if [ ! "$GOPATH" ]; then
+	echo >&2 'error: missing GOPATH; please see http://golang.org/doc/code.html#GOPATH'
+	echo >&2 '  alternatively, set AUTO_GOPATH=1'
+	exit 1
+fi
+
 # Use these flags when compiling the tests and final binary
-LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w'
-LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"'
+LDFLAGS='-X github.com/dotcloud/docker/dockerversion.GITCOMMIT "'$GITCOMMIT'" -X github.com/dotcloud/docker/dockerversion.VERSION "'$VERSION'" -w'
+LDFLAGS_STATIC='-X github.com/dotcloud/docker/dockerversion.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"'
 BUILDFLAGS='-tags netgo -a'
 
 HAVE_GO_TEST_COVER=

+ 1 - 1
hack/make/dynbinary

@@ -12,6 +12,6 @@ export DOCKER_INITSHA1="$(sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)"
 # exported so that "dyntest" can easily access it later without recalculating it
 
 (
-	export LDFLAGS_STATIC="-X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\" -X github.com/dotcloud/docker/utils.INITPATH \"$DOCKER_INITPATH\""
+	export LDFLAGS_STATIC="-X github.com/dotcloud/docker/dockerversion.INITSHA1 \"$DOCKER_INITSHA1\" -X github.com/dotcloud/docker/dockerversion.INITPATH \"$DOCKER_INITPATH\""
 	source "$(dirname "$BASH_SOURCE")/binary"
 )

+ 12 - 11
image.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker/archive"
 	"github.com/dotcloud/docker/graphdriver"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -18,17 +19,17 @@ import (
 )
 
 type Image struct {
-	ID              string    `json:"id"`
-	Parent          string    `json:"parent,omitempty"`
-	Comment         string    `json:"comment,omitempty"`
-	Created         time.Time `json:"created"`
-	Container       string    `json:"container,omitempty"`
-	ContainerConfig Config    `json:"container_config,omitempty"`
-	DockerVersion   string    `json:"docker_version,omitempty"`
-	Author          string    `json:"author,omitempty"`
-	Config          *Config   `json:"config,omitempty"`
-	Architecture    string    `json:"architecture,omitempty"`
-	OS              string    `json:"os,omitempty"`
+	ID              string            `json:"id"`
+	Parent          string            `json:"parent,omitempty"`
+	Comment         string            `json:"comment,omitempty"`
+	Created         time.Time         `json:"created"`
+	Container       string            `json:"container,omitempty"`
+	ContainerConfig runconfig.Config  `json:"container_config,omitempty"`
+	DockerVersion   string            `json:"docker_version,omitempty"`
+	Author          string            `json:"author,omitempty"`
+	Config          *runconfig.Config `json:"config,omitempty"`
+	Architecture    string            `json:"architecture,omitempty"`
+	OS              string            `json:"os,omitempty"`
 	graph           *Graph
 	Size            int64
 }

+ 22 - 20
integration/api_test.go

@@ -8,7 +8,9 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/api"
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"net"
@@ -45,7 +47,7 @@ func TestGetVersion(t *testing.T) {
 		t.Fatal(err)
 	}
 	out.Close()
-	expected := docker.VERSION
+	expected := dockerversion.VERSION
 	if result := v.Get("Version"); result != expected {
 		t.Errorf("Expected version %s, %s found", expected, result)
 	}
@@ -308,7 +310,7 @@ func TestGetContainersJSON(t *testing.T) {
 	}
 	beginLen := len(outs.Data)
 
-	containerID := createTestContainer(eng, &docker.Config{
+	containerID := createTestContainer(eng, &runconfig.Config{
 		Image: unitTestImageID,
 		Cmd:   []string{"echo", "test"},
 	}, t)
@@ -345,7 +347,7 @@ func TestGetContainersExport(t *testing.T) {
 
 	// Create a container and remove a file
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image: unitTestImageID,
 			Cmd:   []string{"touch", "/test"},
 		},
@@ -393,7 +395,7 @@ func TestGetContainersChanges(t *testing.T) {
 
 	// Create a container and remove a file
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image: unitTestImageID,
 			Cmd:   []string{"/bin/rm", "/etc/passwd"},
 		},
@@ -432,7 +434,7 @@ func TestGetContainersTop(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/sh", "-c", "cat"},
 			OpenStdin: true,
@@ -509,7 +511,7 @@ func TestGetContainersByName(t *testing.T) {
 
 	// Create a container and remove a file
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image: unitTestImageID,
 			Cmd:   []string{"echo", "test"},
 		},
@@ -541,7 +543,7 @@ func TestPostCommit(t *testing.T) {
 
 	// Create a container and remove a file
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image: unitTestImageID,
 			Cmd:   []string{"touch", "/test"},
 		},
@@ -577,7 +579,7 @@ func TestPostContainersCreate(t *testing.T) {
 	eng := NewTestEngine(t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	configJSON, err := json.Marshal(&docker.Config{
+	configJSON, err := json.Marshal(&runconfig.Config{
 		Image:  unitTestImageID,
 		Memory: 33554432,
 		Cmd:    []string{"touch", "/test"},
@@ -619,7 +621,7 @@ func TestPostContainersKill(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/cat"},
 			OpenStdin: true,
@@ -658,7 +660,7 @@ func TestPostContainersRestart(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/top"},
 			OpenStdin: true,
@@ -704,7 +706,7 @@ func TestPostContainersStart(t *testing.T) {
 
 	containerID := createTestContainer(
 		eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/cat"},
 			OpenStdin: true,
@@ -712,7 +714,7 @@ func TestPostContainersStart(t *testing.T) {
 		t,
 	)
 
-	hostConfigJSON, err := json.Marshal(&docker.HostConfig{})
+	hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{})
 
 	req, err := http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON))
 	if err != nil {
@@ -757,7 +759,7 @@ func TestRunErrorBindMountRootSource(t *testing.T) {
 
 	containerID := createTestContainer(
 		eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/cat"},
 			OpenStdin: true,
@@ -765,7 +767,7 @@ func TestRunErrorBindMountRootSource(t *testing.T) {
 		t,
 	)
 
-	hostConfigJSON, err := json.Marshal(&docker.HostConfig{
+	hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{
 		Binds: []string{"/:/tmp"},
 	})
 
@@ -791,7 +793,7 @@ func TestPostContainersStop(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/top"},
 			OpenStdin: true,
@@ -831,7 +833,7 @@ func TestPostContainersWait(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/sleep", "1"},
 			OpenStdin: true,
@@ -869,7 +871,7 @@ func TestPostContainersAttach(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/cat"},
 			OpenStdin: true,
@@ -947,7 +949,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image:     unitTestImageID,
 			Cmd:       []string{"/bin/sh", "-c", "/bin/cat >&2"},
 			OpenStdin: true,
@@ -1028,7 +1030,7 @@ func TestDeleteContainers(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image: unitTestImageID,
 			Cmd:   []string{"touch", "/test"},
 		},
@@ -1163,7 +1165,7 @@ func TestPostContainersCopy(t *testing.T) {
 
 	// Create a container and remove a file
 	containerID := createTestContainer(eng,
-		&docker.Config{
+		&runconfig.Config{
 			Image: unitTestImageID,
 			Cmd:   []string{"touch", "/test.txt"},
 		},

+ 59 - 0
integration/buildfile_test.go

@@ -148,6 +148,65 @@ RUN [ "$(/hello.sh)" = "hello world" ]
 		nil,
 	},
 
+	// Users and groups
+	{
+		`
+FROM {IMAGE}
+
+# Make sure our defaults work
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)" = '0:0/root:root' ]
+
+# TODO decide if "args.user = strconv.Itoa(syscall.Getuid())" is acceptable behavior for changeUser in sysvinit instead of "return nil" when "USER" isn't specified (so that we get the proper group list even if that is the empty list, even in the default case of not supplying an explicit USER to run as, which implies USER 0)
+USER root
+RUN [ "$(id -G):$(id -Gn)" = '0:root' ]
+
+# Setup dockerio user and group
+RUN echo 'dockerio:x:1000:1000::/bin:/bin/false' >> /etc/passwd
+RUN echo 'dockerio:x:1000:' >> /etc/group
+
+# Make sure we can switch to our user and all the information is exactly as we expect it to be
+USER dockerio
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
+
+# Switch back to root and double check that worked exactly as we might expect it to
+USER root
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0:root' ]
+
+# Add a "supplementary" group for our dockerio user
+RUN echo 'supplementary:x:1001:dockerio' >> /etc/group
+
+# ... and then go verify that we get it like we expect
+USER dockerio
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000 1001:dockerio supplementary' ]
+USER 1000
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000 1001:dockerio supplementary' ]
+
+# super test the new "user:group" syntax
+USER dockerio:dockerio
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
+USER 1000:dockerio
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
+USER dockerio:1000
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
+USER 1000:1000
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ]
+USER dockerio:supplementary
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
+USER dockerio:1001
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
+USER 1000:supplementary
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
+USER 1000:1001
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ]
+
+# make sure unknown uid/gid still works properly
+USER 1042:1043
+RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]
+`,
+		nil,
+		nil,
+	},
+
 	// Environment variable
 	{
 		`

+ 39 - 39
integration/container_test.go

@@ -3,7 +3,7 @@ package docker
 import (
 	"bufio"
 	"fmt"
-	"github.com/dotcloud/docker"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -20,7 +20,7 @@ func TestIDFormat(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container1, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"/bin/sh", "-c", "echo hello world"},
 		},
@@ -234,7 +234,7 @@ func TestCommitAutoRun(t *testing.T) {
 		t.Errorf("Container shouldn't be running")
 	}
 
-	img, err := runtime.Commit(container1, "", "", "unit test commited image", "", &docker.Config{Cmd: []string{"cat", "/world"}})
+	img, err := runtime.Commit(container1, "", "", "unit test commited image", "", &runconfig.Config{Cmd: []string{"cat", "/world"}})
 	if err != nil {
 		t.Error(err)
 	}
@@ -415,7 +415,7 @@ func TestOutput(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"echo", "-n", "foobar"},
 		},
@@ -438,7 +438,7 @@ func TestContainerNetwork(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"ping", "-c", "1", "127.0.0.1"},
 		},
@@ -460,7 +460,7 @@ func TestKillDifferentUser(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image:     GetTestImage(runtime).ID,
 		Cmd:       []string{"cat"},
 		OpenStdin: true,
@@ -520,7 +520,7 @@ func TestCreateVolume(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer nuke(runtime)
 
-	config, hc, _, err := docker.ParseRun([]string{"-v", "/var/lib/data", unitTestImageID, "echo", "hello", "world"}, nil)
+	config, hc, _, err := runconfig.Parse([]string{"-v", "/var/lib/data", unitTestImageID, "echo", "hello", "world"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -552,7 +552,7 @@ func TestCreateVolume(t *testing.T) {
 func TestKill(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"sleep", "2"},
 	},
@@ -596,7 +596,7 @@ func TestExitCode(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	trueContainer, _, err := runtime.Create(&docker.Config{
+	trueContainer, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"/bin/true"},
 	}, "")
@@ -611,7 +611,7 @@ func TestExitCode(t *testing.T) {
 		t.Fatalf("Unexpected exit code %d (expected 0)", code)
 	}
 
-	falseContainer, _, err := runtime.Create(&docker.Config{
+	falseContainer, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"/bin/false"},
 	}, "")
@@ -630,7 +630,7 @@ func TestExitCode(t *testing.T) {
 func TestRestart(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"echo", "-n", "foobar"},
 	},
@@ -661,7 +661,7 @@ func TestRestart(t *testing.T) {
 func TestRestartStdin(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"cat"},
 
@@ -739,7 +739,7 @@ func TestUser(t *testing.T) {
 	defer nuke(runtime)
 
 	// Default user must be root
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"id"},
 	},
@@ -758,7 +758,7 @@ func TestUser(t *testing.T) {
 	}
 
 	// Set a username
-	container, _, err = runtime.Create(&docker.Config{
+	container, _, err = runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"id"},
 
@@ -779,7 +779,7 @@ func TestUser(t *testing.T) {
 	}
 
 	// Set a UID
-	container, _, err = runtime.Create(&docker.Config{
+	container, _, err = runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"id"},
 
@@ -800,7 +800,7 @@ func TestUser(t *testing.T) {
 	}
 
 	// Set a different user by uid
-	container, _, err = runtime.Create(&docker.Config{
+	container, _, err = runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"id"},
 
@@ -823,7 +823,7 @@ func TestUser(t *testing.T) {
 	}
 
 	// Set a different user by username
-	container, _, err = runtime.Create(&docker.Config{
+	container, _, err = runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"id"},
 
@@ -844,7 +844,7 @@ func TestUser(t *testing.T) {
 	}
 
 	// Test an wrong username
-	container, _, err = runtime.Create(&docker.Config{
+	container, _, err = runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"id"},
 
@@ -866,7 +866,7 @@ func TestMultipleContainers(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container1, _, err := runtime.Create(&docker.Config{
+	container1, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"sleep", "2"},
 	},
@@ -877,7 +877,7 @@ func TestMultipleContainers(t *testing.T) {
 	}
 	defer runtime.Destroy(container1)
 
-	container2, _, err := runtime.Create(&docker.Config{
+	container2, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"sleep", "2"},
 	},
@@ -921,7 +921,7 @@ func TestMultipleContainers(t *testing.T) {
 func TestStdin(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"cat"},
 
@@ -966,7 +966,7 @@ func TestStdin(t *testing.T) {
 func TestTty(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"cat"},
 
@@ -1013,7 +1013,7 @@ func TestEnv(t *testing.T) {
 	os.Setenv("TRICKY", "tri\ncky\n")
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
-	config, _, _, err := docker.ParseRun([]string{"-e=FALSE=true", "-e=TRUE", "-e=TRICKY", GetTestImage(runtime).ID, "env"}, nil)
+	config, _, _, err := runconfig.Parse([]string{"-e=FALSE=true", "-e=TRUE", "-e=TRICKY", GetTestImage(runtime).ID, "env"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1067,7 +1067,7 @@ func TestEntrypoint(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:      GetTestImage(runtime).ID,
 			Entrypoint: []string{"/bin/echo"},
 			Cmd:        []string{"-n", "foobar"},
@@ -1091,7 +1091,7 @@ func TestEntrypointNoCmd(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:      GetTestImage(runtime).ID,
 			Entrypoint: []string{"/bin/echo", "foobar"},
 		},
@@ -1114,7 +1114,7 @@ func BenchmarkRunSequencial(b *testing.B) {
 	runtime := mkRuntime(b)
 	defer nuke(runtime)
 	for i := 0; i < b.N; i++ {
-		container, _, err := runtime.Create(&docker.Config{
+		container, _, err := runtime.Create(&runconfig.Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"echo", "-n", "foo"},
 		},
@@ -1147,7 +1147,7 @@ func BenchmarkRunParallel(b *testing.B) {
 		complete := make(chan error)
 		tasks = append(tasks, complete)
 		go func(i int, complete chan error) {
-			container, _, err := runtime.Create(&docker.Config{
+			container, _, err := runtime.Create(&runconfig.Config{
 				Image: GetTestImage(runtime).ID,
 				Cmd:   []string{"echo", "-n", "foo"},
 			},
@@ -1301,7 +1301,7 @@ func TestFromVolumesInReadonlyMode(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:   GetTestImage(runtime).ID,
 			Cmd:     []string{"/bin/echo", "-n", "foobar"},
 			Volumes: map[string]struct{}{"/test": {}},
@@ -1321,7 +1321,7 @@ func TestFromVolumesInReadonlyMode(t *testing.T) {
 	}
 
 	container2, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:       GetTestImage(runtime).ID,
 			Cmd:         []string{"/bin/echo", "-n", "foobar"},
 			VolumesFrom: container.ID + ":ro",
@@ -1362,7 +1362,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:   GetTestImage(runtime).ID,
 			Cmd:     []string{"/bin/echo", "-n", "foobar"},
 			Volumes: map[string]struct{}{"/test": {}},
@@ -1382,7 +1382,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
 	}
 
 	container2, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:       GetTestImage(runtime).ID,
 			Cmd:         []string{"/bin/echo", "-n", "foobar"},
 			VolumesFrom: container.ID,
@@ -1418,7 +1418,7 @@ func TestRestartWithVolumes(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image:   GetTestImage(runtime).ID,
 		Cmd:     []string{"echo", "-n", "foobar"},
 		Volumes: map[string]struct{}{"/test": {}},
@@ -1462,7 +1462,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image:   GetTestImage(runtime).ID,
 		Cmd:     []string{"sh", "-c", "echo -n bar > /test/foo"},
 		Volumes: map[string]struct{}{"/test": {}},
@@ -1491,7 +1491,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
 	}
 
 	container2, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:       GetTestImage(runtime).ID,
 			Cmd:         []string{"cat", "/test/foo"},
 			VolumesFrom: container.ID,
@@ -1529,7 +1529,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer nuke(runtime)
 
-	config, hc, _, err := docker.ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil)
+	config, hc, _, err := runconfig.Parse([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1617,7 +1617,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image:   GetTestImage(runtime).ID,
 		Cmd:     []string{"sh", "-c", "echo -n bar > /test/foo"},
 		Volumes: map[string]struct{}{"/test": {}},
@@ -1646,7 +1646,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
 	}
 
 	container2, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:   GetTestImage(runtime).ID,
 			Cmd:     []string{"sh", "-c", "echo -n bar > /other/foo"},
 			Volumes: map[string]struct{}{"/other": {}},
@@ -1668,7 +1668,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
 	}
 
 	container3, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:       GetTestImage(runtime).ID,
 			Cmd:         []string{"/bin/echo", "-n", "foobar"},
 			VolumesFrom: strings.Join([]string{container.ID, container2.ID}, ","),
@@ -1696,7 +1696,7 @@ func TestRestartGhost(t *testing.T) {
 	defer nuke(runtime)
 
 	container, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image:   GetTestImage(runtime).ID,
 			Cmd:     []string{"sh", "-c", "echo -n bar > /test/foo"},
 			Volumes: map[string]struct{}{"/test": {}},

+ 3 - 2
integration/graph_test.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/archive"
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/graphdriver"
 	"github.com/dotcloud/docker/utils"
 	"io"
@@ -105,8 +106,8 @@ func TestGraphCreate(t *testing.T) {
 	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)
+	if image.DockerVersion != dockerversion.VERSION {
+		t.Fatalf("Wrong docker_version: should be '%s', not '%s'", dockerversion.VERSION, image.DockerVersion)
 	}
 	images, err := graph.Map()
 	if err != nil {

+ 23 - 21
integration/runtime_test.go

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/nat"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/sysinit"
 	"github.com/dotcloud/docker/utils"
 	"io"
@@ -199,7 +201,7 @@ func TestRuntimeCreate(t *testing.T) {
 		t.Errorf("Expected 0 containers, %v found", len(runtime.List()))
 	}
 
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"ls", "-al"},
 	},
@@ -242,23 +244,23 @@ func TestRuntimeCreate(t *testing.T) {
 
 	// Test that conflict error displays correct details
 	testContainer, _, _ := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"ls", "-al"},
 		},
 		"conflictname",
 	)
-	if _, _, err := runtime.Create(&docker.Config{Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}}, testContainer.Name); err == nil || !strings.Contains(err.Error(), utils.TruncateID(testContainer.ID)) {
+	if _, _, err := runtime.Create(&runconfig.Config{Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}}, testContainer.Name); err == nil || !strings.Contains(err.Error(), utils.TruncateID(testContainer.ID)) {
 		t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %s", err.Error())
 	}
 
 	// Make sure create with bad parameters returns an error
-	if _, _, err = runtime.Create(&docker.Config{Image: GetTestImage(runtime).ID}, ""); err == nil {
+	if _, _, err = runtime.Create(&runconfig.Config{Image: GetTestImage(runtime).ID}, ""); err == nil {
 		t.Fatal("Builder.Create should throw an error when Cmd is missing")
 	}
 
 	if _, _, err := runtime.Create(
-		&docker.Config{
+		&runconfig.Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{},
 		},
@@ -267,7 +269,7 @@ func TestRuntimeCreate(t *testing.T) {
 		t.Fatal("Builder.Create should throw an error when Cmd is empty")
 	}
 
-	config := &docker.Config{
+	config := &runconfig.Config{
 		Image:     GetTestImage(runtime).ID,
 		Cmd:       []string{"/bin/ls"},
 		PortSpecs: []string{"80"},
@@ -280,7 +282,7 @@ func TestRuntimeCreate(t *testing.T) {
 	}
 
 	// test expose 80:8000
-	container, warnings, err := runtime.Create(&docker.Config{
+	container, warnings, err := runtime.Create(&runconfig.Config{
 		Image:     GetTestImage(runtime).ID,
 		Cmd:       []string{"ls", "-al"},
 		PortSpecs: []string{"80:8000"},
@@ -299,7 +301,7 @@ func TestDestroy(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"ls", "-al"},
 	}, "")
@@ -368,7 +370,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc
 		eng     = NewTestEngine(t)
 		runtime = mkRuntimeFromEngine(eng, t)
 		port    = 5554
-		p       docker.Port
+		p       nat.Port
 	)
 	defer func() {
 		if err != nil {
@@ -387,8 +389,8 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc
 		} else {
 			t.Fatal(fmt.Errorf("Unknown protocol %v", proto))
 		}
-		ep := make(map[docker.Port]struct{}, 1)
-		p = docker.Port(fmt.Sprintf("%s/%s", strPort, proto))
+		ep := make(map[nat.Port]struct{}, 1)
+		p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto))
 		ep[p] = struct{}{}
 
 		jobCreate := eng.Job("create")
@@ -411,8 +413,8 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc
 	}
 
 	jobStart := eng.Job("start", id)
-	portBindings := make(map[docker.Port][]docker.PortBinding)
-	portBindings[p] = []docker.PortBinding{
+	portBindings := make(map[nat.Port][]nat.PortBinding)
+	portBindings[p] = []nat.PortBinding{
 		{},
 	}
 	if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil {
@@ -711,7 +713,7 @@ func TestDefaultContainerName(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer nuke(runtime)
 
-	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -735,7 +737,7 @@ func TestRandomContainerName(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer nuke(runtime)
 
-	config, _, _, err := docker.ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -766,7 +768,7 @@ func TestContainerNameValidation(t *testing.T) {
 		{"abc-123_AAA.1", true},
 		{"\000asdf", false},
 	} {
-		config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+		config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
 		if err != nil {
 			if !test.Valid {
 				continue
@@ -807,7 +809,7 @@ func TestLinkChildContainer(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer nuke(runtime)
 
-	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -823,7 +825,7 @@ func TestLinkChildContainer(t *testing.T) {
 		t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
 	}
 
-	config, _, _, err = docker.ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
+	config, _, _, err = runconfig.Parse([]string{GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -849,7 +851,7 @@ func TestGetAllChildren(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer nuke(runtime)
 
-	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -865,7 +867,7 @@ func TestGetAllChildren(t *testing.T) {
 		t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
 	}
 
-	config, _, _, err = docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+	config, _, _, err = runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -902,7 +904,7 @@ func TestDestroyWithInitLayer(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container, _, err := runtime.Create(&docker.Config{
+	container, _, err := runtime.Create(&runconfig.Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"ls", "-al"},
 	}, "")

+ 11 - 10
integration/server_test.go

@@ -3,6 +3,7 @@ package docker
 import (
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/runconfig"
 	"strings"
 	"testing"
 	"time"
@@ -71,7 +72,7 @@ func TestCreateRm(t *testing.T) {
 	eng := NewTestEngine(t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -118,7 +119,7 @@ func TestCreateNumberHostname(t *testing.T) {
 	eng := NewTestEngine(t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, _, _, err := docker.ParseRun([]string{"-h", "web.0", unitTestImageID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{"-h", "web.0", unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -130,7 +131,7 @@ func TestCreateNumberUsername(t *testing.T) {
 	eng := NewTestEngine(t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, _, _, err := docker.ParseRun([]string{"-u", "1002", unitTestImageID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{"-u", "1002", unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -142,7 +143,7 @@ func TestCreateRmVolumes(t *testing.T) {
 	eng := NewTestEngine(t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
+	config, hostConfig, _, err := runconfig.Parse([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -202,7 +203,7 @@ func TestCommit(t *testing.T) {
 	eng := NewTestEngine(t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil)
+	config, _, _, err := runconfig.Parse([]string{unitTestImageID, "/bin/cat"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -224,7 +225,7 @@ func TestRestartKillWait(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer runtime.Nuke()
 
-	config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
+	config, hostConfig, _, err := runconfig.Parse([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -302,7 +303,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 	srv := mkServerFromEngine(eng, t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
+	config, hostConfig, _, err := runconfig.Parse([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -401,7 +402,7 @@ func TestRmi(t *testing.T) {
 
 	initialImages := getAllImages(eng, t)
 
-	config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil)
+	config, hostConfig, _, err := runconfig.Parse([]string{unitTestImageID, "echo", "test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -548,7 +549,7 @@ func TestListContainers(t *testing.T) {
 	srv := mkServerFromEngine(eng, t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config := docker.Config{
+	config := runconfig.Config{
 		Image:     unitTestImageID,
 		Cmd:       []string{"/bin/sh", "-c", "cat"},
 		OpenStdin: true,
@@ -671,7 +672,7 @@ func TestDeleteTagWithExistingContainers(t *testing.T) {
 	}
 
 	// Create a container from the image
-	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+	config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 5 - 4
integration/utils_test.go

@@ -16,6 +16,7 @@ import (
 
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 )
 
@@ -48,7 +49,7 @@ func mkRuntime(f utils.Fataler) *docker.Runtime {
 	return r
 }
 
-func createNamedTestContainer(eng *engine.Engine, config *docker.Config, f utils.Fataler, name string) (shortId string) {
+func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler, name string) (shortId string) {
 	job := eng.Job("create", name)
 	if err := job.ImportEnv(config); err != nil {
 		f.Fatal(err)
@@ -60,7 +61,7 @@ func createNamedTestContainer(eng *engine.Engine, config *docker.Config, f utils
 	return
 }
 
-func createTestContainer(eng *engine.Engine, config *docker.Config, f utils.Fataler) (shortId string) {
+func createTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler) (shortId string) {
 	return createNamedTestContainer(eng, config, f, "")
 }
 
@@ -252,8 +253,8 @@ func readFile(src string, t *testing.T) (content string) {
 // dynamically replaced by the current test image.
 // The caller is responsible for destroying the container.
 // Call t.Fatal() at the first error.
-func mkContainer(r *docker.Runtime, args []string, t *testing.T) (*docker.Container, *docker.HostConfig, error) {
-	config, hc, _, err := docker.ParseRun(args, nil)
+func mkContainer(r *docker.Runtime, args []string, t *testing.T) (*docker.Container, *runconfig.HostConfig, error) {
+	config, hc, _, err := runconfig.Parse(args, nil)
 	defer func() {
 		if err != nil && t != nil {
 			t.Fatal(err)

+ 6 - 5
links.go

@@ -3,6 +3,7 @@ package docker
 import (
 	"fmt"
 	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/nat"
 	"path"
 	"strings"
 )
@@ -12,7 +13,7 @@ type Link struct {
 	ChildIP          string
 	Name             string
 	ChildEnvironment []string
-	Ports            []Port
+	Ports            []nat.Port
 	IsEnabled        bool
 	eng              *engine.Engine
 }
@@ -25,7 +26,7 @@ func NewLink(parent, child *Container, name string, eng *engine.Engine) (*Link,
 		return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name)
 	}
 
-	ports := make([]Port, len(child.Config.ExposedPorts))
+	ports := make([]nat.Port, len(child.Config.ExposedPorts))
 	var i int
 	for p := range child.Config.ExposedPorts {
 		ports[i] = p
@@ -85,14 +86,14 @@ func (l *Link) ToEnv() []string {
 }
 
 // Default port rules
-func (l *Link) getDefaultPort() *Port {
-	var p Port
+func (l *Link) getDefaultPort() *nat.Port {
+	var p nat.Port
 	i := len(l.Ports)
 
 	if i == 0 {
 		return nil
 	} else if i > 1 {
-		sortPorts(l.Ports, func(ip, jp Port) bool {
+		nat.Sort(l.Ports, func(ip, jp nat.Port) bool {
 			// If the two ports have the same number, tcp takes priority
 			// Sort in desc order
 			return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp")

+ 8 - 6
links_test.go

@@ -1,13 +1,15 @@
 package docker
 
 import (
+	"github.com/dotcloud/docker/nat"
+	"github.com/dotcloud/docker/runconfig"
 	"strings"
 	"testing"
 )
 
 func newMockLinkContainer(id string, ip string) *Container {
 	return &Container{
-		Config: &Config{},
+		Config: &runconfig.Config{},
 		ID:     id,
 		NetworkSettings: &NetworkSettings{
 			IPAddress: ip,
@@ -22,9 +24,9 @@ func TestLinkNew(t *testing.T) {
 	from := newMockLinkContainer(fromID, "172.0.17.2")
 	from.Config.Env = []string{}
 	from.State = State{Running: true}
-	ports := make(map[Port]struct{})
+	ports := make(nat.PortSet)
 
-	ports[Port("6379/tcp")] = struct{}{}
+	ports[nat.Port("6379/tcp")] = struct{}{}
 
 	from.Config.ExposedPorts = ports
 
@@ -51,7 +53,7 @@ func TestLinkNew(t *testing.T) {
 		t.Fail()
 	}
 	for _, p := range link.Ports {
-		if p != Port("6379/tcp") {
+		if p != nat.Port("6379/tcp") {
 			t.Fail()
 		}
 	}
@@ -64,9 +66,9 @@ func TestLinkEnv(t *testing.T) {
 	from := newMockLinkContainer(fromID, "172.0.17.2")
 	from.Config.Env = []string{"PASSWORD=gordon"}
 	from.State = State{Running: true}
-	ports := make(map[Port]struct{})
+	ports := make(nat.PortSet)
 
-	ports[Port("6379/tcp")] = struct{}{}
+	ports[nat.Port("6379/tcp")] = struct{}{}
 
 	from.Config.ExposedPorts = ports
 

+ 133 - 0
nat/nat.go

@@ -0,0 +1,133 @@
+package nat
+
+// nat is a convenience package for docker's manipulation of strings describing
+// network ports.
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/utils"
+	"strconv"
+	"strings"
+)
+
+const (
+	PortSpecTemplate       = "ip:hostPort:containerPort"
+	PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort"
+)
+
+type PortBinding struct {
+	HostIp   string
+	HostPort string
+}
+
+type PortMap map[Port][]PortBinding
+
+type PortSet map[Port]struct{}
+
+// 80/tcp
+type Port string
+
+func NewPort(proto, port string) Port {
+	return Port(fmt.Sprintf("%s/%s", port, proto))
+}
+
+func ParsePort(rawPort string) (int, error) {
+	port, err := strconv.ParseUint(rawPort, 10, 16)
+	if err != nil {
+		return 0, err
+	}
+	return int(port), nil
+}
+
+func (p Port) Proto() string {
+	parts := strings.Split(string(p), "/")
+	if len(parts) == 1 {
+		return "tcp"
+	}
+	return parts[1]
+}
+
+func (p Port) Port() string {
+	return strings.Split(string(p), "/")[0]
+}
+
+func (p Port) Int() int {
+	i, err := ParsePort(p.Port())
+	if err != nil {
+		panic(err)
+	}
+	return i
+}
+
+// Splits a port in the format of port/proto
+func SplitProtoPort(rawPort string) (string, string) {
+	parts := strings.Split(rawPort, "/")
+	l := len(parts)
+	if l == 0 {
+		return "", ""
+	}
+	if l == 1 {
+		return "tcp", rawPort
+	}
+	return parts[0], parts[1]
+}
+
+// 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) {
+	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]
+		}
+		if !strings.Contains(rawPort, ":") {
+			rawPort = fmt.Sprintf("::%s", rawPort)
+		} else if len(strings.Split(rawPort, ":")) == 2 {
+			rawPort = fmt.Sprintf(":%s", rawPort)
+		}
+
+		parts, err := utils.PartParser(PortSpecTemplate, rawPort)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		var (
+			containerPort = parts["containerPort"]
+			rawIp         = parts["ip"]
+			hostPort      = parts["hostPort"]
+		)
+
+		if containerPort == "" {
+			return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
+		}
+		if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil {
+			return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
+		}
+		if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil {
+			return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
+		}
+
+		port := NewPort(proto, containerPort)
+		if _, exists := exposedPorts[port]; !exists {
+			exposedPorts[port] = struct{}{}
+		}
+
+		binding := PortBinding{
+			HostIp:   rawIp,
+			HostPort: hostPort,
+		}
+		bslice, exists := bindings[port]
+		if !exists {
+			bslice = []PortBinding{}
+		}
+		bindings[port] = append(bslice, binding)
+	}
+	return exposedPorts, bindings, nil
+}

+ 28 - 0
nat/sort.go

@@ -0,0 +1,28 @@
+package nat
+
+import "sort"
+
+type portSorter struct {
+	ports []Port
+	by    func(i, j Port) bool
+}
+
+func (s *portSorter) Len() int {
+	return len(s.ports)
+}
+
+func (s *portSorter) Swap(i, j int) {
+	s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
+}
+
+func (s *portSorter) Less(i, j int) bool {
+	ip := s.ports[i]
+	jp := s.ports[j]
+
+	return s.by(ip, jp)
+}
+
+func Sort(ports []Port, predicate func(i, j Port) bool) {
+	s := &portSorter{ports, predicate}
+	sort.Sort(s)
+}

+ 3 - 3
sorter_unit_test.go → nat/sort_test.go

@@ -1,4 +1,4 @@
-package docker
+package nat
 
 import (
 	"fmt"
@@ -11,7 +11,7 @@ func TestSortUniquePorts(t *testing.T) {
 		Port("22/tcp"),
 	}
 
-	sortPorts(ports, func(ip, jp Port) bool {
+	Sort(ports, func(ip, jp Port) bool {
 		return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
 	})
 
@@ -30,7 +30,7 @@ func TestSortSamePortWithDifferentProto(t *testing.T) {
 		Port("6379/udp"),
 	}
 
-	sortPorts(ports, func(ip, jp Port) bool {
+	Sort(ports, func(ip, jp Port) bool {
 		return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
 	})
 

+ 18 - 1
networkdriver/lxc/driver.go

@@ -172,7 +172,6 @@ func setupIPTables(addr net.Addr, icc bool) error {
 		iptables.Raw(append([]string{"-D"}, acceptArgs...)...)
 
 		if !iptables.Exists(dropArgs...) {
-
 			utils.Debugf("Disable inter-container communication")
 			if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil {
 				return fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
@@ -353,6 +352,10 @@ func Release(job *engine.Job) engine.Status {
 		proto              string
 	)
 
+	if containerInterface == nil {
+		return job.Errorf("No network information to release for %s", id)
+	}
+
 	for _, nat := range containerInterface.PortMappings {
 		if err := portmapper.Unmap(nat); err != nil {
 			log.Printf("Unable to unmap port %s: %s", nat, err)
@@ -466,6 +469,20 @@ func LinkContainers(job *engine.Job) engine.Status {
 			job.Errorf("Error toggle iptables forward: %s", output)
 			return engine.StatusErr
 		}
+
+		if output, err := iptables.Raw(action, "FORWARD",
+			"-i", bridgeIface, "-o", bridgeIface,
+			"-p", proto,
+			"-s", childIP,
+			"--sport", port,
+			"-d", parentIP,
+			"-j", "ACCEPT"); !ignoreErrors && err != nil {
+			job.Error(err)
+			return engine.StatusErr
+		} else if len(output) != 0 {
+			job.Errorf("Error toggle iptables forward: %s", output)
+			return engine.StatusErr
+		}
 	}
 	return engine.StatusOK
 }

+ 11 - 10
opts.go → pkg/opts/opts.go

@@ -1,8 +1,7 @@
-package docker
+package opts
 
 import (
 	"fmt"
-	"github.com/dotcloud/docker/api"
 	"github.com/dotcloud/docker/utils"
 	"os"
 	"path/filepath"
@@ -99,6 +98,16 @@ func ValidateLink(val string) (string, error) {
 	return val, nil
 }
 
+// FIXME: this is a duplicate of docker.utils.parseLink.
+// 	it can't be moved to a separate links/ package because
+//	links depends on Container which is defined in the core.
+//
+// Links come in the format of
+// name:alias
+func parseLink(rawLink string) (map[string]string, error) {
+	return utils.PartParser("name:alias", rawLink)
+}
+
 func ValidatePath(val string) (string, error) {
 	var containerPath string
 
@@ -129,14 +138,6 @@ func ValidateEnv(val string) (string, error) {
 	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
 }
 
-func ValidateHost(val string) (string, error) {
-	host, err := utils.ParseHost(api.DEFAULTHTTPHOST, api.DEFAULTHTTPPORT, api.DEFAULTUNIXSOCKET, val)
-	if err != nil {
-		return val, err
-	}
-	return host, nil
-}
-
 func ValidateIp4Address(val string) (string, error) {
 	re := regexp.MustCompile(`^(([0-9]+\.){3}([0-9]+))\s*$`)
 	var ns = re.FindSubmatch([]byte(val))

+ 1 - 1
opts_unit_test.go → pkg/opts/opts_test.go

@@ -1,4 +1,4 @@
-package docker
+package opts
 
 import (
 	"testing"

+ 1 - 0
pkg/systemd/MAINTAINERS

@@ -0,0 +1 @@
+Brandon Philips <brandon.philips@coreos.com> (@philips)

+ 1 - 0
pkg/user/MAINTAINERS

@@ -0,0 +1 @@
+Tianon Gravi <admwiggin@gmail.com> (@tianon)

+ 241 - 0
pkg/user/user.go

@@ -0,0 +1,241 @@
+package user
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+)
+
+type User struct {
+	Name  string
+	Pass  string
+	Uid   int
+	Gid   int
+	Gecos string
+	Home  string
+	Shell string
+}
+
+type Group struct {
+	Name string
+	Pass string
+	Gid  int
+	List []string
+}
+
+func parseLine(line string, v ...interface{}) {
+	if line == "" {
+		return
+	}
+
+	parts := strings.Split(line, ":")
+	for i, p := range parts {
+		if len(v) <= i {
+			// if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files
+			break
+		}
+
+		switch e := v[i].(type) {
+		case *string:
+			// "root", "adm", "/bin/bash"
+			*e = p
+		case *int:
+			// "0", "4", "1000"
+			// ignore string to int conversion errors, for great "tolerance" of naughty configuration files
+			*e, _ = strconv.Atoi(p)
+		case *[]string:
+			// "", "root", "root,adm,daemon"
+			if p != "" {
+				*e = strings.Split(p, ",")
+			} else {
+				*e = []string{}
+			}
+		default:
+			// panic, because this is a programming/logic error, not a runtime one
+			panic("parseLine expects only pointers!  argument " + strconv.Itoa(i) + " is not a pointer!")
+		}
+	}
+}
+
+func ParsePasswd() ([]*User, error) {
+	return ParsePasswdFilter(nil)
+}
+
+func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) {
+	f, err := os.Open("/etc/passwd")
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return parsePasswdFile(f, filter)
+}
+
+func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
+	var (
+		s   = bufio.NewScanner(r)
+		out = []*User{}
+	)
+
+	for s.Scan() {
+		if err := s.Err(); err != nil {
+			return nil, err
+		}
+
+		text := strings.TrimSpace(s.Text())
+		if text == "" {
+			continue
+		}
+
+		// see: man 5 passwd
+		//  name:password:UID:GID:GECOS:directory:shell
+		// Name:Pass:Uid:Gid:Gecos:Home:Shell
+		//  root:x:0:0:root:/root:/bin/bash
+		//  adm:x:3:4:adm:/var/adm:/bin/false
+		p := &User{}
+		parseLine(
+			text,
+			&p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell,
+		)
+
+		if filter == nil || filter(p) {
+			out = append(out, p)
+		}
+	}
+
+	return out, nil
+}
+
+func ParseGroup() ([]*Group, error) {
+	return ParseGroupFilter(nil)
+}
+
+func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) {
+	f, err := os.Open("/etc/group")
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return parseGroupFile(f, filter)
+}
+
+func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
+	var (
+		s   = bufio.NewScanner(r)
+		out = []*Group{}
+	)
+
+	for s.Scan() {
+		if err := s.Err(); err != nil {
+			return nil, err
+		}
+
+		text := s.Text()
+		if text == "" {
+			continue
+		}
+
+		// see: man 5 group
+		//  group_name:password:GID:user_list
+		// Name:Pass:Gid:List
+		//  root:x:0:root
+		//  adm:x:4:root,adm,daemon
+		p := &Group{}
+		parseLine(
+			text,
+			&p.Name, &p.Pass, &p.Gid, &p.List,
+		)
+
+		if filter == nil || filter(p) {
+			out = append(out, p)
+		}
+	}
+
+	return out, nil
+}
+
+// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, and list of supplementary group IDs, if possible.
+func GetUserGroupSupplementary(userSpec string, defaultUid int, defaultGid int) (int, int, []int, error) {
+	var (
+		uid      = defaultUid
+		gid      = defaultGid
+		suppGids = []int{}
+
+		userArg, groupArg string
+	)
+
+	// allow for userArg to have either "user" syntax, or optionally "user:group" syntax
+	parseLine(userSpec, &userArg, &groupArg)
+
+	users, err := ParsePasswdFilter(func(u *User) bool {
+		if userArg == "" {
+			return u.Uid == uid
+		}
+		return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
+	})
+	if err != nil && !os.IsNotExist(err) {
+		if userArg == "" {
+			userArg = strconv.Itoa(uid)
+		}
+		return 0, 0, nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
+	}
+
+	haveUser := users != nil && len(users) > 0
+	if haveUser {
+		// if we found any user entries that matched our filter, let's take the first one as "correct"
+		uid = users[0].Uid
+		gid = users[0].Gid
+	} else if userArg != "" {
+		// we asked for a user but didn't find them...  let's check to see if we wanted a numeric user
+		uid, err = strconv.Atoi(userArg)
+		if err != nil {
+			// not numeric - we have to bail
+			return 0, 0, nil, fmt.Errorf("Unable to find user %v", userArg)
+		}
+
+		// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
+	}
+
+	if groupArg != "" || (haveUser && users[0].Name != "") {
+		groups, err := ParseGroupFilter(func(g *Group) bool {
+			if groupArg != "" {
+				return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
+			}
+			for _, u := range g.List {
+				if u == users[0].Name {
+					return true
+				}
+			}
+			return false
+		})
+		if err != nil && !os.IsNotExist(err) {
+			return 0, 0, nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
+		}
+
+		haveGroup := groups != nil && len(groups) > 0
+		if groupArg != "" {
+			if haveGroup {
+				// if we found any group entries that matched our filter, let's take the first one as "correct"
+				gid = groups[0].Gid
+			} else {
+				// we asked for a group but didn't find id...  let's check to see if we wanted a numeric group
+				gid, err = strconv.Atoi(groupArg)
+				if err != nil {
+					// not numeric - we have to bail
+					return 0, 0, nil, fmt.Errorf("Unable to find group %v", groupArg)
+				}
+
+				// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
+			}
+		} else if haveGroup {
+			suppGids = make([]int, len(groups))
+			for i, group := range groups {
+				suppGids[i] = group.Gid
+			}
+		}
+	}
+
+	return uid, gid, suppGids, nil
+}

+ 94 - 0
pkg/user/user_test.go

@@ -0,0 +1,94 @@
+package user
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestUserParseLine(t *testing.T) {
+	var (
+		a, b string
+		c    []string
+		d    int
+	)
+
+	parseLine("", &a, &b)
+	if a != "" || b != "" {
+		t.Fatalf("a and b should be empty ('%v', '%v')", a, b)
+	}
+
+	parseLine("a", &a, &b)
+	if a != "a" || b != "" {
+		t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b)
+	}
+
+	parseLine("bad boys:corny cows", &a, &b)
+	if a != "bad boys" || b != "corny cows" {
+		t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b)
+	}
+
+	parseLine("", &c)
+	if len(c) != 0 {
+		t.Fatalf("c should be empty (%#v)", c)
+	}
+
+	parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c)
+	if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" {
+		t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c)
+	}
+
+	parseLine("::::::::::", &a, &b, &c)
+	if a != "" || b != "" || len(c) != 0 {
+		t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c)
+	}
+
+	parseLine("not a number", &d)
+	if d != 0 {
+		t.Fatalf("d should be 0 (%v)", d)
+	}
+
+	parseLine("b:12:c", &a, &d, &b)
+	if a != "b" || b != "c" || d != 12 {
+		t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d)
+	}
+}
+
+func TestUserParsePasswd(t *testing.T) {
+	users, err := parsePasswdFile(strings.NewReader(`
+root:x:0:0:root:/root:/bin/bash
+adm:x:3:4:adm:/var/adm:/bin/false
+this is just some garbage data
+`), nil)
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+	}
+	if len(users) != 3 {
+		t.Fatalf("Expected 3 users, got %v", len(users))
+	}
+	if users[0].Uid != 0 || users[0].Name != "root" {
+		t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name)
+	}
+	if users[1].Uid != 3 || users[1].Name != "adm" {
+		t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name)
+	}
+}
+
+func TestUserParseGroup(t *testing.T) {
+	groups, err := parseGroupFile(strings.NewReader(`
+root:x:0:root
+adm:x:4:root,adm,daemon
+this is just some garbage data
+`), nil)
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+	}
+	if len(groups) != 3 {
+		t.Fatalf("Expected 3 groups, got %v", len(groups))
+	}
+	if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 {
+		t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List))
+	}
+	if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 {
+		t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List))
+	}
+}

+ 67 - 0
runconfig/compare.go

@@ -0,0 +1,67 @@
+package runconfig
+
+// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
+// If OpenStdin is set, then it differs
+func Compare(a, b *Config) bool {
+	if a == nil || b == nil ||
+		a.OpenStdin || b.OpenStdin {
+		return false
+	}
+	if a.AttachStdout != b.AttachStdout ||
+		a.AttachStderr != b.AttachStderr ||
+		a.User != b.User ||
+		a.Memory != b.Memory ||
+		a.MemorySwap != b.MemorySwap ||
+		a.CpuShares != b.CpuShares ||
+		a.OpenStdin != b.OpenStdin ||
+		a.Tty != b.Tty ||
+		a.VolumesFrom != b.VolumesFrom {
+		return false
+	}
+	if len(a.Cmd) != len(b.Cmd) ||
+		len(a.Dns) != len(b.Dns) ||
+		len(a.Env) != len(b.Env) ||
+		len(a.PortSpecs) != len(b.PortSpecs) ||
+		len(a.ExposedPorts) != len(b.ExposedPorts) ||
+		len(a.Entrypoint) != len(b.Entrypoint) ||
+		len(a.Volumes) != len(b.Volumes) {
+		return false
+	}
+
+	for i := 0; i < len(a.Cmd); i++ {
+		if a.Cmd[i] != b.Cmd[i] {
+			return false
+		}
+	}
+	for i := 0; i < len(a.Dns); i++ {
+		if a.Dns[i] != b.Dns[i] {
+			return false
+		}
+	}
+	for i := 0; i < len(a.Env); i++ {
+		if a.Env[i] != b.Env[i] {
+			return false
+		}
+	}
+	for i := 0; i < len(a.PortSpecs); i++ {
+		if a.PortSpecs[i] != b.PortSpecs[i] {
+			return false
+		}
+	}
+	for k := range a.ExposedPorts {
+		if _, exists := b.ExposedPorts[k]; !exists {
+			return false
+		}
+	}
+	for i := 0; i < len(a.Entrypoint); i++ {
+		if a.Entrypoint[i] != b.Entrypoint[i] {
+			return false
+		}
+	}
+	for key := range a.Volumes {
+		if _, exists := b.Volumes[key]; !exists {
+			return false
+		}
+	}
+	return true
+}

+ 76 - 0
runconfig/config.go

@@ -0,0 +1,76 @@
+package runconfig
+
+import (
+	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/nat"
+)
+
+// Note: the Config structure should hold only portable information about the container.
+// Here, "portable" means "independent from the host we are running on".
+// Non-portable information *should* appear in HostConfig.
+type Config struct {
+	Hostname        string
+	Domainname      string
+	User            string
+	Memory          int64 // Memory limit (in bytes)
+	MemorySwap      int64 // Total memory usage (memory + swap); set `-1' to disable swap
+	CpuShares       int64 // CPU shares (relative weight vs. other containers)
+	AttachStdin     bool
+	AttachStdout    bool
+	AttachStderr    bool
+	PortSpecs       []string // Deprecated - Can be in the format of 8080/tcp
+	ExposedPorts    map[nat.Port]struct{}
+	Tty             bool // Attach standard streams to a tty, including stdin if it is not closed.
+	OpenStdin       bool // Open stdin
+	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.
+	Env             []string
+	Cmd             []string
+	Dns             []string
+	Image           string // Name of the image as it was passed by the operator (eg. could be symbolic)
+	Volumes         map[string]struct{}
+	VolumesFrom     string
+	WorkingDir      string
+	Entrypoint      []string
+	NetworkDisabled bool
+	OnBuild         []string
+}
+
+func ContainerConfigFromJob(job *engine.Job) *Config {
+	config := &Config{
+		Hostname:        job.Getenv("Hostname"),
+		Domainname:      job.Getenv("Domainname"),
+		User:            job.Getenv("User"),
+		Memory:          job.GetenvInt64("Memory"),
+		MemorySwap:      job.GetenvInt64("MemorySwap"),
+		CpuShares:       job.GetenvInt64("CpuShares"),
+		AttachStdin:     job.GetenvBool("AttachStdin"),
+		AttachStdout:    job.GetenvBool("AttachStdout"),
+		AttachStderr:    job.GetenvBool("AttachStderr"),
+		Tty:             job.GetenvBool("Tty"),
+		OpenStdin:       job.GetenvBool("OpenStdin"),
+		StdinOnce:       job.GetenvBool("StdinOnce"),
+		Image:           job.Getenv("Image"),
+		VolumesFrom:     job.Getenv("VolumesFrom"),
+		WorkingDir:      job.Getenv("WorkingDir"),
+		NetworkDisabled: job.GetenvBool("NetworkDisabled"),
+	}
+	job.GetenvJson("ExposedPorts", &config.ExposedPorts)
+	job.GetenvJson("Volumes", &config.Volumes)
+	if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
+		config.PortSpecs = PortSpecs
+	}
+	if Env := job.GetenvList("Env"); Env != nil {
+		config.Env = Env
+	}
+	if Cmd := job.GetenvList("Cmd"); Cmd != nil {
+		config.Cmd = Cmd
+	}
+	if Dns := job.GetenvList("Dns"); Dns != nil {
+		config.Dns = Dns
+	}
+	if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
+		config.Entrypoint = Entrypoint
+	}
+
+	return config
+}

+ 17 - 16
config_test.go → runconfig/config_test.go

@@ -1,10 +1,11 @@
-package docker
+package runconfig
 
 import (
+	"github.com/dotcloud/docker/nat"
 	"testing"
 )
 
-func TestCompareConfig(t *testing.T) {
+func TestCompare(t *testing.T) {
 	volumes1 := make(map[string]struct{})
 	volumes1["/test1"] = struct{}{}
 	config1 := Config{
@@ -44,24 +45,24 @@ func TestCompareConfig(t *testing.T) {
 		VolumesFrom: "11111111",
 		Volumes:     volumes2,
 	}
-	if CompareConfig(&config1, &config2) {
-		t.Fatalf("CompareConfig should return false, Dns are different")
+	if Compare(&config1, &config2) {
+		t.Fatalf("Compare should return false, Dns are different")
 	}
-	if CompareConfig(&config1, &config3) {
-		t.Fatalf("CompareConfig should return false, PortSpecs are different")
+	if Compare(&config1, &config3) {
+		t.Fatalf("Compare should return false, PortSpecs are different")
 	}
-	if CompareConfig(&config1, &config4) {
-		t.Fatalf("CompareConfig should return false, VolumesFrom are different")
+	if Compare(&config1, &config4) {
+		t.Fatalf("Compare should return false, VolumesFrom are different")
 	}
-	if CompareConfig(&config1, &config5) {
-		t.Fatalf("CompareConfig should return false, Volumes are different")
+	if Compare(&config1, &config5) {
+		t.Fatalf("Compare should return false, Volumes are different")
 	}
-	if !CompareConfig(&config1, &config1) {
-		t.Fatalf("CompareConfig should return true")
+	if !Compare(&config1, &config1) {
+		t.Fatalf("Compare should return true")
 	}
 }
 
-func TestMergeConfig(t *testing.T) {
+func TestMerge(t *testing.T) {
 	volumesImage := make(map[string]struct{})
 	volumesImage["/test1"] = struct{}{}
 	volumesImage["/test2"] = struct{}{}
@@ -82,7 +83,7 @@ func TestMergeConfig(t *testing.T) {
 		Volumes:   volumesUser,
 	}
 
-	if err := MergeConfig(configUser, configImage); err != nil {
+	if err := Merge(configUser, configImage); err != nil {
 		t.Error(err)
 	}
 
@@ -125,7 +126,7 @@ func TestMergeConfig(t *testing.T) {
 		t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom)
 	}
 
-	ports, _, err := parsePortSpecs([]string{"0000"})
+	ports, _, err := nat.ParsePortSpecs([]string{"0000"})
 	if err != nil {
 		t.Error(err)
 	}
@@ -133,7 +134,7 @@ func TestMergeConfig(t *testing.T) {
 		ExposedPorts: ports,
 	}
 
-	if err := MergeConfig(configUser, configImage2); err != nil {
+	if err := Merge(configUser, configImage2); err != nil {
 		t.Error(err)
 	}
 

+ 39 - 0
runconfig/hostconfig.go

@@ -0,0 +1,39 @@
+package runconfig
+
+import (
+	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/nat"
+)
+
+type HostConfig struct {
+	Binds           []string
+	ContainerIDFile string
+	LxcConf         []KeyValuePair
+	Privileged      bool
+	PortBindings    nat.PortMap
+	Links           []string
+	PublishAllPorts bool
+}
+
+type KeyValuePair struct {
+	Key   string
+	Value string
+}
+
+func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
+	hostConfig := &HostConfig{
+		ContainerIDFile: job.Getenv("ContainerIDFile"),
+		Privileged:      job.GetenvBool("Privileged"),
+		PublishAllPorts: job.GetenvBool("PublishAllPorts"),
+	}
+	job.GetenvJson("LxcConf", &hostConfig.LxcConf)
+	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
+	if Binds := job.GetenvList("Binds"); Binds != nil {
+		hostConfig.Binds = Binds
+	}
+	if Links := job.GetenvList("Links"); Links != nil {
+		hostConfig.Links = Links
+	}
+
+	return hostConfig
+}

+ 119 - 0
runconfig/merge.go

@@ -0,0 +1,119 @@
+package runconfig
+
+import (
+	"github.com/dotcloud/docker/nat"
+	"github.com/dotcloud/docker/utils"
+	"strings"
+)
+
+func Merge(userConf, imageConf *Config) error {
+	if userConf.User == "" {
+		userConf.User = imageConf.User
+	}
+	if userConf.Memory == 0 {
+		userConf.Memory = imageConf.Memory
+	}
+	if userConf.MemorySwap == 0 {
+		userConf.MemorySwap = imageConf.MemorySwap
+	}
+	if userConf.CpuShares == 0 {
+		userConf.CpuShares = imageConf.CpuShares
+	}
+	if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
+		userConf.ExposedPorts = imageConf.ExposedPorts
+	} else if imageConf.ExposedPorts != nil {
+		if userConf.ExposedPorts == nil {
+			userConf.ExposedPorts = make(nat.PortSet)
+		}
+		for port := range imageConf.ExposedPorts {
+			if _, exists := userConf.ExposedPorts[port]; !exists {
+				userConf.ExposedPorts[port] = struct{}{}
+			}
+		}
+	}
+
+	if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
+		if userConf.ExposedPorts == nil {
+			userConf.ExposedPorts = make(nat.PortSet)
+		}
+		ports, _, err := nat.ParsePortSpecs(userConf.PortSpecs)
+		if err != nil {
+			return err
+		}
+		for port := range ports {
+			if _, exists := userConf.ExposedPorts[port]; !exists {
+				userConf.ExposedPorts[port] = struct{}{}
+			}
+		}
+		userConf.PortSpecs = nil
+	}
+	if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 {
+		// FIXME: I think we can safely remove this. Leaving it for now for the sake of reverse-compat paranoia.
+		utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", "))
+		if userConf.ExposedPorts == nil {
+			userConf.ExposedPorts = make(nat.PortSet)
+		}
+
+		ports, _, err := nat.ParsePortSpecs(imageConf.PortSpecs)
+		if err != nil {
+			return err
+		}
+		for port := range ports {
+			if _, exists := userConf.ExposedPorts[port]; !exists {
+				userConf.ExposedPorts[port] = struct{}{}
+			}
+		}
+	}
+	if !userConf.Tty {
+		userConf.Tty = imageConf.Tty
+	}
+	if !userConf.OpenStdin {
+		userConf.OpenStdin = imageConf.OpenStdin
+	}
+	if !userConf.StdinOnce {
+		userConf.StdinOnce = imageConf.StdinOnce
+	}
+	if userConf.Env == nil || len(userConf.Env) == 0 {
+		userConf.Env = imageConf.Env
+	} else {
+		for _, imageEnv := range imageConf.Env {
+			found := false
+			imageEnvKey := strings.Split(imageEnv, "=")[0]
+			for _, userEnv := range userConf.Env {
+				userEnvKey := strings.Split(userEnv, "=")[0]
+				if imageEnvKey == userEnvKey {
+					found = true
+				}
+			}
+			if !found {
+				userConf.Env = append(userConf.Env, imageEnv)
+			}
+		}
+	}
+	if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
+		userConf.Cmd = imageConf.Cmd
+	}
+	if userConf.Dns == nil || len(userConf.Dns) == 0 {
+		userConf.Dns = imageConf.Dns
+	} else {
+		//duplicates aren't an issue here
+		userConf.Dns = append(userConf.Dns, imageConf.Dns...)
+	}
+	if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
+		userConf.Entrypoint = imageConf.Entrypoint
+	}
+	if userConf.WorkingDir == "" {
+		userConf.WorkingDir = imageConf.WorkingDir
+	}
+	if userConf.VolumesFrom == "" {
+		userConf.VolumesFrom = imageConf.VolumesFrom
+	}
+	if userConf.Volumes == nil || len(userConf.Volumes) == 0 {
+		userConf.Volumes = imageConf.Volumes
+	} else {
+		for k, v := range imageConf.Volumes {
+			userConf.Volumes[k] = v
+		}
+	}
+	return nil
+}

+ 246 - 0
runconfig/parse.go

@@ -0,0 +1,246 @@
+package runconfig
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/nat"
+	flag "github.com/dotcloud/docker/pkg/mflag"
+	"github.com/dotcloud/docker/pkg/opts"
+	"github.com/dotcloud/docker/pkg/sysinfo"
+	"github.com/dotcloud/docker/utils"
+	"io/ioutil"
+	"path"
+	"strings"
+)
+
+var (
+	ErrInvalidWorikingDirectory = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.")
+	ErrConflictAttachDetach     = fmt.Errorf("Conflicting options: -a and -d")
+	ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: -rm and -d")
+)
+
+//FIXME Only used in tests
+func Parse(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
+	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
+	cmd.SetOutput(ioutil.Discard)
+	cmd.Usage = nil
+	return parseRun(cmd, args, sysInfo)
+}
+
+// FIXME: this maps the legacy commands.go code. It should be merged with Parse to only expose a single parse function.
+func ParseSubcommand(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
+	return parseRun(cmd, args, sysInfo)
+}
+
+func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
+	var (
+		// FIXME: use utils.ListOpts for attach and volumes?
+		flAttach  = opts.NewListOpts(opts.ValidateAttach)
+		flVolumes = opts.NewListOpts(opts.ValidatePath)
+		flLinks   = opts.NewListOpts(opts.ValidateLink)
+		flEnv     = opts.NewListOpts(opts.ValidateEnv)
+
+		flPublish     opts.ListOpts
+		flExpose      opts.ListOpts
+		flDns         opts.ListOpts
+		flVolumesFrom opts.ListOpts
+		flLxcOpts     opts.ListOpts
+
+		flAutoRemove      = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
+		flDetach          = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id")
+		flNetwork         = cmd.Bool([]string{"n", "-networking"}, true, "Enable networking for this container")
+		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
+		flPublishAll      = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces")
+		flStdin           = cmd.Bool([]string{"i", "-interactive"}, false, "Keep stdin open even if not attached")
+		flTty             = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-tty")
+		flContainerIDFile = cmd.String([]string{"#cidfile", "-cidfile"}, "", "Write the container ID to the file")
+		flEntrypoint      = cmd.String([]string{"#entrypoint", "-entrypoint"}, "", "Overwrite the default entrypoint of the image")
+		flHostname        = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
+		flMemoryString    = cmd.String([]string{"m", "-memory"}, "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
+		flUser            = cmd.String([]string{"u", "-user"}, "", "Username or UID")
+		flWorkingDir      = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
+		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
+
+		// For documentation purpose
+		_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
+		_ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
+	)
+
+	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to stdin, stdout or stderr.")
+	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
+	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)")
+	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
+
+	cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat))
+	cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host")
+	cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers")
+	cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)")
+	cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-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 sysInfo != nil && *flMemoryString != "" && !sysInfo.MemoryLimit {
+		*flMemoryString = ""
+	}
+
+	// Validate input params
+	if *flDetach && flAttach.Len() > 0 {
+		return nil, nil, cmd, ErrConflictAttachDetach
+	}
+	if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
+		return nil, nil, cmd, ErrInvalidWorikingDirectory
+	}
+	if *flDetach && *flAutoRemove {
+		return nil, nil, cmd, ErrConflictDetachAutoRemove
+	}
+
+	// If neither -d or -a are set, attach to everything by default
+	if flAttach.Len() == 0 && !*flDetach {
+		if !*flDetach {
+			flAttach.Set("stdout")
+			flAttach.Set("stderr")
+			if *flStdin {
+				flAttach.Set("stdin")
+			}
+		}
+	}
+
+	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.GetMap() {
+		if arr := strings.Split(bind, ":"); len(arr) > 1 {
+			if arr[0] == "/" {
+				return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
+			}
+			dstDir := arr[1]
+			flVolumes.Set(dstDir)
+			binds = append(binds, bind)
+			flVolumes.Delete(bind)
+		} else if bind == "/" {
+			return nil, nil, cmd, fmt.Errorf("Invalid volume: path can't be '/'")
+		}
+	}
+
+	var (
+		parsedArgs = cmd.Args()
+		runCmd     []string
+		entrypoint []string
+		image      string
+	)
+	if len(parsedArgs) >= 1 {
+		image = cmd.Arg(0)
+	}
+	if len(parsedArgs) > 1 {
+		runCmd = parsedArgs[1:]
+	}
+	if *flEntrypoint != "" {
+		entrypoint = []string{*flEntrypoint}
+	}
+
+	lxcConf, err := parseLxcConfOpts(flLxcOpts)
+	if err != nil {
+		return nil, nil, cmd, err
+	}
+
+	var (
+		domainname string
+		hostname   = *flHostname
+		parts      = strings.SplitN(hostname, ".", 2)
+	)
+	if len(parts) > 1 {
+		hostname = parts[0]
+		domainname = parts[1]
+	}
+
+	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
+	if err != nil {
+		return nil, nil, cmd, err
+	}
+
+	// Merge in exposed ports to the map of published ports
+	for _, e := range flExpose.GetAll() {
+		if strings.Contains(e, ":") {
+			return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
+		}
+		p := nat.NewPort(nat.SplitProtoPort(e))
+		if _, exists := ports[p]; !exists {
+			ports[p] = struct{}{}
+		}
+	}
+
+	config := &Config{
+		Hostname:        hostname,
+		Domainname:      domainname,
+		PortSpecs:       nil, // Deprecated
+		ExposedPorts:    ports,
+		User:            *flUser,
+		Tty:             *flTty,
+		NetworkDisabled: !*flNetwork,
+		OpenStdin:       *flStdin,
+		Memory:          flMemory,
+		CpuShares:       *flCpuShares,
+		AttachStdin:     flAttach.Get("stdin"),
+		AttachStdout:    flAttach.Get("stdout"),
+		AttachStderr:    flAttach.Get("stderr"),
+		Env:             flEnv.GetAll(),
+		Cmd:             runCmd,
+		Dns:             flDns.GetAll(),
+		Image:           image,
+		Volumes:         flVolumes.GetMap(),
+		VolumesFrom:     strings.Join(flVolumesFrom.GetAll(), ","),
+		Entrypoint:      entrypoint,
+		WorkingDir:      *flWorkingDir,
+	}
+
+	hostConfig := &HostConfig{
+		Binds:           binds,
+		ContainerIDFile: *flContainerIDFile,
+		LxcConf:         lxcConf,
+		Privileged:      *flPrivileged,
+		PortBindings:    portBindings,
+		Links:           flLinks.GetAll(),
+		PublishAllPorts: *flPublishAll,
+	}
+
+	if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
+		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
+		config.MemorySwap = -1
+	}
+
+	// When allocating stdin in attached mode, close stdin at client disconnect
+	if config.OpenStdin && config.AttachStdin {
+		config.StdinOnce = true
+	}
+	return config, hostConfig, cmd, nil
+}
+
+func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) {
+	out := make([]KeyValuePair, opts.Len())
+	for i, o := range opts.GetAll() {
+		k, v, err := parseLxcOpt(o)
+		if err != nil {
+			return nil, err
+		}
+		out[i] = KeyValuePair{Key: k, Value: v}
+	}
+	return out, nil
+}
+
+func parseLxcOpt(opt string) (string, string, error) {
+	parts := strings.SplitN(opt, "=", 2)
+	if len(parts) != 2 {
+		return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt)
+	}
+	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
+}

+ 22 - 0
runconfig/parse_test.go

@@ -0,0 +1,22 @@
+package runconfig
+
+import (
+	"testing"
+)
+
+func TestParseLxcConfOpt(t *testing.T) {
+	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
+
+	for _, o := range opts {
+		k, v, err := parseLxcOpt(o)
+		if err != nil {
+			t.FailNow()
+		}
+		if k != "lxc.utsname" {
+			t.Fail()
+		}
+		if v != "docker" {
+			t.Fail()
+		}
+	}
+}

+ 9 - 17
runtime.go

@@ -4,6 +4,7 @@ import (
 	"container/list"
 	"fmt"
 	"github.com/dotcloud/docker/archive"
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/execdriver"
 	"github.com/dotcloud/docker/execdriver/chroot"
@@ -17,6 +18,7 @@ import (
 	"github.com/dotcloud/docker/networkdriver/portallocator"
 	"github.com/dotcloud/docker/pkg/graphdb"
 	"github.com/dotcloud/docker/pkg/sysinfo"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -133,14 +135,6 @@ func (runtime *Runtime) Register(container *Container) error {
 		return err
 	}
 
-	// Get the root filesystem from the driver
-	basefs, err := runtime.driver.Get(container.ID)
-	if err != nil {
-		return fmt.Errorf("Error getting container filesystem %s from driver %s: %s", container.ID, runtime.driver, err)
-	}
-	defer runtime.driver.Put(container.ID)
-	container.basefs = basefs
-
 	container.runtime = runtime
 
 	// Attach to stdout and stderr
@@ -336,7 +330,7 @@ func (runtime *Runtime) restore() error {
 }
 
 // Create creates a new container from the given configuration with a given name.
-func (runtime *Runtime) Create(config *Config, name string) (*Container, []string, error) {
+func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Container, []string, error) {
 	// Lookup image
 	img, err := runtime.repositories.LookupImage(config.Image)
 	if err != nil {
@@ -354,7 +348,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
 		return nil, nil, fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth)
 	}
 
-	checkDeprecatedExpose := func(config *Config) bool {
+	checkDeprecatedExpose := func(config *runconfig.Config) bool {
 		if config != nil {
 			if config.PortSpecs != nil {
 				for _, p := range config.PortSpecs {
@@ -373,14 +367,12 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
 	}
 
 	if img.Config != nil {
-		if err := MergeConfig(config, img.Config); err != nil {
+		if err := runconfig.Merge(config, img.Config); err != nil {
 			return nil, nil, err
 		}
 	}
 
-	if len(config.Entrypoint) != 0 && config.Cmd == nil {
-		config.Cmd = []string{}
-	} else if config.Cmd == nil || len(config.Cmd) == 0 {
+	if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 {
 		return nil, nil, fmt.Errorf("No command specified")
 	}
 
@@ -450,7 +442,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
 		Path:            entrypoint,
 		Args:            args, //FIXME: de-duplicate from config
 		Config:          config,
-		hostConfig:      &HostConfig{},
+		hostConfig:      &runconfig.HostConfig{},
 		Image:           img.ID, // Always use the resolved image id
 		NetworkSettings: &NetworkSettings{},
 		Name:            name,
@@ -527,7 +519,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
 
 // Commit creates a new filesystem image from the current state of a container.
 // The image can optionally be tagged into a repository
-func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
+func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*Image, error) {
 	// FIXME: freeze the container before copying it to avoid data corruption?
 	// FIXME: this shouldn't be in commands.
 	if err := container.Mount(); err != nil {
@@ -688,7 +680,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime
 		return nil, err
 	}
 
-	localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", VERSION))
+	localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.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.")

+ 24 - 10
server.go

@@ -6,9 +6,11 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker/archive"
 	"github.com/dotcloud/docker/auth"
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/pkg/graphdb"
 	"github.com/dotcloud/docker/registry"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -200,8 +202,20 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status {
 }
 
 func (srv *Server) Auth(job *engine.Job) engine.Status {
-	authConfig := &auth.AuthConfig{}
+	var (
+		err        error
+		authConfig = &auth.AuthConfig{}
+	)
+
 	job.GetenvJson("authConfig", authConfig)
+	// TODO: this is only done here because auth and registry need to be merged into one pkg
+	if addr := authConfig.ServerAddress; addr != "" && addr != auth.IndexServerAddress() {
+		addr, err = registry.ExpandAndVerifyRegistryUrl(addr)
+		if err != nil {
+			return job.Error(err)
+		}
+		authConfig.ServerAddress = addr
+	}
 	status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil))
 	if err != nil {
 		return job.Error(err)
@@ -649,7 +663,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status {
 	}
 	defer file.Body.Close()
 
-	config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo)
+	config, _, _, err := runconfig.Parse([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo)
 	if err != nil {
 		return job.Error(err)
 	}
@@ -815,7 +829,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status {
 	v.SetInt("NEventsListener", len(srv.events))
 	v.Set("KernelVersion", kernelVersion)
 	v.Set("IndexServerAddress", auth.IndexServerAddress())
-	v.Set("InitSha1", utils.INITSHA1)
+	v.Set("InitSha1", dockerversion.INITSHA1)
 	v.Set("InitPath", initPath)
 	if _, err := v.WriteTo(job.Stdout); err != nil {
 		return job.Error(err)
@@ -1030,7 +1044,7 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status {
 	if container == nil {
 		return job.Errorf("No such container: %s", name)
 	}
-	var config Config
+	var config runconfig.Config
 	if err := job.GetenvJson("config", &config); err != nil {
 		return job.Error(err)
 	}
@@ -1610,7 +1624,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status {
 	} else if len(job.Args) > 1 {
 		return job.Errorf("Usage: %s", job.Name)
 	}
-	config := ContainerConfigFromJob(job)
+	config := runconfig.ContainerConfigFromJob(job)
 	if config.Memory != 0 && config.Memory < 524288 {
 		return job.Errorf("Minimum memory limit allowed is 512k")
 	}
@@ -1976,7 +1990,7 @@ func (srv *Server) canDeleteImage(imgID string) error {
 	return nil
 }
 
-func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) {
+func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*Image, error) {
 
 	// Retrieve all images
 	images, err := srv.runtime.graph.Map()
@@ -2000,7 +2014,7 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error)
 		if err != nil {
 			return nil, err
 		}
-		if CompareConfig(&img.ContainerConfig, config) {
+		if runconfig.Compare(&img.ContainerConfig, config) {
 			if match == nil || match.Created.Before(img.Created) {
 				match = img
 			}
@@ -2009,7 +2023,7 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error)
 	return match, nil
 }
 
-func (srv *Server) RegisterLinks(container *Container, hostConfig *HostConfig) error {
+func (srv *Server) RegisterLinks(container *Container, hostConfig *runconfig.HostConfig) error {
 	runtime := srv.runtime
 
 	if hostConfig != nil && hostConfig.Links != nil {
@@ -2053,7 +2067,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status {
 	}
 	// If no environment was set, then no hostconfig was passed.
 	if len(job.Environ()) > 0 {
-		hostConfig := ContainerHostConfigFromJob(job)
+		hostConfig := runconfig.ContainerHostConfigFromJob(job)
 		// Validate the HostConfig binds. Make sure that:
 		// 1) the source of a bind mount isn't /
 		//         The bind mount "/:/foo" isn't allowed.
@@ -2297,7 +2311,7 @@ func (srv *Server) JobInspect(job *engine.Job) engine.Status {
 		}
 		object = &struct {
 			*Container
-			HostConfig *HostConfig
+			HostConfig *runconfig.HostConfig
 		}{container, container.hostConfig}
 	default:
 		return job.Errorf("Unknown kind: %s", kind)

+ 0 - 25
sorter.go

@@ -2,31 +2,6 @@ package docker
 
 import "sort"
 
-type portSorter struct {
-	ports []Port
-	by    func(i, j Port) bool
-}
-
-func (s *portSorter) Len() int {
-	return len(s.ports)
-}
-
-func (s *portSorter) Swap(i, j int) {
-	s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
-}
-
-func (s *portSorter) Less(i, j int) bool {
-	ip := s.ports[i]
-	jp := s.ports[j]
-
-	return s.by(ip, jp)
-}
-
-func sortPorts(ports []Port, predicate func(i, j Port) bool) {
-	s := &portSorter{ports, predicate}
-	sort.Sort(s)
-}
-
 type containerSorter struct {
 	containers []*Container
 	by         func(i, j *Container) bool

+ 6 - 291
utils.go

@@ -1,13 +1,12 @@
 package docker
 
 import (
-	"fmt"
 	"github.com/dotcloud/docker/archive"
+	"github.com/dotcloud/docker/nat"
 	"github.com/dotcloud/docker/pkg/namesgenerator"
+	"github.com/dotcloud/docker/runconfig"
 	"github.com/dotcloud/docker/utils"
 	"io"
-	"strconv"
-	"strings"
 	"sync/atomic"
 )
 
@@ -15,306 +14,22 @@ type Change struct {
 	archive.Change
 }
 
-// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
-// If OpenStdin is set, then it differs
-func CompareConfig(a, b *Config) bool {
-	if a == nil || b == nil ||
-		a.OpenStdin || b.OpenStdin {
-		return false
-	}
-	if a.AttachStdout != b.AttachStdout ||
-		a.AttachStderr != b.AttachStderr ||
-		a.User != b.User ||
-		a.Memory != b.Memory ||
-		a.MemorySwap != b.MemorySwap ||
-		a.CpuShares != b.CpuShares ||
-		a.OpenStdin != b.OpenStdin ||
-		a.Tty != b.Tty ||
-		a.VolumesFrom != b.VolumesFrom {
-		return false
-	}
-	if len(a.Cmd) != len(b.Cmd) ||
-		len(a.Dns) != len(b.Dns) ||
-		len(a.Env) != len(b.Env) ||
-		len(a.PortSpecs) != len(b.PortSpecs) ||
-		len(a.ExposedPorts) != len(b.ExposedPorts) ||
-		len(a.Entrypoint) != len(b.Entrypoint) ||
-		len(a.Volumes) != len(b.Volumes) {
-		return false
-	}
-
-	for i := 0; i < len(a.Cmd); i++ {
-		if a.Cmd[i] != b.Cmd[i] {
-			return false
-		}
-	}
-	for i := 0; i < len(a.Dns); i++ {
-		if a.Dns[i] != b.Dns[i] {
-			return false
-		}
-	}
-	for i := 0; i < len(a.Env); i++ {
-		if a.Env[i] != b.Env[i] {
-			return false
-		}
-	}
-	for i := 0; i < len(a.PortSpecs); i++ {
-		if a.PortSpecs[i] != b.PortSpecs[i] {
-			return false
-		}
-	}
-	for k := range a.ExposedPorts {
-		if _, exists := b.ExposedPorts[k]; !exists {
-			return false
-		}
-	}
-	for i := 0; i < len(a.Entrypoint); i++ {
-		if a.Entrypoint[i] != b.Entrypoint[i] {
-			return false
-		}
-	}
-	for key := range a.Volumes {
-		if _, exists := b.Volumes[key]; !exists {
-			return false
-		}
-	}
-	return true
-}
-
-func MergeConfig(userConf, imageConf *Config) error {
-	if userConf.User == "" {
-		userConf.User = imageConf.User
-	}
-	if userConf.Memory == 0 {
-		userConf.Memory = imageConf.Memory
-	}
-	if userConf.MemorySwap == 0 {
-		userConf.MemorySwap = imageConf.MemorySwap
-	}
-	if userConf.CpuShares == 0 {
-		userConf.CpuShares = imageConf.CpuShares
-	}
-	if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
-		userConf.ExposedPorts = imageConf.ExposedPorts
-	} else if imageConf.ExposedPorts != nil {
-		if userConf.ExposedPorts == nil {
-			userConf.ExposedPorts = make(map[Port]struct{})
-		}
-		for port := range imageConf.ExposedPorts {
-			if _, exists := userConf.ExposedPorts[port]; !exists {
-				userConf.ExposedPorts[port] = struct{}{}
-			}
-		}
-	}
-
-	if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
-		if userConf.ExposedPorts == nil {
-			userConf.ExposedPorts = make(map[Port]struct{})
-		}
-		ports, _, err := parsePortSpecs(userConf.PortSpecs)
-		if err != nil {
-			return err
-		}
-		for port := range ports {
-			if _, exists := userConf.ExposedPorts[port]; !exists {
-				userConf.ExposedPorts[port] = struct{}{}
-			}
-		}
-		userConf.PortSpecs = nil
-	}
-	if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 {
-		utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", "))
-		if userConf.ExposedPorts == nil {
-			userConf.ExposedPorts = make(map[Port]struct{})
-		}
-
-		ports, _, err := parsePortSpecs(imageConf.PortSpecs)
-		if err != nil {
-			return err
-		}
-		for port := range ports {
-			if _, exists := userConf.ExposedPorts[port]; !exists {
-				userConf.ExposedPorts[port] = struct{}{}
-			}
-		}
-	}
-	if !userConf.Tty {
-		userConf.Tty = imageConf.Tty
-	}
-	if !userConf.OpenStdin {
-		userConf.OpenStdin = imageConf.OpenStdin
-	}
-	if !userConf.StdinOnce {
-		userConf.StdinOnce = imageConf.StdinOnce
-	}
-	if userConf.Env == nil || len(userConf.Env) == 0 {
-		userConf.Env = imageConf.Env
-	} else {
-		for _, imageEnv := range imageConf.Env {
-			found := false
-			imageEnvKey := strings.Split(imageEnv, "=")[0]
-			for _, userEnv := range userConf.Env {
-				userEnvKey := strings.Split(userEnv, "=")[0]
-				if imageEnvKey == userEnvKey {
-					found = true
-				}
-			}
-			if !found {
-				userConf.Env = append(userConf.Env, imageEnv)
-			}
-		}
-	}
-	if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
-		userConf.Cmd = imageConf.Cmd
-	}
-	if userConf.Dns == nil || len(userConf.Dns) == 0 {
-		userConf.Dns = imageConf.Dns
-	} else {
-		//duplicates aren't an issue here
-		userConf.Dns = append(userConf.Dns, imageConf.Dns...)
-	}
-	if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
-		userConf.Entrypoint = imageConf.Entrypoint
-	}
-	if userConf.WorkingDir == "" {
-		userConf.WorkingDir = imageConf.WorkingDir
-	}
-	if userConf.VolumesFrom == "" {
-		userConf.VolumesFrom = imageConf.VolumesFrom
-	}
-	if userConf.Volumes == nil || len(userConf.Volumes) == 0 {
-		userConf.Volumes = imageConf.Volumes
-	} else {
-		for k, v := range imageConf.Volumes {
-			userConf.Volumes[k] = v
-		}
-	}
-	return nil
-}
-
-func parseLxcConfOpts(opts ListOpts) ([]KeyValuePair, error) {
-	out := make([]KeyValuePair, opts.Len())
-	for i, o := range opts.GetAll() {
-		k, v, err := parseLxcOpt(o)
-		if err != nil {
-			return nil, err
-		}
-		out[i] = KeyValuePair{Key: k, Value: v}
-	}
-	return out, nil
-}
-
-func parseLxcOpt(opt string) (string, string, error) {
-	parts := strings.SplitN(opt, "=", 2)
-	if len(parts) != 2 {
-		return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt)
-	}
-	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) {
-	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]
-		}
-		if !strings.Contains(rawPort, ":") {
-			rawPort = fmt.Sprintf("::%s", rawPort)
-		} else if len(strings.Split(rawPort, ":")) == 2 {
-			rawPort = fmt.Sprintf(":%s", rawPort)
-		}
-
-		parts, err := utils.PartParser(PortSpecTemplate, rawPort)
-		if err != nil {
-			return nil, nil, err
-		}
-
-		var (
-			containerPort = parts["containerPort"]
-			rawIp         = parts["ip"]
-			hostPort      = parts["hostPort"]
-		)
-
-		if containerPort == "" {
-			return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
-		}
-		if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil {
-			return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
-		}
-		if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil {
-			return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
-		}
-
-		port := NewPort(proto, containerPort)
-		if _, exists := exposedPorts[port]; !exists {
-			exposedPorts[port] = struct{}{}
-		}
-
-		binding := PortBinding{
-			HostIp:   rawIp,
-			HostPort: hostPort,
-		}
-		bslice, exists := bindings[port]
-		if !exists {
-			bslice = []PortBinding{}
-		}
-		bindings[port] = append(bslice, binding)
-	}
-	return exposedPorts, bindings, nil
-}
-
-// Splits a port in the format of port/proto
-func splitProtoPort(rawPort string) (string, string) {
-	parts := strings.Split(rawPort, "/")
-	l := len(parts)
-	if l == 0 {
-		return "", ""
-	}
-	if l == 1 {
-		return "tcp", rawPort
-	}
-	return parts[0], parts[1]
-}
-
-func parsePort(rawPort string) (int, error) {
-	port, err := strconv.ParseUint(rawPort, 10, 16)
-	if err != nil {
-		return 0, err
-	}
-	return int(port), nil
-}
-
-func migratePortMappings(config *Config, hostConfig *HostConfig) error {
+func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error {
 	if config.PortSpecs != nil {
-		ports, bindings, err := parsePortSpecs(config.PortSpecs)
+		ports, bindings, err := nat.ParsePortSpecs(config.PortSpecs)
 		if err != nil {
 			return err
 		}
 		config.PortSpecs = nil
 		if len(bindings) > 0 {
 			if hostConfig == nil {
-				hostConfig = &HostConfig{}
+				hostConfig = &runconfig.HostConfig{}
 			}
 			hostConfig.PortBindings = bindings
 		}
 
 		if config.ExposedPorts == nil {
-			config.ExposedPorts = make(map[Port]struct{}, len(ports))
+			config.ExposedPorts = make(nat.PortSet, len(ports))
 		}
 		for k, v := range ports {
 			config.ExposedPorts[k] = v

+ 4 - 40
utils/utils.go

@@ -8,6 +8,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/dotcloud/docker/dockerversion"
 	"index/suffixarray"
 	"io"
 	"io/ioutil"
@@ -23,12 +24,6 @@ import (
 	"time"
 )
 
-var (
-	IAMSTATIC bool   // whether or not Docker itself was compiled statically via ./hack/make.sh binary
-	INITSHA1  string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary
-	INITPATH  string // custom location to search for a valid dockerinit binary (available for packagers as a last resort escape hatch)
-)
-
 // A common interface to access the Fatal method of
 // both testing.B and testing.T.
 type Fataler interface {
@@ -201,7 +196,7 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and
 	if target == "" {
 		return false
 	}
-	if IAMSTATIC {
+	if dockerversion.IAMSTATIC {
 		if selfPath == "" {
 			return false
 		}
@@ -218,7 +213,7 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and
 		}
 		return os.SameFile(targetFileInfo, selfPathFileInfo)
 	}
-	return INITSHA1 != "" && dockerInitSha1(target) == INITSHA1
+	return dockerversion.INITSHA1 != "" && dockerInitSha1(target) == dockerversion.INITSHA1
 }
 
 // Figure out the path of our dockerinit (which may be SelfPath())
@@ -230,7 +225,7 @@ func DockerInitPath(localCopy string) string {
 	}
 	var possibleInits = []string{
 		localCopy,
-		INITPATH,
+		dockerversion.INITPATH,
 		filepath.Join(filepath.Dir(selfPath), "dockerinit"),
 
 		// FHS 3.0 Draft: "/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."
@@ -836,37 +831,6 @@ func ParseRepositoryTag(repos string) (string, string) {
 	return repos, ""
 }
 
-type User struct {
-	Uid      string // user id
-	Gid      string // primary group id
-	Username string
-	Name     string
-	HomeDir  string
-}
-
-// UserLookup check if the given username or uid is present in /etc/passwd
-// and returns the user struct.
-// If the username is not found, an error is returned.
-func UserLookup(uid string) (*User, error) {
-	file, err := ioutil.ReadFile("/etc/passwd")
-	if err != nil {
-		return nil, err
-	}
-	for _, line := range strings.Split(string(file), "\n") {
-		data := strings.Split(line, ":")
-		if len(data) > 5 && (data[0] == uid || data[2] == uid) {
-			return &User{
-				Uid:      data[2],
-				Gid:      data[3],
-				Username: data[0],
-				Name:     data[4],
-				HomeDir:  data[5],
-			}, nil
-		}
-	}
-	return nil, fmt.Errorf("User not found in /etc/passwd")
-}
-
 // An StatusError reports an unsuccessful exit by a command.
 type StatusError struct {
 	Status     string

+ 3 - 2
version.go

@@ -1,6 +1,7 @@
 package docker
 
 import (
+	"github.com/dotcloud/docker/dockerversion"
 	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/utils"
 	"runtime"
@@ -22,8 +23,8 @@ func jobVersion(job *engine.Job) engine.Status {
 // environment.
 func dockerVersion() *engine.Env {
 	v := &engine.Env{}
-	v.Set("Version", VERSION)
-	v.Set("GitCommit", GITCOMMIT)
+	v.Set("Version", dockerversion.VERSION)
+	v.Set("GitCommit", dockerversion.GITCOMMIT)
 	v.Set("GoVersion", runtime.Version())
 	v.Set("Os", runtime.GOOS)
 	v.Set("Arch", runtime.GOARCH)