func TestCmd(t *testing.T) { b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true} command := "./executable" err := cmd(b, []string{command}, nil, "") if err != nil { t.Fatalf("Error should be empty, got: %s", err.Error()) } var expectedCommand strslice.StrSlice if runtime.GOOS == "windows" { expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command)) } else { expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command)) } if !compareStrSlice(b.runConfig.Cmd, expectedCommand) { t.Fatalf("Command should be set to %s, got %s", command, b.runConfig.Cmd) } if !b.cmdSet { t.Fatalf("Command should be marked as set") } }
func TestEntrypoint(t *testing.T) { b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true} entrypointCmd := "/usr/sbin/nginx" if err := entrypoint(b, []string{entrypointCmd}, nil, ""); err != nil { t.Fatalf("Error should be empty, got: %s", err.Error()) } if b.runConfig.Entrypoint == nil { t.Fatalf("Entrypoint should be set") } var expectedEntrypoint strslice.StrSlice if runtime.GOOS == "windows" { expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd)) } else { expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd)) } if !compareStrSlice(expectedEntrypoint, b.runConfig.Entrypoint) { t.Fatalf("Entrypoint command should be set to %s, got %s", expectedEntrypoint, b.runConfig.Entrypoint) } }
// ENTRYPOINT /usr/sbin/nginx // // Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments // to /usr/sbin/nginx. Uses the default shell if not in JSON format. // // Handles command processing similar to CMD and RUN, only b.runConfig.Entrypoint // is initialized at NewBuilder time instead of through argument parsing. // func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error { if err := b.flags.Parse(); err != nil { return err } parsed := handleJSONArgs(args, attributes) switch { case attributes["json"]: // ENTRYPOINT ["echo", "hi"] b.runConfig.Entrypoint = strslice.StrSlice(parsed) case len(parsed) == 0: // ENTRYPOINT [] b.runConfig.Entrypoint = nil default: // ENTRYPOINT echo hi b.runConfig.Entrypoint = strslice.StrSlice(append(getShell(b.runConfig), parsed[0])) } // when setting the entrypoint if a CMD was not explicitly set then // set the command to nil if !b.cmdSet { b.runConfig.Cmd = nil } if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.runConfig.Entrypoint)); err != nil { return err } return nil }
// CMD foo // // Set the default command to run in the container (which may be empty). // Argument handling is the same as RUN. // func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { if err := b.flags.Parse(); err != nil { return err } cmdSlice := handleJSONArgs(args, attributes) if !attributes["json"] { cmdSlice = append(getShell(b.runConfig), cmdSlice...) } b.runConfig.Cmd = strslice.StrSlice(cmdSlice) // set config as already being escaped, this prevents double escaping on windows b.runConfig.ArgsEscaped = true if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { return err } if len(args) != 0 { b.cmdSet = true } return nil }
// WORKDIR /tmp // // Set the working directory for future RUN/CMD/etc statements. // func workdir(b *Builder, args []string, attributes map[string]bool, original string) error { if len(args) != 1 { return errExactlyOneArgument("WORKDIR") } err := b.flags.Parse() if err != nil { return err } // This is from the Dockerfile and will not necessarily be in platform // specific semantics, hence ensure it is converted. b.runConfig.WorkingDir, err = normaliseWorkdir(b.runConfig.WorkingDir, args[0]) if err != nil { return err } // For performance reasons, we explicitly do a create/mkdir now // This avoids having an unnecessary expensive mount/unmount calls // (on Windows in particular) during each container create. // Prior to 1.13, the mkdir was deferred and not executed at this step. if b.disableCommit { // Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo". // We've already updated the runConfig and that's enough. return nil } b.runConfig.Image = b.image cmd := b.runConfig.Cmd comment := "WORKDIR " + b.runConfig.WorkingDir // reset the command for cache detection b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) "+comment)) defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd) if hit, err := b.probeCache(); err != nil { return err } else if hit { return nil } container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig}) if err != nil { return err } b.tmpContainers[container.ID] = struct{}{} if err := b.docker.ContainerCreateWorkdir(container.ID); err != nil { return err } return b.commit(container.ID, cmd, comment) }
func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) error { if b.disableCommit { return nil } if b.image == "" && !b.noBaseImage { return fmt.Errorf("Please provide a source image with `from` prior to commit") } b.runConfig.Image = b.image if id == "" { cmd := b.runConfig.Cmd b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) ", comment)) defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd) hit, err := b.probeCache() if err != nil { return err } else if hit { return nil } id, err = b.create() if err != nil { return err } } // Note: Actually copy the struct autoConfig := *b.runConfig autoConfig.Cmd = autoCmd commitCfg := &backend.ContainerCommitConfig{ ContainerCommitConfig: types.ContainerCommitConfig{ Author: b.maintainer, Pause: true, Config: &autoConfig, }, } // Commit the container imageID, err := b.docker.Commit(id, commitCfg) if err != nil { return err } b.image = imageID return nil }
// exec the healthcheck command in the container. // Returns the exit code and probe output (if any) func (p *cmdProbe) run(ctx context.Context, d *Daemon, container *container.Container) (*types.HealthcheckResult, error) { cmdSlice := strslice.StrSlice(container.Config.Healthcheck.Test)[1:] if p.shell { if runtime.GOOS != "windows" { cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) } else { cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...) } } entrypoint, args := d.getEntrypointAndArgs(strslice.StrSlice{}, cmdSlice) execConfig := exec.NewConfig() execConfig.OpenStdin = false execConfig.OpenStdout = true execConfig.OpenStderr = true execConfig.ContainerID = container.ID execConfig.DetachKeys = []byte{} execConfig.Entrypoint = entrypoint execConfig.Args = args execConfig.Tty = false execConfig.Privileged = false execConfig.User = container.Config.User d.registerExecCommand(container, execConfig) d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " ")) output := &limitedBuffer{} err := d.ContainerExecStart(ctx, execConfig.ID, nil, output, output) if err != nil { return nil, err } info, err := d.getExecConfig(execConfig.ID) if err != nil { return nil, err } if info.ExitCode == nil { return nil, fmt.Errorf("Healthcheck for container %s has no exit code!", container.ID) } // Note: Go's json package will handle invalid UTF-8 for us out := output.String() return &types.HealthcheckResult{ End: time.Now(), ExitCode: *info.ExitCode, Output: out, }, nil }
func TestHealthcheckCmd(t *testing.T) { b := &Builder{flags: &BFlags{flags: make(map[string]*Flag)}, runConfig: &container.Config{}, disableCommit: true} if err := healthcheck(b, []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}, nil, ""); err != nil { t.Fatalf("Error should be empty, got: %s", err.Error()) } if b.runConfig.Healthcheck == nil { t.Fatal("Healthcheck should be set, got nil") } expectedTest := strslice.StrSlice(append([]string{"CMD-SHELL"}, "curl -f http://localhost/ || exit 1")) if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) { t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test) } }
func TestHealthcheckNone(t *testing.T) { b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true} if err := healthcheck(b, []string{"NONE"}, nil, ""); err != nil { t.Fatalf("Error should be empty, got: %s", err.Error()) } if b.runConfig.Healthcheck == nil { t.Fatal("Healthcheck should be set, got nil") } expectedTest := strslice.StrSlice(append([]string{"NONE"})) if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) { t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test) } }
// ContainerExecCreate sets up an exec in a running container. func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (string, error) { container, err := d.getActiveContainer(name) if err != nil { return "", err } cmd := strslice.StrSlice(config.Cmd) entrypoint, args := d.getEntrypointAndArgs(strslice.StrSlice{}, cmd) keys := []byte{} if config.DetachKeys != "" { keys, err = term.ToBytes(config.DetachKeys) if err != nil { err = fmt.Errorf("Invalid escape keys (%s) provided", config.DetachKeys) return "", err } } execConfig := exec.NewConfig() execConfig.OpenStdin = config.AttachStdin execConfig.OpenStdout = config.AttachStdout execConfig.OpenStderr = config.AttachStderr execConfig.ContainerID = container.ID execConfig.DetachKeys = keys execConfig.Entrypoint = entrypoint execConfig.Args = args execConfig.Tty = config.Tty execConfig.Privileged = config.Privileged execConfig.User = config.User execConfig.Env = []string{ "PATH=" + system.DefaultPathEnv, } if config.Tty { execConfig.Env = append(execConfig.Env, "TERM=xterm") } execConfig.Env = utils.ReplaceOrAppendEnvValues(execConfig.Env, container.Config.Env) if len(execConfig.User) == 0 { execConfig.User = container.Config.User } d.registerExecCommand(container, execConfig) d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " ")) return execConfig.ID, nil }
// SHELL powershell -command // // Set the non-default shell to use. func shell(b *Builder, args []string, attributes map[string]bool, original string) error { if err := b.flags.Parse(); err != nil { return err } shellSlice := handleJSONArgs(args, attributes) switch { case len(shellSlice) == 0: // SHELL [] return errAtLeastOneArgument("SHELL") case attributes["json"]: // SHELL ["powershell", "-command"] b.runConfig.Shell = strslice.StrSlice(shellSlice) default: // SHELL powershell -command - not JSON return errNotJSON("SHELL", original) } return b.commit("", b.runConfig.Cmd, fmt.Sprintf("SHELL %v", shellSlice)) }
func TestShell(t *testing.T) { b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true} shellCmd := "powershell" attrs := make(map[string]bool) attrs["json"] = true if err := shell(b, []string{shellCmd}, attrs, ""); err != nil { t.Fatalf("Error should be empty, got: %s", err.Error()) } if b.runConfig.Shell == nil { t.Fatalf("Shell should be set") } expectedShell := strslice.StrSlice([]string{shellCmd}) if !compareStrSlice(expectedShell, b.runConfig.Shell) { t.Fatalf("Shell should be set to %s, got %s", expectedShell, b.runConfig.Shell) } }
// Convert converts a service configuration to an docker API structures (Config and HostConfig) func Convert(c *config.ServiceConfig, ctx project.Context, clientFactory composeclient.Factory) (*container.Config, *container.HostConfig, error) { restartPolicy, err := restartPolicy(c) if err != nil { return nil, nil, err } exposedPorts, portBindings, err := ports(c) if err != nil { return nil, nil, err } deviceMappings, err := parseDevices(c.Devices) if err != nil { return nil, nil, err } var volumesFrom []string if c.VolumesFrom != nil { volumesFrom, err = getVolumesFrom(c.VolumesFrom, ctx.Project.ServiceConfigs, ctx.ProjectName) if err != nil { return nil, nil, err } } vols := volumes(c, ctx) config := &container.Config{ Entrypoint: strslice.StrSlice(utils.CopySlice(c.Entrypoint)), Hostname: c.Hostname, Domainname: c.DomainName, User: c.User, Env: utils.CopySlice(c.Environment), Cmd: strslice.StrSlice(utils.CopySlice(c.Command)), Image: c.Image, Labels: utils.CopyMap(c.Labels), ExposedPorts: exposedPorts, Tty: c.Tty, OpenStdin: c.StdinOpen, WorkingDir: c.WorkingDir, Volumes: toMap(Filter(vols, isVolume)), MacAddress: c.MacAddress, StopSignal: c.StopSignal, } ulimits := []*units.Ulimit{} if c.Ulimits.Elements != nil { for _, ulimit := range c.Ulimits.Elements { ulimits = append(ulimits, &units.Ulimit{ Name: ulimit.Name, Soft: ulimit.Soft, Hard: ulimit.Hard, }) } } memorySwappiness := int64(c.MemSwappiness) resources := container.Resources{ CgroupParent: c.CgroupParent, Memory: int64(c.MemLimit), MemorySwap: int64(c.MemSwapLimit), MemorySwappiness: &memorySwappiness, CPUShares: int64(c.CPUShares), CPUQuota: int64(c.CPUQuota), CpusetCpus: c.CPUSet, Ulimits: ulimits, Devices: deviceMappings, } networkMode := c.NetworkMode if c.NetworkMode == "" { if c.Networks != nil && len(c.Networks.Networks) > 0 { networkMode = c.Networks.Networks[0].RealName } } else { switch { case strings.HasPrefix(c.NetworkMode, "service:"): serviceName := c.NetworkMode[8:] if serviceConfig, ok := ctx.Project.ServiceConfigs.Get(serviceName); ok { // FIXME(vdemeester) this is actually not right, should be fixed but not there service, err := ctx.ServiceFactory.Create(ctx.Project, serviceName, serviceConfig) if err != nil { return nil, nil, err } containers, err := service.Containers(context.Background()) if err != nil { return nil, nil, err } if len(containers) != 0 { container := containers[0] containerID, err := container.ID() if err != nil { return nil, nil, err } networkMode = "container:" + containerID } // FIXME(vdemeester) log/warn in case of len(containers) == 0 } case strings.HasPrefix(c.NetworkMode, "container:"): containerName := c.NetworkMode[10:] client := clientFactory.Create(nil) container, err := composecontainer.Get(context.Background(), client, containerName) if err != nil { return nil, nil, err } networkMode = "container:" + container.ID default: // do nothing :) } } tmpfs := map[string]string{} for _, path := range c.Tmpfs { split := strings.SplitN(path, ":", 2) if len(split) == 1 { tmpfs[split[0]] = "" } else if len(split) == 2 { tmpfs[split[0]] = split[1] } } hostConfig := &container.HostConfig{ VolumesFrom: volumesFrom, CapAdd: strslice.StrSlice(utils.CopySlice(c.CapAdd)), CapDrop: strslice.StrSlice(utils.CopySlice(c.CapDrop)), GroupAdd: c.GroupAdd, ExtraHosts: utils.CopySlice(c.ExtraHosts), Privileged: c.Privileged, Binds: Filter(vols, isBind), DNS: utils.CopySlice(c.DNS), DNSOptions: utils.CopySlice(c.DNSOpts), DNSSearch: utils.CopySlice(c.DNSSearch), Isolation: container.Isolation(c.Isolation), LogConfig: container.LogConfig{ Type: c.Logging.Driver, Config: utils.CopyMap(c.Logging.Options), }, NetworkMode: container.NetworkMode(networkMode), ReadonlyRootfs: c.ReadOnly, OomScoreAdj: int(c.OomScoreAdj), PidMode: container.PidMode(c.Pid), UTSMode: container.UTSMode(c.Uts), IpcMode: container.IpcMode(c.Ipc), PortBindings: portBindings, RestartPolicy: *restartPolicy, ShmSize: int64(c.ShmSize), SecurityOpt: utils.CopySlice(c.SecurityOpt), Tmpfs: tmpfs, VolumeDriver: c.VolumeDriver, Resources: resources, } if config.Labels == nil { config.Labels = map[string]string{} } return config, hostConfig, nil }
// HEALTHCHECK foo // // Set the default healthcheck command to run in the container (which may be empty). // Argument handling is the same as RUN. // func healthcheck(b *Builder, args []string, attributes map[string]bool, original string) error { if len(args) == 0 { return errAtLeastOneArgument("HEALTHCHECK") } typ := strings.ToUpper(args[0]) args = args[1:] if typ == "NONE" { if len(args) != 0 { return fmt.Errorf("HEALTHCHECK NONE takes no arguments") } test := strslice.StrSlice{typ} b.runConfig.Healthcheck = &container.HealthConfig{ Test: test, } } else { if b.runConfig.Healthcheck != nil { oldCmd := b.runConfig.Healthcheck.Test if len(oldCmd) > 0 && oldCmd[0] != "NONE" { fmt.Fprintf(b.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd) } } healthcheck := container.HealthConfig{} flInterval := b.flags.AddString("interval", "") flTimeout := b.flags.AddString("timeout", "") flRetries := b.flags.AddString("retries", "") if err := b.flags.Parse(); err != nil { return err } switch typ { case "CMD": cmdSlice := handleJSONArgs(args, attributes) if len(cmdSlice) == 0 { return fmt.Errorf("Missing command after HEALTHCHECK CMD") } if !attributes["json"] { typ = "CMD-SHELL" } healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...)) default: return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ) } interval, err := parseOptInterval(flInterval) if err != nil { return err } healthcheck.Interval = interval timeout, err := parseOptInterval(flTimeout) if err != nil { return err } healthcheck.Timeout = timeout if flRetries.Value != "" { retries, err := strconv.ParseInt(flRetries.Value, 10, 32) if err != nil { return err } if retries < 1 { return fmt.Errorf("--retries must be at least 1 (not %d)", retries) } healthcheck.Retries = int(retries) } else { healthcheck.Retries = 0 } b.runConfig.Healthcheck = &healthcheck } return b.commit("", b.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", b.runConfig.Healthcheck)) }
// RUN some command yo // // run a command and commit the image. Args are automatically prepended with // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under // Windows, in the event there is only one argument The difference in processing: // // RUN echo hi # sh -c echo hi (Linux) // RUN echo hi # cmd /S /C echo hi (Windows) // RUN [ "echo", "hi" ] # echo hi // func run(b *Builder, args []string, attributes map[string]bool, original string) error { if b.image == "" && !b.noBaseImage { return fmt.Errorf("Please provide a source image with `from` prior to run") } if err := b.flags.Parse(); err != nil { return err } args = handleJSONArgs(args, attributes) if !attributes["json"] { args = append(getShell(b.runConfig), args...) } config := &container.Config{ Cmd: strslice.StrSlice(args), Image: b.image, } // stash the cmd cmd := b.runConfig.Cmd if len(b.runConfig.Entrypoint) == 0 && len(b.runConfig.Cmd) == 0 { b.runConfig.Cmd = config.Cmd } // stash the config environment env := b.runConfig.Env defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd) defer func(env []string) { b.runConfig.Env = env }(env) // derive the net build-time environment for this run. We let config // environment override the build time environment. // This means that we take the b.buildArgs list of env vars and remove // any of those variables that are defined as part of the container. In other // words, anything in b.Config.Env. What's left is the list of build-time env // vars that we need to add to each RUN command - note the list could be empty. // // We don't persist the build time environment with container's config // environment, but just sort and prepend it to the command string at time // of commit. // This helps with tracing back the image's actual environment at the time // of RUN, without leaking it to the final image. It also aids cache // lookup for same image built with same build time environment. cmdBuildEnv := []string{} configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env) for key, val := range b.options.BuildArgs { if !b.isBuildArgAllowed(key) { // skip build-args that are not in allowed list, meaning they have // not been defined by an "ARG" Dockerfile command yet. // This is an error condition but only if there is no "ARG" in the entire // Dockerfile, so we'll generate any necessary errors after we parsed // the entire file (see 'leftoverArgs' processing in evaluator.go ) continue } if _, ok := configEnv[key]; !ok { cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, val)) } } // derive the command to use for probeCache() and to commit in this container. // Note that we only do this if there are any build-time env vars. Also, we // use the special argument "|#" at the start of the args array. This will // avoid conflicts with any RUN command since commands can not // start with | (vertical bar). The "#" (number of build envs) is there to // help ensure proper cache matches. We don't want a RUN command // that starts with "foo=abc" to be considered part of a build-time env var. saveCmd := config.Cmd if len(cmdBuildEnv) > 0 { sort.Strings(cmdBuildEnv) tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...) saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...)) } b.runConfig.Cmd = saveCmd hit, err := b.probeCache() if err != nil { return err } if hit { return nil } // set Cmd manually, this is special case only for Dockerfiles b.runConfig.Cmd = config.Cmd // set build-time environment for 'run'. b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...) // set config as already being escaped, this prevents double escaping on windows b.runConfig.ArgsEscaped = true logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd) cID, err := b.create() if err != nil { return err } if err := b.run(cID); err != nil { return err } // revert to original config environment and set the command string to // have the build-time env vars in it (if any) so that future cache look-ups // properly match it. b.runConfig.Env = env b.runConfig.Cmd = saveCmd return b.commit(cID, cmd, "run") }
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string) error { if b.context == nil { return fmt.Errorf("No context given. Impossible to use %s", cmdName) } if len(args) < 2 { return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName) } // Work in daemon-specific filepath semantics dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest b.runConfig.Image = b.image var infos []copyInfo // Loop through each src file and calculate the info we need to // do the copy (e.g. hash value if cached). Don't actually do // the copy until we've looked at all src files var err error for _, orig := range args[0 : len(args)-1] { var fi builder.FileInfo decompress := allowLocalDecompression if urlutil.IsURL(orig) { if !allowRemote { return fmt.Errorf("Source can't be a URL for %s", cmdName) } fi, err = b.download(orig) if err != nil { return err } defer os.RemoveAll(filepath.Dir(fi.Path())) decompress = false infos = append(infos, copyInfo{fi, decompress}) continue } // not a URL subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true) if err != nil { return err } infos = append(infos, subInfos...) } if len(infos) == 0 { return fmt.Errorf("No source files were specified") } if len(infos) > 1 && !strings.HasSuffix(dest, string(os.PathSeparator)) { return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName) } // For backwards compat, if there's just one info then use it as the // cache look-up string, otherwise hash 'em all into one var srcHash string var origPaths string if len(infos) == 1 { fi := infos[0].FileInfo origPaths = fi.Name() if hfi, ok := fi.(builder.Hashed); ok { srcHash = hfi.Hash() } } else { var hashs []string var origs []string for _, info := range infos { fi := info.FileInfo origs = append(origs, fi.Name()) if hfi, ok := fi.(builder.Hashed); ok { hashs = append(hashs, hfi.Hash()) } } hasher := sha256.New() hasher.Write([]byte(strings.Join(hashs, ","))) srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil)) origPaths = strings.Join(origs, " ") } cmd := b.runConfig.Cmd b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest))) defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd) if hit, err := b.probeCache(); err != nil { return err } else if hit { return nil } container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig}) if err != nil { return err } b.tmpContainers[container.ID] = struct{}{} comment := fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest) // Twiddle the destination when its a relative path - meaning, make it // relative to the WORKINGDIR if dest, err = normaliseDest(cmdName, b.runConfig.WorkingDir, dest); err != nil { return err } for _, info := range infos { if err := b.docker.CopyOnBuild(container.ID, dest, info.FileInfo, info.decompress); err != nil { return err } } return b.commit(container.ID, cmd, comment) }
// Convert converts a service configuration to an docker API structures (Config and HostConfig) func Convert(c *config.ServiceConfig, ctx project.Context) (*container.Config, *container.HostConfig, error) { restartPolicy, err := restartPolicy(c) if err != nil { return nil, nil, err } exposedPorts, portBindings, err := ports(c) if err != nil { return nil, nil, err } deviceMappings, err := parseDevices(c.Devices) if err != nil { return nil, nil, err } var volumesFrom []string if c.VolumesFrom != nil { volumesFrom, err = getVolumesFrom(c.VolumesFrom, ctx.Project.ServiceConfigs, ctx.ProjectName) if err != nil { return nil, nil, err } } vols := volumes(c, ctx) config := &container.Config{ Entrypoint: strslice.StrSlice(utils.CopySlice(c.Entrypoint)), Hostname: c.Hostname, Domainname: c.DomainName, User: c.User, Env: utils.CopySlice(c.Environment), Cmd: strslice.StrSlice(utils.CopySlice(c.Command)), Image: c.Image, Labels: utils.CopyMap(c.Labels), ExposedPorts: exposedPorts, Tty: c.Tty, OpenStdin: c.StdinOpen, WorkingDir: c.WorkingDir, Volumes: toMap(Filter(vols, isVolume)), MacAddress: c.MacAddress, StopSignal: c.StopSignal, } ulimits := []*units.Ulimit{} if c.Ulimits.Elements != nil { for _, ulimit := range c.Ulimits.Elements { ulimits = append(ulimits, &units.Ulimit{ Name: ulimit.Name, Soft: ulimit.Soft, Hard: ulimit.Hard, }) } } memorySwappiness := int64(c.MemSwappiness) tmpfs := map[string]string{} for _, path := range c.Tmpfs { split := strings.SplitN(path, ":", 2) if len(split) == 1 { tmpfs[split[0]] = "" } else if len(split) == 2 { tmpfs[split[0]] = split[1] } } blkioWeightDevices := []*blkiodev.WeightDevice{} for _, blkioWeightDevice := range c.BlkioWeightDevice { split := strings.Split(blkioWeightDevice, ":") if len(split) == 2 { weight, err := strconv.ParseUint(split[1], 10, 16) if err != nil { return nil, nil, err } blkioWeightDevices = append(blkioWeightDevices, &blkiodev.WeightDevice{ Path: split[0], Weight: uint16(weight), }) } } blkioDeviceReadBps, err := getThrottleDevice(c.DeviceReadBps) if err != nil { return nil, nil, err } blkioDeviceReadIOps, err := getThrottleDevice(c.DeviceReadIOps) if err != nil { return nil, nil, err } blkioDeviceWriteBps, err := getThrottleDevice(c.DeviceWriteBps) if err != nil { return nil, nil, err } blkioDeviceWriteIOps, err := getThrottleDevice(c.DeviceWriteIOps) if err != nil { return nil, nil, err } resources := container.Resources{ BlkioWeight: uint16(c.BlkioWeight), BlkioWeightDevice: blkioWeightDevices, CgroupParent: c.CgroupParent, Memory: int64(c.MemLimit), MemoryReservation: int64(c.MemReservation), MemorySwap: int64(c.MemSwapLimit), MemorySwappiness: &memorySwappiness, CPUPeriod: int64(c.CPUPeriod), CPUShares: int64(c.CPUShares), CPUQuota: int64(c.CPUQuota), CpusetCpus: c.CPUSet, Ulimits: ulimits, Devices: deviceMappings, OomKillDisable: &c.OomKillDisable, BlkioDeviceReadBps: blkioDeviceReadBps, BlkioDeviceReadIOps: blkioDeviceReadIOps, BlkioDeviceWriteBps: blkioDeviceWriteBps, BlkioDeviceWriteIOps: blkioDeviceWriteIOps, } hostConfig := &container.HostConfig{ VolumesFrom: volumesFrom, CapAdd: strslice.StrSlice(utils.CopySlice(c.CapAdd)), CapDrop: strslice.StrSlice(utils.CopySlice(c.CapDrop)), GroupAdd: c.GroupAdd, ExtraHosts: utils.CopySlice(c.ExtraHosts), Privileged: c.Privileged, Binds: Filter(vols, isBind), DNS: utils.CopySlice(c.DNS), DNSOptions: utils.CopySlice(c.DNSOpt), DNSSearch: utils.CopySlice(c.DNSSearch), Isolation: container.Isolation(c.Isolation), LogConfig: container.LogConfig{ Type: c.Logging.Driver, Config: utils.CopyMap(c.Logging.Options), }, NetworkMode: container.NetworkMode(c.NetworkMode), ReadonlyRootfs: c.ReadOnly, OomScoreAdj: int(c.OomScoreAdj), PidMode: container.PidMode(c.Pid), UTSMode: container.UTSMode(c.Uts), IpcMode: container.IpcMode(c.Ipc), PortBindings: portBindings, RestartPolicy: *restartPolicy, ShmSize: int64(c.ShmSize), SecurityOpt: utils.CopySlice(c.SecurityOpt), Tmpfs: tmpfs, VolumeDriver: c.VolumeDriver, Resources: resources, } if config.Labels == nil { config.Labels = map[string]string{} } return config, hostConfig, nil }
// Parse parses the args for the specified command and generates a Config, // a HostConfig and returns them with the specified command. // If the specified args are not valid, it will return an error. func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { var ( attachStdin = copts.attach.Get("stdin") attachStdout = copts.attach.Get("stdout") attachStderr = copts.attach.Get("stderr") ) // Validate the input mac address if copts.macAddress != "" { if _, err := ValidateMACAddress(copts.macAddress); err != nil { return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress) } } if copts.stdin { attachStdin = true } // If -a is not set, attach to stdout and stderr if copts.attach.Len() == 0 { attachStdout = true attachStderr = true } var err error var memory int64 if copts.memoryString != "" { memory, err = units.RAMInBytes(copts.memoryString) if err != nil { return nil, nil, nil, err } } var memoryReservation int64 if copts.memoryReservation != "" { memoryReservation, err = units.RAMInBytes(copts.memoryReservation) if err != nil { return nil, nil, nil, err } } var memorySwap int64 if copts.memorySwap != "" { if copts.memorySwap == "-1" { memorySwap = -1 } else { memorySwap, err = units.RAMInBytes(copts.memorySwap) if err != nil { return nil, nil, nil, err } } } var kernelMemory int64 if copts.kernelMemory != "" { kernelMemory, err = units.RAMInBytes(copts.kernelMemory) if err != nil { return nil, nil, nil, err } } swappiness := copts.swappiness if swappiness != -1 && (swappiness < 0 || swappiness > 100) { return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) } var shmSize int64 if copts.shmSize != "" { shmSize, err = units.RAMInBytes(copts.shmSize) if err != nil { return nil, nil, nil, err } } // TODO FIXME units.RAMInBytes should have a uint64 version var maxIOBandwidth int64 if copts.ioMaxBandwidth != "" { maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth) if err != nil { return nil, nil, nil, err } if maxIOBandwidth < 0 { return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth) } } var binds []string // add any bind targets to the list of container volumes for bind := range copts.volumes.GetMap() { if arr := volumeSplitN(bind, 2); len(arr) > 1 { // after creating the bind mount we want to delete it from the copts.volumes values because // we do not want bind mounts being committed to image configs binds = append(binds, bind) copts.volumes.Delete(bind) } } // Can't evaluate options passed into --tmpfs until we actually mount tmpfs := make(map[string]string) for _, t := range copts.tmpfs.GetAll() { if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil { return nil, nil, nil, err } tmpfs[arr[0]] = arr[1] } else { tmpfs[arr[0]] = "" } } var ( runCmd strslice.StrSlice entrypoint strslice.StrSlice ) if len(copts.Args) > 0 { runCmd = strslice.StrSlice(copts.Args) } if copts.entrypoint != "" { entrypoint = strslice.StrSlice{copts.entrypoint} } else if flags.Changed("entrypoint") { // if `--entrypoint=` is parsed then Entrypoint is reset entrypoint = []string{""} } ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll()) if err != nil { return nil, nil, nil, err } // Merge in exposed ports to the map of published ports for _, e := range copts.expose.GetAll() { if strings.Contains(e, ":") { return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e) } //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] proto, port := nat.SplitProtoPort(e) //parse the start and end port and create a sequence of ports to expose //if expose a port, the start and end port are the same start, end, err := nat.ParsePortRange(port) if err != nil { return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err) } for i := start; i <= end; i++ { p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) if err != nil { return nil, nil, nil, err } if _, exists := ports[p]; !exists { ports[p] = struct{}{} } } } // parse device mappings deviceMappings := []container.DeviceMapping{} for _, device := range copts.devices.GetAll() { deviceMapping, err := ParseDevice(device) if err != nil { return nil, nil, nil, err } deviceMappings = append(deviceMappings, deviceMapping) } // collect all the environment variables for the container envVariables, err := readKVStrings(copts.envFile.GetAll(), copts.env.GetAll()) if err != nil { return nil, nil, nil, err } // collect all the labels for the container labels, err := readKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) if err != nil { return nil, nil, nil, err } ipcMode := container.IpcMode(copts.ipcMode) if !ipcMode.Valid() { return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode") } pidMode := container.PidMode(copts.pidMode) if !pidMode.Valid() { return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode") } utsMode := container.UTSMode(copts.utsMode) if !utsMode.Valid() { return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode") } usernsMode := container.UsernsMode(copts.usernsMode) if !usernsMode.Valid() { return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode") } restartPolicy, err := ParseRestartPolicy(copts.restartPolicy) if err != nil { return nil, nil, nil, err } loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll()) if err != nil { return nil, nil, nil, err } securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll()) if err != nil { return nil, nil, nil, err } storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) if err != nil { return nil, nil, nil, err } // Healthcheck var healthConfig *container.HealthConfig haveHealthSettings := copts.healthCmd != "" || copts.healthInterval != 0 || copts.healthTimeout != 0 || copts.healthRetries != 0 if copts.noHealthcheck { if haveHealthSettings { return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options") } test := strslice.StrSlice{"NONE"} healthConfig = &container.HealthConfig{Test: test} } else if haveHealthSettings { var probe strslice.StrSlice if copts.healthCmd != "" { args := []string{"CMD-SHELL", copts.healthCmd} probe = strslice.StrSlice(args) } if copts.healthInterval < 0 { return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative") } if copts.healthTimeout < 0 { return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative") } healthConfig = &container.HealthConfig{ Test: probe, Interval: copts.healthInterval, Timeout: copts.healthTimeout, Retries: copts.healthRetries, } } resources := container.Resources{ CgroupParent: copts.cgroupParent, Memory: memory, MemoryReservation: memoryReservation, MemorySwap: memorySwap, MemorySwappiness: &copts.swappiness, KernelMemory: kernelMemory, OomKillDisable: &copts.oomKillDisable, CPUPercent: copts.cpuPercent, CPUShares: copts.cpuShares, CPUPeriod: copts.cpuPeriod, CpusetCpus: copts.cpusetCpus, CpusetMems: copts.cpusetMems, CPUQuota: copts.cpuQuota, PidsLimit: copts.pidsLimit, BlkioWeight: copts.blkioWeight, BlkioWeightDevice: copts.blkioWeightDevice.GetList(), BlkioDeviceReadBps: copts.deviceReadBps.GetList(), BlkioDeviceWriteBps: copts.deviceWriteBps.GetList(), BlkioDeviceReadIOps: copts.deviceReadIOps.GetList(), BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(), IOMaximumIOps: copts.ioMaxIOps, IOMaximumBandwidth: uint64(maxIOBandwidth), Ulimits: copts.ulimits.GetList(), Devices: deviceMappings, } config := &container.Config{ Hostname: copts.hostname, ExposedPorts: ports, User: copts.user, Tty: copts.tty, // TODO: deprecated, it comes from -n, --networking // it's still needed internally to set the network to disabled // if e.g. bridge is none in daemon opts, and in inspect NetworkDisabled: false, OpenStdin: copts.stdin, AttachStdin: attachStdin, AttachStdout: attachStdout, AttachStderr: attachStderr, Env: envVariables, Cmd: runCmd, Image: copts.Image, Volumes: copts.volumes.GetMap(), MacAddress: copts.macAddress, Entrypoint: entrypoint, WorkingDir: copts.workingDir, Labels: ConvertKVStringsToMap(labels), Healthcheck: healthConfig, } if flags.Changed("stop-signal") { config.StopSignal = copts.stopSignal } hostConfig := &container.HostConfig{ Binds: binds, ContainerIDFile: copts.containerIDFile, OomScoreAdj: copts.oomScoreAdj, AutoRemove: copts.autoRemove, Privileged: copts.privileged, PortBindings: portBindings, Links: copts.links.GetAll(), PublishAllPorts: copts.publishAll, // Make sure the dns fields are never nil. // New containers don't ever have those fields nil, // but pre created containers can still have those nil values. // See https://github.com/docker/docker/pull/17779 // for a more detailed explanation on why we don't want that. DNS: copts.dns.GetAllOrEmpty(), DNSSearch: copts.dnsSearch.GetAllOrEmpty(), DNSOptions: copts.dnsOptions.GetAllOrEmpty(), ExtraHosts: copts.extraHosts.GetAll(), VolumesFrom: copts.volumesFrom.GetAll(), NetworkMode: container.NetworkMode(copts.netMode), IpcMode: ipcMode, PidMode: pidMode, UTSMode: utsMode, UsernsMode: usernsMode, CapAdd: strslice.StrSlice(copts.capAdd.GetAll()), CapDrop: strslice.StrSlice(copts.capDrop.GetAll()), GroupAdd: copts.groupAdd.GetAll(), RestartPolicy: restartPolicy, SecurityOpt: securityOpts, StorageOpt: storageOpts, ReadonlyRootfs: copts.readonlyRootfs, LogConfig: container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts}, VolumeDriver: copts.volumeDriver, Isolation: container.Isolation(copts.isolation), ShmSize: shmSize, Resources: resources, Tmpfs: tmpfs, Sysctls: copts.sysctls.GetAll(), Runtime: copts.runtime, } // only set this value if the user provided the flag, else it should default to nil if flags.Changed("init") { hostConfig.Init = &copts.init } // When allocating stdin in attached mode, close stdin at client disconnect if config.OpenStdin && config.AttachStdin { config.StdinOnce = true } networkingConfig := &networktypes.NetworkingConfig{ EndpointsConfig: make(map[string]*networktypes.EndpointSettings), } if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 { epConfig := &networktypes.EndpointSettings{} networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ IPv4Address: copts.ipv4Address, IPv6Address: copts.ipv6Address, } if copts.linkLocalIPs.Len() > 0 { epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll()) } } if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 { epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] if epConfig == nil { epConfig = &networktypes.EndpointSettings{} } epConfig.Links = make([]string, len(hostConfig.Links)) copy(epConfig.Links, hostConfig.Links) networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig } if copts.aliases.Len() > 0 { epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] if epConfig == nil { epConfig = &networktypes.EndpointSettings{} } epConfig.Aliases = make([]string, copts.aliases.Len()) copy(epConfig.Aliases, copts.aliases.GetAll()) networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig } return config, hostConfig, networkingConfig, nil }