github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/shared/create.go (about) 1 package shared 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 goruntime "runtime" 11 "strconv" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/containers/image/v5/manifest" 17 "github.com/containers/libpod/cmd/podman/shared/parse" 18 "github.com/containers/libpod/libpod" 19 "github.com/containers/libpod/libpod/image" 20 ann "github.com/containers/libpod/pkg/annotations" 21 "github.com/containers/libpod/pkg/autoupdate" 22 envLib "github.com/containers/libpod/pkg/env" 23 "github.com/containers/libpod/pkg/errorhandling" 24 "github.com/containers/libpod/pkg/inspect" 25 ns "github.com/containers/libpod/pkg/namespaces" 26 "github.com/containers/libpod/pkg/rootless" 27 "github.com/containers/libpod/pkg/seccomp" 28 cc "github.com/containers/libpod/pkg/spec" 29 systemdGen "github.com/containers/libpod/pkg/systemd/generate" 30 "github.com/containers/libpod/pkg/util" 31 "github.com/docker/go-connections/nat" 32 "github.com/docker/go-units" 33 "github.com/opentracing/opentracing-go" 34 "github.com/pkg/errors" 35 "github.com/sirupsen/logrus" 36 ) 37 38 func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { 39 var ( 40 healthCheck *manifest.Schema2HealthConfig 41 err error 42 cidFile *os.File 43 ) 44 if c.Bool("trace") { 45 span, _ := opentracing.StartSpanFromContext(ctx, "createContainer") 46 defer span.Finish() 47 } 48 if c.Bool("rm") && c.String("restart") != "" && c.String("restart") != "no" { 49 return nil, nil, errors.Errorf("the --rm option conflicts with --restart") 50 } 51 52 rtc, err := runtime.GetConfig() 53 if err != nil { 54 return nil, nil, err 55 } 56 rootfs := "" 57 if c.Bool("rootfs") { 58 rootfs = c.InputArgs[0] 59 } 60 61 if c.IsSet("cidfile") { 62 cidFile, err = util.OpenExclusiveFile(c.String("cidfile")) 63 if err != nil && os.IsExist(err) { 64 return nil, nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", c.String("cidfile")) 65 } 66 if err != nil { 67 return nil, nil, errors.Errorf("error opening cidfile %s", c.String("cidfile")) 68 } 69 defer errorhandling.CloseQuiet(cidFile) 70 defer errorhandling.SyncQuiet(cidFile) 71 } 72 73 imageName := "" 74 rawImageName := "" 75 var imageData *inspect.ImageData = nil 76 77 // Set the storage if there is no rootfs specified 78 if rootfs == "" { 79 var writer io.Writer 80 if !c.Bool("quiet") { 81 writer = os.Stderr 82 } 83 84 if len(c.InputArgs) != 0 { 85 rawImageName = c.InputArgs[0] 86 } else { 87 return nil, nil, errors.Errorf("error, image name not provided") 88 } 89 90 pullType, err := util.ValidatePullType(c.String("pull")) 91 if err != nil { 92 return nil, nil, err 93 } 94 95 overrideOS := c.String("override-os") 96 overrideArch := c.String("override-arch") 97 dockerRegistryOptions := image.DockerRegistryOptions{ 98 OSChoice: overrideOS, 99 ArchitectureChoice: overrideArch, 100 } 101 102 newImage, err := runtime.ImageRuntime().New(ctx, rawImageName, rtc.Engine.SignaturePolicyPath, c.String("authfile"), writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullType) 103 if err != nil { 104 return nil, nil, err 105 } 106 imageData, err = newImage.InspectNoSize(ctx) 107 if err != nil { 108 return nil, nil, err 109 } 110 111 if overrideOS == "" && imageData.Os != goruntime.GOOS { 112 logrus.Infof("Using %q (OS) image on %q host", imageData.Os, goruntime.GOOS) 113 } 114 115 if overrideArch == "" && imageData.Architecture != goruntime.GOARCH { 116 logrus.Infof("Using %q (architecture) on %q host", imageData.Architecture, goruntime.GOARCH) 117 } 118 119 names := newImage.Names() 120 if len(names) > 0 { 121 imageName = names[0] 122 } else { 123 imageName = newImage.ID() 124 } 125 126 // if the user disabled the healthcheck with "none" or the no-healthcheck 127 // options is provided, we skip adding it 128 healthCheckCommandInput := c.String("healthcheck-command") 129 130 // the user didn't disable the healthcheck but did pass in a healthcheck command 131 // now we need to make a healthcheck from the commandline input 132 if healthCheckCommandInput != "none" && !c.Bool("no-healthcheck") { 133 if len(healthCheckCommandInput) > 0 { 134 healthCheck, err = makeHealthCheckFromCli(c) 135 if err != nil { 136 return nil, nil, errors.Wrapf(err, "unable to create healthcheck") 137 } 138 } else { 139 // the user did not disable the health check and did not pass in a healthcheck 140 // command as input. so now we add healthcheck if it exists AND is correct mediatype 141 _, mediaType, err := newImage.Manifest(ctx) 142 if err != nil { 143 return nil, nil, errors.Wrapf(err, "unable to determine mediatype of image %s", newImage.ID()) 144 } 145 if mediaType == manifest.DockerV2Schema2MediaType { 146 healthCheck, err = newImage.GetHealthCheck(ctx) 147 if err != nil { 148 return nil, nil, errors.Wrapf(err, "unable to get healthcheck for %s", c.InputArgs[0]) 149 } 150 151 if healthCheck != nil { 152 hcCommand := healthCheck.Test 153 if len(hcCommand) < 1 || hcCommand[0] == "" || hcCommand[0] == "NONE" { 154 // disable health check 155 healthCheck = nil 156 } else { 157 // apply defaults if image doesn't override them 158 if healthCheck.Interval == 0 { 159 healthCheck.Interval = 30 * time.Second 160 } 161 if healthCheck.Timeout == 0 { 162 healthCheck.Timeout = 30 * time.Second 163 } 164 /* Docker default is 0s, so the following would be a no-op 165 if healthCheck.StartPeriod == 0 { 166 healthCheck.StartPeriod = 0 * time.Second 167 } 168 */ 169 if healthCheck.Retries == 0 { 170 healthCheck.Retries = 3 171 } 172 } 173 } 174 } 175 } 176 } 177 } 178 179 createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, rawImageName, imageData) 180 if err != nil { 181 return nil, nil, err 182 } 183 184 // (VR): Ideally we perform the checks _before_ pulling the image but that 185 // would require some bigger code refactoring of `ParseCreateOpts` and the 186 // logic here. But as the creation code will be consolidated in the future 187 // and given auto updates are experimental, we can live with that for now. 188 // In the end, the user may only need to correct the policy or the raw image 189 // name. 190 autoUpdatePolicy, autoUpdatePolicySpecified := createConfig.Labels[autoupdate.Label] 191 if autoUpdatePolicySpecified { 192 if _, err := autoupdate.LookupPolicy(autoUpdatePolicy); err != nil { 193 return nil, nil, err 194 } 195 // Now we need to make sure we're having a fully-qualified image reference. 196 if rootfs != "" { 197 return nil, nil, errors.Errorf("auto updates do not work with --rootfs") 198 } 199 // Make sure the input image is a docker. 200 if err := autoupdate.ValidateImageReference(rawImageName); err != nil { 201 return nil, nil, err 202 } 203 } 204 205 // Because parseCreateOpts does derive anything from the image, we add health check 206 // at this point. The rest is done by WithOptions. 207 createConfig.HealthCheck = healthCheck 208 209 // TODO: Should be able to return this from ParseCreateOpts 210 var pod *libpod.Pod 211 if createConfig.Pod != "" { 212 pod, err = runtime.LookupPod(createConfig.Pod) 213 if err != nil { 214 return nil, nil, errors.Wrapf(err, "error looking up pod to join") 215 } 216 } 217 218 ctr, err := CreateContainerFromCreateConfig(runtime, createConfig, ctx, pod) 219 if err != nil { 220 return nil, nil, err 221 } 222 if cidFile != nil { 223 _, err = cidFile.WriteString(ctr.ID()) 224 if err != nil { 225 logrus.Error(err) 226 } 227 228 } 229 230 logrus.Debugf("New container created %q", ctr.ID()) 231 return ctr, createConfig, nil 232 } 233 234 func configureEntrypoint(c *GenericCLIResults, data *inspect.ImageData) []string { 235 entrypoint := []string{} 236 if c.IsSet("entrypoint") { 237 // Force entrypoint to "" 238 if c.String("entrypoint") == "" { 239 return entrypoint 240 } 241 // Check if entrypoint specified is json 242 if err := json.Unmarshal([]byte(c.String("entrypoint")), &entrypoint); err == nil { 243 return entrypoint 244 } 245 // Return entrypoint as a single command 246 return []string{c.String("entrypoint")} 247 } 248 if data != nil { 249 return data.Config.Entrypoint 250 } 251 return entrypoint 252 } 253 254 func configurePod(c *GenericCLIResults, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, string, error) { 255 pod, err := runtime.LookupPod(podName) 256 if err != nil { 257 return namespaces, "", err 258 } 259 podInfraID, err := pod.InfraContainerID() 260 if err != nil { 261 return namespaces, "", err 262 } 263 hasUserns := false 264 if podInfraID != "" { 265 podCtr, err := runtime.GetContainer(podInfraID) 266 if err != nil { 267 return namespaces, "", err 268 } 269 mappings, err := podCtr.IDMappings() 270 if err != nil { 271 return namespaces, "", err 272 } 273 hasUserns = len(mappings.UIDMap) > 0 274 } 275 276 if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) { 277 namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID) 278 } 279 if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) { 280 namespaces["net"] = fmt.Sprintf("container:%s", podInfraID) 281 } 282 if hasUserns && (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) { 283 namespaces["user"] = fmt.Sprintf("container:%s", podInfraID) 284 } 285 if (namespaces["ipc"] == cc.Pod) || (!c.IsSet("ipc") && pod.SharesIPC()) { 286 namespaces["ipc"] = fmt.Sprintf("container:%s", podInfraID) 287 } 288 if (namespaces["uts"] == cc.Pod) || (!c.IsSet("uts") && pod.SharesUTS()) { 289 namespaces["uts"] = fmt.Sprintf("container:%s", podInfraID) 290 } 291 return namespaces, podInfraID, nil 292 } 293 294 // Parses CLI options related to container creation into a config which can be 295 // parsed into an OCI runtime spec 296 func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime, imageName string, rawImageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { 297 var ( 298 inputCommand, command []string 299 memoryLimit, memoryReservation, memorySwap, memoryKernel int64 300 blkioWeight uint16 301 namespaces map[string]string 302 ) 303 304 idmappings, err := util.ParseIDMapping(ns.UsernsMode(c.String("userns")), c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) 305 if err != nil { 306 return nil, err 307 } 308 309 imageID := "" 310 311 inputCommand = c.InputArgs[1:] 312 if data != nil { 313 imageID = data.ID 314 } 315 316 rootfs := "" 317 if c.Bool("rootfs") { 318 rootfs = c.InputArgs[0] 319 } 320 321 if c.String("memory") != "" { 322 memoryLimit, err = units.RAMInBytes(c.String("memory")) 323 if err != nil { 324 return nil, errors.Wrapf(err, "invalid value for memory") 325 } 326 } 327 if c.String("memory-reservation") != "" { 328 memoryReservation, err = units.RAMInBytes(c.String("memory-reservation")) 329 if err != nil { 330 return nil, errors.Wrapf(err, "invalid value for memory-reservation") 331 } 332 } 333 if c.String("memory-swap") != "" { 334 if c.String("memory-swap") == "-1" { 335 memorySwap = -1 336 } else { 337 memorySwap, err = units.RAMInBytes(c.String("memory-swap")) 338 if err != nil { 339 return nil, errors.Wrapf(err, "invalid value for memory-swap") 340 } 341 } 342 } 343 if c.String("kernel-memory") != "" { 344 memoryKernel, err = units.RAMInBytes(c.String("kernel-memory")) 345 if err != nil { 346 return nil, errors.Wrapf(err, "invalid value for kernel-memory") 347 } 348 } 349 if c.String("blkio-weight") != "" { 350 u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16) 351 if err != nil { 352 return nil, errors.Wrapf(err, "invalid value for blkio-weight") 353 } 354 blkioWeight = uint16(u) 355 } 356 357 tty := c.Bool("tty") 358 359 if c.Changed("cpu-period") && c.Changed("cpus") { 360 return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") 361 } 362 if c.Changed("cpu-quota") && c.Changed("cpus") { 363 return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") 364 } 365 366 if c.Bool("no-hosts") && c.Changed("add-host") { 367 return nil, errors.Errorf("--no-hosts and --add-host cannot be set together") 368 } 369 370 // EXPOSED PORTS 371 var portBindings map[nat.Port][]nat.PortBinding 372 if data != nil { 373 portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.Config.ExposedPorts) 374 if err != nil { 375 return nil, err 376 } 377 } 378 379 usernsType := c.String("userns") 380 if !c.IsSet("userns") && !idmappings.HostUIDMapping { 381 usernsType = "private" 382 } 383 // Kernel Namespaces 384 // TODO Fix handling of namespace from pod 385 // Instead of integrating here, should be done in libpod 386 // However, that also involves setting up security opts 387 // when the pod's namespace is integrated 388 namespaces = map[string]string{ 389 "cgroup": c.String("cgroupns"), 390 "pid": c.String("pid"), 391 "net": c.String("network"), 392 "ipc": c.String("ipc"), 393 "user": usernsType, 394 "uts": c.String("uts"), 395 } 396 397 originalPodName := c.String("pod") 398 podName := strings.Replace(originalPodName, "new:", "", 1) 399 // after we strip out :new, make sure there is something left for a pod name 400 if len(podName) < 1 && c.IsSet("pod") { 401 return nil, errors.Errorf("new pod name must be at least one character") 402 } 403 404 // If we are adding a container to a pod, we would like to add an annotation for the infra ID 405 // so kata containers can share VMs inside the pod 406 var podInfraID string 407 if c.IsSet("pod") { 408 if strings.HasPrefix(originalPodName, "new:") { 409 // pod does not exist; lets make it 410 var podOptions []libpod.PodCreateOption 411 podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) 412 if len(portBindings) > 0 { 413 ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) 414 if err != nil { 415 return nil, err 416 } 417 podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) 418 } 419 420 podNsOptions, err := GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) 421 if err != nil { 422 return nil, err 423 } 424 podOptions = append(podOptions, podNsOptions...) 425 // make pod 426 pod, err := runtime.NewPod(ctx, podOptions...) 427 if err != nil { 428 return nil, err 429 } 430 logrus.Debugf("pod %s created by new container request", pod.ID()) 431 432 // The container now cannot have port bindings; so we reset the map 433 portBindings = make(map[nat.Port][]nat.PortBinding) 434 } 435 namespaces, podInfraID, err = configurePod(c, runtime, namespaces, podName) 436 if err != nil { 437 return nil, err 438 } 439 } 440 441 pidMode := ns.PidMode(namespaces["pid"]) 442 if !cc.Valid(string(pidMode), pidMode) { 443 return nil, errors.Errorf("--pid %q is not valid", c.String("pid")) 444 } 445 446 usernsMode := ns.UsernsMode(namespaces["user"]) 447 if !cc.Valid(string(usernsMode), usernsMode) { 448 return nil, errors.Errorf("--userns %q is not valid", namespaces["user"]) 449 } 450 451 utsMode := ns.UTSMode(namespaces["uts"]) 452 if !cc.Valid(string(utsMode), utsMode) { 453 return nil, errors.Errorf("--uts %q is not valid", namespaces["uts"]) 454 } 455 456 cgroupMode := ns.CgroupMode(namespaces["cgroup"]) 457 if !cgroupMode.Valid() { 458 return nil, errors.Errorf("--cgroup %q is not valid", namespaces["cgroup"]) 459 } 460 461 ipcMode := ns.IpcMode(namespaces["ipc"]) 462 if !cc.Valid(string(ipcMode), ipcMode) { 463 return nil, errors.Errorf("--ipc %q is not valid", ipcMode) 464 } 465 466 // Make sure if network is set to container namespace, port binding is not also being asked for 467 netMode := ns.NetworkMode(namespaces["net"]) 468 if netMode.IsContainer() { 469 if len(portBindings) > 0 { 470 return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") 471 } 472 } 473 474 // USER 475 user := c.String("user") 476 if user == "" { 477 switch { 478 case usernsMode.IsKeepID(): 479 user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) 480 case data == nil: 481 user = "0" 482 default: 483 user = data.Config.User 484 } 485 } 486 487 // STOP SIGNAL 488 stopSignal := syscall.SIGTERM 489 signalString := "" 490 if data != nil { 491 signalString = data.Config.StopSignal 492 } 493 if c.IsSet("stop-signal") { 494 signalString = c.String("stop-signal") 495 } 496 if signalString != "" { 497 stopSignal, err = util.ParseSignal(signalString) 498 if err != nil { 499 return nil, err 500 } 501 } 502 503 // ENVIRONMENT VARIABLES 504 // 505 // Precedence order (higher index wins): 506 // 1) env-host, 2) image data, 3) env-file, 4) env 507 env := map[string]string{ 508 "container": "podman", 509 } 510 511 // First transform the os env into a map. We need it for the labels later in 512 // any case. 513 osEnv, err := envLib.ParseSlice(os.Environ()) 514 if err != nil { 515 return nil, errors.Wrap(err, "error parsing host environment variables") 516 } 517 518 // Start with env-host 519 520 if c.Bool("env-host") { 521 env = envLib.Join(env, osEnv) 522 } 523 524 // Image data overrides any previous variables 525 if data != nil { 526 configEnv, err := envLib.ParseSlice(data.Config.Env) 527 if err != nil { 528 return nil, errors.Wrap(err, "error passing image environment variables") 529 } 530 env = envLib.Join(env, configEnv) 531 } 532 533 // env-file overrides any previous variables 534 if c.IsSet("env-file") { 535 for _, f := range c.StringSlice("env-file") { 536 fileEnv, err := envLib.ParseFile(f) 537 if err != nil { 538 return nil, err 539 } 540 // File env is overridden by env. 541 env = envLib.Join(env, fileEnv) 542 } 543 } 544 545 if c.IsSet("env") { 546 // env overrides any previous variables 547 cmdlineEnv := c.StringSlice("env") 548 if len(cmdlineEnv) > 0 { 549 parsedEnv, err := envLib.ParseSlice(cmdlineEnv) 550 if err != nil { 551 return nil, err 552 } 553 env = envLib.Join(env, parsedEnv) 554 } 555 } 556 557 // LABEL VARIABLES 558 labels, err := parse.GetAllLabels(c.StringSlice("label-file"), c.StringArray("label")) 559 if err != nil { 560 return nil, errors.Wrapf(err, "unable to process labels") 561 } 562 if data != nil { 563 for key, val := range data.Config.Labels { 564 if _, ok := labels[key]; !ok { 565 labels[key] = val 566 } 567 } 568 } 569 570 if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists { 571 labels[systemdGen.EnvVariable] = systemdUnit 572 } 573 574 // ANNOTATIONS 575 annotations := make(map[string]string) 576 577 // First, add our default annotations 578 annotations[ann.TTY] = "false" 579 if tty { 580 annotations[ann.TTY] = "true" 581 } 582 583 // in the event this container is in a pod, and the pod has an infra container 584 // we will want to configure it as a type "container" instead defaulting to 585 // the behavior of a "sandbox" container 586 // In Kata containers: 587 // - "sandbox" is the annotation that denotes the container should use its own 588 // VM, which is the default behavior 589 // - "container" denotes the container should join the VM of the SandboxID 590 // (the infra container) 591 if podInfraID != "" { 592 annotations[ann.SandboxID] = podInfraID 593 annotations[ann.ContainerType] = ann.ContainerTypeContainer 594 } 595 596 if data != nil { 597 // Next, add annotations from the image 598 for key, value := range data.Annotations { 599 annotations[key] = value 600 } 601 } 602 // Last, add user annotations 603 for _, annotation := range c.StringSlice("annotation") { 604 splitAnnotation := strings.SplitN(annotation, "=", 2) 605 if len(splitAnnotation) < 2 { 606 return nil, errors.Errorf("Annotations must be formatted KEY=VALUE") 607 } 608 annotations[splitAnnotation[0]] = splitAnnotation[1] 609 } 610 611 // WORKING DIRECTORY 612 workDir := "/" 613 if c.IsSet("workdir") { 614 workDir = c.String("workdir") 615 } else if data != nil && data.Config.WorkingDir != "" { 616 workDir = data.Config.WorkingDir 617 } 618 619 userCommand := []string{} 620 entrypoint := configureEntrypoint(c, data) 621 // Build the command 622 // If we have an entry point, it goes first 623 if len(entrypoint) > 0 { 624 command = entrypoint 625 } 626 if len(inputCommand) > 0 { 627 // User command overrides data CMD 628 command = append(command, inputCommand...) 629 userCommand = append(userCommand, inputCommand...) 630 } else if data != nil && len(data.Config.Cmd) > 0 && !c.IsSet("entrypoint") { 631 // If not user command, add CMD 632 command = append(command, data.Config.Cmd...) 633 userCommand = append(userCommand, data.Config.Cmd...) 634 } 635 636 if data != nil && len(command) == 0 { 637 return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") 638 } 639 640 // SHM Size 641 shmSize, err := units.FromHumanSize(c.String("shm-size")) 642 if err != nil { 643 return nil, errors.Wrapf(err, "unable to translate --shm-size") 644 } 645 646 if c.IsSet("add-host") { 647 // Verify the additional hosts are in correct format 648 for _, host := range c.StringSlice("add-host") { 649 if _, err := parse.ValidateExtraHost(host); err != nil { 650 return nil, err 651 } 652 } 653 } 654 655 var ( 656 dnsSearches []string 657 dnsServers []string 658 dnsOptions []string 659 ) 660 if c.Changed("dns-search") { 661 dnsSearches = c.StringSlice("dns-search") 662 // Check for explicit dns-search domain of '' 663 if len(dnsSearches) == 0 { 664 return nil, errors.Errorf("'' is not a valid domain") 665 } 666 // Validate domains are good 667 for _, dom := range dnsSearches { 668 if dom == "." { 669 if len(dnsSearches) > 1 { 670 return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") 671 } 672 continue 673 } 674 if _, err := parse.ValidateDomain(dom); err != nil { 675 return nil, err 676 } 677 } 678 } 679 if c.IsSet("dns") { 680 dnsServers = append(dnsServers, c.StringSlice("dns")...) 681 } 682 if c.IsSet("dns-opt") { 683 dnsOptions = c.StringSlice("dns-opt") 684 } 685 686 var ImageVolumes map[string]struct{} 687 if data != nil && c.String("image-volume") != "ignore" { 688 ImageVolumes = data.Config.Volumes 689 } 690 691 var imageVolType = map[string]string{ 692 "bind": "", 693 "tmpfs": "", 694 "ignore": "", 695 } 696 if _, ok := imageVolType[c.String("image-volume")]; !ok { 697 return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) 698 } 699 700 systemd := c.String("systemd") == "always" 701 if !systemd && command != nil { 702 x, err := strconv.ParseBool(c.String("systemd")) 703 if err != nil { 704 return nil, errors.Wrapf(err, "cannot parse bool %s", c.String("systemd")) 705 } 706 if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) { 707 systemd = true 708 } 709 } 710 if systemd { 711 if signalString == "" { 712 stopSignal, err = util.ParseSignal("RTMIN+3") 713 if err != nil { 714 return nil, errors.Wrapf(err, "error parsing systemd signal") 715 } 716 } 717 } 718 // This is done because cobra cannot have two aliased flags. So we have to check 719 // both 720 memorySwappiness := c.Int64("memory-swappiness") 721 722 logDriver := libpod.KubernetesLogging 723 if c.Changed("log-driver") { 724 logDriver = c.String("log-driver") 725 } 726 727 pidsLimit := c.Int64("pids-limit") 728 if c.String("cgroups") == "disabled" && !c.Changed("pids-limit") { 729 pidsLimit = -1 730 } 731 732 pid := &cc.PidConfig{ 733 PidMode: pidMode, 734 } 735 ipc := &cc.IpcConfig{ 736 IpcMode: ipcMode, 737 } 738 739 cgroup := &cc.CgroupConfig{ 740 Cgroups: c.String("cgroups"), 741 Cgroupns: c.String("cgroupns"), 742 CgroupParent: c.String("cgroup-parent"), 743 CgroupMode: cgroupMode, 744 } 745 746 userns := &cc.UserConfig{ 747 GroupAdd: c.StringSlice("group-add"), 748 IDMappings: idmappings, 749 UsernsMode: usernsMode, 750 User: user, 751 } 752 753 uts := &cc.UtsConfig{ 754 UtsMode: utsMode, 755 NoHosts: c.Bool("no-hosts"), 756 HostAdd: c.StringSlice("add-host"), 757 Hostname: c.String("hostname"), 758 } 759 net := &cc.NetworkConfig{ 760 DNSOpt: dnsOptions, 761 DNSSearch: dnsSearches, 762 DNSServers: dnsServers, 763 HTTPProxy: c.Bool("http-proxy"), 764 MacAddress: c.String("mac-address"), 765 Network: c.String("network"), 766 NetMode: netMode, 767 IPAddress: c.String("ip"), 768 Publish: c.StringSlice("publish"), 769 PublishAll: c.Bool("publish-all"), 770 PortBindings: portBindings, 771 } 772 773 sysctl := map[string]string{} 774 if c.Changed("sysctl") { 775 sysctl, err = util.ValidateSysctls(c.StringSlice("sysctl")) 776 if err != nil { 777 return nil, errors.Wrapf(err, "invalid value for sysctl") 778 } 779 } 780 781 secConfig := &cc.SecurityConfig{ 782 CapAdd: c.StringSlice("cap-add"), 783 CapDrop: c.StringSlice("cap-drop"), 784 Privileged: c.Bool("privileged"), 785 ReadOnlyRootfs: c.Bool("read-only"), 786 ReadOnlyTmpfs: c.Bool("read-only-tmpfs"), 787 Sysctl: sysctl, 788 } 789 790 var securityOpt []string 791 if c.Changed("security-opt") { 792 securityOpt = c.StringArray("security-opt") 793 } 794 if err := secConfig.SetSecurityOpts(runtime, securityOpt); err != nil { 795 return nil, err 796 } 797 798 // SECCOMP 799 if data != nil { 800 if value, exists := labels[seccomp.ContainerImageLabel]; exists { 801 secConfig.SeccompProfileFromImage = value 802 } 803 } 804 if policy, err := seccomp.LookupPolicy(c.String("seccomp-policy")); err != nil { 805 return nil, err 806 } else { 807 secConfig.SeccompPolicy = policy 808 } 809 rtc, err := runtime.GetConfig() 810 if err != nil { 811 return nil, err 812 } 813 volumes := rtc.Containers.Volumes 814 if c.Changed("volume") { 815 volumes = append(volumes, c.StringSlice("volume")...) 816 } 817 818 devices := rtc.Containers.Devices 819 if c.Changed("device") { 820 devices = append(devices, c.StringSlice("device")...) 821 } 822 823 config := &cc.CreateConfig{ 824 Annotations: annotations, 825 BuiltinImgVolumes: ImageVolumes, 826 ConmonPidFile: c.String("conmon-pidfile"), 827 ImageVolumeType: c.String("image-volume"), 828 CidFile: c.String("cidfile"), 829 Command: command, 830 UserCommand: userCommand, 831 Detach: c.Bool("detach"), 832 Devices: devices, 833 Entrypoint: entrypoint, 834 Env: env, 835 // ExposedPorts: ports, 836 Init: c.Bool("init"), 837 InitPath: c.String("init-path"), 838 Image: imageName, 839 RawImageName: rawImageName, 840 ImageID: imageID, 841 Interactive: c.Bool("interactive"), 842 // IP6Address: c.String("ipv6"), // Not implemented yet - needs CNI support for static v6 843 Labels: labels, 844 // LinkLocalIP: c.StringSlice("link-local-ip"), // Not implemented yet 845 LogDriver: logDriver, 846 LogDriverOpt: c.StringSlice("log-opt"), 847 Name: c.String("name"), 848 // NetworkAlias: c.StringSlice("network-alias"), // Not implemented - does this make sense in Podman? 849 Pod: podName, 850 Quiet: c.Bool("quiet"), 851 Resources: cc.CreateResourceConfig{ 852 BlkioWeight: blkioWeight, 853 BlkioWeightDevice: c.StringSlice("blkio-weight-device"), 854 CPUShares: c.Uint64("cpu-shares"), 855 CPUPeriod: c.Uint64("cpu-period"), 856 CPUsetCPUs: c.String("cpuset-cpus"), 857 CPUsetMems: c.String("cpuset-mems"), 858 CPUQuota: c.Int64("cpu-quota"), 859 CPURtPeriod: c.Uint64("cpu-rt-period"), 860 CPURtRuntime: c.Int64("cpu-rt-runtime"), 861 CPUs: c.Float64("cpus"), 862 DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), 863 DeviceReadBps: c.StringSlice("device-read-bps"), 864 DeviceReadIOps: c.StringSlice("device-read-iops"), 865 DeviceWriteBps: c.StringSlice("device-write-bps"), 866 DeviceWriteIOps: c.StringSlice("device-write-iops"), 867 DisableOomKiller: c.Bool("oom-kill-disable"), 868 ShmSize: shmSize, 869 Memory: memoryLimit, 870 MemoryReservation: memoryReservation, 871 MemorySwap: memorySwap, 872 MemorySwappiness: int(memorySwappiness), 873 KernelMemory: memoryKernel, 874 OomScoreAdj: c.Int("oom-score-adj"), 875 PidsLimit: pidsLimit, 876 Ulimit: c.StringSlice("ulimit"), 877 }, 878 RestartPolicy: c.String("restart"), 879 Rm: c.Bool("rm"), 880 Security: *secConfig, 881 StopSignal: stopSignal, 882 StopTimeout: c.Uint("stop-timeout"), 883 Systemd: systemd, 884 Tmpfs: c.StringArray("tmpfs"), 885 Tty: tty, 886 MountsFlag: c.StringArray("mount"), 887 Volumes: volumes, 888 WorkDir: workDir, 889 Rootfs: rootfs, 890 VolumesFrom: c.StringSlice("volumes-from"), 891 Syslog: c.Bool("syslog"), 892 893 Pid: *pid, 894 Ipc: *ipc, 895 Cgroup: *cgroup, 896 User: *userns, 897 Uts: *uts, 898 Network: *net, 899 } 900 901 warnings, err := verifyContainerResources(config, false) 902 if err != nil { 903 return nil, err 904 } 905 for _, warning := range warnings { 906 fmt.Fprintln(os.Stderr, warning) 907 } 908 return config, nil 909 } 910 911 func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) { 912 runtimeSpec, options, err := createConfig.MakeContainerConfig(r, pod) 913 if err != nil { 914 return nil, err 915 } 916 917 // Set the CreateCommand explicitly. Some (future) consumers of libpod 918 // might not want to set it. 919 options = append(options, libpod.WithCreateCommand()) 920 921 ctr, err := r.NewContainer(ctx, runtimeSpec, options...) 922 if err != nil { 923 return nil, err 924 } 925 return ctr, nil 926 } 927 928 func makeHealthCheckFromCli(c *GenericCLIResults) (*manifest.Schema2HealthConfig, error) { 929 inCommand := c.String("healthcheck-command") 930 inInterval := c.String("healthcheck-interval") 931 inRetries := c.Uint("healthcheck-retries") 932 inTimeout := c.String("healthcheck-timeout") 933 inStartPeriod := c.String("healthcheck-start-period") 934 935 // Every healthcheck requires a command 936 if len(inCommand) == 0 { 937 return nil, errors.New("Must define a healthcheck command for all healthchecks") 938 } 939 940 // first try to parse option value as JSON array of strings... 941 cmd := []string{} 942 err := json.Unmarshal([]byte(inCommand), &cmd) 943 if err != nil { 944 // ...otherwise pass it to "/bin/sh -c" inside the container 945 cmd = []string{"CMD-SHELL", inCommand} 946 } 947 hc := manifest.Schema2HealthConfig{ 948 Test: cmd, 949 } 950 951 if inInterval == "disable" { 952 inInterval = "0" 953 } 954 intervalDuration, err := time.ParseDuration(inInterval) 955 if err != nil { 956 return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", inInterval) 957 } 958 959 hc.Interval = intervalDuration 960 961 if inRetries < 1 { 962 return nil, errors.New("healthcheck-retries must be greater than 0.") 963 } 964 hc.Retries = int(inRetries) 965 timeoutDuration, err := time.ParseDuration(inTimeout) 966 if err != nil { 967 return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", inTimeout) 968 } 969 if timeoutDuration < time.Duration(1) { 970 return nil, errors.New("healthcheck-timeout must be at least 1 second") 971 } 972 hc.Timeout = timeoutDuration 973 974 startPeriodDuration, err := time.ParseDuration(inStartPeriod) 975 if err != nil { 976 return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", inStartPeriod) 977 } 978 if startPeriodDuration < time.Duration(0) { 979 return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") 980 } 981 hc.StartPeriod = startPeriodDuration 982 983 return &hc, nil 984 }