示例#1
0
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")
	}
}
示例#2
0
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)
	}
}
示例#3
0
// 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
}
示例#4
0
// 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
}
示例#5
0
// WORKDIR /tmp
//
// Set the working directory for future RUN/CMD/etc statements.
//
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
	if len(args) != 1 {
		return errExactlyOneArgument("WORKDIR")
	}

	err := b.flags.Parse()
	if err != nil {
		return err
	}

	// This is from the Dockerfile and will not necessarily be in platform
	// specific semantics, hence ensure it is converted.
	b.runConfig.WorkingDir, err = normaliseWorkdir(b.runConfig.WorkingDir, args[0])
	if err != nil {
		return err
	}

	// For performance reasons, we explicitly do a create/mkdir now
	// This avoids having an unnecessary expensive mount/unmount calls
	// (on Windows in particular) during each container create.
	// Prior to 1.13, the mkdir was deferred and not executed at this step.
	if b.disableCommit {
		// Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo".
		// We've already updated the runConfig and that's enough.
		return nil
	}
	b.runConfig.Image = b.image

	cmd := b.runConfig.Cmd
	comment := "WORKDIR " + b.runConfig.WorkingDir
	// reset the command for cache detection
	b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) "+comment))
	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)

	if hit, err := b.probeCache(); err != nil {
		return err
	} else if hit {
		return nil
	}

	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig})
	if err != nil {
		return err
	}
	b.tmpContainers[container.ID] = struct{}{}
	if err := b.docker.ContainerCreateWorkdir(container.ID); err != nil {
		return err
	}

	return b.commit(container.ID, cmd, comment)
}
示例#6
0
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
}
示例#7
0
文件: health.go 项目: Mic92/docker
// exec the healthcheck command in the container.
// Returns the exit code and probe output (if any)
func (p *cmdProbe) run(ctx context.Context, d *Daemon, container *container.Container) (*types.HealthcheckResult, error) {

	cmdSlice := strslice.StrSlice(container.Config.Healthcheck.Test)[1:]
	if p.shell {
		if runtime.GOOS != "windows" {
			cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
		} else {
			cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...)
		}
	}
	entrypoint, args := d.getEntrypointAndArgs(strslice.StrSlice{}, cmdSlice)
	execConfig := exec.NewConfig()
	execConfig.OpenStdin = false
	execConfig.OpenStdout = true
	execConfig.OpenStderr = true
	execConfig.ContainerID = container.ID
	execConfig.DetachKeys = []byte{}
	execConfig.Entrypoint = entrypoint
	execConfig.Args = args
	execConfig.Tty = false
	execConfig.Privileged = false
	execConfig.User = container.Config.User

	d.registerExecCommand(container, execConfig)
	d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " "))

	output := &limitedBuffer{}
	err := d.ContainerExecStart(ctx, execConfig.ID, nil, output, output)
	if err != nil {
		return nil, err
	}
	info, err := d.getExecConfig(execConfig.ID)
	if err != nil {
		return nil, err
	}
	if info.ExitCode == nil {
		return nil, fmt.Errorf("Healthcheck for container %s has no exit code!", container.ID)
	}
	// Note: Go's json package will handle invalid UTF-8 for us
	out := output.String()
	return &types.HealthcheckResult{
		End:      time.Now(),
		ExitCode: *info.ExitCode,
		Output:   out,
	}, nil
}
示例#8
0
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)
	}
}
示例#9
0
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)
	}
}
示例#10
0
文件: exec.go 项目: CWSpear/docker
// ContainerExecCreate sets up an exec in a running container.
func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (string, error) {
	container, err := d.getActiveContainer(name)
	if err != nil {
		return "", err
	}

	cmd := strslice.StrSlice(config.Cmd)
	entrypoint, args := d.getEntrypointAndArgs(strslice.StrSlice{}, cmd)

	keys := []byte{}
	if config.DetachKeys != "" {
		keys, err = term.ToBytes(config.DetachKeys)
		if err != nil {
			err = fmt.Errorf("Invalid escape keys (%s) provided", config.DetachKeys)
			return "", err
		}
	}

	execConfig := exec.NewConfig()
	execConfig.OpenStdin = config.AttachStdin
	execConfig.OpenStdout = config.AttachStdout
	execConfig.OpenStderr = config.AttachStderr
	execConfig.ContainerID = container.ID
	execConfig.DetachKeys = keys
	execConfig.Entrypoint = entrypoint
	execConfig.Args = args
	execConfig.Tty = config.Tty
	execConfig.Privileged = config.Privileged
	execConfig.User = config.User
	execConfig.Env = []string{
		"PATH=" + system.DefaultPathEnv,
	}
	if config.Tty {
		execConfig.Env = append(execConfig.Env, "TERM=xterm")
	}
	execConfig.Env = utils.ReplaceOrAppendEnvValues(execConfig.Env, container.Config.Env)
	if len(execConfig.User) == 0 {
		execConfig.User = container.Config.User
	}

	d.registerExecCommand(container, execConfig)

	d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " "))

	return execConfig.ID, nil
}
示例#11
0
// 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))
}
示例#12
0
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)
	}
}
示例#13
0
// 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
}
示例#14
0
// HEALTHCHECK foo
//
// Set the default healthcheck command to run in the container (which may be empty).
// Argument handling is the same as RUN.
//
func healthcheck(b *Builder, args []string, attributes map[string]bool, original string) error {
	if len(args) == 0 {
		return errAtLeastOneArgument("HEALTHCHECK")
	}
	typ := strings.ToUpper(args[0])
	args = args[1:]
	if typ == "NONE" {
		if len(args) != 0 {
			return fmt.Errorf("HEALTHCHECK NONE takes no arguments")
		}
		test := strslice.StrSlice{typ}
		b.runConfig.Healthcheck = &container.HealthConfig{
			Test: test,
		}
	} else {
		if b.runConfig.Healthcheck != nil {
			oldCmd := b.runConfig.Healthcheck.Test
			if len(oldCmd) > 0 && oldCmd[0] != "NONE" {
				fmt.Fprintf(b.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd)
			}
		}

		healthcheck := container.HealthConfig{}

		flInterval := b.flags.AddString("interval", "")
		flTimeout := b.flags.AddString("timeout", "")
		flRetries := b.flags.AddString("retries", "")

		if err := b.flags.Parse(); err != nil {
			return err
		}

		switch typ {
		case "CMD":
			cmdSlice := handleJSONArgs(args, attributes)
			if len(cmdSlice) == 0 {
				return fmt.Errorf("Missing command after HEALTHCHECK CMD")
			}

			if !attributes["json"] {
				typ = "CMD-SHELL"
			}

			healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...))
		default:
			return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ)
		}

		interval, err := parseOptInterval(flInterval)
		if err != nil {
			return err
		}
		healthcheck.Interval = interval

		timeout, err := parseOptInterval(flTimeout)
		if err != nil {
			return err
		}
		healthcheck.Timeout = timeout

		if flRetries.Value != "" {
			retries, err := strconv.ParseInt(flRetries.Value, 10, 32)
			if err != nil {
				return err
			}
			if retries < 1 {
				return fmt.Errorf("--retries must be at least 1 (not %d)", retries)
			}
			healthcheck.Retries = int(retries)
		} else {
			healthcheck.Retries = 0
		}

		b.runConfig.Healthcheck = &healthcheck
	}

	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", b.runConfig.Healthcheck))
}
示例#15
0
// 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")
}
示例#16
0
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string) error {
	if b.context == nil {
		return fmt.Errorf("No context given. Impossible to use %s", cmdName)
	}

	if len(args) < 2 {
		return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
	}

	// Work in daemon-specific filepath semantics
	dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest

	b.runConfig.Image = b.image

	var infos []copyInfo

	// Loop through each src file and calculate the info we need to
	// do the copy (e.g. hash value if cached).  Don't actually do
	// the copy until we've looked at all src files
	var err error
	for _, orig := range args[0 : len(args)-1] {
		var fi builder.FileInfo
		decompress := allowLocalDecompression
		if urlutil.IsURL(orig) {
			if !allowRemote {
				return fmt.Errorf("Source can't be a URL for %s", cmdName)
			}
			fi, err = b.download(orig)
			if err != nil {
				return err
			}
			defer os.RemoveAll(filepath.Dir(fi.Path()))
			decompress = false
			infos = append(infos, copyInfo{fi, decompress})
			continue
		}
		// not a URL
		subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true)
		if err != nil {
			return err
		}

		infos = append(infos, subInfos...)
	}

	if len(infos) == 0 {
		return fmt.Errorf("No source files were specified")
	}
	if len(infos) > 1 && !strings.HasSuffix(dest, string(os.PathSeparator)) {
		return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
	}

	// For backwards compat, if there's just one info then use it as the
	// cache look-up string, otherwise hash 'em all into one
	var srcHash string
	var origPaths string

	if len(infos) == 1 {
		fi := infos[0].FileInfo
		origPaths = fi.Name()
		if hfi, ok := fi.(builder.Hashed); ok {
			srcHash = hfi.Hash()
		}
	} else {
		var hashs []string
		var origs []string
		for _, info := range infos {
			fi := info.FileInfo
			origs = append(origs, fi.Name())
			if hfi, ok := fi.(builder.Hashed); ok {
				hashs = append(hashs, hfi.Hash())
			}
		}
		hasher := sha256.New()
		hasher.Write([]byte(strings.Join(hashs, ",")))
		srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil))
		origPaths = strings.Join(origs, " ")
	}

	cmd := b.runConfig.Cmd
	b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest)))
	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)

	if hit, err := b.probeCache(); err != nil {
		return err
	} else if hit {
		return nil
	}

	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig})
	if err != nil {
		return err
	}
	b.tmpContainers[container.ID] = struct{}{}

	comment := fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)

	// Twiddle the destination when its a relative path - meaning, make it
	// relative to the WORKINGDIR
	if dest, err = normaliseDest(cmdName, b.runConfig.WorkingDir, dest); err != nil {
		return err
	}

	for _, info := range infos {
		if err := b.docker.CopyOnBuild(container.ID, dest, info.FileInfo, info.decompress); err != nil {
			return err
		}
	}

	return b.commit(container.ID, cmd, comment)
}
示例#17
0
// 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
}
示例#18
0
// 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
}