github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/create.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package container 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "net/url" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "runtime" 29 "strconv" 30 "strings" 31 32 "github.com/containerd/containerd" 33 "github.com/containerd/containerd/cio" 34 "github.com/containerd/containerd/containers" 35 "github.com/containerd/containerd/oci" 36 gocni "github.com/containerd/go-cni" 37 "github.com/containerd/log" 38 "github.com/containerd/nerdctl/v2/pkg/annotations" 39 "github.com/containerd/nerdctl/v2/pkg/api/types" 40 "github.com/containerd/nerdctl/v2/pkg/clientutil" 41 "github.com/containerd/nerdctl/v2/pkg/cmd/image" 42 "github.com/containerd/nerdctl/v2/pkg/containerutil" 43 "github.com/containerd/nerdctl/v2/pkg/flagutil" 44 "github.com/containerd/nerdctl/v2/pkg/idgen" 45 "github.com/containerd/nerdctl/v2/pkg/imgutil" 46 "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" 47 "github.com/containerd/nerdctl/v2/pkg/ipcutil" 48 "github.com/containerd/nerdctl/v2/pkg/labels" 49 "github.com/containerd/nerdctl/v2/pkg/logging" 50 "github.com/containerd/nerdctl/v2/pkg/maputil" 51 "github.com/containerd/nerdctl/v2/pkg/mountutil" 52 "github.com/containerd/nerdctl/v2/pkg/namestore" 53 "github.com/containerd/nerdctl/v2/pkg/platformutil" 54 "github.com/containerd/nerdctl/v2/pkg/referenceutil" 55 "github.com/containerd/nerdctl/v2/pkg/rootlessutil" 56 "github.com/containerd/nerdctl/v2/pkg/strutil" 57 dockercliopts "github.com/docker/cli/opts" 58 dockeropts "github.com/docker/docker/opts" 59 "github.com/opencontainers/runtime-spec/specs-go" 60 ) 61 62 // Create will create a container. 63 func Create(ctx context.Context, client *containerd.Client, args []string, netManager containerutil.NetworkOptionsManager, options types.ContainerCreateOptions) (containerd.Container, func(), error) { 64 // simulate the behavior of double dash 65 newArg := []string{} 66 if len(args) >= 2 && args[1] == "--" { 67 newArg = append(newArg, args[:1]...) 68 newArg = append(newArg, args[2:]...) 69 args = newArg 70 } 71 var internalLabels internalLabels 72 internalLabels.platform = options.Platform 73 internalLabels.namespace = options.GOptions.Namespace 74 75 var ( 76 id = idgen.GenerateID() 77 opts []oci.SpecOpts 78 cOpts []containerd.NewContainerOpts 79 ) 80 81 if options.CidFile != "" { 82 if err := writeCIDFile(options.CidFile, id); err != nil { 83 return nil, nil, err 84 } 85 } 86 dataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address) 87 if err != nil { 88 return nil, nil, err 89 } 90 91 internalLabels.stateDir, err = containerutil.ContainerStateDirPath(options.GOptions.Namespace, dataStore, id) 92 if err != nil { 93 return nil, nil, err 94 } 95 if err := os.MkdirAll(internalLabels.stateDir, 0700); err != nil { 96 return nil, nil, err 97 } 98 99 opts = append(opts, 100 oci.WithDefaultSpec(), 101 ) 102 103 platformOpts, err := setPlatformOptions(ctx, client, id, netManager.NetworkOptions().UTSNamespace, &internalLabels, options) 104 if err != nil { 105 return nil, nil, err 106 } 107 opts = append(opts, platformOpts...) 108 109 var ensuredImage *imgutil.EnsuredImage 110 if !options.Rootfs { 111 var platformSS []string // len: 0 or 1 112 if options.Platform != "" { 113 platformSS = append(platformSS, options.Platform) 114 } 115 ocispecPlatforms, err := platformutil.NewOCISpecPlatformSlice(false, platformSS) 116 if err != nil { 117 return nil, nil, err 118 } 119 rawRef := args[0] 120 121 ensuredImage, err = image.EnsureImage(ctx, client, rawRef, ocispecPlatforms, options.Pull, nil, false, options.ImagePullOpt) 122 if err != nil { 123 return nil, nil, err 124 } 125 } 126 127 rootfsOpts, rootfsCOpts, err := generateRootfsOpts(args, id, ensuredImage, options) 128 if err != nil { 129 return nil, nil, err 130 } 131 opts = append(opts, rootfsOpts...) 132 cOpts = append(cOpts, rootfsCOpts...) 133 134 if options.Workdir != "" { 135 opts = append(opts, oci.WithProcessCwd(options.Workdir)) 136 } 137 138 envs, err := flagutil.MergeEnvFileAndOSEnv(options.EnvFile, options.Env) 139 if err != nil { 140 return nil, nil, err 141 } 142 opts = append(opts, oci.WithEnv(envs)) 143 144 if options.Interactive { 145 if options.Detach { 146 return nil, nil, errors.New("currently flag -i and -d cannot be specified together (FIXME)") 147 } 148 } 149 150 if options.TTY { 151 opts = append(opts, oci.WithTTY) 152 } 153 154 var mountOpts []oci.SpecOpts 155 mountOpts, internalLabels.anonVolumes, internalLabels.mountPoints, err = generateMountOpts(ctx, client, ensuredImage, options) 156 if err != nil { 157 return nil, nil, err 158 } 159 opts = append(opts, mountOpts...) 160 161 // Always set internalLabels.logURI 162 // to support restart the container that run with "-it", like 163 // 164 // 1, nerdctl run --name demo -it imagename 165 // 2, ctrl + c to stop demo container 166 // 3, nerdctl start/restart demo 167 logConfig, err := generateLogConfig(dataStore, id, options.LogDriver, options.LogOpt, options.GOptions.Namespace) 168 if err != nil { 169 return nil, nil, err 170 } 171 internalLabels.logURI = logConfig.LogURI 172 173 restartOpts, err := generateRestartOpts(ctx, client, options.Restart, logConfig.LogURI, options.InRun) 174 if err != nil { 175 return nil, nil, err 176 } 177 cOpts = append(cOpts, restartOpts...) 178 179 if err = netManager.VerifyNetworkOptions(ctx); err != nil { 180 return nil, nil, fmt.Errorf("failed to verify networking settings: %s", err) 181 } 182 183 netOpts, netNewContainerOpts, err := netManager.ContainerNetworkingOpts(ctx, id) 184 if err != nil { 185 return nil, nil, fmt.Errorf("failed to generate networking spec options: %s", err) 186 } 187 opts = append(opts, netOpts...) 188 cOpts = append(cOpts, netNewContainerOpts...) 189 190 netLabelOpts, err := netManager.InternalNetworkingOptionLabels(ctx) 191 if err != nil { 192 return nil, nil, fmt.Errorf("failed to generate internal networking labels: %s", err) 193 } 194 // TODO(aznashwan): more formal way to load net opts into internalLabels: 195 internalLabels.hostname = netLabelOpts.Hostname 196 internalLabels.ports = netLabelOpts.PortMappings 197 internalLabels.ipAddress = netLabelOpts.IPAddress 198 internalLabels.ip6Address = netLabelOpts.IP6Address 199 internalLabels.networks = netLabelOpts.NetworkSlice 200 internalLabels.macAddress = netLabelOpts.MACAddress 201 202 // NOTE: OCI hooks are currently not supported on Windows so we skip setting them altogether. 203 // The OCI hooks we define (whose logic can be found in pkg/ocihook) primarily 204 // perform network setup and teardown when using CNI networking. 205 // On Windows, we are forced to set up and tear down the networking from within nerdctl. 206 if runtime.GOOS != "windows" { 207 hookOpt, err := withNerdctlOCIHook(options.NerdctlCmd, options.NerdctlArgs) 208 if err != nil { 209 return nil, nil, err 210 } 211 opts = append(opts, hookOpt) 212 } 213 214 uOpts, err := generateUserOpts(options.User) 215 if err != nil { 216 return nil, nil, err 217 } 218 opts = append(opts, uOpts...) 219 gOpts, err := generateGroupsOpts(options.GroupAdd) 220 if err != nil { 221 return nil, nil, err 222 } 223 opts = append(opts, gOpts...) 224 225 umaskOpts, err := generateUmaskOpts(options.Umask) 226 if err != nil { 227 return nil, nil, err 228 } 229 opts = append(opts, umaskOpts...) 230 231 rtCOpts, err := generateRuntimeCOpts(options.GOptions.CgroupManager, options.Runtime) 232 if err != nil { 233 return nil, nil, err 234 } 235 cOpts = append(cOpts, rtCOpts...) 236 237 lCOpts, err := withContainerLabels(options.Label, options.LabelFile) 238 if err != nil { 239 return nil, nil, err 240 } 241 cOpts = append(cOpts, lCOpts...) 242 243 var containerNameStore namestore.NameStore 244 if options.Name == "" && !options.NameChanged { 245 // Automatically set the container name, unless `--name=""` was explicitly specified. 246 var imageRef string 247 if ensuredImage != nil { 248 imageRef = ensuredImage.Ref 249 } 250 options.Name = referenceutil.SuggestContainerName(imageRef, id) 251 } 252 if options.Name != "" { 253 containerNameStore, err = namestore.New(dataStore, options.GOptions.Namespace) 254 if err != nil { 255 return nil, nil, err 256 } 257 if err := containerNameStore.Acquire(options.Name, id); err != nil { 258 return nil, nil, err 259 } 260 } 261 internalLabels.name = options.Name 262 internalLabels.pidFile = options.PidFile 263 internalLabels.extraHosts = strutil.DedupeStrSlice(netManager.NetworkOptions().AddHost) 264 for i, host := range internalLabels.extraHosts { 265 if _, err := dockercliopts.ValidateExtraHost(host); err != nil { 266 return nil, nil, err 267 } 268 parts := strings.SplitN(host, ":", 2) 269 // If the IP Address is a string called "host-gateway", replace this value with the IP address stored 270 // in the daemon level HostGateway IP config variable. 271 if parts[1] == dockeropts.HostGatewayName { 272 if options.GOptions.HostGatewayIP == "" { 273 return nil, nil, fmt.Errorf("unable to derive the IP value for host-gateway") 274 } 275 parts[1] = options.GOptions.HostGatewayIP 276 internalLabels.extraHosts[i] = fmt.Sprintf(`%s:%s`, parts[0], parts[1]) 277 } 278 } 279 280 // TODO: abolish internal labels and only use annotations 281 ilOpt, err := withInternalLabels(internalLabels) 282 if err != nil { 283 return nil, nil, err 284 } 285 cOpts = append(cOpts, ilOpt) 286 287 opts = append(opts, propagateInternalContainerdLabelsToOCIAnnotations(), 288 oci.WithAnnotations(strutil.ConvertKVStringsToMap(options.Annotations))) 289 290 var s specs.Spec 291 spec := containerd.WithSpec(&s, opts...) 292 293 cOpts = append(cOpts, spec) 294 295 c, containerErr := client.NewContainer(ctx, id, cOpts...) 296 var netSetupErr error 297 // NOTE: on non-Windows platforms, network setup is performed by OCI hooks. 298 // Seeing as though Windows does not currently support OCI hooks, we must explicitly 299 // perform network setup/teardown in the main nerdctl executable. 300 if containerErr == nil && runtime.GOOS == "windows" { 301 netSetupErr = netManager.SetupNetworking(ctx, id) 302 if netSetupErr != nil { 303 log.G(ctx).WithError(netSetupErr).Warnf("networking setup error has occurred") 304 } 305 } 306 307 if containerErr != nil || netSetupErr != nil { 308 returnedError := containerErr 309 if netSetupErr != nil { 310 returnedError = netSetupErr // mutually exclusive 311 } 312 return nil, generateGcFunc(ctx, c, options.GOptions.Namespace, id, options.Name, dataStore, containerErr, containerNameStore, netManager, internalLabels), returnedError 313 } 314 315 return c, nil, nil 316 } 317 318 func generateRootfsOpts(args []string, id string, ensured *imgutil.EnsuredImage, options types.ContainerCreateOptions) (opts []oci.SpecOpts, cOpts []containerd.NewContainerOpts, err error) { 319 if !options.Rootfs { 320 cOpts = append(cOpts, 321 containerd.WithImage(ensured.Image), 322 containerd.WithSnapshotter(ensured.Snapshotter), 323 containerd.WithNewSnapshot(id, ensured.Image), 324 containerd.WithImageStopSignal(ensured.Image, "SIGTERM"), 325 ) 326 327 if len(ensured.ImageConfig.Env) == 0 { 328 opts = append(opts, oci.WithDefaultPathEnv) 329 } 330 for ind, env := range ensured.ImageConfig.Env { 331 if strings.HasPrefix(env, "PATH=") { 332 break 333 } 334 if ind == len(ensured.ImageConfig.Env)-1 { 335 opts = append(opts, oci.WithDefaultPathEnv) 336 } 337 } 338 } else { 339 absRootfs, err := filepath.Abs(args[0]) 340 if err != nil { 341 return nil, nil, err 342 } 343 opts = append(opts, oci.WithRootFSPath(absRootfs), oci.WithDefaultPathEnv) 344 } 345 346 entrypointPath := "" 347 if ensured != nil { 348 if len(ensured.ImageConfig.Entrypoint) > 0 { 349 entrypointPath = ensured.ImageConfig.Entrypoint[0] 350 } else if len(ensured.ImageConfig.Cmd) > 0 { 351 entrypointPath = ensured.ImageConfig.Cmd[0] 352 } 353 } 354 355 if !options.Rootfs && !options.EntrypointChanged { 356 opts = append(opts, oci.WithImageConfigArgs(ensured.Image, args[1:])) 357 } else { 358 if !options.Rootfs { 359 opts = append(opts, oci.WithImageConfig(ensured.Image)) 360 } 361 var processArgs []string 362 if len(options.Entrypoint) != 0 { 363 processArgs = append(processArgs, options.Entrypoint...) 364 } 365 if len(args) > 1 { 366 processArgs = append(processArgs, args[1:]...) 367 } 368 if len(processArgs) == 0 { 369 // error message is from Podman 370 return nil, nil, errors.New("no command or entrypoint provided, and no CMD or ENTRYPOINT from image") 371 } 372 373 entrypointPath = processArgs[0] 374 375 opts = append(opts, oci.WithProcessArgs(processArgs...)) 376 } 377 378 isEntryPointSystemd := (entrypointPath == "/sbin/init" || 379 entrypointPath == "/usr/sbin/init" || 380 entrypointPath == "/usr/local/sbin/init") 381 382 stopSignal := options.StopSignal 383 384 if options.Systemd == "always" || (options.Systemd == "true" && isEntryPointSystemd) { 385 if options.Privileged { 386 securityOptsMap := strutil.ConvertKVStringsToMap(strutil.DedupeStrSlice(options.SecurityOpt)) 387 privilegedWithoutHostDevices, err := maputil.MapBoolValueAsOpt(securityOptsMap, "privileged-without-host-devices") 388 if err != nil { 389 return nil, nil, err 390 } 391 392 // See: https://github.com/containers/podman/issues/15878 393 if !privilegedWithoutHostDevices { 394 return nil, nil, errors.New("if --privileged is used with systemd `--security-opt privileged-without-host-devices` must also be used") 395 } 396 } 397 398 opts = append(opts, 399 oci.WithoutMounts("/sys/fs/cgroup"), 400 oci.WithMounts([]specs.Mount{ 401 {Type: "cgroup", Source: "cgroup", Destination: "/sys/fs/cgroup", Options: []string{"rw"}}, 402 {Type: "tmpfs", Source: "tmpfs", Destination: "/run"}, 403 {Type: "tmpfs", Source: "tmpfs", Destination: "/run/lock"}, 404 {Type: "tmpfs", Source: "tmpfs", Destination: "/tmp"}, 405 {Type: "tmpfs", Source: "tmpfs", Destination: "/var/lib/journal"}, 406 }), 407 ) 408 stopSignal = "SIGRTMIN+3" 409 } 410 411 cOpts = append(cOpts, withStop(stopSignal, options.StopTimeout, ensured)) 412 413 if options.InitBinary != nil { 414 options.InitProcessFlag = true 415 } 416 if options.InitProcessFlag { 417 binaryPath, err := exec.LookPath(*options.InitBinary) 418 if err != nil { 419 if errors.Is(err, exec.ErrNotFound) { 420 return nil, nil, fmt.Errorf(`init binary %q not found`, *options.InitBinary) 421 } 422 return nil, nil, err 423 } 424 inContainerPath := filepath.Join("/sbin", filepath.Base(*options.InitBinary)) 425 opts = append(opts, func(_ context.Context, _ oci.Client, _ *containers.Container, spec *oci.Spec) error { 426 spec.Process.Args = append([]string{inContainerPath, "--"}, spec.Process.Args...) 427 spec.Mounts = append([]specs.Mount{{ 428 Destination: inContainerPath, 429 Type: "bind", 430 Source: binaryPath, 431 Options: []string{"bind", "ro"}, 432 }}, spec.Mounts...) 433 return nil 434 }) 435 } 436 if options.ReadOnly { 437 opts = append(opts, oci.WithRootFSReadonly()) 438 } 439 return opts, cOpts, nil 440 } 441 442 // GenerateLogURI generates a log URI for the current container store 443 func GenerateLogURI(dataStore string) (*url.URL, error) { 444 selfExe, err := os.Executable() 445 if err != nil { 446 return nil, err 447 } 448 args := map[string]string{ 449 logging.MagicArgv1: dataStore, 450 } 451 452 return cio.LogURIGenerator("binary", selfExe, args) 453 } 454 455 func withNerdctlOCIHook(cmd string, args []string) (oci.SpecOpts, error) { 456 if rootlessutil.IsRootless() { 457 detachedNetNS, err := rootlessutil.DetachedNetNS() 458 if err != nil { 459 return nil, fmt.Errorf("failed to check whether RootlessKit is running with --detach-netns: %w", err) 460 } 461 if detachedNetNS != "" { 462 // Rewrite {cmd, args} if RootlessKit is running with --detach-netns, so that the hook can gain 463 // CAP_NET_ADMIN in the namespaces. 464 // - Old: 465 // - cmd: "/usr/local/bin/nerdctl" 466 // - args: {"--data-root=/foo", "internal", "oci-hook"} 467 // - New: 468 // - cmd: "/usr/bin/nsenter" 469 // - args: {"-n/run/user/1000/containerd-rootless/netns", "-F", "--", "/usr/local/bin/nerdctl", "--data-root=/foo", "internal", "oci-hook"} 470 oldCmd, oldArgs := cmd, args 471 cmd, err = exec.LookPath("nsenter") 472 if err != nil { 473 return nil, err 474 } 475 args = append([]string{"-n" + detachedNetNS, "-F", "--", oldCmd}, oldArgs...) 476 } 477 } 478 479 args = append([]string{cmd}, append(args, "internal", "oci-hook")...) 480 return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { 481 if s.Hooks == nil { 482 s.Hooks = &specs.Hooks{} 483 } 484 crArgs := append(args, "createRuntime") 485 s.Hooks.CreateRuntime = append(s.Hooks.CreateRuntime, specs.Hook{ 486 Path: cmd, 487 Args: crArgs, 488 Env: os.Environ(), 489 }) 490 scArgs := append(args, "startContainer") 491 s.Hooks.CreateRuntime = append(s.Hooks.StartContainer, specs.Hook{ 492 Path: cmd, 493 Args: scArgs, 494 Env: os.Environ(), 495 }) 496 argsCopy := append([]string(nil), args...) 497 psArgs := append(argsCopy, "postStop") 498 s.Hooks.Poststop = append(s.Hooks.Poststop, specs.Hook{ 499 Path: cmd, 500 Args: psArgs, 501 Env: os.Environ(), 502 }) 503 return nil 504 }, nil 505 } 506 507 func withContainerLabels(label, labelFile []string) ([]containerd.NewContainerOpts, error) { 508 labelMap, err := readKVStringsMapfFromLabel(label, labelFile) 509 if err != nil { 510 return nil, err 511 } 512 for k := range labelMap { 513 if strings.HasPrefix(k, annotations.Bypass4netns) { 514 log.L.Warnf("Label %q is deprecated, use an annotation instead", k) 515 } else if strings.HasPrefix(k, labels.Prefix) { 516 return nil, fmt.Errorf("internal label %q must not be specified manually", k) 517 } 518 } 519 o := containerd.WithAdditionalContainerLabels(labelMap) 520 return []containerd.NewContainerOpts{o}, nil 521 } 522 523 func readKVStringsMapfFromLabel(label, labelFile []string) (map[string]string, error) { 524 labelsMap := strutil.DedupeStrSlice(label) 525 labelsFilePath := strutil.DedupeStrSlice(labelFile) 526 kvStrings, err := dockercliopts.ReadKVStrings(labelsFilePath, labelsMap) 527 if err != nil { 528 return nil, err 529 } 530 return strutil.ConvertKVStringsToMap(kvStrings), nil 531 } 532 533 // parseKVStringsMapFromLogOpt parse log options KV entries and convert to Map 534 func parseKVStringsMapFromLogOpt(logOpt []string, logDriver string) (map[string]string, error) { 535 logOptArray := strutil.DedupeStrSlice(logOpt) 536 logOptMap := strutil.ConvertKVStringsToMap(logOptArray) 537 if logDriver == "json-file" { 538 if _, ok := logOptMap[logging.MaxSize]; !ok { 539 delete(logOptMap, logging.MaxFile) 540 } 541 } 542 if err := logging.ValidateLogOpts(logDriver, logOptMap); err != nil { 543 return nil, err 544 } 545 return logOptMap, nil 546 } 547 548 func withStop(stopSignal string, stopTimeout int, ensuredImage *imgutil.EnsuredImage) containerd.NewContainerOpts { 549 return func(ctx context.Context, _ *containerd.Client, c *containers.Container) error { 550 if c.Labels == nil { 551 c.Labels = make(map[string]string) 552 } 553 var err error 554 if ensuredImage != nil { 555 stopSignal, err = containerd.GetOCIStopSignal(ctx, ensuredImage.Image, stopSignal) 556 if err != nil { 557 return err 558 } 559 } 560 c.Labels[containerd.StopSignalLabel] = stopSignal 561 if stopTimeout != 0 { 562 c.Labels[labels.StopTimeout] = strconv.Itoa(stopTimeout) 563 } 564 return nil 565 } 566 } 567 568 type internalLabels struct { 569 // labels from cmd options 570 namespace string 571 platform string 572 extraHosts []string 573 pidFile string 574 // labels from cmd options or automatically set 575 name string 576 hostname string 577 // automatically generated 578 stateDir string 579 // network 580 networks []string 581 ipAddress string 582 ip6Address string 583 ports []gocni.PortMapping 584 macAddress string 585 // volume 586 mountPoints []*mountutil.Processed 587 anonVolumes []string 588 // pid namespace 589 pidContainer string 590 // ipc namespace & dev/shm 591 ipc string 592 // log 593 logURI string 594 } 595 596 // WithInternalLabels sets the internal labels for a container. 597 func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerOpts, error) { 598 m := make(map[string]string) 599 m[labels.Namespace] = internalLabels.namespace 600 if internalLabels.name != "" { 601 m[labels.Name] = internalLabels.name 602 } 603 m[labels.Hostname] = internalLabels.hostname 604 extraHostsJSON, err := json.Marshal(internalLabels.extraHosts) 605 if err != nil { 606 return nil, err 607 } 608 m[labels.ExtraHosts] = string(extraHostsJSON) 609 m[labels.StateDir] = internalLabels.stateDir 610 networksJSON, err := json.Marshal(internalLabels.networks) 611 if err != nil { 612 return nil, err 613 } 614 m[labels.Networks] = string(networksJSON) 615 if len(internalLabels.ports) > 0 { 616 portsJSON, err := json.Marshal(internalLabels.ports) 617 if err != nil { 618 return nil, err 619 } 620 m[labels.Ports] = string(portsJSON) 621 } 622 if internalLabels.logURI != "" { 623 m[labels.LogURI] = internalLabels.logURI 624 } 625 if len(internalLabels.anonVolumes) > 0 { 626 anonVolumeJSON, err := json.Marshal(internalLabels.anonVolumes) 627 if err != nil { 628 return nil, err 629 } 630 m[labels.AnonymousVolumes] = string(anonVolumeJSON) 631 } 632 633 if internalLabels.pidFile != "" { 634 m[labels.PIDFile] = internalLabels.pidFile 635 } 636 637 if internalLabels.ipAddress != "" { 638 m[labels.IPAddress] = internalLabels.ipAddress 639 } 640 641 if internalLabels.ip6Address != "" { 642 m[labels.IP6Address] = internalLabels.ip6Address 643 } 644 645 m[labels.Platform], err = platformutil.NormalizeString(internalLabels.platform) 646 if err != nil { 647 return nil, err 648 } 649 650 if len(internalLabels.mountPoints) > 0 { 651 mounts := dockercompatMounts(internalLabels.mountPoints) 652 mountPointsJSON, err := json.Marshal(mounts) 653 if err != nil { 654 return nil, err 655 } 656 m[labels.Mounts] = string(mountPointsJSON) 657 } 658 659 if internalLabels.macAddress != "" { 660 m[labels.MACAddress] = internalLabels.macAddress 661 } 662 663 if internalLabels.pidContainer != "" { 664 m[labels.PIDContainer] = internalLabels.pidContainer 665 } 666 667 if internalLabels.ipc != "" { 668 m[labels.IPC] = internalLabels.ipc 669 } 670 671 return containerd.WithAdditionalContainerLabels(m), nil 672 } 673 674 func dockercompatMounts(mountPoints []*mountutil.Processed) []dockercompat.MountPoint { 675 result := make([]dockercompat.MountPoint, len(mountPoints)) 676 for i := range mountPoints { 677 mp := mountPoints[i] 678 result[i] = dockercompat.MountPoint{ 679 Type: mp.Type, 680 Name: mp.Name, 681 Source: mp.Mount.Source, 682 Destination: mp.Mount.Destination, 683 Driver: "", 684 Mode: mp.Mode, 685 } 686 result[i].RW, result[i].Propagation = dockercompat.ParseMountProperties(strings.Split(mp.Mode, ",")) 687 688 // it's an anonymous volume 689 if mp.AnonymousVolume != "" { 690 result[i].Name = mp.AnonymousVolume 691 } 692 693 // volume only support local driver 694 if mp.Type == "volume" { 695 result[i].Driver = "local" 696 } 697 } 698 return result 699 } 700 701 func processeds(mountPoints []dockercompat.MountPoint) []*mountutil.Processed { 702 result := make([]*mountutil.Processed, len(mountPoints)) 703 for i := range mountPoints { 704 mp := mountPoints[i] 705 result[i] = &mountutil.Processed{ 706 Type: mp.Type, 707 Name: mp.Name, 708 Mount: specs.Mount{ 709 Source: mp.Source, 710 Destination: mp.Destination, 711 }, 712 Mode: mp.Mode, 713 } 714 } 715 return result 716 } 717 718 func propagateInternalContainerdLabelsToOCIAnnotations() oci.SpecOpts { 719 return func(ctx context.Context, oc oci.Client, c *containers.Container, s *oci.Spec) error { 720 allowed := make(map[string]string) 721 for k, v := range c.Labels { 722 if strings.Contains(k, labels.Prefix) { 723 allowed[k] = v 724 } 725 } 726 return oci.WithAnnotations(allowed)(ctx, oc, c, s) 727 } 728 } 729 730 func writeCIDFile(path, id string) error { 731 _, err := os.Stat(path) 732 if err == nil { 733 return fmt.Errorf("container ID file found, make sure the other container isn't running or delete %s", path) 734 } else if errors.Is(err, os.ErrNotExist) { 735 f, err := os.Create(path) 736 if err != nil { 737 return fmt.Errorf("failed to create the container ID file: %s for %s, err: %s", path, id, err) 738 } 739 defer f.Close() 740 741 if _, err := f.WriteString(id); err != nil { 742 return err 743 } 744 return nil 745 } 746 return err 747 } 748 749 // generateLogConfig creates a LogConfig for the current container store 750 func generateLogConfig(dataStore string, id string, logDriver string, logOpt []string, ns string) (logConfig logging.LogConfig, err error) { 751 var u *url.URL 752 if u, err = url.Parse(logDriver); err == nil && u.Scheme != "" { 753 logConfig.LogURI = logDriver 754 } else { 755 logConfig.Driver = logDriver 756 logConfig.Opts, err = parseKVStringsMapFromLogOpt(logOpt, logDriver) 757 if err != nil { 758 return 759 } 760 var ( 761 logDriverInst logging.Driver 762 logConfigB []byte 763 lu *url.URL 764 ) 765 logDriverInst, err = logging.GetDriver(logDriver, logConfig.Opts) 766 if err != nil { 767 return 768 } 769 if err = logDriverInst.Init(dataStore, ns, id); err != nil { 770 return 771 } 772 773 logConfigB, err = json.Marshal(logConfig) 774 if err != nil { 775 return 776 } 777 778 logConfigFilePath := logging.LogConfigFilePath(dataStore, ns, id) 779 if err = os.WriteFile(logConfigFilePath, logConfigB, 0600); err != nil { 780 return 781 } 782 783 lu, err = GenerateLogURI(dataStore) 784 if err != nil { 785 return 786 } 787 if lu != nil { 788 log.L.Debugf("generated log driver: %s", lu.String()) 789 logConfig.LogURI = lu.String() 790 } 791 } 792 return logConfig, nil 793 } 794 795 func generateGcFunc(ctx context.Context, container containerd.Container, ns, id, name, dataStore string, containerErr error, containerNameStore namestore.NameStore, netManager containerutil.NetworkOptionsManager, internalLabels internalLabels) func() { 796 return func() { 797 if containerErr == nil { 798 netGcErr := netManager.CleanupNetworking(ctx, container) 799 if netGcErr != nil { 800 log.G(ctx).WithError(netGcErr).Warnf("failed to revert container %q networking settings", id) 801 } 802 } 803 804 ipc, ipcErr := ipcutil.DecodeIPCLabel(internalLabels.ipc) 805 if ipcErr != nil { 806 log.G(ctx).WithError(ipcErr).Warnf("failed to decode ipc label for container %q", id) 807 } 808 if ipcErr := ipcutil.CleanUp(ipc); ipcErr != nil { 809 log.G(ctx).WithError(ipcErr).Warnf("failed to clean up ipc for container %q", id) 810 } 811 if rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil { 812 log.G(ctx).WithError(rmErr).Warnf("failed to remove container %q state dir %q", id, internalLabels.stateDir) 813 } 814 815 if name != "" { 816 var errE error 817 if containerNameStore, errE = namestore.New(dataStore, ns); errE != nil { 818 log.G(ctx).WithError(errE).Warnf("failed to instantiate container name store during cleanup for container %q", id) 819 } 820 if errE = containerNameStore.Release(name, id); errE != nil { 821 log.G(ctx).WithError(errE).Warnf("failed to release container name store for container %q (%s)", name, id) 822 } 823 } 824 } 825 }