| package daemon |
| |
| import ( |
| "fmt" |
| "net" |
| "runtime" |
| "strings" |
| "time" |
| |
| "github.com/pkg/errors" |
| |
| "github.com/docker/docker/api/types" |
| containertypes "github.com/docker/docker/api/types/container" |
| networktypes "github.com/docker/docker/api/types/network" |
| "github.com/docker/docker/container" |
| "github.com/docker/docker/image" |
| "github.com/docker/docker/layer" |
| "github.com/docker/docker/pkg/idtools" |
| "github.com/docker/docker/pkg/stringid" |
| "github.com/docker/docker/pkg/system" |
| "github.com/docker/docker/runconfig" |
| "github.com/opencontainers/selinux/go-selinux/label" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| // CreateManagedContainer creates a container that is managed by a Service |
| func (daemon *Daemon) CreateManagedContainer(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) { |
| return daemon.containerCreate(params, true) |
| } |
| |
| // ContainerCreate creates a regular container |
| func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) { |
| return daemon.containerCreate(params, false) |
| } |
| |
| func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) { |
| start := time.Now() |
| if params.Config == nil { |
| return containertypes.ContainerCreateCreatedBody{}, validationError{errors.New("Config cannot be empty in order to create a container")} |
| } |
| |
| os := runtime.GOOS |
| if params.Config.Image != "" { |
| img, err := daemon.GetImage(params.Config.Image) |
| if err == nil { |
| os = img.OS |
| } |
| } else { |
| // This mean scratch. On Windows, we can safely assume that this is a linux |
| // container. On other platforms, it's the host OS (which it already is) |
| if runtime.GOOS == "windows" && system.LCOWSupported() { |
| os = "linux" |
| } |
| } |
| |
| warnings, err := daemon.verifyContainerSettings(os, params.HostConfig, params.Config, false) |
| if err != nil { |
| return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err} |
| } |
| |
| err = verifyNetworkingConfig(params.NetworkingConfig) |
| if err != nil { |
| return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err} |
| } |
| |
| if params.HostConfig == nil { |
| params.HostConfig = &containertypes.HostConfig{} |
| } |
| err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares) |
| if err != nil { |
| return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err} |
| } |
| |
| container, err := daemon.create(params, managed) |
| if err != nil { |
| return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err |
| } |
| containerActions.WithValues("create").UpdateSince(start) |
| |
| return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil |
| } |
| |
| // Create creates a new container from the given configuration with a given name. |
| func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) { |
| var ( |
| container *container.Container |
| img *image.Image |
| imgID image.ID |
| err error |
| ) |
| |
| os := runtime.GOOS |
| if params.Config.Image != "" { |
| img, err = daemon.GetImage(params.Config.Image) |
| if err != nil { |
| return nil, err |
| } |
| os = img.OS |
| imgID = img.ID() |
| |
| if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() { |
| return nil, errors.New("operating system on which parent image was created is not Windows") |
| } |
| } else { |
| if runtime.GOOS == "windows" { |
| os = "linux" // 'scratch' case. |
| } |
| } |
| |
| if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil { |
| return nil, validationError{err} |
| } |
| |
| if err := daemon.mergeAndVerifyLogConfig(¶ms.HostConfig.LogConfig); err != nil { |
| return nil, validationError{err} |
| } |
| |
| if container, err = daemon.newContainer(params.Name, os, params.Config, params.HostConfig, imgID, managed); err != nil { |
| return nil, err |
| } |
| defer func() { |
| if retErr != nil { |
| if err := daemon.cleanupContainer(container, true, true); err != nil { |
| logrus.Errorf("failed to cleanup container on create error: %v", err) |
| } |
| } |
| }() |
| |
| if err := daemon.setSecurityOptions(container, params.HostConfig); err != nil { |
| return nil, err |
| } |
| |
| container.HostConfig.StorageOpt = params.HostConfig.StorageOpt |
| |
| // Fixes: https://github.com/moby/moby/issues/34074 and |
| // https://github.com/docker/for-win/issues/999. |
| // Merge the daemon's storage options if they aren't already present. We only |
| // do this on Windows as there's no effective sandbox size limit other than |
| // physical on Linux. |
| if runtime.GOOS == "windows" { |
| if container.HostConfig.StorageOpt == nil { |
| container.HostConfig.StorageOpt = make(map[string]string) |
| } |
| for _, v := range daemon.configStore.GraphOptions { |
| opt := strings.SplitN(v, "=", 2) |
| if _, ok := container.HostConfig.StorageOpt[opt[0]]; !ok { |
| container.HostConfig.StorageOpt[opt[0]] = opt[1] |
| } |
| } |
| } |
| |
| // Set RWLayer for container after mount labels have been set |
| if err := daemon.setRWLayer(container); err != nil { |
| return nil, systemError{err} |
| } |
| |
| rootIDs := daemon.idMappings.RootPair() |
| if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil { |
| return nil, err |
| } |
| if err := idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err != nil { |
| return nil, err |
| } |
| |
| if err := daemon.setHostConfig(container, params.HostConfig); err != nil { |
| return nil, err |
| } |
| |
| if err := daemon.createContainerOSSpecificSettings(container, params.Config, params.HostConfig); err != nil { |
| return nil, err |
| } |
| |
| var endpointsConfigs map[string]*networktypes.EndpointSettings |
| if params.NetworkingConfig != nil { |
| endpointsConfigs = params.NetworkingConfig.EndpointsConfig |
| } |
| // Make sure NetworkMode has an acceptable value. We do this to ensure |
| // backwards API compatibility. |
| runconfig.SetDefaultNetModeIfBlank(container.HostConfig) |
| |
| daemon.updateContainerNetworkSettings(container, endpointsConfigs) |
| if err := daemon.Register(container); err != nil { |
| return nil, err |
| } |
| stateCtr.set(container.ID, "stopped") |
| daemon.LogContainerEvent(container, "create") |
| return container, nil |
| } |
| |
| func toHostConfigSelinuxLabels(labels []string) []string { |
| for i, l := range labels { |
| labels[i] = "label=" + l |
| } |
| return labels |
| } |
| |
| func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig) ([]string, error) { |
| for _, opt := range hostConfig.SecurityOpt { |
| con := strings.Split(opt, "=") |
| if con[0] == "label" { |
| // Caller overrode SecurityOpts |
| return nil, nil |
| } |
| } |
| ipcMode := hostConfig.IpcMode |
| pidMode := hostConfig.PidMode |
| privileged := hostConfig.Privileged |
| if ipcMode.IsHost() || pidMode.IsHost() || privileged { |
| return toHostConfigSelinuxLabels(label.DisableSecOpt()), nil |
| } |
| |
| var ipcLabel []string |
| var pidLabel []string |
| ipcContainer := ipcMode.Container() |
| pidContainer := pidMode.Container() |
| if ipcContainer != "" { |
| c, err := daemon.GetContainer(ipcContainer) |
| if err != nil { |
| return nil, err |
| } |
| ipcLabel = label.DupSecOpt(c.ProcessLabel) |
| if pidContainer == "" { |
| return toHostConfigSelinuxLabels(ipcLabel), err |
| } |
| } |
| if pidContainer != "" { |
| c, err := daemon.GetContainer(pidContainer) |
| if err != nil { |
| return nil, err |
| } |
| |
| pidLabel = label.DupSecOpt(c.ProcessLabel) |
| if ipcContainer == "" { |
| return toHostConfigSelinuxLabels(pidLabel), err |
| } |
| } |
| |
| if pidLabel != nil && ipcLabel != nil { |
| for i := 0; i < len(pidLabel); i++ { |
| if pidLabel[i] != ipcLabel[i] { |
| return nil, fmt.Errorf("--ipc and --pid containers SELinux labels aren't the same") |
| } |
| } |
| return toHostConfigSelinuxLabels(pidLabel), nil |
| } |
| return nil, nil |
| } |
| |
| func (daemon *Daemon) setRWLayer(container *container.Container) error { |
| var layerID layer.ChainID |
| if container.ImageID != "" { |
| img, err := daemon.stores[container.OS].imageStore.Get(container.ImageID) |
| if err != nil { |
| return err |
| } |
| layerID = img.RootFS.ChainID() |
| } |
| |
| rwLayerOpts := &layer.CreateRWLayerOpts{ |
| MountLabel: container.MountLabel, |
| InitFunc: daemon.getLayerInit(), |
| StorageOpt: container.HostConfig.StorageOpt, |
| } |
| |
| rwLayer, err := daemon.stores[container.OS].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts) |
| if err != nil { |
| return err |
| } |
| container.RWLayer = rwLayer |
| |
| return nil |
| } |
| |
| // VolumeCreate creates a volume with the specified name, driver, and opts |
| // This is called directly from the Engine API |
| func (daemon *Daemon) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) { |
| if name == "" { |
| name = stringid.GenerateNonCryptoID() |
| } |
| |
| v, err := daemon.volumes.Create(name, driverName, opts, labels) |
| if err != nil { |
| return nil, err |
| } |
| |
| daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()}) |
| apiV := volumeToAPIType(v) |
| apiV.Mountpoint = v.Path() |
| return apiV, nil |
| } |
| |
| func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error { |
| if img != nil && img.Config != nil { |
| if err := merge(config, img.Config); err != nil { |
| return err |
| } |
| } |
| // Reset the Entrypoint if it is [""] |
| if len(config.Entrypoint) == 1 && config.Entrypoint[0] == "" { |
| config.Entrypoint = nil |
| } |
| if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { |
| return fmt.Errorf("No command specified") |
| } |
| return nil |
| } |
| |
| // Checks if the client set configurations for more than one network while creating a container |
| // Also checks if the IPAMConfig is valid |
| func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error { |
| if nwConfig == nil || len(nwConfig.EndpointsConfig) == 0 { |
| return nil |
| } |
| if len(nwConfig.EndpointsConfig) == 1 { |
| for _, v := range nwConfig.EndpointsConfig { |
| if v != nil && v.IPAMConfig != nil { |
| if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil { |
| return errors.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address) |
| } |
| if v.IPAMConfig.IPv6Address != "" { |
| n := net.ParseIP(v.IPAMConfig.IPv6Address) |
| // if the address is an invalid network address (ParseIP == nil) or if it is |
| // an IPv4 address (To4() != nil), then it is an invalid IPv6 address |
| if n == nil || n.To4() != nil { |
| return errors.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address) |
| } |
| } |
| } |
| } |
| return nil |
| } |
| l := make([]string, 0, len(nwConfig.EndpointsConfig)) |
| for k := range nwConfig.EndpointsConfig { |
| l = append(l, k) |
| } |
| return errors.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", ")) |
| } |