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
  ...
This commit is contained in:
Adam Miller 2014-02-12 12:04:02 -06:00
commit e36d4d8821
86 changed files with 2134 additions and 1282 deletions

View File

@ -7,8 +7,10 @@ feels wrong or incomplete.
## Reporting Issues ## Reporting Issues
When reporting [issues](https://github.com/dotcloud/docker/issues) When reporting [issues](https://github.com/dotcloud/docker/issues)
on GitHub please include your host OS ( Ubuntu 12.04, Fedora 19, etc... ) 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. 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. This information will help us review and fix your issue faster.
## Build Environment ## 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 Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address. 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 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 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 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 regenerated occasionally from the git commit history, so a mismatch may result
in your changes being overwritten. 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 ### Sign your work
The sign-off is a simple line at the end of the explanation for the 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 1: learn the component inside out
* Step 2: make yourself useful by contributing code, bugfixes, support etc. * Step 2: make yourself useful by contributing code, bugfixes, support etc.
* Step 3: volunteer on the irc channel (#docker@freenode) * 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. 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! You don't have to be a maintainer to make a difference on the project!

View File

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

View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"compress/bzip2" "compress/bzip2"
"compress/gzip" "compress/gzip"
"errors"
"fmt" "fmt"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
@ -17,14 +18,18 @@ import (
"syscall" "syscall"
) )
type Archive io.Reader type (
Archive io.Reader
Compression int
TarOptions struct {
Includes []string
Compression Compression
}
)
type Compression int var (
ErrNotImplemented = errors.New("Function not implemented")
type TarOptions struct { )
Includes []string
Compression Compression
}
const ( const (
Uncompressed Compression = iota 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) 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 return err
} }
// There is no LChmod, so ignore mode for symlink. Also, this // There is no LChmod, so ignore mode for symlink. Also, this
// must happen after chown, as that can modify the file mode // must happen after chown, as that can modify the file mode
if hdr.Typeflag != tar.TypeSymlink { 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 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)} ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
if hdr.Typeflag != tar.TypeSymlink { if hdr.Typeflag != tar.TypeSymlink {
if err := syscall.UtimesNano(path, ts); err != nil { if err := UtimesNano(path, ts); err != nil {
return err return err
} }
} else { } else {

View File

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

View File

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

View File

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

View File

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

View File

@ -11,11 +11,13 @@ import (
"github.com/dotcloud/docker/api" "github.com/dotcloud/docker/api"
"github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/dockerversion"
"github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/nat"
flag "github.com/dotcloud/docker/pkg/mflag" flag "github.com/dotcloud/docker/pkg/mflag"
"github.com/dotcloud/docker/pkg/sysinfo"
"github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/pkg/term"
"github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "io/ioutil"
@ -38,11 +40,6 @@ import (
"time" "time"
) )
var (
GITCOMMIT string
VERSION string
)
var ( var (
ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?") 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() serverAddress := auth.IndexServerAddress()
if len(cmd.Args()) > 0 { if len(cmd.Args()) > 0 {
serverAddress, err = registry.ExpandAndVerifyRegistryUrl(cmd.Arg(0)) serverAddress = cmd.Arg(0)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Login against server at %s\n", serverAddress)
} }
promptDefault := func(prompt string, configDefault string) { promptDefault := func(prompt string, configDefault string) {
@ -392,12 +385,12 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
cmd.Usage() cmd.Usage()
return nil return nil
} }
if VERSION != "" { if dockerversion.VERSION != "" {
fmt.Fprintf(cli.out, "Client version: %s\n", VERSION) fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
} }
fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version())
if GITCOMMIT != "" { if dockerversion.GITCOMMIT != "" {
fmt.Fprintf(cli.out, "Git commit (client): %s\n", GITCOMMIT) fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT)
} }
body, _, err := readBody(cli.call("GET", "/version", nil, false)) body, _, err := readBody(cli.call("GET", "/version", nil, false))
@ -422,7 +415,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
release := utils.GetReleaseVersion() release := utils.GetReleaseVersion()
if release != "" { if release != "" {
fmt.Fprintf(cli.out, "Last stable version: %s", 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, ", please update docker")
} }
fmt.Fprintf(cli.out, "\n") fmt.Fprintf(cli.out, "\n")
@ -803,7 +796,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
return err 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 { for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) 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("comment", *flComment)
v.Set("author", *flAuthor) v.Set("author", *flAuthor)
var ( var (
config *Config config *runconfig.Config
env engine.Env env engine.Env
) )
if *flConfig != "" { if *flConfig != "" {
config = &Config{} config = &runconfig.Config{}
if err := json.Unmarshal([]byte(*flConfig), config); err != nil { if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
return err return err
} }
@ -1749,210 +1742,9 @@ func (cli *DockerCli) CmdTag(args ...string) error {
return nil 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 { 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 { if err != nil {
return err 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) stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
//if image not found try to pull it //if image not found try to pull it
if statusCode == 404 { if statusCode == 404 {
_, tag := utils.ParseRepositoryTag(config.Image) fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
if tag == "" {
tag = DEFAULTTAG
}
fmt.Fprintf(cli.err, "Unable to find image '%s' (tag: %s) locally\n", config.Image, tag)
v := url.Values{} v := url.Values{}
repos, tag := utils.ParseRepositoryTag(config.Image) 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 req.Host = cli.addr
if data != nil { if data != nil {
req.Header.Set("Content-Type", "application/json") 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 { if err != nil {
return err return err
} }
req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
req.Host = cli.addr req.Host = cli.addr
if method == "POST" { if method == "POST" {
req.Header.Set("Content-Type", "plain/text") 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 { if err != nil {
return err 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.Header.Set("Content-Type", "plain/text")
req.Host = cli.addr req.Host = cli.addr

View File

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

View File

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

View File

@ -8,8 +8,10 @@ import (
"github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/execdriver"
"github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/nat"
"github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/mount"
"github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/pkg/term"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"github.com/kr/pty" "github.com/kr/pty"
"io" "io"
@ -41,7 +43,7 @@ type Container struct {
Path string Path string
Args []string Args []string
Config *Config Config *runconfig.Config
State State State State
Image string Image string
@ -67,109 +69,11 @@ type Container struct {
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. // Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
// Easier than migrating older container configs :) // Easier than migrating older container configs :)
VolumesRW map[string]bool VolumesRW map[string]bool
hostConfig *HostConfig hostConfig *runconfig.HostConfig
activeLinks map[string]*Link 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 { type BindMap struct {
SrcPath string SrcPath string
DstPath string DstPath string
@ -177,50 +81,11 @@ type BindMap struct {
} }
var ( var (
ErrContainerStart = errors.New("The container failed to start. Unknown error") ErrContainerStart = errors.New("The container failed to start. Unknown error")
ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.") 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")
) )
type KeyValuePair struct { // FIXME: move deprecated port stuff to nat to clean up the core.
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))
}
type PortMapping map[string]string // Deprecated type PortMapping map[string]string // Deprecated
type NetworkSettings struct { type NetworkSettings struct {
@ -229,13 +94,13 @@ type NetworkSettings struct {
Gateway string Gateway string
Bridge string Bridge string
PortMapping map[string]PortMapping // Deprecated PortMapping map[string]PortMapping // Deprecated
Ports map[Port][]PortBinding Ports nat.PortMap
} }
func (settings *NetworkSettings) PortMappingAPI() *engine.Table { func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
var outs = engine.NewTable("", 0) var outs = engine.NewTable("", 0)
for port, bindings := range settings.Ports { for port, bindings := range settings.Ports {
p, _ := parsePort(port.Port()) p, _ := nat.ParsePort(port.Port())
if len(bindings) == 0 { if len(bindings) == 0 {
out := &engine.Env{} out := &engine.Env{}
out.SetInt("PublicPort", p) out.SetInt("PublicPort", p)
@ -245,7 +110,7 @@ func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
} }
for _, binding := range bindings { for _, binding := range bindings {
out := &engine.Env{} out := &engine.Env{}
h, _ := parsePort(binding.HostPort) h, _ := nat.ParsePort(binding.HostPort)
out.SetInt("PrivatePort", p) out.SetInt("PrivatePort", p)
out.SetInt("PublicPort", h) out.SetInt("PublicPort", h)
out.Set("Type", port.Proto()) out.Set("Type", port.Proto())
@ -322,7 +187,7 @@ func (container *Container) ToDisk() (err error) {
} }
func (container *Container) readHostConfig() error { func (container *Container) readHostConfig() error {
container.hostConfig = &HostConfig{} container.hostConfig = &runconfig.HostConfig{}
// If the hostconfig file does not exist, do not read it. // If the hostconfig file does not exist, do not read it.
// (We still have to initialize container.hostConfig, // (We still have to initialize container.hostConfig,
// but that's OK, since we just did that above.) // but that's OK, since we just did that above.)
@ -1152,8 +1017,8 @@ func (container *Container) allocateNetwork() error {
} }
var ( var (
portSpecs = make(map[Port]struct{}) portSpecs = make(nat.PortSet)
bindings = make(map[Port][]PortBinding) bindings = make(nat.PortMap)
) )
if !container.State.IsGhost() { if !container.State.IsGhost() {
@ -1177,7 +1042,7 @@ func (container *Container) allocateNetwork() error {
for port := range portSpecs { for port := range portSpecs {
binding := bindings[port] binding := bindings[port]
if container.hostConfig.PublishAllPorts && len(binding) == 0 { if container.hostConfig.PublishAllPorts && len(binding) == 0 {
binding = append(binding, PortBinding{}) binding = append(binding, nat.PortBinding{})
} }
for i := 0; i < len(binding); i++ { 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 // 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] _, exists := container.Config.ExposedPorts[p]
return exists return exists
} }

View File

@ -1,28 +1,12 @@
package docker package docker
import ( import (
"github.com/dotcloud/docker/nat"
"testing" "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) { 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -64,7 +48,7 @@ func TestParseNetworkOptsPrivateOnly(t *testing.T) {
} }
func TestParseNetworkOptsPublic(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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -106,7 +90,7 @@ func TestParseNetworkOptsPublic(t *testing.T) {
} }
func TestParseNetworkOptsUdp(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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -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>

View File

@ -12,15 +12,37 @@
<array> <array>
<dict> <dict>
<key>match</key> <key>match</key>
<string>^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s</string> <string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR)\s</string>
<key>name</key> <key>captures</key>
<string>keyword.control.dockerfile</string> <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>
<dict> <dict>
<key>match</key> <key>match</key>
<string>^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s</string> <string>^\s*(ONBUILD\s+)?(CMD|ENTRYPOINT)\s</string>
<key>name</key> <key>captures</key>
<string>keyword.operator.dockerfile</string> <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>
<dict> <dict>
<key>begin</key> <key>begin</key>
@ -39,6 +61,23 @@
</dict> </dict>
</array> </array>
</dict> </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> <dict>
<key>match</key> <key>match</key>
<string>^\s*#.*$</string> <string>^\s*#.*$</string>

View File

@ -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>

View File

@ -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*#.*$
...

View File

@ -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. enjoy.

View File

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

View File

@ -8,17 +8,14 @@ import (
"github.com/dotcloud/docker" "github.com/dotcloud/docker"
"github.com/dotcloud/docker/api" "github.com/dotcloud/docker/api"
"github.com/dotcloud/docker/dockerversion"
"github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/engine"
flag "github.com/dotcloud/docker/pkg/mflag" flag "github.com/dotcloud/docker/pkg/mflag"
"github.com/dotcloud/docker/pkg/opts"
"github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/sysinit"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
) )
var (
GITCOMMIT string
VERSION string
)
func main() { func main() {
if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
// Running in init mode // 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") 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") 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") 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") 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") 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") 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") 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") 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") 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") flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
@ -71,8 +68,6 @@ func main() {
if *flDebug { if *flDebug {
os.Setenv("DEBUG", "1") os.Setenv("DEBUG", "1")
} }
docker.GITCOMMIT = GITCOMMIT
docker.VERSION = VERSION
if *flDaemon { if *flDaemon {
if flag.NArg() != 0 { if flag.NArg() != 0 {
flag.Usage() flag.Usage()
@ -104,7 +99,7 @@ func main() {
job = eng.Job("serveapi", flHosts.GetAll()...) job = eng.Job("serveapi", flHosts.GetAll()...)
job.SetenvBool("Logging", true) job.SetenvBool("Logging", true)
job.SetenvBool("EnableCors", *flEnableCors) job.SetenvBool("EnableCors", *flEnableCors)
job.Setenv("Version", VERSION) job.Setenv("Version", dockerversion.VERSION)
if err := job.Run(); err != nil { if err := job.Run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -126,5 +121,5 @@ func main() {
} }
func showVersion() { func showVersion() {
fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT) fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT)
} }

View File

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

View File

@ -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)
)

View File

@ -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"]

View File

@ -9,152 +9,109 @@ PostgreSQL Service
.. include:: example_header.inc .. 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 Installing PostgreSQL on Docker
------------------------------- -------------------------------
Run an interactive shell in a Docker container. 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
sudo docker run -i -t ubuntu /bin/bash Start by creating a new Dockerfile:
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
.. code-block:: bash
apt-get -y install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
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.
.. code-block:: bash
su postgres -c "createuser -P -d -r -s docker"
Create a test database also named ``docker`` owned by previously created ``docker``
role.
.. code-block:: bash
su postgres -c "createdb -O docker docker"
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:
.. 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:
.. code-block:: bash
listen_addresses='*'
.. note:: .. note::
This PostgreSQL setup is for development only purposes. Refer This PostgreSQL setup is for development only purposes. Refer
to PostgreSQL documentation how to fine-tune these settings so that it to the PostgreSQL documentation to fine-tune these settings so that it
is secure enough. is suitably secure.
Exit. .. literalinclude:: postgresql_service.Dockerfile
Build an image from the Dockerfile assign it a name.
.. code-block:: bash .. code-block:: bash
exit $ sudo docker build -t eg_postgresql .
Create an image from our container and assign it a name. The ``<container_id>`` And run the PostgreSQL server container (in the foreground):
is in the Bash prompt; you can also locate it using ``docker ps -a``.
.. code-block:: bash .. code-block:: bash
sudo docker commit <container_id> <your username>/postgresql $ sudo docker run -rm -P -name pg_test eg_postgresql
Finally, run the PostgreSQL server via ``docker``. 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).
.. note:: The ``-rm`` removes the container and its image when the container
exists successfully.
Using container linking
^^^^^^^^^^^^^^^^^^^^^^^
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 .. code-block:: bash
CONTAINER=$(sudo docker run -d -p 5432 \ $ sudo docker run -rm -t -i -link pg_test:pg eg_postgresql bash
-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 postgres@7ef98b1b7243:/$ psql -h $PG_PORT_5432_TCP_ADDR -p $PG_PORT_5432_TCP_PORT -d docker -U docker --password
postgresql client installed on the machine. For ubuntu, use something
like ``sudo apt-get install postgresql-client``). Connecting from your host system
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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 .. code-block:: bash
CONTAINER_IP=$(sudo docker inspect -format='{{.NetworkSettings.IPAddress}}' $CONTAINER) $ docker ps
psql -h $CONTAINER_IP -p 5432 -d docker -U docker -W 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
As before, create roles or databases if needed. Testing the database
^^^^^^^^^^^^^^^^^^^^
Once you have authenticated and have a ``docker =#`` prompt, you can
create a table and populate it.
.. code-block:: bash .. code-block:: bash
psql (9.3.1) psql (9.3.1)
Type "help" for help. Type "help" for help.
docker=# CREATE DATABASE foo OWNER=docker; docker=# CREATE TABLE cities (
CREATE DATABASE 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)
Additionally, publish your newly created image on the Docker Index. Using the container volumes
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can use the defined volumes to inspect the PostgreSQL log files and to backup your
configuration and data:
.. code-block:: bash .. code-block:: bash
sudo docker login docker run -rm --volumes-from pg_test -t -i busybox sh
Username: <your username>
[...]
.. code-block:: bash / # 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
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
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
From now on, just type ``docker run <your username>/postgresql`` and
PostgreSQL should automatically start.

View File

@ -175,6 +175,7 @@ Linux:
- Gentoo - Gentoo
- ArchLinux - ArchLinux
- openSUSE 12.3+ - openSUSE 12.3+
- CRUX 3.0+
Cloud: Cloud:
@ -182,6 +183,12 @@ Cloud:
- Google Compute Engine - Google Compute Engine
- Rackspace - 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? Can I help by adding some questions and answers?
................................................ ................................................

View File

@ -1,5 +1,5 @@
:title: Installation on Amazon EC2 :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 :keywords: amazon ec2, virtualization, cloud, docker, documentation, installation
Amazon EC2 Amazon EC2

View File

@ -1,5 +1,5 @@
:title: Installation on Arch Linux :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 :keywords: arch linux, virtualization, docker, documentation, installation
.. _arch_linux: .. _arch_linux:

View File

@ -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``.

View File

@ -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. :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 :keywords: Docker, Docker documentation, Fedora, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux

View File

@ -1,5 +1,5 @@
:title: Installation on FrugalWare :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 :keywords: frugalware linux, virtualization, docker, documentation, installation
.. _frugalware: .. _frugalware:

View File

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

View File

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

View File

@ -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. :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 :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 # Enter the installation directory
cd ~/bin cd ~/bin
# Get the file # Get the file
curl https://raw.github.com/steeve/boot2docker/master/boot2docker > boot2docker curl https://raw.github.com/steeve/boot2docker/master/boot2docker > boot2docker
# Mark it executable # Mark it executable
chmod +x boot2docker chmod +x boot2docker
@ -66,14 +66,14 @@ Run the following commands to get it downloaded and set up:
.. code-block:: bash .. code-block:: bash
# Get the file # 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 # Mark it executable
chmod +x docker chmod +x docker
# Set the environment variable for the docker daemon # Set the environment variable for the docker daemon
export DOCKER_HOST=tcp:// export DOCKER_HOST=tcp://
# Copy the executable file # Copy the executable file
sudo cp docker /usr/local/bin/ sudo cp docker /usr/local/bin/
@ -94,7 +94,7 @@ Inside the ``~/bin`` directory, run the following commands:
# Run the VM (the docker daemon) # Run the VM (the docker daemon)
./boot2docker up ./boot2docker up
# To see all available commands: # To see all available commands:
./boot2docker ./boot2docker
@ -116,6 +116,21 @@ client just like any other application.
# Git commit (server): c348c04 # Git commit (server): c348c04
# Go version (server): go1.2 # 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 SSH-ing The VM
-------------- --------------
@ -147,6 +162,18 @@ If SSH complains about keys:
ssh-keygen -R '[localhost]:2022' 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: About the way Docker works on Mac OS X:
--------------------------------------- ---------------------------------------

View File

@ -1,5 +1,5 @@
:title: Installation on openSUSE :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 :keywords: openSUSE, virtualbox, docker, documentation, installation
.. _openSUSE: .. _openSUSE:

View File

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

View File

@ -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. :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 :keywords: Docker, Docker documentation, requirements, linux, rhel, centos

View File

@ -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. :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 :keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux

View File

@ -1,11 +1,11 @@
:title: Requirements and Installation on Windows :title: Installation on Windows
:description: Docker's tutorial to run docker 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 :keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
.. _windows: .. _windows:
Installing Docker on Windows Windows
============================ =======
Docker can run on Windows using a VM like VirtualBox. You then run Docker can run on Windows using a VM like VirtualBox. You then run
Linux within the VM. Linux within the VM.

View File

@ -26,6 +26,9 @@ and we will add the libraries here.
+----------------------+----------------+--------------------------------------------+----------+ +----------------------+----------------+--------------------------------------------+----------+
| Javascript | docker-js | https://github.com/dgoujard/docker-js | Active | | 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 | | Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | Active |
| **WebUI** | | | | | **WebUI** | | | |
+----------------------+----------------+--------------------------------------------+----------+ +----------------------+----------------+--------------------------------------------+----------+

View File

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

View File

@ -102,12 +102,17 @@ the ``-H`` flag for the client.
docker ps docker ps
# both are equal # both are equal
To run the daemon with `systemd socket activation <http://0pointer.de/blog/projects/socket-activation.html>`_, use ``docker -d -H fd://``. 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``. 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. 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/>`_. 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: .. _cli_attach:
``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 The ``docker run`` command can be used in combination with ``docker commit`` to
:ref:`change the command that a container runs <cli_commit_examples>`. :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) Known Issues (run -volumes-from)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

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

View File

@ -31,6 +31,15 @@ container, Docker provide ways to bind the container port to an
interface of the host system. To simplify communication between interface of the host system. To simplify communication between
containers, Docker provides the linking mechanism. 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 Binding a port to a host interface
----------------------------------- -----------------------------------

View File

@ -279,7 +279,8 @@ func (i *info) IsRunning() bool {
output, err := i.driver.getInfo(i.ID) output, err := i.driver.getInfo(i.ID)
if err != nil { 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") { if strings.Contains(string(output), "RUNNING") {
running = true running = true

View File

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

View File

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

View File

@ -3,7 +3,9 @@ package docker
import ( import (
"fmt" "fmt"
"github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/dockerversion"
"github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "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. // 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{ img := &Image{
ID: GenerateID(), ID: GenerateID(),
Comment: comment, Comment: comment,
Created: time.Now().UTC(), Created: time.Now().UTC(),
DockerVersion: VERSION, DockerVersion: dockerversion.VERSION,
Author: author, Author: author,
Config: config, Config: config,
Architecture: runtime.GOARCH, Architecture: runtime.GOARCH,

View File

@ -12,6 +12,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
) )
@ -29,6 +30,15 @@ type DevInfo struct {
TransactionId uint64 `json:"transaction_id"` TransactionId uint64 `json:"transaction_id"`
Initialized bool `json:"initialized"` Initialized bool `json:"initialized"`
devices *DeviceSet `json:"-"` 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 { type MetaData struct {
@ -43,7 +53,7 @@ type DeviceSet struct {
TransactionId uint64 TransactionId uint64
NewTransactionId uint64 NewTransactionId uint64
nextFreeDevice int nextFreeDevice int
activeMounts map[string]int sawBusy bool
} }
type DiskUsage struct { type DiskUsage struct {
@ -69,6 +79,14 @@ type DevStatus struct {
HighestMappedSector uint64 HighestMappedSector uint64
} }
type UnmountMode int
const (
UnmountRegular UnmountMode = iota
UnmountFloat
UnmountSink
)
func getDevName(name string) string { func getDevName(name string) string {
return "/dev/mapper/" + name return "/dev/mapper/" + name
} }
@ -290,7 +308,7 @@ func (devices *DeviceSet) setupBaseImage() error {
if oldInfo != nil && !oldInfo.Initialized { if oldInfo != nil && !oldInfo.Initialized {
utils.Debugf("Removing uninitialized base image") 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) utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
@ -355,6 +373,10 @@ func (devices *DeviceSet) log(level int, file string, line int, dmError int, mes
return // Ignore _LOG_DEBUG 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) 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 return nil
} }
func (devices *DeviceSet) removeDevice(hash string) error { func (devices *DeviceSet) deleteDevice(hash string) error {
info := devices.Devices[hash] info := devices.Devices[hash]
if info == nil { if info == nil {
return fmt.Errorf("hash %s doesn't exists", hash) return fmt.Errorf("hash %s doesn't exists", hash)
@ -579,7 +601,7 @@ func (devices *DeviceSet) removeDevice(hash string) error {
devinfo, _ := getInfo(info.Name()) devinfo, _ := getInfo(info.Name())
if devinfo != nil && devinfo.Exists != 0 { 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) utils.Debugf("Error removing device: %s\n", err)
return err return err
} }
@ -610,11 +632,11 @@ func (devices *DeviceSet) removeDevice(hash string) error {
return nil return nil
} }
func (devices *DeviceSet) RemoveDevice(hash string) error { func (devices *DeviceSet) DeleteDevice(hash string) error {
devices.Lock() devices.Lock()
defer devices.Unlock() defer devices.Unlock()
return devices.removeDevice(hash) return devices.deleteDevice(hash)
} }
func (devices *DeviceSet) deactivateDevice(hash string) error { func (devices *DeviceSet) deactivateDevice(hash string) error {
@ -632,28 +654,50 @@ func (devices *DeviceSet) deactivateDevice(hash string) error {
return err return err
} }
if devinfo.Exists != 0 { if devinfo.Exists != 0 {
if err := removeDevice(devname); err != nil { if err := devices.removeDeviceAndWait(devname); err != nil {
utils.Debugf("\n--->Err: %s\n", err) utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
if err := devices.waitRemove(hash); err != nil {
return err
}
} }
return nil return nil
} }
// waitRemove blocks until either: // Issues the underlying dm remove operation and then waits
// a) the device registered at <device_set_prefix>-<hash> is removed, // for it to finish.
// or b) the 1 second timeout expires. func (devices *DeviceSet) removeDeviceAndWait(devname string) error {
func (devices *DeviceSet) waitRemove(hash string) error { var err error
utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
defer utils.Debugf("[deviceset %s] waitRemove(%) END", devices.devicePrefix, hash) for i := 0; i < 10; i++ {
devname, err := devices.byHash(hash) 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 { if err != nil {
return err 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(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 i := 0
for ; i < 1000; i += 1 { for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname) devinfo, err := getInfo(devname)
@ -728,13 +772,12 @@ func (devices *DeviceSet) Shutdown() error {
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
for path, count := range devices.activeMounts { for _, info := range devices.Devices {
for i := count; i > 0; i-- { if info.mountCount > 0 {
if err := sysUnmount(path, 0); err != nil { if err := sysUnmount(info.mountPath, 0); err != nil {
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err) utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err)
} }
} }
delete(devices.activeMounts, path)
} }
for _, d := range devices.Devices { for _, d := range devices.Devices {
@ -756,22 +799,35 @@ func (devices *DeviceSet) Shutdown() error {
return nil return nil
} }
func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { func (devices *DeviceSet) MountDevice(hash, path string) error {
devices.Lock() devices.Lock()
defer devices.Unlock() defer devices.Unlock()
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", 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)
}
if info.floating {
// Steal floating ref
info.floating = false
} else {
info.mountCount++
}
return nil
}
if err := devices.activateDeviceIfNeeded(hash); err != nil { if err := devices.activateDeviceIfNeeded(hash); err != nil {
return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
} }
info := devices.Devices[hash]
var flags uintptr = sysMsMgcVal var flags uintptr = sysMsMgcVal
if readOnly {
flags = flags | sysMsRdOnly
}
err := sysMount(info.DevName(), path, "ext4", flags, "discard") err := sysMount(info.DevName(), path, "ext4", flags, "discard")
if err != nil && err == sysEInval { if err != nil && err == sysEInval {
err = sysMount(info.DevName(), path, "ext4", flags, "") 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) return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
} }
count := devices.activeMounts[path] info.mountCount = 1
devices.activeMounts[path] = count + 1 info.mountPath = path
info.floating = false
return devices.setInitialized(hash) return devices.setInitialized(hash)
} }
func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error { func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error {
utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path) utils.Debugf("[devmapper] UnmountDevice(hash=%s, mode=%d)", hash, mode)
defer utils.Debugf("[devmapper] UnmountDevice END") defer utils.Debugf("[devmapper] UnmountDevice END")
devices.Lock() devices.Lock()
defer devices.Unlock() defer devices.Unlock()
utils.Debugf("[devmapper] Unmount(%s)", path) info := devices.Devices[hash]
if err := sysUnmount(path, 0); err != nil { 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) utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
@ -804,15 +893,9 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro
return err return err
} }
if count := devices.activeMounts[path]; count > 1 { devices.deactivateDevice(hash)
devices.activeMounts[path] = count - 1
} else {
delete(devices.activeMounts, path)
}
if deactivate { info.mountPath = ""
devices.deactivateDevice(hash)
}
return nil return nil
} }
@ -955,9 +1038,8 @@ func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
SetDevDir("/dev") SetDevDir("/dev")
devices := &DeviceSet{ devices := &DeviceSet{
root: root, root: root,
MetaData: MetaData{Devices: make(map[string]*DevInfo)}, MetaData: MetaData{Devices: make(map[string]*DevInfo)},
activeMounts: make(map[string]int),
} }
if err := devices.initDevmapper(doInit); err != nil { if err := devices.initDevmapper(doInit); err != nil {

View File

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

View File

@ -7,8 +7,8 @@ import (
"github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io/ioutil" "io/ioutil"
"os"
"path" "path"
"sync"
) )
func init() { func init() {
@ -22,9 +22,7 @@ func init() {
type Driver struct { type Driver struct {
*DeviceSet *DeviceSet
home string home string
sync.Mutex // Protects concurrent modification to active
active map[string]int
} }
var Init = func(home string) (graphdriver.Driver, error) { var Init = func(home string) (graphdriver.Driver, error) {
@ -35,7 +33,6 @@ var Init = func(home string) (graphdriver.Driver, error) {
d := &Driver{ d := &Driver{
DeviceSet: deviceSet, DeviceSet: deviceSet,
home: home, home: home,
active: make(map[string]int),
} }
return d, nil return d, nil
} }
@ -83,55 +80,45 @@ func (d *Driver) Create(id, parent string) error {
return err 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 return nil
} }
func (d *Driver) Remove(id string) error { func (d *Driver) Remove(id string) error {
// Protect the d.active from concurrent access // Sink the float from create in case no Get() call was made
d.Lock() if err := d.DeviceSet.UnmountDevice(id, UnmountSink); err != nil {
defer d.Unlock() return err
}
if d.active[id] != 0 { // This assumes the device has been properly Get/Put:ed and thus is unmounted
utils.Errorf("Warning: removing active id %s\n", id) if err := d.DeviceSet.DeleteDevice(id); err != nil {
return err
} }
mp := path.Join(d.home, "mnt", id) 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 err
} }
return d.DeviceSet.RemoveDevice(id)
return nil
} }
func (d *Driver) Get(id string) (string, error) { 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) mp := path.Join(d.home, "mnt", id)
if count == 0 { if err := d.mount(id, mp); err != nil {
if err := d.mount(id, mp); err != nil { return "", err
return "", err
}
} }
d.active[id] = count + 1
return path.Join(mp, "rootfs"), nil return path.Join(mp, "rootfs"), nil
} }
func (d *Driver) Put(id string) { func (d *Driver) Put(id string) {
// Protect the d.active from concurrent access if err := d.DeviceSet.UnmountDevice(id, UnmountRegular); err != nil {
d.Lock() utils.Errorf("Warning: error unmounting device %s: %s\n", id, err)
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)
} }
} }
@ -140,25 +127,8 @@ func (d *Driver) mount(id, mountPoint string) error {
if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) { if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
return 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 // Mount the device
return d.DeviceSet.MountDevice(id, mountPoint, false) return d.DeviceSet.MountDevice(id, mountPoint)
}
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)
} }
func (d *Driver) Exists(id string) bool { func (d *Driver) Exists(id string) bool {

View File

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

View File

@ -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

View File

@ -68,9 +68,22 @@ else
exit 1 exit 1
fi 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 # Use these flags when compiling the tests and final binary
LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w' 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/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' 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' BUILDFLAGS='-tags netgo -a'
HAVE_GO_TEST_COVER= HAVE_GO_TEST_COVER=

View File

@ -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 # 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" source "$(dirname "$BASH_SOURCE")/binary"
) )

View File

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

View File

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

View File

@ -148,6 +148,65 @@ RUN [ "$(/hello.sh)" = "hello world" ]
nil, 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 // Environment variable
{ {
` `

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

133
nat/nat.go Normal file
View File

@ -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
nat/sort.go Normal file
View File

@ -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)
}

View File

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

View File

@ -172,7 +172,6 @@ func setupIPTables(addr net.Addr, icc bool) error {
iptables.Raw(append([]string{"-D"}, acceptArgs...)...) iptables.Raw(append([]string{"-D"}, acceptArgs...)...)
if !iptables.Exists(dropArgs...) { if !iptables.Exists(dropArgs...) {
utils.Debugf("Disable inter-container communication") utils.Debugf("Disable inter-container communication")
if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil { if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil {
return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) return fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
@ -353,6 +352,10 @@ func Release(job *engine.Job) engine.Status {
proto string proto string
) )
if containerInterface == nil {
return job.Errorf("No network information to release for %s", id)
}
for _, nat := range containerInterface.PortMappings { for _, nat := range containerInterface.PortMappings {
if err := portmapper.Unmap(nat); err != nil { if err := portmapper.Unmap(nat); err != nil {
log.Printf("Unable to unmap port %s: %s", nat, err) 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) job.Errorf("Error toggle iptables forward: %s", output)
return engine.StatusErr 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 return engine.StatusOK
} }

View File

@ -1,8 +1,7 @@
package docker package opts
import ( import (
"fmt" "fmt"
"github.com/dotcloud/docker/api"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"os" "os"
"path/filepath" "path/filepath"
@ -99,6 +98,16 @@ func ValidateLink(val string) (string, error) {
return val, nil 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) { func ValidatePath(val string) (string, error) {
var containerPath string var containerPath string
@ -129,14 +138,6 @@ func ValidateEnv(val string) (string, error) {
return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil 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) { func ValidateIp4Address(val string) (string, error) {
re := regexp.MustCompile(`^(([0-9]+\.){3}([0-9]+))\s*$`) re := regexp.MustCompile(`^(([0-9]+\.){3}([0-9]+))\s*$`)
var ns = re.FindSubmatch([]byte(val)) var ns = re.FindSubmatch([]byte(val))

View File

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

1
pkg/systemd/MAINTAINERS Normal file
View File

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

1
pkg/user/MAINTAINERS Normal file
View File

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

241
pkg/user/user.go Normal file
View File

@ -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
pkg/user/user_test.go Normal file
View File

@ -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
runconfig/compare.go Normal file
View File

@ -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
runconfig/config.go Normal file
View File

@ -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
}

View File

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

39
runconfig/hostconfig.go Normal file
View File

@ -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
runconfig/merge.go Normal file
View File

@ -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
runconfig/parse.go Normal file
View File

@ -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
runconfig/parse_test.go Normal file
View File

@ -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()
}
}
}

View File

@ -4,6 +4,7 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/dockerversion"
"github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/execdriver"
"github.com/dotcloud/docker/execdriver/chroot" "github.com/dotcloud/docker/execdriver/chroot"
@ -17,6 +18,7 @@ import (
"github.com/dotcloud/docker/networkdriver/portallocator" "github.com/dotcloud/docker/networkdriver/portallocator"
"github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/graphdb"
"github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/pkg/sysinfo"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "io/ioutil"
@ -133,14 +135,6 @@ func (runtime *Runtime) Register(container *Container) error {
return err 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 container.runtime = runtime
// Attach to stdout and stderr // 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. // 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 // Lookup image
img, err := runtime.repositories.LookupImage(config.Image) img, err := runtime.repositories.LookupImage(config.Image)
if err != nil { 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) 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 != nil {
if config.PortSpecs != nil { if config.PortSpecs != nil {
for _, p := range config.PortSpecs { for _, p := range config.PortSpecs {
@ -373,14 +367,12 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
} }
if img.Config != nil { 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 return nil, nil, err
} }
} }
if len(config.Entrypoint) != 0 && config.Cmd == nil { if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 {
config.Cmd = []string{}
} else if config.Cmd == nil || len(config.Cmd) == 0 {
return nil, nil, fmt.Errorf("No command specified") return nil, nil, fmt.Errorf("No command specified")
} }
@ -450,7 +442,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
Path: entrypoint, Path: entrypoint,
Args: args, //FIXME: de-duplicate from config Args: args, //FIXME: de-duplicate from config
Config: config, Config: config,
hostConfig: &HostConfig{}, hostConfig: &runconfig.HostConfig{},
Image: img.ID, // Always use the resolved image id Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{}, NetworkSettings: &NetworkSettings{},
Name: name, 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. // Commit creates a new filesystem image from the current state of a container.
// The image can optionally be tagged into a repository // 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: freeze the container before copying it to avoid data corruption?
// FIXME: this shouldn't be in commands. // FIXME: this shouldn't be in commands.
if err := container.Mount(); err != nil { if err := container.Mount(); err != nil {
@ -688,7 +680,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime
return nil, err 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) sysInitPath := utils.DockerInitPath(localCopy)
if sysInitPath == "" { 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.") 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.")

View File

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

View File

@ -2,31 +2,6 @@ package docker
import "sort" 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 { type containerSorter struct {
containers []*Container containers []*Container
by func(i, j *Container) bool by func(i, j *Container) bool

297
utils.go
View File

@ -1,13 +1,12 @@
package docker package docker
import ( import (
"fmt"
"github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/nat"
"github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/pkg/namesgenerator"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"strconv"
"strings"
"sync/atomic" "sync/atomic"
) )
@ -15,306 +14,22 @@ type Change struct {
archive.Change archive.Change
} }
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error {
// 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 {
if config.PortSpecs != nil { if config.PortSpecs != nil {
ports, bindings, err := parsePortSpecs(config.PortSpecs) ports, bindings, err := nat.ParsePortSpecs(config.PortSpecs)
if err != nil { if err != nil {
return err return err
} }
config.PortSpecs = nil config.PortSpecs = nil
if len(bindings) > 0 { if len(bindings) > 0 {
if hostConfig == nil { if hostConfig == nil {
hostConfig = &HostConfig{} hostConfig = &runconfig.HostConfig{}
} }
hostConfig.PortBindings = bindings hostConfig.PortBindings = bindings
} }
if config.ExposedPorts == nil { if config.ExposedPorts == nil {
config.ExposedPorts = make(map[Port]struct{}, len(ports)) config.ExposedPorts = make(nat.PortSet, len(ports))
} }
for k, v := range ports { for k, v := range ports {
config.ExposedPorts[k] = v config.ExposedPorts[k] = v

View File

@ -8,6 +8,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/dotcloud/docker/dockerversion"
"index/suffixarray" "index/suffixarray"
"io" "io"
"io/ioutil" "io/ioutil"
@ -23,12 +24,6 @@ import (
"time" "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 // A common interface to access the Fatal method of
// both testing.B and testing.T. // both testing.B and testing.T.
type Fataler interface { type Fataler interface {
@ -201,7 +196,7 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and
if target == "" { if target == "" {
return false return false
} }
if IAMSTATIC { if dockerversion.IAMSTATIC {
if selfPath == "" { if selfPath == "" {
return false return false
} }
@ -218,7 +213,7 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and
} }
return os.SameFile(targetFileInfo, selfPathFileInfo) 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()) // Figure out the path of our dockerinit (which may be SelfPath())
@ -230,7 +225,7 @@ func DockerInitPath(localCopy string) string {
} }
var possibleInits = []string{ var possibleInits = []string{
localCopy, localCopy,
INITPATH, dockerversion.INITPATH,
filepath.Join(filepath.Dir(selfPath), "dockerinit"), 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." // 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, "" 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. // An StatusError reports an unsuccessful exit by a command.
type StatusError struct { type StatusError struct {
Status string Status string

View File

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