github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgenutil/specgen.go (about) 1 package specgenutil 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/containers/common/pkg/config" 12 "github.com/containers/image/v5/manifest" 13 "github.com/hanks177/podman/v4/cmd/podman/parse" 14 "github.com/hanks177/podman/v4/libpod/define" 15 ann "github.com/hanks177/podman/v4/pkg/annotations" 16 "github.com/hanks177/podman/v4/pkg/domain/entities" 17 envLib "github.com/hanks177/podman/v4/pkg/env" 18 "github.com/hanks177/podman/v4/pkg/namespaces" 19 "github.com/hanks177/podman/v4/pkg/specgen" 20 systemdDefine "github.com/hanks177/podman/v4/pkg/systemd/define" 21 "github.com/hanks177/podman/v4/pkg/util" 22 "github.com/docker/docker/opts" 23 "github.com/docker/go-units" 24 "github.com/opencontainers/runtime-spec/specs-go" 25 "github.com/pkg/errors" 26 ) 27 28 func getCPULimits(c *entities.ContainerCreateOptions) *specs.LinuxCPU { 29 cpu := &specs.LinuxCPU{} 30 hasLimits := false 31 32 if c.CPUS > 0 { 33 period, quota := util.CoresToPeriodAndQuota(c.CPUS) 34 35 cpu.Period = &period 36 cpu.Quota = "a 37 hasLimits = true 38 } 39 if c.CPUShares > 0 { 40 cpu.Shares = &c.CPUShares 41 hasLimits = true 42 } 43 if c.CPUPeriod > 0 { 44 cpu.Period = &c.CPUPeriod 45 hasLimits = true 46 } 47 if c.CPUSetCPUs != "" { 48 cpu.Cpus = c.CPUSetCPUs 49 hasLimits = true 50 } 51 if c.CPUSetMems != "" { 52 cpu.Mems = c.CPUSetMems 53 hasLimits = true 54 } 55 if c.CPUQuota > 0 { 56 cpu.Quota = &c.CPUQuota 57 hasLimits = true 58 } 59 if c.CPURTPeriod > 0 { 60 cpu.RealtimePeriod = &c.CPURTPeriod 61 hasLimits = true 62 } 63 if c.CPURTRuntime > 0 { 64 cpu.RealtimeRuntime = &c.CPURTRuntime 65 hasLimits = true 66 } 67 68 if !hasLimits { 69 return nil 70 } 71 return cpu 72 } 73 74 func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxBlockIO, error) { 75 var err error 76 io := &specs.LinuxBlockIO{} 77 hasLimits := false 78 if b := c.BlkIOWeight; len(b) > 0 { 79 u, err := strconv.ParseUint(b, 10, 16) 80 if err != nil { 81 return nil, errors.Wrapf(err, "invalid value for blkio-weight") 82 } 83 nu := uint16(u) 84 io.Weight = &nu 85 hasLimits = true 86 } 87 88 if len(c.BlkIOWeightDevice) > 0 { 89 if s.WeightDevice, err = parseWeightDevices(c.BlkIOWeightDevice); err != nil { 90 return nil, err 91 } 92 hasLimits = true 93 } 94 95 if bps := c.DeviceReadBPs; len(bps) > 0 { 96 if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { 97 return nil, err 98 } 99 hasLimits = true 100 } 101 102 if bps := c.DeviceWriteBPs; len(bps) > 0 { 103 if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { 104 return nil, err 105 } 106 hasLimits = true 107 } 108 109 if iops := c.DeviceReadIOPs; len(iops) > 0 { 110 if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { 111 return nil, err 112 } 113 hasLimits = true 114 } 115 116 if iops := c.DeviceWriteIOPs; len(iops) > 0 { 117 if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { 118 return nil, err 119 } 120 hasLimits = true 121 } 122 123 if !hasLimits { 124 return nil, nil 125 } 126 return io, nil 127 } 128 129 func LimitToSwap(memory *specs.LinuxMemory, swap string, ml int64) { 130 if ml > 0 { 131 memory.Limit = &ml 132 if swap == "" { 133 limit := 2 * ml 134 memory.Swap = &(limit) 135 } 136 } 137 } 138 139 func getMemoryLimits(c *entities.ContainerCreateOptions) (*specs.LinuxMemory, error) { 140 var err error 141 memory := &specs.LinuxMemory{} 142 hasLimits := false 143 if m := c.Memory; len(m) > 0 { 144 ml, err := units.RAMInBytes(m) 145 if err != nil { 146 return nil, errors.Wrapf(err, "invalid value for memory") 147 } 148 LimitToSwap(memory, c.MemorySwap, ml) 149 hasLimits = true 150 } 151 if m := c.MemoryReservation; len(m) > 0 { 152 mr, err := units.RAMInBytes(m) 153 if err != nil { 154 return nil, errors.Wrapf(err, "invalid value for memory") 155 } 156 memory.Reservation = &mr 157 hasLimits = true 158 } 159 if m := c.MemorySwap; len(m) > 0 { 160 var ms int64 161 // only set memory swap if it was set 162 // -1 indicates unlimited 163 if m != "-1" { 164 ms, err = units.RAMInBytes(m) 165 memory.Swap = &ms 166 if err != nil { 167 return nil, errors.Wrapf(err, "invalid value for memory") 168 } 169 hasLimits = true 170 } 171 } 172 if c.MemorySwappiness >= 0 { 173 swappiness := uint64(c.MemorySwappiness) 174 memory.Swappiness = &swappiness 175 hasLimits = true 176 } 177 if c.OOMKillDisable { 178 memory.DisableOOMKiller = &c.OOMKillDisable 179 hasLimits = true 180 } 181 if !hasLimits { 182 return nil, nil 183 } 184 return memory, nil 185 } 186 187 func setNamespaces(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) error { 188 var err error 189 190 if c.PID != "" { 191 s.PidNS, err = specgen.ParseNamespace(c.PID) 192 if err != nil { 193 return err 194 } 195 } 196 if c.IPC != "" { 197 s.IpcNS, err = specgen.ParseIPCNamespace(c.IPC) 198 if err != nil { 199 return err 200 } 201 } 202 if c.UTS != "" { 203 s.UtsNS, err = specgen.ParseNamespace(c.UTS) 204 if err != nil { 205 return err 206 } 207 } 208 if c.CgroupNS != "" { 209 s.CgroupNS, err = specgen.ParseNamespace(c.CgroupNS) 210 if err != nil { 211 return err 212 } 213 } 214 userns := os.Getenv("PODMAN_USERNS") 215 if c.UserNS != "" { 216 userns = c.UserNS 217 } 218 // userns must be treated differently 219 if userns != "" { 220 s.UserNS, err = specgen.ParseUserNamespace(userns) 221 if err != nil { 222 return err 223 } 224 } 225 if c.Net != nil { 226 s.NetNS = c.Net.Network 227 } 228 return nil 229 } 230 231 func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions, args []string) error { 232 rtc, err := config.Default() 233 if err != nil { 234 return err 235 } 236 237 // validate flags as needed 238 if err := validate(c); err != nil { 239 return err 240 } 241 s.User = c.User 242 var inputCommand []string 243 if !c.IsInfra { 244 if len(args) > 1 { 245 inputCommand = args[1:] 246 } 247 } 248 249 if len(c.HealthCmd) > 0 { 250 if c.NoHealthCheck { 251 return errors.New("Cannot specify both --no-healthcheck and --health-cmd") 252 } 253 s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod) 254 if err != nil { 255 return err 256 } 257 } else if c.NoHealthCheck { 258 s.HealthConfig = &manifest.Schema2HealthConfig{ 259 Test: []string{"NONE"}, 260 } 261 } 262 if err := setNamespaces(s, c); err != nil { 263 return err 264 } 265 266 if s.IDMappings == nil { 267 userNS := namespaces.UsernsMode(s.UserNS.NSMode) 268 tempIDMap, err := util.ParseIDMapping(namespaces.UsernsMode(c.UserNS), []string{}, []string{}, "", "") 269 if err != nil { 270 return err 271 } 272 s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName) 273 if err != nil { 274 return err 275 } 276 if len(s.IDMappings.GIDMap) == 0 { 277 s.IDMappings.AutoUserNsOpts.AdditionalGIDMappings = tempIDMap.AutoUserNsOpts.AdditionalGIDMappings 278 if s.UserNS.NSMode == specgen.NamespaceMode("auto") { 279 s.IDMappings.AutoUserNs = true 280 } 281 } 282 if len(s.IDMappings.UIDMap) == 0 { 283 s.IDMappings.AutoUserNsOpts.AdditionalUIDMappings = tempIDMap.AutoUserNsOpts.AdditionalUIDMappings 284 if s.UserNS.NSMode == specgen.NamespaceMode("auto") { 285 s.IDMappings.AutoUserNs = true 286 } 287 } 288 if tempIDMap.AutoUserNsOpts.Size != 0 { 289 s.IDMappings.AutoUserNsOpts.Size = tempIDMap.AutoUserNsOpts.Size 290 } 291 // If some mappings are specified, assume a private user namespace 292 if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) { 293 s.UserNS.NSMode = specgen.Private 294 } else { 295 s.UserNS.NSMode = specgen.NamespaceMode(userNS) 296 } 297 } 298 299 if !s.Terminal { 300 s.Terminal = c.TTY 301 } 302 303 if err := verifyExpose(c.Expose); err != nil { 304 return err 305 } 306 // We are not handling the Expose flag yet. 307 // s.PortsExpose = c.Expose 308 if c.Net != nil { 309 s.PortMappings = c.Net.PublishPorts 310 } 311 if !s.PublishExposedPorts { 312 s.PublishExposedPorts = c.PublishAll 313 } 314 315 if len(s.Pod) == 0 { 316 s.Pod = c.Pod 317 } 318 319 if len(c.PodIDFile) > 0 { 320 if len(s.Pod) > 0 { 321 return errors.New("Cannot specify both --pod and --pod-id-file") 322 } 323 podID, err := ReadPodIDFile(c.PodIDFile) 324 if err != nil { 325 return err 326 } 327 s.Pod = podID 328 } 329 330 expose, err := CreateExpose(c.Expose) 331 if err != nil { 332 return err 333 } 334 335 if len(s.Expose) == 0 { 336 s.Expose = expose 337 } 338 339 if sig := c.StopSignal; len(sig) > 0 { 340 stopSignal, err := util.ParseSignal(sig) 341 if err != nil { 342 return err 343 } 344 s.StopSignal = &stopSignal 345 } 346 347 // ENVIRONMENT VARIABLES 348 // 349 // Precedence order (higher index wins): 350 // 1) containers.conf (EnvHost, EnvHTTP, Env) 2) image data, 3 User EnvHost/EnvHTTP, 4) env-file, 5) env 351 // containers.conf handled and image data handled on the server side 352 // user specified EnvHost and EnvHTTP handled on Server Side relative to Server 353 // env-file and env handled on client side 354 var env map[string]string 355 356 // First transform the os env into a map. We need it for the labels later in 357 // any case. 358 osEnv, err := envLib.ParseSlice(os.Environ()) 359 if err != nil { 360 return errors.Wrap(err, "error parsing host environment variables") 361 } 362 363 if !s.EnvHost { 364 s.EnvHost = c.EnvHost 365 } 366 367 if !s.HTTPProxy { 368 s.HTTPProxy = c.HTTPProxy 369 } 370 371 // env-file overrides any previous variables 372 for _, f := range c.EnvFile { 373 fileEnv, err := envLib.ParseFile(f) 374 if err != nil { 375 return err 376 } 377 // File env is overridden by env. 378 env = envLib.Join(env, fileEnv) 379 } 380 381 parsedEnv, err := envLib.ParseSlice(c.Env) 382 if err != nil { 383 return err 384 } 385 386 if len(s.Env) == 0 { 387 s.Env = envLib.Join(env, parsedEnv) 388 } 389 390 // LABEL VARIABLES 391 labels, err := parse.GetAllLabels(c.LabelFile, c.Label) 392 if err != nil { 393 return errors.Wrapf(err, "unable to process labels") 394 } 395 396 if systemdUnit, exists := osEnv[systemdDefine.EnvVariable]; exists { 397 labels[systemdDefine.EnvVariable] = systemdUnit 398 } 399 400 if len(s.Labels) == 0 { 401 s.Labels = labels 402 } 403 404 // ANNOTATIONS 405 annotations := make(map[string]string) 406 407 // First, add our default annotations 408 annotations[ann.TTY] = "false" 409 if c.TTY { 410 annotations[ann.TTY] = "true" 411 } 412 413 // Last, add user annotations 414 for _, annotation := range c.Annotation { 415 splitAnnotation := strings.SplitN(annotation, "=", 2) 416 if len(splitAnnotation) < 2 { 417 return errors.Errorf("Annotations must be formatted KEY=VALUE") 418 } 419 annotations[splitAnnotation[0]] = splitAnnotation[1] 420 } 421 if len(s.Annotations) == 0 { 422 s.Annotations = annotations 423 } 424 425 if len(c.StorageOpts) > 0 { 426 opts := make(map[string]string, len(c.StorageOpts)) 427 for _, opt := range c.StorageOpts { 428 split := strings.SplitN(opt, "=", 2) 429 if len(split) != 2 { 430 return errors.Errorf("storage-opt must be formatted KEY=VALUE") 431 } 432 opts[split[0]] = split[1] 433 } 434 s.StorageOpts = opts 435 } 436 if len(s.WorkDir) == 0 { 437 s.WorkDir = c.Workdir 438 } 439 if c.Entrypoint != nil { 440 entrypoint := []string{} 441 // Check if entrypoint specified is json 442 if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil { 443 entrypoint = append(entrypoint, *c.Entrypoint) 444 } 445 s.Entrypoint = entrypoint 446 } 447 448 // Include the command used to create the container. 449 450 if len(s.ContainerCreateCommand) == 0 { 451 s.ContainerCreateCommand = os.Args 452 } 453 454 if len(inputCommand) > 0 { 455 s.Command = inputCommand 456 } 457 458 // SHM Size 459 if c.ShmSize != "" { 460 var m opts.MemBytes 461 if err := m.Set(c.ShmSize); err != nil { 462 return errors.Wrapf(err, "unable to translate --shm-size") 463 } 464 val := m.Value() 465 s.ShmSize = &val 466 } 467 468 if c.Net != nil { 469 s.Networks = c.Net.Networks 470 } 471 472 if c.Net != nil { 473 s.HostAdd = c.Net.AddHosts 474 s.UseImageResolvConf = c.Net.UseImageResolvConf 475 s.DNSServers = c.Net.DNSServers 476 s.DNSSearch = c.Net.DNSSearch 477 s.DNSOptions = c.Net.DNSOptions 478 s.NetworkOptions = c.Net.NetworkOptions 479 s.UseImageHosts = c.Net.NoHosts 480 } 481 if len(s.HostUsers) == 0 || len(c.HostUsers) != 0 { 482 s.HostUsers = c.HostUsers 483 } 484 if len(c.ImageVolume) != 0 { 485 if len(s.ImageVolumeMode) == 0 { 486 s.ImageVolumeMode = c.ImageVolume 487 } 488 } 489 if len(s.ImageVolumeMode) == 0 { 490 s.ImageVolumeMode = rtc.Engine.ImageVolumeMode 491 } 492 if s.ImageVolumeMode == "bind" { 493 s.ImageVolumeMode = "anonymous" 494 } 495 496 if len(s.Systemd) == 0 || len(c.Systemd) != 0 { 497 s.Systemd = strings.ToLower(c.Systemd) 498 } 499 if len(s.SdNotifyMode) == 0 || len(c.SdNotifyMode) != 0 { 500 s.SdNotifyMode = c.SdNotifyMode 501 } 502 if s.ResourceLimits == nil { 503 s.ResourceLimits = &specs.LinuxResources{} 504 } 505 506 if s.ResourceLimits.Memory == nil || (len(c.Memory) != 0 || len(c.MemoryReservation) != 0 || len(c.MemorySwap) != 0 || c.MemorySwappiness != 0) { 507 s.ResourceLimits.Memory, err = getMemoryLimits(c) 508 if err != nil { 509 return err 510 } 511 } 512 if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0) { 513 s.ResourceLimits.BlockIO, err = getIOLimits(s, c) 514 if err != nil { 515 return err 516 } 517 } 518 if c.PIDsLimit != nil { 519 pids := specs.LinuxPids{ 520 Limit: *c.PIDsLimit, 521 } 522 523 s.ResourceLimits.Pids = &pids 524 } 525 526 if s.ResourceLimits.CPU == nil || (c.CPUPeriod != 0 || c.CPUQuota != 0 || c.CPURTPeriod != 0 || c.CPURTRuntime != 0 || c.CPUS != 0 || len(c.CPUSetCPUs) != 0 || len(c.CPUSetMems) != 0 || c.CPUShares != 0) { 527 s.ResourceLimits.CPU = getCPULimits(c) 528 } 529 530 unifieds := make(map[string]string) 531 for _, unified := range c.CgroupConf { 532 splitUnified := strings.SplitN(unified, "=", 2) 533 if len(splitUnified) < 2 { 534 return errors.Errorf("--cgroup-conf must be formatted KEY=VALUE") 535 } 536 unifieds[splitUnified[0]] = splitUnified[1] 537 } 538 if len(unifieds) > 0 { 539 s.ResourceLimits.Unified = unifieds 540 } 541 542 if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil && s.ResourceLimits.Unified == nil { 543 s.ResourceLimits = nil 544 } 545 546 if s.LogConfiguration == nil { 547 s.LogConfiguration = &specgen.LogConfig{} 548 } 549 550 if ld := c.LogDriver; len(ld) > 0 { 551 s.LogConfiguration.Driver = ld 552 } 553 if len(s.CgroupParent) == 0 || len(c.CgroupParent) != 0 { 554 s.CgroupParent = c.CgroupParent 555 } 556 if len(s.CgroupsMode) == 0 { 557 s.CgroupsMode = c.CgroupsMode 558 } 559 if s.CgroupsMode == "" { 560 s.CgroupsMode = rtc.Cgroups() 561 } 562 563 if len(s.Groups) == 0 || len(c.GroupAdd) != 0 { 564 s.Groups = c.GroupAdd 565 } 566 567 if len(s.Hostname) == 0 || len(c.Hostname) != 0 { 568 s.Hostname = c.Hostname 569 } 570 sysctl := map[string]string{} 571 if ctl := c.Sysctl; len(ctl) > 0 { 572 sysctl, err = util.ValidateSysctls(ctl) 573 if err != nil { 574 return err 575 } 576 } 577 if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 { 578 s.Sysctl = sysctl 579 } 580 581 if len(s.CapAdd) == 0 || len(c.CapAdd) != 0 { 582 s.CapAdd = c.CapAdd 583 } 584 if len(s.CapDrop) == 0 || len(c.CapDrop) != 0 { 585 s.CapDrop = c.CapDrop 586 } 587 if !s.Privileged { 588 s.Privileged = c.Privileged 589 } 590 if !s.ReadOnlyFilesystem { 591 s.ReadOnlyFilesystem = c.ReadOnly 592 } 593 if len(s.ConmonPidFile) == 0 || len(c.ConmonPIDFile) != 0 { 594 s.ConmonPidFile = c.ConmonPIDFile 595 } 596 597 if len(s.DependencyContainers) == 0 || len(c.Requires) != 0 { 598 s.DependencyContainers = c.Requires 599 } 600 601 // TODO 602 // outside of specgen and oci though 603 // defaults to true, check spec/storage 604 // s.readonly = c.ReadOnlyTmpFS 605 // TODO convert to map? 606 // check if key=value and convert 607 sysmap := make(map[string]string) 608 for _, ctl := range c.Sysctl { 609 splitCtl := strings.SplitN(ctl, "=", 2) 610 if len(splitCtl) < 2 { 611 return errors.Errorf("invalid sysctl value %q", ctl) 612 } 613 sysmap[splitCtl[0]] = splitCtl[1] 614 } 615 if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 { 616 s.Sysctl = sysmap 617 } 618 619 if c.CIDFile != "" { 620 s.Annotations[define.InspectAnnotationCIDFile] = c.CIDFile 621 } 622 623 for _, opt := range c.SecurityOpt { 624 if opt == "no-new-privileges" { 625 s.ContainerSecurityConfig.NoNewPrivileges = true 626 } else { 627 // Docker deprecated the ":" syntax but still supports it, 628 // so we need to as well 629 var con []string 630 if strings.Contains(opt, "=") { 631 con = strings.SplitN(opt, "=", 2) 632 } else { 633 con = strings.SplitN(opt, ":", 2) 634 } 635 if len(con) != 2 { 636 return fmt.Errorf("invalid --security-opt 1: %q", opt) 637 } 638 switch con[0] { 639 case "apparmor": 640 s.ContainerSecurityConfig.ApparmorProfile = con[1] 641 s.Annotations[define.InspectAnnotationApparmor] = con[1] 642 case "label": 643 // TODO selinux opts and label opts are the same thing 644 s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) 645 s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=") 646 case "mask": 647 s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...) 648 case "proc-opts": 649 s.ProcOpts = strings.Split(con[1], ",") 650 case "seccomp": 651 s.SeccompProfilePath = con[1] 652 s.Annotations[define.InspectAnnotationSeccomp] = con[1] 653 // this option is for docker compatibility, it is the same as unmask=ALL 654 case "systempaths": 655 if con[1] == "unconfined" { 656 s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...) 657 } else { 658 return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1]) 659 } 660 case "unmask": 661 s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...) 662 case "no-new-privileges": 663 noNewPrivileges, err := strconv.ParseBool(con[1]) 664 if err != nil { 665 return fmt.Errorf("invalid --security-opt 2: %q", opt) 666 } 667 s.ContainerSecurityConfig.NoNewPrivileges = noNewPrivileges 668 default: 669 return fmt.Errorf("invalid --security-opt 2: %q", opt) 670 } 671 } 672 } 673 674 if len(s.SeccompPolicy) == 0 || len(c.SeccompPolicy) != 0 { 675 s.SeccompPolicy = c.SeccompPolicy 676 } 677 678 if len(s.VolumesFrom) == 0 || len(c.VolumesFrom) != 0 { 679 s.VolumesFrom = c.VolumesFrom 680 } 681 682 // Only add read-only tmpfs mounts in case that we are read-only and the 683 // read-only tmpfs flag has been set. 684 mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly) 685 if err != nil { 686 return err 687 } 688 if len(s.Mounts) == 0 || len(c.Mount) != 0 { 689 s.Mounts = mounts 690 } 691 if len(s.Volumes) == 0 || len(c.Volume) != 0 { 692 s.Volumes = volumes 693 } 694 // TODO make sure these work in clone 695 if len(s.OverlayVolumes) == 0 { 696 s.OverlayVolumes = overlayVolumes 697 } 698 if len(s.ImageVolumes) == 0 { 699 s.ImageVolumes = imageVolumes 700 } 701 702 for _, dev := range c.Devices { 703 s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev}) 704 } 705 706 for _, rule := range c.DeviceCgroupRule { 707 dev, err := parseLinuxResourcesDeviceAccess(rule) 708 if err != nil { 709 return err 710 } 711 s.DeviceCgroupRule = append(s.DeviceCgroupRule, dev) 712 } 713 714 if !s.Init { 715 s.Init = c.Init 716 } 717 if len(s.InitPath) == 0 || len(c.InitPath) != 0 { 718 s.InitPath = c.InitPath 719 } 720 if !s.Stdin { 721 s.Stdin = c.Interactive 722 } 723 // quiet 724 // DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), 725 726 // Rlimits/Ulimits 727 for _, u := range c.Ulimit { 728 if u == "host" { 729 s.Rlimits = nil 730 break 731 } 732 ul, err := units.ParseUlimit(u) 733 if err != nil { 734 return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) 735 } 736 rl := specs.POSIXRlimit{ 737 Type: ul.Name, 738 Hard: uint64(ul.Hard), 739 Soft: uint64(ul.Soft), 740 } 741 s.Rlimits = append(s.Rlimits, rl) 742 } 743 744 logOpts := make(map[string]string) 745 for _, o := range c.LogOptions { 746 split := strings.SplitN(o, "=", 2) 747 if len(split) < 2 { 748 return errors.Errorf("invalid log option %q", o) 749 } 750 switch strings.ToLower(split[0]) { 751 case "driver": 752 s.LogConfiguration.Driver = split[1] 753 case "path": 754 s.LogConfiguration.Path = split[1] 755 case "max-size": 756 logSize, err := units.FromHumanSize(split[1]) 757 if err != nil { 758 return err 759 } 760 s.LogConfiguration.Size = logSize 761 default: 762 logOpts[split[0]] = split[1] 763 } 764 } 765 if len(s.LogConfiguration.Options) == 0 || len(c.LogOptions) != 0 { 766 s.LogConfiguration.Options = logOpts 767 } 768 if len(s.Name) == 0 || len(c.Name) != 0 { 769 s.Name = c.Name 770 } 771 if s.PreserveFDs == 0 || c.PreserveFDs != 0 { 772 s.PreserveFDs = c.PreserveFDs 773 } 774 775 if s.OOMScoreAdj == nil || c.OOMScoreAdj != nil { 776 s.OOMScoreAdj = c.OOMScoreAdj 777 } 778 if c.Restart != "" { 779 splitRestart := strings.Split(c.Restart, ":") 780 switch len(splitRestart) { 781 case 1: 782 // No retries specified 783 case 2: 784 if strings.ToLower(splitRestart[0]) != "on-failure" { 785 return errors.Errorf("restart policy retries can only be specified with on-failure restart policy") 786 } 787 retries, err := strconv.Atoi(splitRestart[1]) 788 if err != nil { 789 return errors.Wrapf(err, "error parsing restart policy retry count") 790 } 791 if retries < 0 { 792 return errors.Errorf("must specify restart policy retry count as a number greater than 0") 793 } 794 var retriesUint = uint(retries) 795 s.RestartRetries = &retriesUint 796 default: 797 return errors.Errorf("invalid restart policy: may specify retries at most once") 798 } 799 s.RestartPolicy = splitRestart[0] 800 } 801 802 if len(s.Secrets) == 0 || len(c.Secrets) != 0 { 803 s.Secrets, s.EnvSecrets, err = parseSecrets(c.Secrets) 804 if err != nil { 805 return err 806 } 807 } 808 809 if c.Personality != "" { 810 s.Personality = &specs.LinuxPersonality{} 811 s.Personality.Domain = specs.LinuxPersonalityDomain(c.Personality) 812 } 813 814 if !s.Remove { 815 s.Remove = c.Rm 816 } 817 if s.StopTimeout == nil || c.StopTimeout != 0 { 818 s.StopTimeout = &c.StopTimeout 819 } 820 if s.Timeout == 0 || c.Timeout != 0 { 821 s.Timeout = c.Timeout 822 } 823 if len(s.Timezone) == 0 || len(c.Timezone) != 0 { 824 s.Timezone = c.Timezone 825 } 826 if len(s.Umask) == 0 || len(c.Umask) != 0 { 827 s.Umask = c.Umask 828 } 829 if len(s.PidFile) == 0 || len(c.PidFile) != 0 { 830 s.PidFile = c.PidFile 831 } 832 if !s.Volatile { 833 s.Volatile = c.Rm 834 } 835 if len(s.UnsetEnv) == 0 || len(c.UnsetEnv) != 0 { 836 s.UnsetEnv = c.UnsetEnv 837 } 838 if !s.UnsetEnvAll { 839 s.UnsetEnvAll = c.UnsetEnvAll 840 } 841 if len(s.ChrootDirs) == 0 || len(c.ChrootDirs) != 0 { 842 s.ChrootDirs = c.ChrootDirs 843 } 844 845 // Initcontainers 846 if len(s.InitContainerType) == 0 || len(c.InitContainerType) != 0 { 847 s.InitContainerType = c.InitContainerType 848 } 849 850 t := true 851 if s.Passwd == nil { 852 s.Passwd = &t 853 } 854 855 if len(s.PasswdEntry) == 0 || len(c.PasswdEntry) != 0 { 856 s.PasswdEntry = c.PasswdEntry 857 } 858 859 return nil 860 } 861 862 func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) { 863 cmdArr := []string{} 864 isArr := true 865 err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling 866 if err != nil { 867 cmdArr = strings.SplitN(inCmd, " ", 2) // default for compat 868 isArr = false 869 } 870 // Every healthcheck requires a command 871 if len(cmdArr) == 0 { 872 return nil, errors.New("Must define a healthcheck command for all healthchecks") 873 } 874 875 var concat string 876 if cmdArr[0] == "CMD" || cmdArr[0] == "none" { // this is for compat, we are already split properly for most compat cases 877 cmdArr = strings.Fields(inCmd) 878 } else if cmdArr[0] != "CMD-SHELL" { // this is for podman side of things, won't contain the keywords 879 if isArr && len(cmdArr) > 1 { // an array of consecutive commands 880 cmdArr = append([]string{"CMD"}, cmdArr...) 881 } else { // one singular command 882 if len(cmdArr) == 1 { 883 concat = cmdArr[0] 884 } else { 885 concat = strings.Join(cmdArr[0:], " ") 886 } 887 cmdArr = append([]string{"CMD-SHELL"}, concat) 888 } 889 } 890 891 if cmdArr[0] == "none" { // if specified to remove healtcheck 892 cmdArr = []string{"NONE"} 893 } 894 895 // healthcheck is by default an array, so we simply pass the user input 896 hc := manifest.Schema2HealthConfig{ 897 Test: cmdArr, 898 } 899 900 if interval == "disable" { 901 interval = "0" 902 } 903 intervalDuration, err := time.ParseDuration(interval) 904 if err != nil { 905 return nil, errors.Wrapf(err, "invalid healthcheck-interval") 906 } 907 908 hc.Interval = intervalDuration 909 910 if retries < 1 { 911 return nil, errors.New("healthcheck-retries must be greater than 0") 912 } 913 hc.Retries = int(retries) 914 timeoutDuration, err := time.ParseDuration(timeout) 915 if err != nil { 916 return nil, errors.Wrapf(err, "invalid healthcheck-timeout") 917 } 918 if timeoutDuration < time.Duration(1) { 919 return nil, errors.New("healthcheck-timeout must be at least 1 second") 920 } 921 hc.Timeout = timeoutDuration 922 923 startPeriodDuration, err := time.ParseDuration(startPeriod) 924 if err != nil { 925 return nil, errors.Wrapf(err, "invalid healthcheck-start-period") 926 } 927 if startPeriodDuration < time.Duration(0) { 928 return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") 929 } 930 hc.StartPeriod = startPeriodDuration 931 932 return &hc, nil 933 } 934 935 func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) { 936 wd := make(map[string]specs.LinuxWeightDevice) 937 for _, val := range weightDevs { 938 split := strings.SplitN(val, ":", 2) 939 if len(split) != 2 { 940 return nil, fmt.Errorf("bad format: %s", val) 941 } 942 if !strings.HasPrefix(split[0], "/dev/") { 943 return nil, fmt.Errorf("bad format for device path: %s", val) 944 } 945 weight, err := strconv.ParseUint(split[1], 10, 0) 946 if err != nil { 947 return nil, fmt.Errorf("invalid weight for device: %s", val) 948 } 949 if weight > 0 && (weight < 10 || weight > 1000) { 950 return nil, fmt.Errorf("invalid weight for device: %s", val) 951 } 952 w := uint16(weight) 953 wd[split[0]] = specs.LinuxWeightDevice{ 954 Weight: &w, 955 LeafWeight: nil, 956 } 957 } 958 return wd, nil 959 } 960 961 func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { 962 td := make(map[string]specs.LinuxThrottleDevice) 963 for _, val := range bpsDevices { 964 split := strings.SplitN(val, ":", 2) 965 if len(split) != 2 { 966 return nil, fmt.Errorf("bad format: %s", val) 967 } 968 if !strings.HasPrefix(split[0], "/dev/") { 969 return nil, fmt.Errorf("bad format for device path: %s", val) 970 } 971 rate, err := units.RAMInBytes(split[1]) 972 if err != nil { 973 return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) 974 } 975 if rate < 0 { 976 return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) 977 } 978 td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)} 979 } 980 return td, nil 981 } 982 983 func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { 984 td := make(map[string]specs.LinuxThrottleDevice) 985 for _, val := range iopsDevices { 986 split := strings.SplitN(val, ":", 2) 987 if len(split) != 2 { 988 return nil, fmt.Errorf("bad format: %s", val) 989 } 990 if !strings.HasPrefix(split[0], "/dev/") { 991 return nil, fmt.Errorf("bad format for device path: %s", val) 992 } 993 rate, err := strconv.ParseUint(split[1], 10, 64) 994 if err != nil { 995 return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) 996 } 997 td[split[0]] = specs.LinuxThrottleDevice{Rate: rate} 998 } 999 return td, nil 1000 } 1001 1002 func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) { 1003 secretParseError := errors.New("parsing secret") 1004 var mount []specgen.Secret 1005 envs := make(map[string]string) 1006 for _, val := range secrets { 1007 // mount only tells if user has set an option that can only be used with mount secret type 1008 mountOnly := false 1009 source := "" 1010 secretType := "" 1011 target := "" 1012 var uid, gid uint32 1013 // default mode 444 octal = 292 decimal 1014 var mode uint32 = 292 1015 split := strings.Split(val, ",") 1016 1017 // --secret mysecret 1018 if len(split) == 1 { 1019 mountSecret := specgen.Secret{ 1020 Source: val, 1021 Target: target, 1022 UID: uid, 1023 GID: gid, 1024 Mode: mode, 1025 } 1026 mount = append(mount, mountSecret) 1027 continue 1028 } 1029 // --secret mysecret,opt=opt 1030 if !strings.Contains(split[0], "=") { 1031 source = split[0] 1032 split = split[1:] 1033 } 1034 1035 for _, val := range split { 1036 kv := strings.SplitN(val, "=", 2) 1037 if len(kv) < 2 { 1038 return nil, nil, errors.Wrapf(secretParseError, "option %s must be in form option=value", val) 1039 } 1040 switch kv[0] { 1041 case "source": 1042 source = kv[1] 1043 case "type": 1044 if secretType != "" { 1045 return nil, nil, errors.Wrap(secretParseError, "cannot set more than one secret type") 1046 } 1047 if kv[1] != "mount" && kv[1] != "env" { 1048 return nil, nil, errors.Wrapf(secretParseError, "type %s is invalid", kv[1]) 1049 } 1050 secretType = kv[1] 1051 case "target": 1052 target = kv[1] 1053 case "mode": 1054 mountOnly = true 1055 mode64, err := strconv.ParseUint(kv[1], 8, 32) 1056 if err != nil { 1057 return nil, nil, errors.Wrapf(secretParseError, "mode %s invalid", kv[1]) 1058 } 1059 mode = uint32(mode64) 1060 case "uid", "UID": 1061 mountOnly = true 1062 uid64, err := strconv.ParseUint(kv[1], 10, 32) 1063 if err != nil { 1064 return nil, nil, errors.Wrapf(secretParseError, "UID %s invalid", kv[1]) 1065 } 1066 uid = uint32(uid64) 1067 case "gid", "GID": 1068 mountOnly = true 1069 gid64, err := strconv.ParseUint(kv[1], 10, 32) 1070 if err != nil { 1071 return nil, nil, errors.Wrapf(secretParseError, "GID %s invalid", kv[1]) 1072 } 1073 gid = uint32(gid64) 1074 1075 default: 1076 return nil, nil, errors.Wrapf(secretParseError, "option %s invalid", val) 1077 } 1078 } 1079 1080 if secretType == "" { 1081 secretType = "mount" 1082 } 1083 if source == "" { 1084 return nil, nil, errors.Wrapf(secretParseError, "no source found %s", val) 1085 } 1086 if secretType == "mount" { 1087 mountSecret := specgen.Secret{ 1088 Source: source, 1089 Target: target, 1090 UID: uid, 1091 GID: gid, 1092 Mode: mode, 1093 } 1094 mount = append(mount, mountSecret) 1095 } 1096 if secretType == "env" { 1097 if mountOnly { 1098 return nil, nil, errors.Wrap(secretParseError, "UID, GID, Mode options cannot be set with secret type env") 1099 } 1100 if target == "" { 1101 target = source 1102 } 1103 envs[target] = source 1104 } 1105 } 1106 return mount, envs, nil 1107 } 1108 1109 var cgroupDeviceType = map[string]bool{ 1110 "a": true, // all 1111 "b": true, // block device 1112 "c": true, // character device 1113 } 1114 1115 var cgroupDeviceAccess = map[string]bool{ 1116 "r": true, // read 1117 "w": true, // write 1118 "m": true, // mknod 1119 } 1120 1121 // parseLinuxResourcesDeviceAccess parses the raw string passed with the --device-access-add flag 1122 func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, error) { 1123 var devType, access string 1124 var major, minor *int64 1125 1126 value := strings.Split(device, " ") 1127 if len(value) != 3 { 1128 return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device cgroup rule requires type, major:Minor, and access rules: %q", device) 1129 } 1130 1131 devType = value[0] 1132 if !cgroupDeviceType[devType] { 1133 return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device type in device-access-add: %s", devType) 1134 } 1135 1136 number := strings.SplitN(value[1], ":", 2) 1137 i, err := strconv.ParseInt(number[0], 10, 64) 1138 if err != nil { 1139 return specs.LinuxDeviceCgroup{}, err 1140 } 1141 major = &i 1142 if len(number) == 2 && number[1] != "*" { 1143 i, err := strconv.ParseInt(number[1], 10, 64) 1144 if err != nil { 1145 return specs.LinuxDeviceCgroup{}, err 1146 } 1147 minor = &i 1148 } 1149 access = value[2] 1150 for _, c := range strings.Split(access, "") { 1151 if !cgroupDeviceAccess[c] { 1152 return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device access in device-access-add: %s", c) 1153 } 1154 } 1155 return specs.LinuxDeviceCgroup{ 1156 Allow: true, 1157 Type: devType, 1158 Major: major, 1159 Minor: minor, 1160 Access: access, 1161 }, nil 1162 }