// setDefaultIsolation determine the default isolation mode for the // daemon to run in. This is only applicable on Windows func (daemon *Daemon) setDefaultIsolation() error { daemon.defaultIsolation = containertypes.Isolation("process") // On client SKUs, default to Hyper-V if system.IsWindowsClient() { daemon.defaultIsolation = containertypes.Isolation("hyperv") } for _, option := range daemon.configStore.ExecOptions { key, val, err := parsers.ParseKeyValueOpt(option) if err != nil { return err } key = strings.ToLower(key) switch key { case "isolation": if !containertypes.Isolation(val).IsValid() { return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val) } if containertypes.Isolation(val).IsHyperV() { daemon.defaultIsolation = containertypes.Isolation("hyperv") } if containertypes.Isolation(val).IsProcess() { if system.IsWindowsClient() { return fmt.Errorf("Windows client operating systems only support Hyper-V containers") } daemon.defaultIsolation = containertypes.Isolation("process") } default: return fmt.Errorf("Unrecognised exec-opt '%s'\n", key) } } logrus.Infof("Windows default isolation mode: %s", daemon.defaultIsolation) return nil }
func TestIsolation(t *testing.T) { ctx := &ctx.Context{} sc := &config.ServiceConfig{ Isolation: "default", } _, hostCfg, err := Convert(sc, ctx.Context, nil) assert.Nil(t, err) assert.Equal(t, container.Isolation("default"), hostCfg.Isolation) }
// callDecodeContainerConfigIsolation is a utility function to call // DecodeContainerConfig for validating isolation func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { var ( b []byte err error ) w := ContainerConfigWrapper{ Config: &container.Config{}, HostConfig: &container.HostConfig{ NetworkMode: "none", Isolation: container.Isolation(isolation)}, } if b, err = json.Marshal(w); err != nil { return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error()) } return DecodeContainerConfig(bytes.NewReader(b)) }
// setDefaultIsolation determine the default isolation mode for the // daemon to run in. This is only applicable on Windows func (daemon *Daemon) setDefaultIsolation() error { daemon.defaultIsolation = containertypes.Isolation("process") // On client SKUs, default to Hyper-V if system.IsWindowsClient() { daemon.defaultIsolation = containertypes.Isolation("hyperv") } for _, option := range daemon.configStore.ExecOptions { key, val, err := parsers.ParseKeyValueOpt(option) if err != nil { return err } key = strings.ToLower(key) switch key { case "isolation": if !containertypes.Isolation(val).IsValid() { return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val) } if containertypes.Isolation(val).IsHyperV() { daemon.defaultIsolation = containertypes.Isolation("hyperv") } if containertypes.Isolation(val).IsProcess() { if system.IsWindowsClient() { // @engine maintainers. This block should not be removed. It partially enforces licensing // restrictions on Windows. Ping @jhowardmsft if there are concerns or PRs to change this. return fmt.Errorf("Windows client operating systems only support Hyper-V containers") } daemon.defaultIsolation = containertypes.Isolation("process") } default: return fmt.Errorf("Unrecognised exec-opt '%s'\n", key) } } logrus.Infof("Windows default isolation mode: %s", daemon.defaultIsolation) 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, } // 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 }
func runBuild(dockerCli *command.DockerCli, options buildOptions) error { var ( buildCtx io.ReadCloser err error ) specifiedContext := options.context var ( contextDir string tempDir string relDockerfile string progBuff io.Writer buildBuff io.Writer ) progBuff = dockerCli.Out() buildBuff = dockerCli.Out() if options.quiet { progBuff = bytes.NewBuffer(nil) buildBuff = bytes.NewBuffer(nil) } switch { case specifiedContext == "-": buildCtx, relDockerfile, err = builder.GetContextFromReader(dockerCli.In(), options.dockerfileName) case urlutil.IsGitURL(specifiedContext): tempDir, relDockerfile, err = builder.GetContextFromGitURL(specifiedContext, options.dockerfileName) case urlutil.IsURL(specifiedContext): buildCtx, relDockerfile, err = builder.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName) default: contextDir, relDockerfile, err = builder.GetContextFromLocalDir(specifiedContext, options.dockerfileName) } if err != nil { if options.quiet && urlutil.IsURL(specifiedContext) { fmt.Fprintln(dockerCli.Err(), progBuff) } return fmt.Errorf("unable to prepare context: %s", err) } if tempDir != "" { defer os.RemoveAll(tempDir) contextDir = tempDir } if buildCtx == nil { // And canonicalize dockerfile name to a platform-independent one relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile) if err != nil { return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err) } f, err := os.Open(filepath.Join(contextDir, ".dockerignore")) if err != nil && !os.IsNotExist(err) { return err } defer f.Close() var excludes []string if err == nil { excludes, err = dockerignore.ReadAll(f) if err != nil { return err } } if err := builder.ValidateContextDirectory(contextDir, excludes); err != nil { return fmt.Errorf("Error checking context: '%s'.", err) } // If .dockerignore mentions .dockerignore or the Dockerfile // then make sure we send both files over to the daemon // because Dockerfile is, obviously, needed no matter what, and // .dockerignore is needed to know if either one needs to be // removed. The daemon will remove them for us, if needed, after it // parses the Dockerfile. Ignore errors here, as they will have been // caught by validateContextDirectory above. var includes = []string{"."} keepThem1, _ := fileutils.Matches(".dockerignore", excludes) keepThem2, _ := fileutils.Matches(relDockerfile, excludes) if keepThem1 || keepThem2 { includes = append(includes, ".dockerignore", relDockerfile) } buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{ Compression: archive.Uncompressed, ExcludePatterns: excludes, IncludeFiles: includes, }) if err != nil { return err } } ctx := context.Background() var resolvedTags []*resolvedTag if command.IsTrusted() { translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) { return TrustedReference(ctx, dockerCli, ref) } // Wrap the tar archive to replace the Dockerfile entry with the rewritten // Dockerfile which uses trusted pulls. buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags) } // Setup an upload progress bar progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true) if !dockerCli.Out().IsTerminal() { progressOutput = &lastProgressOutput{output: progressOutput} } var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon") var memory int64 if options.memory != "" { parsedMemory, err := units.RAMInBytes(options.memory) if err != nil { return err } memory = parsedMemory } var memorySwap int64 if options.memorySwap != "" { if options.memorySwap == "-1" { memorySwap = -1 } else { parsedMemorySwap, err := units.RAMInBytes(options.memorySwap) if err != nil { return err } memorySwap = parsedMemorySwap } } var shmSize int64 if options.shmSize != "" { shmSize, err = units.RAMInBytes(options.shmSize) if err != nil { return err } } authConfig, _ := dockerCli.CredentialsStore().GetAll() buildOptions := types.ImageBuildOptions{ Memory: memory, MemorySwap: memorySwap, Tags: options.tags.GetAll(), SuppressOutput: options.quiet, NoCache: options.noCache, Remove: options.rm, ForceRemove: options.forceRm, PullParent: options.pull, Isolation: container.Isolation(options.isolation), CPUSetCPUs: options.cpuSetCpus, CPUSetMems: options.cpuSetMems, CPUShares: options.cpuShares, CPUQuota: options.cpuQuota, CPUPeriod: options.cpuPeriod, CgroupParent: options.cgroupParent, Dockerfile: relDockerfile, ShmSize: shmSize, Ulimits: options.ulimits.GetList(), BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()), AuthConfigs: authConfig, Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), CacheFrom: options.cacheFrom, } response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions) if err != nil { if options.quiet { fmt.Fprintf(dockerCli.Err(), "%s", progBuff) } return err } defer response.Body.Close() err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), nil) if err != nil { if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 } if options.quiet { fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff) } return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code} } } // Windows: show error message about modified file permissions if the // daemon isn't running Windows. if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet { fmt.Fprintln(dockerCli.Err(), `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) } // Everything worked so if -q was provided the output from the daemon // should be just the image ID and we'll print that to stdout. if options.quiet { fmt.Fprintf(dockerCli.Out(), "%s", buildBuff) } if command.IsTrusted() { // Since the build was successful, now we must tag any of the resolved // images from the above Dockerfile rewrite. for _, resolved := range resolvedTags { if err := TagTrusted(ctx, dockerCli, resolved.digestRef, resolved.tagRef); err != nil { return err } } } return nil }
func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) { version := httputils.VersionFromContext(ctx) options := &types.ImageBuildOptions{} if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") { options.Remove = true } else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") { options.Remove = true } else { options.Remove = httputils.BoolValue(r, "rm") } if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") { options.PullParent = true } options.Dockerfile = r.FormValue("dockerfile") options.SuppressOutput = httputils.BoolValue(r, "q") options.NoCache = httputils.BoolValue(r, "nocache") options.ForceRemove = httputils.BoolValue(r, "forcerm") options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") options.Memory = httputils.Int64ValueOrZero(r, "memory") options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") options.CPUSetCPUs = r.FormValue("cpusetcpus") options.CPUSetMems = r.FormValue("cpusetmems") options.CgroupParent = r.FormValue("cgroupparent") options.Tags = r.Form["t"] if r.Form.Get("shmsize") != "" { shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64) if err != nil { return nil, err } options.ShmSize = shmSize } if i := container.Isolation(r.FormValue("isolation")); i != "" { if !container.Isolation.IsValid(i) { return nil, fmt.Errorf("Unsupported isolation: %q", i) } options.Isolation = i } var buildUlimits = []*units.Ulimit{} ulimitsJSON := r.FormValue("ulimits") if ulimitsJSON != "" { if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil { return nil, err } options.Ulimits = buildUlimits } var buildArgs = map[string]string{} buildArgsJSON := r.FormValue("buildargs") if buildArgsJSON != "" { if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil { return nil, err } options.BuildArgs = buildArgs } var labels = map[string]string{} labelsJSON := r.FormValue("labels") if labelsJSON != "" { if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil { return nil, err } options.Labels = labels } var cacheFrom = []string{} cacheFromJSON := r.FormValue("cachefrom") if cacheFromJSON != "" { if err := json.Unmarshal([]byte(cacheFromJSON), &cacheFrom); err != nil { return nil, err } options.CacheFrom = cacheFrom } return options, nil }
// createContainerPlatformSpecificSettings performs platform specific container create functionality func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error { // Make sure the host config has the default daemon isolation if not specified by caller. if containertypes.Isolation.IsDefault(containertypes.Isolation(hostConfig.Isolation)) { hostConfig.Isolation = daemon.defaultIsolation } for spec := range config.Volumes { mp, err := volume.ParseMountSpec(spec, hostConfig.VolumeDriver) if err != nil { return fmt.Errorf("Unrecognised volume spec: %v", err) } // If the mountpoint doesn't have a name, generate one. if len(mp.Name) == 0 { mp.Name = stringid.GenerateNonCryptoID() } // Skip volumes for which we already have something mounted on that // destination because of a --volume-from. if container.IsDestinationMounted(mp.Destination) { continue } volumeDriver := hostConfig.VolumeDriver // Create the volume in the volume driver. If it doesn't exist, // a new one will be created. v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil, nil) if err != nil { return err } // FIXME Windows: This code block is present in the Linux version and // allows the contents to be copied to the container FS prior to it // being started. However, the function utilizes the FollowSymLinkInScope // path which does not cope with Windows volume-style file paths. There // is a separate effort to resolve this (@swernli), so this processing // is deferred for now. A case where this would be useful is when // a dockerfile includes a VOLUME statement, but something is created // in that directory during the dockerfile processing. What this means // on Windows for TP5 is that in that scenario, the contents will not // copied, but that's (somewhat) OK as HCS will bomb out soon after // at it doesn't support mapped directories which have contents in the // destination path anyway. // // Example for repro later: // FROM windowsservercore // RUN mkdir c:\myvol // RUN copy c:\windows\system32\ntdll.dll c:\myvol // VOLUME "c:\myvol" // // Then // docker build -t vol . // docker run -it --rm vol cmd <-- This is where HCS will error out. // // // never attempt to copy existing content in a container FS to a shared volume // if v.DriverName() == volume.DefaultDriverName { // if err := container.CopyImagePathContent(v, mp.Destination); err != nil { // return err // } // } // Add it to container.MountPoints container.AddMountPointWithVolume(mp.Destination, v, mp.RW) } 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, 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 }
func TestImageBuild(t *testing.T) { v1 := "value1" v2 := "value2" emptyRegistryConfig := "bnVsbA==" buildCases := []struct { buildOptions types.ImageBuildOptions expectedQueryParams map[string]string expectedTags []string expectedRegistryConfig string }{ { buildOptions: types.ImageBuildOptions{ SuppressOutput: true, NoCache: true, Remove: true, ForceRemove: true, PullParent: true, }, expectedQueryParams: map[string]string{ "q": "1", "nocache": "1", "rm": "1", "forcerm": "1", "pull": "1", }, expectedTags: []string{}, expectedRegistryConfig: emptyRegistryConfig, }, { buildOptions: types.ImageBuildOptions{ SuppressOutput: false, NoCache: false, Remove: false, ForceRemove: false, PullParent: false, }, expectedQueryParams: map[string]string{ "q": "", "nocache": "", "rm": "0", "forcerm": "", "pull": "", }, expectedTags: []string{}, expectedRegistryConfig: emptyRegistryConfig, }, { buildOptions: types.ImageBuildOptions{ RemoteContext: "remoteContext", Isolation: container.Isolation("isolation"), CPUSetCPUs: "2", CPUSetMems: "12", CPUShares: 20, CPUQuota: 10, CPUPeriod: 30, Memory: 256, MemorySwap: 512, ShmSize: 10, CgroupParent: "cgroup_parent", Dockerfile: "Dockerfile", }, expectedQueryParams: map[string]string{ "remote": "remoteContext", "isolation": "isolation", "cpusetcpus": "2", "cpusetmems": "12", "cpushares": "20", "cpuquota": "10", "cpuperiod": "30", "memory": "256", "memswap": "512", "shmsize": "10", "cgroupparent": "cgroup_parent", "dockerfile": "Dockerfile", "rm": "0", }, expectedTags: []string{}, expectedRegistryConfig: emptyRegistryConfig, }, { buildOptions: types.ImageBuildOptions{ BuildArgs: map[string]*string{ "ARG1": &v1, "ARG2": &v2, "ARG3": nil, }, }, expectedQueryParams: map[string]string{ "buildargs": `{"ARG1":"value1","ARG2":"value2","ARG3":null}`, "rm": "0", }, expectedTags: []string{}, expectedRegistryConfig: emptyRegistryConfig, }, { buildOptions: types.ImageBuildOptions{ Ulimits: []*units.Ulimit{ { Name: "nproc", Hard: 65557, Soft: 65557, }, { Name: "nofile", Hard: 20000, Soft: 40000, }, }, }, expectedQueryParams: map[string]string{ "ulimits": `[{"Name":"nproc","Hard":65557,"Soft":65557},{"Name":"nofile","Hard":20000,"Soft":40000}]`, "rm": "0", }, expectedTags: []string{}, expectedRegistryConfig: emptyRegistryConfig, }, { buildOptions: types.ImageBuildOptions{ AuthConfigs: map[string]types.AuthConfig{ "https://index.docker.io/v1/": { Auth: "dG90bwo=", }, }, }, expectedQueryParams: map[string]string{ "rm": "0", }, expectedTags: []string{}, expectedRegistryConfig: "eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsiYXV0aCI6ImRHOTBid289In19", }, } for _, buildCase := range buildCases { expectedURL := "/build" client := &Client{ client: newMockClient(func(r *http.Request) (*http.Response, error) { if !strings.HasPrefix(r.URL.Path, expectedURL) { return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) } // Check request headers registryConfig := r.Header.Get("X-Registry-Config") if registryConfig != buildCase.expectedRegistryConfig { return nil, fmt.Errorf("X-Registry-Config header not properly set in the request. Expected '%s', got %s", buildCase.expectedRegistryConfig, registryConfig) } contentType := r.Header.Get("Content-Type") if contentType != "application/x-tar" { return nil, fmt.Errorf("Content-type header not properly set in the request. Expected 'application/x-tar', got %s", contentType) } // Check query parameters query := r.URL.Query() for key, expected := range buildCase.expectedQueryParams { actual := query.Get(key) if actual != expected { return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) } } // Check tags if len(buildCase.expectedTags) > 0 { tags := query["t"] if !reflect.DeepEqual(tags, buildCase.expectedTags) { return nil, fmt.Errorf("t (tags) not set in URL query properly. Expected '%s', got %s", buildCase.expectedTags, tags) } } headers := http.Header{} headers.Add("Server", "Docker/v1.23 (MyOS)") return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), Header: headers, }, nil }), } buildResponse, err := client.ImageBuild(context.Background(), nil, buildCase.buildOptions) if err != nil { t.Fatal(err) } if buildResponse.OSType != "MyOS" { t.Fatalf("expected OSType to be 'MyOS', got %s", buildResponse.OSType) } response, err := ioutil.ReadAll(buildResponse.Body) if err != nil { t.Fatal(err) } buildResponse.Body.Close() if string(response) != "body" { t.Fatalf("expected Body to contain 'body' string, got %s", response) } } }
func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) { version := httputils.VersionFromContext(ctx) options := &types.ImageBuildOptions{} if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") { options.Remove = true } else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") { options.Remove = true } else { options.Remove = httputils.BoolValue(r, "rm") } if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") { options.PullParent = true } options.Dockerfile = r.FormValue("dockerfile") options.SuppressOutput = httputils.BoolValue(r, "q") options.NoCache = httputils.BoolValue(r, "nocache") options.ForceRemove = httputils.BoolValue(r, "forcerm") options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") options.Memory = httputils.Int64ValueOrZero(r, "memory") options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") options.CPUSetCPUs = r.FormValue("cpusetcpus") options.CPUSetMems = r.FormValue("cpusetmems") options.CgroupParent = r.FormValue("cgroupparent") options.NetworkMode = r.FormValue("networkmode") options.Tags = r.Form["t"] options.SecurityOpt = r.Form["securityopt"] options.Squash = httputils.BoolValue(r, "squash") if r.Form.Get("shmsize") != "" { shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64) if err != nil { return nil, err } options.ShmSize = shmSize } if i := container.Isolation(r.FormValue("isolation")); i != "" { if !container.Isolation.IsValid(i) { return nil, fmt.Errorf("Unsupported isolation: %q", i) } options.Isolation = i } if runtime.GOOS != "windows" && options.SecurityOpt != nil { return nil, fmt.Errorf("the daemon on this platform does not support --security-opt to build") } var buildUlimits = []*units.Ulimit{} ulimitsJSON := r.FormValue("ulimits") if ulimitsJSON != "" { if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil { return nil, err } options.Ulimits = buildUlimits } var buildArgs = map[string]*string{} buildArgsJSON := r.FormValue("buildargs") // Note that there are two ways a --build-arg might appear in the // json of the query param: // "foo":"bar" // and "foo":nil // The first is the normal case, ie. --build-arg foo=bar // or --build-arg foo // where foo's value was picked up from an env var. // The second ("foo":nil) is where they put --build-arg foo // but "foo" isn't set as an env var. In that case we can't just drop // the fact they mentioned it, we need to pass that along to the builder // so that it can print a warning about "foo" being unused if there is // no "ARG foo" in the Dockerfile. if buildArgsJSON != "" { if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil { return nil, err } options.BuildArgs = buildArgs } var labels = map[string]string{} labelsJSON := r.FormValue("labels") if labelsJSON != "" { if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil { return nil, err } options.Labels = labels } var cacheFrom = []string{} cacheFromJSON := r.FormValue("cachefrom") if cacheFromJSON != "" { if err := json.Unmarshal([]byte(cacheFromJSON), &cacheFrom); err != nil { return nil, err } options.CacheFrom = cacheFrom } return options, nil }
// 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 }