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) } }
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") } }
// 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 }
// 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"] { if runtime.GOOS != "windows" { cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) } else { cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...) } } b.runConfig.Cmd = strslice.StrSlice(cmdSlice) 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 }
// asDockerConfig converts a RunContainerOptions into a Config understood by the // docker client func (rco RunContainerOptions) asDockerConfig() dockercontainer.Config { return dockercontainer.Config{ Image: getImageName(rco.Image), User: rco.User, Env: rco.Env, Entrypoint: dockerstrslice.StrSlice(rco.Entrypoint), OpenStdin: rco.Stdin != nil, StdinOnce: rco.Stdin != nil, AttachStdout: rco.Stdout != nil, } }
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 }
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) } }
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) } }
// 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 has no exit code!") } // 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 }
// 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)) }
// CreateContainers creates num of containers, returns a slice of container ids func (d *DockerHelper) CreateContainers(num int) []string { ids := []string{} for i := 0; i < num; i++ { name := newContainerName() cfg := &container.Config{ AttachStderr: false, AttachStdin: false, AttachStdout: false, Tty: true, Cmd: strslice.StrSlice([]string{TestCommand}), Image: TestImage, } container, err := d.client.ContainerCreate(getContext(), cfg, &container.HostConfig{}, &network.NetworkingConfig{}, name) ids = append(ids, container.ID) d.errStats.add("create containers", err) } return ids }
// ContainerExecCreate sets up an exec in a running container. func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) { container, err := d.getActiveContainer(config.Container) 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 { logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error()) } } processConfig := &execdriver.ProcessConfig{ CommonProcessConfig: execdriver.CommonProcessConfig{ Tty: config.Tty, Entrypoint: entrypoint, Arguments: args, }, } setPlatformSpecificExecProcessConfig(config, container, processConfig) execConfig := exec.NewConfig() execConfig.OpenStdin = config.AttachStdin execConfig.OpenStdout = config.AttachStdout execConfig.OpenStderr = config.AttachStderr execConfig.ProcessConfig = processConfig execConfig.ContainerID = container.ID execConfig.DetachKeys = keys d.registerExecCommand(container, execConfig) d.LogContainerEvent(container, "exec_create: "+execConfig.ProcessConfig.Entrypoint+" "+strings.Join(execConfig.ProcessConfig.Arguments, " ")) return execConfig.ID, nil }
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) } }
// 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 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 }
// 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) 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 }
// 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, } // 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 }
// Convert converts a service configuration to an docker API structures (Config and HostConfig) func Convert(c *project.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.Configs, ctx.ProjectName) if err != nil { return nil, nil, err } } config := &container.Config{ Entrypoint: strslice.StrSlice(utils.CopySlice(c.Entrypoint.Slice())), Hostname: c.Hostname, Domainname: c.DomainName, User: c.User, Env: utils.CopySlice(c.Environment.Slice()), Cmd: strslice.StrSlice(utils.CopySlice(c.Command.Slice())), Image: c.Image, Labels: utils.CopyMap(c.Labels.MapParts()), ExposedPorts: exposedPorts, Tty: c.Tty, OpenStdin: c.StdinOpen, WorkingDir: c.WorkingDir, Volumes: volumes(c, ctx), MacAddress: c.MacAddress, } 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, }) } } resources := container.Resources{ CgroupParent: c.CgroupParent, Memory: c.MemLimit, MemorySwap: c.MemSwapLimit, CPUShares: c.CPUShares, CPUQuota: c.CPUQuota, CpusetCpus: c.CPUSet, Ulimits: ulimits, Devices: deviceMappings, } hostConfig := &container.HostConfig{ VolumesFrom: volumesFrom, CapAdd: strslice.StrSlice(utils.CopySlice(c.CapAdd)), CapDrop: strslice.StrSlice(utils.CopySlice(c.CapDrop)), ExtraHosts: utils.CopySlice(c.ExtraHosts), Privileged: c.Privileged, Binds: Filter(c.Volumes, isBind), DNS: utils.CopySlice(c.DNS.Slice()), DNSSearch: utils.CopySlice(c.DNSSearch.Slice()), LogConfig: container.LogConfig{ Type: c.LogDriver, Config: utils.CopyMap(c.LogOpt), }, NetworkMode: container.NetworkMode(c.Net), ReadonlyRootfs: c.ReadOnly, PidMode: container.PidMode(c.Pid), UTSMode: container.UTSMode(c.Uts), IpcMode: container.IpcMode(c.Ipc), PortBindings: portBindings, RestartPolicy: *restartPolicy, SecurityOpt: utils.CopySlice(c.SecurityOpt), VolumeDriver: c.VolumeDriver, Resources: resources, } return config, hostConfig, nil }
func TestRunContainer(t *testing.T) { type runtest struct { calls []string image dockertypes.ImageInspect cmd string externalScripts bool paramScriptsURL string paramDestination string cmdExpected []string } tests := map[string]runtest{ "default": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: true, cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", api.Assemble)}, }, "paramDestination": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: true, paramDestination: "/opt/test", cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", api.Assemble)}, }, "paramDestination¶mScripts": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: true, paramDestination: "/opt/test", paramScriptsURL: "http://my.test.url/test?param=one", cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", api.Assemble)}, }, "scriptsInsideImageEnvironment": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{ScriptsURLEnvironment + "=image:///opt/bin/"}, }, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: false, cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", api.Assemble)}, }, "scriptsInsideImageLabel": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Labels: map[string]string{ScriptsURLLabel: "image:///opt/bin/"}, }, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: false, cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", api.Assemble)}, }, "scriptsInsideImageEnvironmentWithParamDestination": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{ScriptsURLEnvironment + "=image:///opt/bin"}, }, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: false, paramDestination: "/opt/sti", cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", api.Assemble)}, }, "scriptsInsideImageLabelWithParamDestination": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Labels: map[string]string{ScriptsURLLabel: "image:///opt/bin"}, }, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: false, paramDestination: "/opt/sti", cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", api.Assemble)}, }, "paramDestinationFromImageEnvironment": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Env: []string{LocationEnvironment + "=/opt", ScriptsURLEnvironment + "=http://my.test.url/test?param=one"}, }, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: true, cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", api.Assemble)}, }, "paramDestinationFromImageLabel": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{ Labels: map[string]string{DestinationLabel: "/opt", ScriptsURLLabel: "http://my.test.url/test?param=one"}, }, Config: &dockercontainer.Config{}, }, cmd: api.Assemble, externalScripts: true, cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", api.Assemble)}, }, "usageCommand": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, }, cmd: api.Usage, externalScripts: true, cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", api.Usage)}, }, "otherCommand": { calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"}, image: dockertypes.ImageInspect{ ContainerConfig: &dockercontainer.Config{}, Config: &dockercontainer.Config{}, }, cmd: api.Run, externalScripts: true, cmdExpected: []string{fmt.Sprintf("/tmp/scripts/%s", api.Run)}, }, } for desc, tst := range tests { fakeDocker := test.NewFakeDockerClient() dh := getDocker(fakeDocker) tst.image.ID = "test/image:latest" fakeDocker.Images = map[string]dockertypes.ImageInspect{tst.image.ID: tst.image} if len(fakeDocker.Containers) > 0 { t.Errorf("newly created fake client should have empty container map: %+v", fakeDocker.Containers) } //NOTE: the combo of the fake k8s client, go 1.6, and using os.Stderr/os.Stdout caused what appeared to be go test crashes // when we tried to call their closers in RunContainer err := dh.RunContainer(RunContainerOptions{ Image: "test/image", PullImage: true, ExternalScripts: tst.externalScripts, ScriptsURL: tst.paramScriptsURL, Destination: tst.paramDestination, Command: tst.cmd, Env: []string{"Key1=Value1", "Key2=Value2"}, Stdin: os.Stdin, //Stdout: os.Stdout, //Stderr: os.Stdout, }) if err != nil { t.Errorf("%s: Unexpected error: %v", desc, err) } // container ID will be random, so don't look up directly ... just get the 1 entry which should be there if len(fakeDocker.Containers) != 1 { t.Errorf("fake container map should only have 1 entry: %+v", fakeDocker.Containers) } for _, container := range fakeDocker.Containers { // Validate the Container parameters if container.Image != "test/image:latest" { t.Errorf("%s: Unexpected create config image: %s", desc, container.Image) } if !reflect.DeepEqual(container.Cmd, dockerstrslice.StrSlice(tst.cmdExpected)) { t.Errorf("%s: Unexpected create config command: %#v instead of %q", desc, container.Cmd, strings.Join(tst.cmdExpected, " ")) } if !reflect.DeepEqual(container.Env, []string{"Key1=Value1", "Key2=Value2"}) { t.Errorf("%s: Unexpected create config env: %#v", desc, container.Env) } if !reflect.DeepEqual(fakeDocker.Calls, tst.calls) { t.Errorf("%s: Expected fakeDocker.Calls %v, got %v", desc, tst.calls, fakeDocker.Calls) } } } }
// Parse parses the specified 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(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) { var ( // FIXME: use utils.ListOpts for attach and volumes? flAttach = opts.NewListOpts(ValidateAttach) flVolumes = opts.NewListOpts(nil) flTmpfs = opts.NewListOpts(nil) flBlkioWeightDevice = NewWeightdeviceOpt(ValidateWeightDevice) flDeviceReadBps = NewThrottledeviceOpt(ValidateThrottleBpsDevice) flDeviceWriteBps = NewThrottledeviceOpt(ValidateThrottleBpsDevice) flLinks = opts.NewListOpts(ValidateLink) flAliases = opts.NewListOpts(nil) flDeviceReadIOps = NewThrottledeviceOpt(ValidateThrottleIOpsDevice) flDeviceWriteIOps = NewThrottledeviceOpt(ValidateThrottleIOpsDevice) flEnv = opts.NewListOpts(ValidateEnv) flLabels = opts.NewListOpts(ValidateEnv) flDevices = opts.NewListOpts(ValidateDevice) flUlimits = NewUlimitOpt(nil) flPublish = opts.NewListOpts(nil) flExpose = opts.NewListOpts(nil) flDNS = opts.NewListOpts(opts.ValidateIPAddress) flDNSSearch = opts.NewListOpts(opts.ValidateDNSSearch) flDNSOptions = opts.NewListOpts(nil) flExtraHosts = opts.NewListOpts(ValidateExtraHost) flVolumesFrom = opts.NewListOpts(nil) flEnvFile = opts.NewListOpts(nil) flCapAdd = opts.NewListOpts(nil) flCapDrop = opts.NewListOpts(nil) flGroupAdd = opts.NewListOpts(nil) flSecurityOpt = opts.NewListOpts(nil) flLabelsFile = opts.NewListOpts(nil) flLoggingOpts = opts.NewListOpts(nil) flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container") flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use") flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use") flUsernsMode = cmd.String([]string{"-userns"}, "", "User namespace to use") flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports") 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") flOomKillDisable = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer") flOomScoreAdj = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)") flContainerIDFile = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file") flEntrypoint = cmd.String([]string{"-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") flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit") flMemorySwap = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") flKernelMemory = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit") flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])") flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") flCPUShares = cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCPUPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period") flCPUQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota") flCpusetCpus = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") flBlkioWeight = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000") flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)") flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIPv4Address = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)") flIPv6Address = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") flPidsLimit = cmd.Int64([]string{"-pids-limit"}, 0, "Tune container pids limit (set -1 for unlimited)") flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits") flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only") flLoggingDriver = cmd.String([]string{"-log-driver"}, "", "Logging driver for container") flCgroupParent = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container") flVolumeDriver = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container") flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)) flIsolation = cmd.String([]string{"-isolation"}, "", "Container isolation technology") flShmSize = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB") ) cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR") cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)") cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device") cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device") cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device") cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device") cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume") cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory") cmd.Var(&flLinks, []string{"-link"}, "Add link to another container") cmd.Var(&flAliases, []string{"-net-alias"}, "Add network-scoped alias for the container") cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container") cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container") cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels") cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables") cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host") cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports") cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers") cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains") cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options") cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)") cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)") cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities") cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities") cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join") cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options") cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options") cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options") cmd.Require(flag.Min, 1) if err := cmd.ParseFlags(args, true); err != nil { return nil, nil, nil, cmd, err } var ( attachStdin = flAttach.Get("stdin") attachStdout = flAttach.Get("stdout") attachStderr = flAttach.Get("stderr") ) // Validate the input mac address if *flMacAddress != "" { if _, err := ValidateMACAddress(*flMacAddress); err != nil { return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress) } } if *flStdin { attachStdin = true } // If -a is not set attach to the output stdio if flAttach.Len() == 0 { attachStdout = true attachStderr = true } var err error var flMemory int64 if *flMemoryString != "" { flMemory, err = units.RAMInBytes(*flMemoryString) if err != nil { return nil, nil, nil, cmd, err } } var MemoryReservation int64 if *flMemoryReservation != "" { MemoryReservation, err = units.RAMInBytes(*flMemoryReservation) if err != nil { return nil, nil, nil, cmd, err } } var memorySwap int64 if *flMemorySwap != "" { if *flMemorySwap == "-1" { memorySwap = -1 } else { memorySwap, err = units.RAMInBytes(*flMemorySwap) if err != nil { return nil, nil, nil, cmd, err } } } var KernelMemory int64 if *flKernelMemory != "" { KernelMemory, err = units.RAMInBytes(*flKernelMemory) if err != nil { return nil, nil, nil, cmd, err } } swappiness := *flSwappiness if swappiness != -1 && (swappiness < 0 || swappiness > 100) { return nil, nil, nil, cmd, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) } var shmSize int64 if *flShmSize != "" { shmSize, err = units.RAMInBytes(*flShmSize) if err != nil { return nil, nil, nil, cmd, err } } var binds []string // add any bind targets to the list of container volumes for bind := range flVolumes.GetMap() { if arr := volumeSplitN(bind, 2); len(arr) > 1 { // after creating the bind mount we want to delete it from the flVolumes values because // we do not want bind mounts being committed to image configs binds = append(binds, bind) flVolumes.Delete(bind) } } // Can't evaluate options passed into --tmpfs until we actually mount tmpfs := make(map[string]string) for _, t := range flTmpfs.GetAll() { if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil { return nil, nil, nil, cmd, err } tmpfs[arr[0]] = arr[1] } else { tmpfs[arr[0]] = "" } } var ( parsedArgs = cmd.Args() runCmd strslice.StrSlice entrypoint strslice.StrSlice image = cmd.Arg(0) ) if len(parsedArgs) > 1 { runCmd = strslice.StrSlice(parsedArgs[1:]) } if *flEntrypoint != "" { entrypoint = strslice.StrSlice{*flEntrypoint} } // Validate if the given hostname is RFC 1123 (https://tools.ietf.org/html/rfc1123) compliant. hostname := *flHostname if hostname != "" { matched, _ := regexp.MatchString("^(([[:alnum:]]|[[:alnum:]][[:alnum:]\\-]*[[:alnum:]])\\.)*([[:alnum:]]|[[:alnum:]][[:alnum:]\\-]*[[:alnum:]])$", hostname) if !matched { return nil, nil, nil, cmd, fmt.Errorf("invalid hostname format for --hostname: %s", hostname) } } ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) if err != nil { return nil, 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, nil, cmd, 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, cmd, 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, cmd, err } if _, exists := ports[p]; !exists { ports[p] = struct{}{} } } } // parse device mappings deviceMappings := []container.DeviceMapping{} for _, device := range flDevices.GetAll() { deviceMapping, err := ParseDevice(device) if err != nil { return nil, nil, nil, cmd, err } deviceMappings = append(deviceMappings, deviceMapping) } // collect all the environment variables for the container envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll()) if err != nil { return nil, nil, nil, cmd, err } // collect all the labels for the container labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll()) if err != nil { return nil, nil, nil, cmd, err } ipcMode := container.IpcMode(*flIpcMode) if !ipcMode.Valid() { return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode") } pidMode := container.PidMode(*flPidMode) if !pidMode.Valid() { return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode") } utsMode := container.UTSMode(*flUTSMode) if !utsMode.Valid() { return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode") } usernsMode := container.UsernsMode(*flUsernsMode) if !usernsMode.Valid() { return nil, nil, nil, cmd, fmt.Errorf("--userns: invalid USER mode") } restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) if err != nil { return nil, nil, nil, cmd, err } loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll()) if err != nil { return nil, nil, nil, cmd, err } securityOpts, err := parseSecurityOpts(flSecurityOpt.GetAll()) if err != nil { return nil, nil, nil, cmd, err } resources := container.Resources{ CgroupParent: *flCgroupParent, Memory: flMemory, MemoryReservation: MemoryReservation, MemorySwap: memorySwap, MemorySwappiness: flSwappiness, KernelMemory: KernelMemory, OomKillDisable: flOomKillDisable, CPUShares: *flCPUShares, CPUPeriod: *flCPUPeriod, CpusetCpus: *flCpusetCpus, CpusetMems: *flCpusetMems, CPUQuota: *flCPUQuota, PidsLimit: *flPidsLimit, BlkioWeight: *flBlkioWeight, BlkioWeightDevice: flBlkioWeightDevice.GetList(), BlkioDeviceReadBps: flDeviceReadBps.GetList(), BlkioDeviceWriteBps: flDeviceWriteBps.GetList(), BlkioDeviceReadIOps: flDeviceReadIOps.GetList(), BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(), Ulimits: flUlimits.GetList(), Devices: deviceMappings, } config := &container.Config{ Hostname: *flHostname, ExposedPorts: ports, User: *flUser, Tty: *flTty, // 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: *flStdin, AttachStdin: attachStdin, AttachStdout: attachStdout, AttachStderr: attachStderr, Env: envVariables, Cmd: runCmd, Image: image, Volumes: flVolumes.GetMap(), MacAddress: *flMacAddress, Entrypoint: entrypoint, WorkingDir: *flWorkingDir, Labels: ConvertKVStringsToMap(labels), } if cmd.IsSet("-stop-signal") { config.StopSignal = *flStopSignal } hostConfig := &container.HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, OomScoreAdj: *flOomScoreAdj, Privileged: *flPrivileged, PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, // 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: flDNS.GetAllOrEmpty(), DNSSearch: flDNSSearch.GetAllOrEmpty(), DNSOptions: flDNSOptions.GetAllOrEmpty(), ExtraHosts: flExtraHosts.GetAll(), VolumesFrom: flVolumesFrom.GetAll(), NetworkMode: container.NetworkMode(*flNetMode), IpcMode: ipcMode, PidMode: pidMode, UTSMode: utsMode, UsernsMode: usernsMode, CapAdd: strslice.StrSlice(flCapAdd.GetAll()), CapDrop: strslice.StrSlice(flCapDrop.GetAll()), GroupAdd: flGroupAdd.GetAll(), RestartPolicy: restartPolicy, SecurityOpt: securityOpts, ReadonlyRootfs: *flReadonlyRootfs, LogConfig: container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts}, VolumeDriver: *flVolumeDriver, Isolation: container.Isolation(*flIsolation), ShmSize: shmSize, Resources: resources, Tmpfs: tmpfs, } // 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 *flIPv4Address != "" || *flIPv6Address != "" { networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{ IPAMConfig: &networktypes.EndpointIPAMConfig{ IPv4Address: *flIPv4Address, IPv6Address: *flIPv6Address, }, } } 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 flAliases.Len() > 0 { epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] if epConfig == nil { epConfig = &networktypes.EndpointSettings{} } epConfig.Aliases = make([]string, flAliases.Len()) copy(epConfig.Aliases, flAliases.GetAll()) networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig } return config, hostConfig, networkingConfig, cmd, nil }
func dockerContainerCreateParamsToPortlayer(cc types.ContainerCreateConfig, layerID string, imageStore string) *containers.CreateParams { config := &models.ContainerCreateConfig{} // Image config.Image = new(string) *config.Image = layerID // Repo Requested config.RepoName = new(string) *config.RepoName = cc.Config.Image var path string var args []string // Expand cmd into entrypoint and args cmd := strslice.StrSlice(cc.Config.Cmd) if len(cc.Config.Entrypoint) != 0 { path, args = cc.Config.Entrypoint[0], append(cc.Config.Entrypoint[1:], cmd...) } else { path, args = cmd[0], cmd[1:] } //copy friendly name config.Name = new(string) *config.Name = cc.Name // copy the path config.Path = new(string) *config.Path = path // copy the args config.Args = make([]string, len(args)) copy(config.Args, args) // copy the env array config.Env = make([]string, len(cc.Config.Env)) copy(config.Env, cc.Config.Env) // image store config.ImageStore = &models.ImageStore{Name: imageStore} // network config.NetworkDisabled = new(bool) *config.NetworkDisabled = cc.Config.NetworkDisabled // working dir config.WorkingDir = new(string) *config.WorkingDir = cc.Config.WorkingDir // tty config.Tty = new(bool) *config.Tty = cc.Config.Tty // container stop signal config.StopSignal = new(string) *config.StopSignal = cc.Config.StopSignal log.Debugf("dockerContainerCreateParamsToPortlayer = %+v", config) return containers.NewCreateParamsWithContext(ctx).WithCreateConfig(config) }
// CreateContainer creates a new container in the given PodSandbox // Note: docker doesn't use LogPath yet. // TODO: check if the default values returned by the runtime API are ok. func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi.ContainerConfig, sandboxConfig *runtimeApi.PodSandboxConfig) (string, error) { if config == nil { return "", fmt.Errorf("container config is nil") } if sandboxConfig == nil { return "", fmt.Errorf("sandbox config is nil for container %q", config.Metadata.GetName()) } labels := makeLabels(config.GetLabels(), config.GetAnnotations()) // Apply a the container type label. labels[containerTypeLabelKey] = containerTypeLabelContainer // Write the container log path in the labels. labels[containerLogPathLabelKey] = filepath.Join(sandboxConfig.GetLogDirectory(), config.GetLogPath()) // Write the sandbox ID in the labels. labels[sandboxIDLabelKey] = podSandboxID image := "" if iSpec := config.GetImage(); iSpec != nil { image = iSpec.GetImage() } createConfig := dockertypes.ContainerCreateConfig{ Name: makeContainerName(sandboxConfig, config), Config: &dockercontainer.Config{ // TODO: set User. Entrypoint: dockerstrslice.StrSlice(config.GetCommand()), Cmd: dockerstrslice.StrSlice(config.GetArgs()), Env: generateEnvList(config.GetEnvs()), Image: image, WorkingDir: config.GetWorkingDir(), Labels: labels, // Interactive containers: OpenStdin: config.GetStdin(), StdinOnce: config.GetStdinOnce(), Tty: config.GetTty(), }, } // Fill the HostConfig. hc := &dockercontainer.HostConfig{ Binds: generateMountBindings(config.GetMounts()), } // Apply cgroupsParent derived from the sandbox config. if lc := sandboxConfig.GetLinux(); lc != nil { // Apply Cgroup options. // TODO: Check if this works with per-pod cgroups. // TODO: we need to pass the cgroup in syntax expected by cgroup driver but shim does not use docker info yet... hc.CgroupParent = lc.GetCgroupParent() } // Apply Linux-specific options if applicable. if lc := config.GetLinux(); lc != nil { // Apply resource options. // TODO: Check if the units are correct. // TODO: Can we assume the defaults are sane? rOpts := lc.GetResources() if rOpts != nil { hc.Resources = dockercontainer.Resources{ Memory: rOpts.GetMemoryLimitInBytes(), MemorySwap: -1, // Always disable memory swap. CPUShares: rOpts.GetCpuShares(), CPUQuota: rOpts.GetCpuQuota(), CPUPeriod: rOpts.GetCpuPeriod(), } hc.OomScoreAdj = int(rOpts.GetOomScoreAdj()) } // Note: ShmSize is handled in kube_docker_client.go // Apply security context. applyContainerSecurityContext(lc, podSandboxID, createConfig.Config, hc) } // Set devices for container. devices := make([]dockercontainer.DeviceMapping, len(config.Devices)) for i, device := range config.Devices { devices[i] = dockercontainer.DeviceMapping{ PathOnHost: device.GetHostPath(), PathInContainer: device.GetContainerPath(), CgroupPermissions: device.GetPermissions(), } } hc.Resources.Devices = devices // Apply appArmor and seccomp options. securityOpts, err := getContainerSecurityOpts(config.Metadata.GetName(), sandboxConfig, ds.seccompProfileRoot) if err != nil { return "", fmt.Errorf("failed to generate container security options for container %q: %v", config.Metadata.GetName(), err) } hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...) createConfig.HostConfig = hc createResp, err := ds.client.CreateContainer(createConfig) if createResp != nil { return createResp.ID, err } return "", err }
// CreateContainer creates a new container in the given PodSandbox // Note: docker doesn't use LogPath yet. // TODO: check if the default values returned by the runtime API are ok. func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi.ContainerConfig, sandboxConfig *runtimeApi.PodSandboxConfig) (string, error) { if config == nil { return "", fmt.Errorf("container config is nil") } if sandboxConfig == nil { return "", fmt.Errorf("sandbox config is nil for container %q", config.Metadata.GetName()) } labels := makeLabels(config.GetLabels(), config.GetAnnotations()) // Apply a the container type label. labels[containerTypeLabelKey] = containerTypeLabelContainer // Write the sandbox ID in the labels. labels[sandboxIDLabelKey] = podSandboxID image := "" if iSpec := config.GetImage(); iSpec != nil { image = iSpec.GetImage() } createConfig := dockertypes.ContainerCreateConfig{ Name: makeContainerName(sandboxConfig, config), Config: &dockercontainer.Config{ // TODO: set User. Entrypoint: dockerstrslice.StrSlice(config.GetCommand()), Cmd: dockerstrslice.StrSlice(config.GetArgs()), Env: generateEnvList(config.GetEnvs()), Image: image, WorkingDir: config.GetWorkingDir(), Labels: labels, // Interactive containers: OpenStdin: config.GetStdin(), StdinOnce: config.GetStdinOnce(), Tty: config.GetTty(), }, } // Fill the HostConfig. hc := &dockercontainer.HostConfig{ Binds: generateMountBindings(config.GetMounts()), ReadonlyRootfs: config.GetReadonlyRootfs(), Privileged: config.GetPrivileged(), } // Apply options derived from the sandbox config. if lc := sandboxConfig.GetLinux(); lc != nil { // Apply Cgroup options. // TODO: Check if this works with per-pod cgroups. hc.CgroupParent = lc.GetCgroupParent() // Apply namespace options. sandboxNSMode := fmt.Sprintf("container:%v", podSandboxID) hc.NetworkMode = dockercontainer.NetworkMode(sandboxNSMode) hc.IpcMode = dockercontainer.IpcMode(sandboxNSMode) hc.UTSMode = "" hc.PidMode = "" nsOpts := lc.GetNamespaceOptions() if nsOpts != nil { if nsOpts.GetHostNetwork() { hc.UTSMode = namespaceModeHost } if nsOpts.GetHostPid() { hc.PidMode = namespaceModeHost } } } // Apply Linux-specific options if applicable. if lc := config.GetLinux(); lc != nil { // Apply resource options. // TODO: Check if the units are correct. // TODO: Can we assume the defaults are sane? rOpts := lc.GetResources() if rOpts != nil { hc.Resources = dockercontainer.Resources{ Memory: rOpts.GetMemoryLimitInBytes(), MemorySwap: -1, // Always disable memory swap. CPUShares: rOpts.GetCpuShares(), CPUQuota: rOpts.GetCpuQuota(), CPUPeriod: rOpts.GetCpuPeriod(), // TODO: Need to set devices. } hc.OomScoreAdj = int(rOpts.GetOomScoreAdj()) } // Note: ShmSize is handled in kube_docker_client.go } var err error hc.SecurityOpt, err = getContainerSecurityOpts(config.Metadata.GetName(), sandboxConfig, ds.seccompProfileRoot) if err != nil { return "", fmt.Errorf("failed to generate container security options for container %q: %v", config.Metadata.GetName(), err) } // TODO: Add or drop capabilities. createConfig.HostConfig = hc createResp, err := ds.client.CreateContainer(createConfig) if createResp != nil { return createResp.ID, err } return "", err }
// 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}, true) 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) }
// 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 fmt.Errorf("HEALTHCHECK requires an argument") } 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 } if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", b.runConfig.Healthcheck)); err != nil { return err } return nil }
// 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, } 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, }) } } resources := container.Resources{ CgroupParent: c.CgroupParent, Memory: int64(c.MemLimit), MemorySwap: int64(c.MemSwapLimit), 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 :) } } hostConfig := &container.HostConfig{ VolumesFrom: volumesFrom, CapAdd: strslice.StrSlice(utils.CopySlice(c.CapAdd)), CapDrop: strslice.StrSlice(utils.CopySlice(c.CapDrop)), ExtraHosts: utils.CopySlice(c.ExtraHosts), Privileged: c.Privileged, Binds: Filter(vols, isBind), DNS: utils.CopySlice(c.DNS), DNSSearch: utils.CopySlice(c.DNSSearch), LogConfig: container.LogConfig{ Type: c.Logging.Driver, Config: utils.CopyMap(c.Logging.Options), }, NetworkMode: container.NetworkMode(networkMode), ReadonlyRootfs: c.ReadOnly, 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), VolumeDriver: c.VolumeDriver, Resources: resources, } if config.Labels == nil { config.Labels = map[string]string{} } return config, hostConfig, nil }
// CreateContainer creates a new container in the given PodSandbox // Note: docker doesn't use LogPath yet. // TODO: check if the default values returned by the runtime API are ok. func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeApi.ContainerConfig, sandboxConfig *runtimeApi.PodSandboxConfig) (string, error) { if config == nil { return "", fmt.Errorf("container config is nil") } if sandboxConfig == nil { return "", fmt.Errorf("sandbox config is nil for container %q", config.GetName()) } // Merge annotations and labels because docker supports only labels. // TODO: add a prefix to annotations so that we can distinguish labels and // annotations when reading back them from the docker container. // TODO: should we apply docker-specific labels? labels := config.GetLabels() for k, v := range config.GetAnnotations() { if _, ok := labels[k]; !ok { // Only write to labels if the key doesn't exist. labels[k] = v } } image := "" if iSpec := config.GetImage(); iSpec != nil { image = iSpec.GetImage() } createConfig := dockertypes.ContainerCreateConfig{ Name: config.GetName(), Config: &dockercontainer.Config{ // TODO: set User. Hostname: sandboxConfig.GetHostname(), Entrypoint: dockerstrslice.StrSlice(config.GetCommand()), Cmd: dockerstrslice.StrSlice(config.GetArgs()), Env: generateEnvList(config.GetEnvs()), Image: image, WorkingDir: config.GetWorkingDir(), Labels: labels, // Interactive containers: OpenStdin: config.GetStdin(), StdinOnce: config.GetStdinOnce(), Tty: config.GetTty(), }, } // Fill the HostConfig. hc := &dockercontainer.HostConfig{ Binds: generateMountBindings(config.GetMounts()), ReadonlyRootfs: config.GetReadonlyRootfs(), Privileged: config.GetPrivileged(), } // Apply options derived from the sandbox config. if lc := sandboxConfig.GetLinux(); lc != nil { // Apply Cgroup options. // TODO: Check if this works with per-pod cgroups. hc.CgroupParent = lc.GetCgroupParent() // Apply namespace options. sandboxNSMode := fmt.Sprintf("container:%v", podSandboxID) hc.NetworkMode = dockercontainer.NetworkMode(sandboxNSMode) hc.IpcMode = dockercontainer.IpcMode(sandboxNSMode) hc.UTSMode = "" hc.PidMode = "" nsOpts := lc.GetNamespaceOptions() if nsOpts != nil { if nsOpts.GetHostNetwork() { hc.UTSMode = namespaceModeHost } if nsOpts.GetHostPid() { hc.PidMode = namespaceModeHost } } } // Apply Linux-specific options if applicable. if lc := config.GetLinux(); lc != nil { // Apply resource options. // TODO: Check if the units are correct. // TODO: Can we assume the defaults are sane? rOpts := lc.GetResources() if rOpts != nil { hc.Resources = dockercontainer.Resources{ Memory: rOpts.GetMemoryLimitInBytes(), MemorySwap: -1, // Always disable memory swap. CPUShares: rOpts.GetCpuShares(), CPUQuota: rOpts.GetCpuQuota(), CPUPeriod: rOpts.GetCpuPeriod(), // TODO: Need to set devices. } hc.OomScoreAdj = int(rOpts.GetOomScoreAdj()) } // Note: ShmSize is handled in kube_docker_client.go } // TODO: Seccomp support. Need to figure out how to pass seccomp options // through the runtime API (annotations?).See dockerManager.getSecurityOpts() // for the details. Always set the default seccomp profile for now. hc.SecurityOpt = []string{fmt.Sprintf("%s=%s", "seccomp", defaultSeccompProfile)} // TODO: Add or drop capabilities. createConfig.HostConfig = hc createResp, err := ds.client.CreateContainer(createConfig) if createResp != nil { return createResp.ID, err } return "", err }