github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/create.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "runtime" 8 "strings" 9 "time" 10 11 "github.com/containerd/containerd/platforms" 12 "github.com/docker/docker/api/types" 13 containertypes "github.com/docker/docker/api/types/container" 14 imagetypes "github.com/docker/docker/api/types/image" 15 networktypes "github.com/docker/docker/api/types/network" 16 "github.com/docker/docker/container" 17 "github.com/docker/docker/daemon/images" 18 "github.com/docker/docker/errdefs" 19 "github.com/docker/docker/image" 20 "github.com/docker/docker/pkg/idtools" 21 "github.com/docker/docker/runconfig" 22 v1 "github.com/opencontainers/image-spec/specs-go/v1" 23 "github.com/opencontainers/selinux/go-selinux" 24 "github.com/pkg/errors" 25 "github.com/sirupsen/logrus" 26 archvariant "github.com/tonistiigi/go-archvariant" 27 ) 28 29 type createOpts struct { 30 params types.ContainerCreateConfig 31 managed bool 32 ignoreImagesArgsEscaped bool 33 } 34 35 // CreateManagedContainer creates a container that is managed by a Service 36 func (daemon *Daemon) CreateManagedContainer(ctx context.Context, params types.ContainerCreateConfig) (containertypes.CreateResponse, error) { 37 return daemon.containerCreate(ctx, createOpts{ 38 params: params, 39 managed: true, 40 }) 41 } 42 43 // ContainerCreate creates a regular container 44 func (daemon *Daemon) ContainerCreate(ctx context.Context, params types.ContainerCreateConfig) (containertypes.CreateResponse, error) { 45 return daemon.containerCreate(ctx, createOpts{ 46 params: params, 47 }) 48 } 49 50 // ContainerCreateIgnoreImagesArgsEscaped creates a regular container. This is called from the builder RUN case 51 // and ensures that we do not take the images ArgsEscaped 52 func (daemon *Daemon) ContainerCreateIgnoreImagesArgsEscaped(ctx context.Context, params types.ContainerCreateConfig) (containertypes.CreateResponse, error) { 53 return daemon.containerCreate(ctx, createOpts{ 54 params: params, 55 ignoreImagesArgsEscaped: true, 56 }) 57 } 58 59 func (daemon *Daemon) containerCreate(ctx context.Context, opts createOpts) (containertypes.CreateResponse, error) { 60 start := time.Now() 61 if opts.params.Config == nil { 62 return containertypes.CreateResponse{}, errdefs.InvalidParameter(errors.New("Config cannot be empty in order to create a container")) 63 } 64 65 warnings, err := daemon.verifyContainerSettings(opts.params.HostConfig, opts.params.Config, false) 66 if err != nil { 67 return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err) 68 } 69 70 if opts.params.Platform == nil && opts.params.Config.Image != "" { 71 img, err := daemon.imageService.GetImage(ctx, opts.params.Config.Image, imagetypes.GetImageOpts{Platform: opts.params.Platform}) 72 if err != nil { 73 return containertypes.CreateResponse{}, err 74 } 75 if img != nil { 76 p := maximumSpec() 77 imgPlat := v1.Platform{ 78 OS: img.OS, 79 Architecture: img.Architecture, 80 Variant: img.Variant, 81 } 82 83 if !images.OnlyPlatformWithFallback(p).Match(imgPlat) { 84 warnings = append(warnings, fmt.Sprintf("The requested image's platform (%s) does not match the detected host platform (%s) and no specific platform was requested", platforms.Format(imgPlat), platforms.Format(p))) 85 } 86 } 87 } 88 89 err = verifyNetworkingConfig(opts.params.NetworkingConfig) 90 if err != nil { 91 return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err) 92 } 93 94 if opts.params.HostConfig == nil { 95 opts.params.HostConfig = &containertypes.HostConfig{} 96 } 97 err = daemon.adaptContainerSettings(opts.params.HostConfig, opts.params.AdjustCPUShares) 98 if err != nil { 99 return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err) 100 } 101 102 ctr, err := daemon.create(ctx, opts) 103 if err != nil { 104 return containertypes.CreateResponse{Warnings: warnings}, err 105 } 106 containerActions.WithValues("create").UpdateSince(start) 107 108 if warnings == nil { 109 warnings = make([]string, 0) // Create an empty slice to avoid https://github.com/moby/moby/issues/38222 110 } 111 112 return containertypes.CreateResponse{ID: ctr.ID, Warnings: warnings}, nil 113 } 114 115 // Create creates a new container from the given configuration with a given name. 116 func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *container.Container, retErr error) { 117 var ( 118 ctr *container.Container 119 img *image.Image 120 imgID image.ID 121 err error 122 os = runtime.GOOS 123 ) 124 125 if opts.params.Config.Image != "" { 126 img, err = daemon.imageService.GetImage(ctx, opts.params.Config.Image, imagetypes.GetImageOpts{Platform: opts.params.Platform}) 127 if err != nil { 128 return nil, err 129 } 130 os = img.OperatingSystem() 131 imgID = img.ID() 132 } else if isWindows { 133 os = "linux" // 'scratch' case. 134 } 135 136 // On WCOW, if are not being invoked by the builder to create this container (where 137 // ignoreImagesArgEscaped will be true) - if the image already has its arguments escaped, 138 // ensure that this is replicated across to the created container to avoid double-escaping 139 // of the arguments/command line when the runtime attempts to run the container. 140 if os == "windows" && !opts.ignoreImagesArgsEscaped && img != nil && img.RunConfig().ArgsEscaped { 141 opts.params.Config.ArgsEscaped = true 142 } 143 144 if err := daemon.mergeAndVerifyConfig(opts.params.Config, img); err != nil { 145 return nil, errdefs.InvalidParameter(err) 146 } 147 148 if err := daemon.mergeAndVerifyLogConfig(&opts.params.HostConfig.LogConfig); err != nil { 149 return nil, errdefs.InvalidParameter(err) 150 } 151 152 if ctr, err = daemon.newContainer(opts.params.Name, os, opts.params.Config, opts.params.HostConfig, imgID, opts.managed); err != nil { 153 return nil, err 154 } 155 defer func() { 156 if retErr != nil { 157 err = daemon.cleanupContainer(ctr, types.ContainerRmConfig{ 158 ForceRemove: true, 159 RemoveVolume: true, 160 }) 161 if err != nil { 162 logrus.WithError(err).Error("failed to cleanup container on create error") 163 } 164 } 165 }() 166 167 if err := daemon.setSecurityOptions(ctr, opts.params.HostConfig); err != nil { 168 return nil, err 169 } 170 171 ctr.HostConfig.StorageOpt = opts.params.HostConfig.StorageOpt 172 173 // Set RWLayer for container after mount labels have been set 174 rwLayer, err := daemon.imageService.CreateLayer(ctr, setupInitLayer(daemon.idMapping)) 175 if err != nil { 176 return nil, errdefs.System(err) 177 } 178 ctr.RWLayer = rwLayer 179 180 current := idtools.CurrentIdentity() 181 if err := idtools.MkdirAndChown(ctr.Root, 0710, idtools.Identity{UID: current.UID, GID: daemon.IdentityMapping().RootPair().GID}); err != nil { 182 return nil, err 183 } 184 if err := idtools.MkdirAndChown(ctr.CheckpointDir(), 0700, current); err != nil { 185 return nil, err 186 } 187 188 if err := daemon.setHostConfig(ctr, opts.params.HostConfig); err != nil { 189 return nil, err 190 } 191 192 if err := daemon.createContainerOSSpecificSettings(ctr, opts.params.Config, opts.params.HostConfig); err != nil { 193 return nil, err 194 } 195 196 var endpointsConfigs map[string]*networktypes.EndpointSettings 197 if opts.params.NetworkingConfig != nil { 198 endpointsConfigs = opts.params.NetworkingConfig.EndpointsConfig 199 } 200 // Make sure NetworkMode has an acceptable value. We do this to ensure 201 // backwards API compatibility. 202 runconfig.SetDefaultNetModeIfBlank(ctr.HostConfig) 203 204 daemon.updateContainerNetworkSettings(ctr, endpointsConfigs) 205 if err := daemon.Register(ctr); err != nil { 206 return nil, err 207 } 208 stateCtr.set(ctr.ID, "stopped") 209 daemon.LogContainerEvent(ctr, "create") 210 return ctr, nil 211 } 212 213 func toHostConfigSelinuxLabels(labels []string) []string { 214 for i, l := range labels { 215 labels[i] = "label=" + l 216 } 217 return labels 218 } 219 220 func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig) ([]string, error) { 221 for _, opt := range hostConfig.SecurityOpt { 222 con := strings.Split(opt, "=") 223 if con[0] == "label" { 224 // Caller overrode SecurityOpts 225 return nil, nil 226 } 227 } 228 ipcMode := hostConfig.IpcMode 229 pidMode := hostConfig.PidMode 230 privileged := hostConfig.Privileged 231 if ipcMode.IsHost() || pidMode.IsHost() || privileged { 232 return toHostConfigSelinuxLabels(selinux.DisableSecOpt()), nil 233 } 234 235 var ipcLabel []string 236 var pidLabel []string 237 ipcContainer := ipcMode.Container() 238 pidContainer := pidMode.Container() 239 if ipcContainer != "" { 240 c, err := daemon.GetContainer(ipcContainer) 241 if err != nil { 242 return nil, err 243 } 244 ipcLabel, err = selinux.DupSecOpt(c.ProcessLabel) 245 if err != nil { 246 return nil, err 247 } 248 if pidContainer == "" { 249 return toHostConfigSelinuxLabels(ipcLabel), err 250 } 251 } 252 if pidContainer != "" { 253 c, err := daemon.GetContainer(pidContainer) 254 if err != nil { 255 return nil, err 256 } 257 258 pidLabel, err = selinux.DupSecOpt(c.ProcessLabel) 259 if err != nil { 260 return nil, err 261 } 262 if ipcContainer == "" { 263 return toHostConfigSelinuxLabels(pidLabel), err 264 } 265 } 266 267 if pidLabel != nil && ipcLabel != nil { 268 for i := 0; i < len(pidLabel); i++ { 269 if pidLabel[i] != ipcLabel[i] { 270 return nil, fmt.Errorf("--ipc and --pid containers SELinux labels aren't the same") 271 } 272 } 273 return toHostConfigSelinuxLabels(pidLabel), nil 274 } 275 return nil, nil 276 } 277 278 func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error { 279 if img != nil && img.Config != nil { 280 if err := merge(config, img.Config); err != nil { 281 return err 282 } 283 } 284 // Reset the Entrypoint if it is [""] 285 if len(config.Entrypoint) == 1 && config.Entrypoint[0] == "" { 286 config.Entrypoint = nil 287 } 288 if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { 289 return fmt.Errorf("No command specified") 290 } 291 return nil 292 } 293 294 // Checks if the client set configurations for more than one network while creating a container 295 // Also checks if the IPAMConfig is valid 296 func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error { 297 if nwConfig == nil || len(nwConfig.EndpointsConfig) == 0 { 298 return nil 299 } 300 if len(nwConfig.EndpointsConfig) > 1 { 301 l := make([]string, 0, len(nwConfig.EndpointsConfig)) 302 for k := range nwConfig.EndpointsConfig { 303 l = append(l, k) 304 } 305 return errors.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", ")) 306 } 307 308 for k, v := range nwConfig.EndpointsConfig { 309 if v == nil { 310 return errdefs.InvalidParameter(errors.Errorf("no EndpointSettings for %s", k)) 311 } 312 if v.IPAMConfig != nil { 313 if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil { 314 return errors.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address) 315 } 316 if v.IPAMConfig.IPv6Address != "" { 317 n := net.ParseIP(v.IPAMConfig.IPv6Address) 318 // if the address is an invalid network address (ParseIP == nil) or if it is 319 // an IPv4 address (To4() != nil), then it is an invalid IPv6 address 320 if n == nil || n.To4() != nil { 321 return errors.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address) 322 } 323 } 324 } 325 } 326 return nil 327 } 328 329 // maximumSpec returns the distribution platform with maximum compatibility for the current node. 330 func maximumSpec() v1.Platform { 331 p := platforms.DefaultSpec() 332 if p.Architecture == "amd64" { 333 p.Variant = archvariant.AMD64Variant() 334 } 335 return p 336 }