github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgen/generate/kube/kube.go (about) 1 package kube 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "math" 8 "net" 9 "regexp" 10 "runtime" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/containers/common/libimage" 16 "github.com/containers/common/libnetwork/types" 17 "github.com/containers/common/pkg/parse" 18 "github.com/containers/common/pkg/secrets" 19 cutil "github.com/containers/common/pkg/util" 20 "github.com/containers/image/v5/manifest" 21 "github.com/hanks177/podman/v4/libpod/define" 22 ann "github.com/hanks177/podman/v4/pkg/annotations" 23 "github.com/hanks177/podman/v4/pkg/domain/entities" 24 v1 "github.com/hanks177/podman/v4/pkg/k8s.io/api/core/v1" 25 "github.com/hanks177/podman/v4/pkg/k8s.io/apimachinery/pkg/api/resource" 26 "github.com/hanks177/podman/v4/pkg/specgen" 27 "github.com/hanks177/podman/v4/pkg/specgen/generate" 28 "github.com/hanks177/podman/v4/pkg/util" 29 "github.com/docker/docker/pkg/system" 30 "github.com/docker/go-units" 31 spec "github.com/opencontainers/runtime-spec/specs-go" 32 "github.com/pkg/errors" 33 "github.com/sirupsen/logrus" 34 ) 35 36 func ToPodOpt(ctx context.Context, podName string, p entities.PodCreateOptions, podYAML *v1.PodTemplateSpec) (entities.PodCreateOptions, error) { 37 p.Net = &entities.NetOptions{NoHosts: p.Net.NoHosts} 38 39 p.Name = podName 40 p.Labels = podYAML.ObjectMeta.Labels 41 // Kube pods must share {ipc, net, uts} by default 42 p.Share = append(p.Share, "ipc") 43 p.Share = append(p.Share, "net") 44 p.Share = append(p.Share, "uts") 45 // TODO we only configure Process namespace. We also need to account for Host{IPC,Network,PID} 46 // which is not currently possible with pod create 47 if podYAML.Spec.ShareProcessNamespace != nil && *podYAML.Spec.ShareProcessNamespace { 48 p.Share = append(p.Share, "pid") 49 } 50 p.Hostname = podYAML.Spec.Hostname 51 if p.Hostname == "" { 52 p.Hostname = podName 53 } 54 if podYAML.Spec.HostNetwork { 55 p.Net.Network = specgen.Namespace{NSMode: "host"} 56 } 57 if podYAML.Spec.HostAliases != nil { 58 if p.Net.NoHosts { 59 return p, errors.New("HostAliases in yaml file will not work with --no-hosts") 60 } 61 hosts := make([]string, 0, len(podYAML.Spec.HostAliases)) 62 for _, hostAlias := range podYAML.Spec.HostAliases { 63 for _, host := range hostAlias.Hostnames { 64 hosts = append(hosts, host+":"+hostAlias.IP) 65 } 66 } 67 p.Net.AddHosts = hosts 68 } 69 podPorts := getPodPorts(podYAML.Spec.Containers) 70 p.Net.PublishPorts = podPorts 71 72 if dnsConfig := podYAML.Spec.DNSConfig; dnsConfig != nil { 73 // name servers 74 if dnsServers := dnsConfig.Nameservers; len(dnsServers) > 0 { 75 servers := make([]net.IP, 0) 76 for _, server := range dnsServers { 77 servers = append(servers, net.ParseIP(server)) 78 } 79 p.Net.DNSServers = servers 80 } 81 // search domains 82 if domains := dnsConfig.Searches; len(domains) > 0 { 83 p.Net.DNSSearch = domains 84 } 85 // dns options 86 if options := dnsConfig.Options; len(options) > 0 { 87 dnsOptions := make([]string, 0, len(options)) 88 for _, opts := range options { 89 d := opts.Name 90 if opts.Value != nil { 91 d += ":" + *opts.Value 92 } 93 dnsOptions = append(dnsOptions, d) 94 } 95 p.Net.DNSOptions = dnsOptions 96 } 97 } 98 return p, nil 99 } 100 101 type CtrSpecGenOptions struct { 102 // Annotations from the Pod 103 Annotations map[string]string 104 // Container as read from the pod yaml 105 Container v1.Container 106 // Image available to use (pulled or found local) 107 Image *libimage.Image 108 // Volumes for all containers 109 Volumes map[string]*KubeVolume 110 // PodID of the parent pod 111 PodID string 112 // PodName of the parent pod 113 PodName string 114 // PodInfraID as the infrastructure container id 115 PodInfraID string 116 // ConfigMaps the configuration maps for environment variables 117 ConfigMaps []v1.ConfigMap 118 // SeccompPaths for finding the seccomp profile path 119 SeccompPaths *KubeSeccompPaths 120 // RestartPolicy defines the restart policy of the container 121 RestartPolicy string 122 // NetNSIsHost tells the container to use the host netns 123 NetNSIsHost bool 124 // UserNSIsHost tells the container to use the host userns 125 UserNSIsHost bool 126 // SecretManager to access the secrets 127 SecretsManager *secrets.SecretsManager 128 // LogDriver which should be used for the container 129 LogDriver string 130 // LogOptions log options which should be used for the container 131 LogOptions []string 132 // Labels define key-value pairs of metadata 133 Labels map[string]string 134 // 135 IsInfra bool 136 // InitContainerType sets what type the init container is 137 // Note: When playing a kube yaml, the inti container type will be set to "always" only 138 InitContainerType string 139 // PodSecurityContext is the security context specified for the pod 140 PodSecurityContext *v1.PodSecurityContext 141 } 142 143 func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGenerator, error) { 144 s := specgen.NewSpecGenerator(opts.Container.Image, false) 145 146 // pod name should be non-empty for Deployment objects to be able to create 147 // multiple pods having containers with unique names 148 if len(opts.PodName) < 1 { 149 return nil, errors.Errorf("got empty pod name on container creation when playing kube") 150 } 151 152 s.Name = fmt.Sprintf("%s-%s", opts.PodName, opts.Container.Name) 153 154 s.Terminal = opts.Container.TTY 155 156 s.Pod = opts.PodID 157 158 s.LogConfiguration = &specgen.LogConfig{ 159 Driver: opts.LogDriver, 160 } 161 162 s.LogConfiguration.Options = make(map[string]string) 163 for _, o := range opts.LogOptions { 164 split := strings.SplitN(o, "=", 2) 165 if len(split) < 2 { 166 return nil, errors.Errorf("invalid log option %q", o) 167 } 168 switch strings.ToLower(split[0]) { 169 case "driver": 170 s.LogConfiguration.Driver = split[1] 171 case "path": 172 s.LogConfiguration.Path = split[1] 173 case "max-size": 174 logSize, err := units.FromHumanSize(split[1]) 175 if err != nil { 176 return nil, err 177 } 178 s.LogConfiguration.Size = logSize 179 default: 180 switch len(split[1]) { 181 case 0: 182 return nil, errors.Wrapf(define.ErrInvalidArg, "invalid log option") 183 default: 184 // tags for journald only 185 if s.LogConfiguration.Driver == "" || s.LogConfiguration.Driver == define.JournaldLogging { 186 s.LogConfiguration.Options[split[0]] = split[1] 187 } else { 188 logrus.Warnf("Can only set tags with journald log driver but driver is %q", s.LogConfiguration.Driver) 189 } 190 } 191 } 192 } 193 194 s.InitContainerType = opts.InitContainerType 195 196 setupSecurityContext(s, opts.Container.SecurityContext, opts.PodSecurityContext) 197 err := setupLivenessProbe(s, opts.Container, opts.RestartPolicy) 198 if err != nil { 199 return nil, errors.Wrap(err, "Failed to configure livenessProbe") 200 } 201 202 // Since we prefix the container name with pod name to work-around the uniqueness requirement, 203 // the seccomp profile should reference the actual container name from the YAML 204 // but apply to the containers with the prefixed name 205 s.SeccompProfilePath = opts.SeccompPaths.FindForContainer(opts.Container.Name) 206 207 s.ResourceLimits = &spec.LinuxResources{} 208 milliCPU, err := quantityToInt64(opts.Container.Resources.Limits.Cpu()) 209 if err != nil { 210 return nil, errors.Wrap(err, "Failed to set CPU quota") 211 } 212 if milliCPU > 0 { 213 period, quota := util.CoresToPeriodAndQuota(float64(milliCPU)) 214 s.ResourceLimits.CPU = &spec.LinuxCPU{ 215 Quota: "a, 216 Period: &period, 217 } 218 } 219 220 limit, err := quantityToInt64(opts.Container.Resources.Limits.Memory()) 221 if err != nil { 222 return nil, errors.Wrap(err, "Failed to set memory limit") 223 } 224 225 memoryRes, err := quantityToInt64(opts.Container.Resources.Requests.Memory()) 226 if err != nil { 227 return nil, errors.Wrap(err, "Failed to set memory reservation") 228 } 229 230 if limit > 0 || memoryRes > 0 { 231 s.ResourceLimits.Memory = &spec.LinuxMemory{} 232 } 233 234 if limit > 0 { 235 s.ResourceLimits.Memory.Limit = &limit 236 } 237 238 if memoryRes > 0 { 239 s.ResourceLimits.Memory.Reservation = &memoryRes 240 } 241 242 // TODO: We don't understand why specgen does not take of this, but 243 // integration tests clearly pointed out that it was required. 244 imageData, err := opts.Image.Inspect(ctx, nil) 245 if err != nil { 246 return nil, err 247 } 248 s.WorkDir = "/" 249 // Entrypoint/Command handling is based off of 250 // https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#notes 251 if imageData != nil && imageData.Config != nil { 252 if imageData.Config.WorkingDir != "" { 253 s.WorkDir = imageData.Config.WorkingDir 254 } 255 if s.User == "" { 256 s.User = imageData.Config.User 257 } 258 259 exposed, err := generate.GenExposedPorts(imageData.Config.ExposedPorts) 260 if err != nil { 261 return nil, err 262 } 263 264 for k, v := range s.Expose { 265 exposed[k] = v 266 } 267 s.Expose = exposed 268 // Pull entrypoint and cmd from image 269 s.Entrypoint = imageData.Config.Entrypoint 270 s.Command = imageData.Config.Cmd 271 s.Labels = imageData.Config.Labels 272 if len(imageData.Config.StopSignal) > 0 { 273 stopSignal, err := util.ParseSignal(imageData.Config.StopSignal) 274 if err != nil { 275 return nil, err 276 } 277 s.StopSignal = &stopSignal 278 } 279 } 280 // If only the yaml.Command is specified, set it as the entrypoint and drop the image Cmd 281 if !opts.IsInfra && len(opts.Container.Command) != 0 { 282 s.Entrypoint = opts.Container.Command 283 s.Command = []string{} 284 } 285 // Only override the cmd field if yaml.Args is specified 286 // Keep the image entrypoint, or the yaml.command if specified 287 if !opts.IsInfra && len(opts.Container.Args) != 0 { 288 s.Command = opts.Container.Args 289 } 290 291 // FIXME, 292 // we are currently ignoring imageData.Config.ExposedPorts 293 if !opts.IsInfra && opts.Container.WorkingDir != "" { 294 s.WorkDir = opts.Container.WorkingDir 295 } 296 297 annotations := make(map[string]string) 298 if opts.Annotations != nil { 299 annotations = opts.Annotations 300 } 301 if opts.PodInfraID != "" { 302 annotations[ann.SandboxID] = opts.PodInfraID 303 annotations[ann.ContainerType] = ann.ContainerTypeContainer 304 } 305 s.Annotations = annotations 306 307 // Environment Variables 308 envs := map[string]string{} 309 for _, env := range imageData.Config.Env { 310 keyval := strings.SplitN(env, "=", 2) 311 envs[keyval[0]] = keyval[1] 312 } 313 314 for _, env := range opts.Container.Env { 315 value, err := envVarValue(env, opts) 316 if err != nil { 317 return nil, err 318 } 319 320 // Only set the env if the value is not nil 321 if value != nil { 322 envs[env.Name] = *value 323 } 324 } 325 for _, envFrom := range opts.Container.EnvFrom { 326 cmEnvs, err := envVarsFrom(envFrom, opts) 327 if err != nil { 328 return nil, err 329 } 330 331 for k, v := range cmEnvs { 332 envs[k] = v 333 } 334 } 335 s.Env = envs 336 337 for _, volume := range opts.Container.VolumeMounts { 338 volumeSource, exists := opts.Volumes[volume.Name] 339 if !exists { 340 return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name) 341 } 342 // Skip if the volume is optional. This means that a configmap for a configmap volume was not found but it was 343 // optional so we can move on without throwing an error 344 if exists && volumeSource.Optional { 345 continue 346 } 347 348 dest, options, err := parseMountPath(volume.MountPath, volume.ReadOnly, volume.MountPropagation) 349 if err != nil { 350 return nil, err 351 } 352 353 volume.MountPath = dest 354 switch volumeSource.Type { 355 case KubeVolumeTypeBindMount: 356 // If the container has bind mounts, we need to check if 357 // a selinux mount option exists for it 358 for k, v := range opts.Annotations { 359 // Make sure the z/Z option is not already there (from editing the YAML) 360 if strings.Replace(k, define.BindMountPrefix, "", 1) == volumeSource.Source && !cutil.StringInSlice("z", options) && !cutil.StringInSlice("Z", options) { 361 options = append(options, v) 362 } 363 } 364 mount := spec.Mount{ 365 Destination: volume.MountPath, 366 Source: volumeSource.Source, 367 Type: "bind", 368 Options: options, 369 } 370 s.Mounts = append(s.Mounts, mount) 371 case KubeVolumeTypeNamed: 372 namedVolume := specgen.NamedVolume{ 373 Dest: volume.MountPath, 374 Name: volumeSource.Source, 375 Options: options, 376 } 377 s.Volumes = append(s.Volumes, &namedVolume) 378 case KubeVolumeTypeConfigMap: 379 cmVolume := specgen.NamedVolume{ 380 Dest: volume.MountPath, 381 Name: volumeSource.Source, 382 Options: options, 383 } 384 s.Volumes = append(s.Volumes, &cmVolume) 385 case KubeVolumeTypeCharDevice: 386 // We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath. 387 // The type is here just to improve readability as it is not taken into account when the actual device is created. 388 device := spec.LinuxDevice{ 389 Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath), 390 Type: "c", 391 } 392 s.Devices = append(s.Devices, device) 393 case KubeVolumeTypeBlockDevice: 394 // We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath. 395 // The type is here just to improve readability as it is not taken into account when the actual device is created. 396 device := spec.LinuxDevice{ 397 Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath), 398 Type: "b", 399 } 400 s.Devices = append(s.Devices, device) 401 default: 402 return nil, errors.Errorf("Unsupported volume source type") 403 } 404 } 405 406 s.RestartPolicy = opts.RestartPolicy 407 408 if opts.NetNSIsHost { 409 s.NetNS.NSMode = specgen.Host 410 } 411 if opts.UserNSIsHost { 412 s.UserNS.NSMode = specgen.Host 413 } 414 415 // Add labels that come from kube 416 if len(s.Labels) == 0 { 417 // If there are no labels, let's use the map that comes 418 // from kube 419 s.Labels = opts.Labels 420 } else { 421 // If there are already labels in the map, append the ones 422 // obtained from kube 423 for k, v := range opts.Labels { 424 s.Labels[k] = v 425 } 426 } 427 428 return s, nil 429 } 430 431 func parseMountPath(mountPath string, readOnly bool, propagationMode *v1.MountPropagationMode) (string, []string, error) { 432 options := []string{} 433 splitVol := strings.Split(mountPath, ":") 434 if len(splitVol) > 2 { 435 return "", options, errors.Errorf("%q incorrect volume format, should be ctr-dir[:option]", mountPath) 436 } 437 dest := splitVol[0] 438 if len(splitVol) > 1 { 439 options = strings.Split(splitVol[1], ",") 440 } 441 if err := parse.ValidateVolumeCtrDir(dest); err != nil { 442 return "", options, errors.Wrapf(err, "parsing MountPath") 443 } 444 if readOnly { 445 options = append(options, "ro") 446 } 447 opts, err := parse.ValidateVolumeOpts(options) 448 if err != nil { 449 return "", opts, errors.Wrapf(err, "parsing MountOptions") 450 } 451 if propagationMode != nil { 452 switch *propagationMode { 453 case v1.MountPropagationNone: 454 opts = append(opts, "private") 455 case v1.MountPropagationHostToContainer: 456 opts = append(opts, "rslave") 457 case v1.MountPropagationBidirectional: 458 opts = append(opts, "rshared") 459 default: 460 return "", opts, errors.Errorf("unknown propagation mode %q", *propagationMode) 461 } 462 } 463 return dest, opts, nil 464 } 465 466 func setupLivenessProbe(s *specgen.SpecGenerator, containerYAML v1.Container, restartPolicy string) error { 467 var err error 468 if containerYAML.LivenessProbe == nil { 469 return nil 470 } 471 emptyHandler := v1.Handler{} 472 if containerYAML.LivenessProbe.Handler != emptyHandler { 473 var commandString string 474 failureCmd := "exit 1" 475 probe := containerYAML.LivenessProbe 476 probeHandler := probe.Handler 477 478 // append `exit 1` to `cmd` so healthcheck can be marked as `unhealthy`. 479 // append `kill 1` to `cmd` if appropriate restart policy is configured. 480 if restartPolicy == "always" || restartPolicy == "onfailure" { 481 // container will be restarted so we can kill init. 482 failureCmd = "kill 1" 483 } 484 485 // configure healthcheck on the basis of Handler Actions. 486 switch { 487 case probeHandler.Exec != nil: 488 execString := strings.Join(probeHandler.Exec.Command, " ") 489 commandString = fmt.Sprintf("%s || %s", execString, failureCmd) 490 case probeHandler.HTTPGet != nil: 491 commandString = fmt.Sprintf("curl %s://%s:%d/%s || %s", probeHandler.HTTPGet.Scheme, probeHandler.HTTPGet.Host, probeHandler.HTTPGet.Port.IntValue(), probeHandler.HTTPGet.Path, failureCmd) 492 case probeHandler.TCPSocket != nil: 493 commandString = fmt.Sprintf("nc -z -v %s %d || %s", probeHandler.TCPSocket.Host, probeHandler.TCPSocket.Port.IntValue(), failureCmd) 494 } 495 s.HealthConfig, err = makeHealthCheck(commandString, probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds) 496 if err != nil { 497 return err 498 } 499 return nil 500 } 501 return nil 502 } 503 504 func makeHealthCheck(inCmd string, interval int32, retries int32, timeout int32, startPeriod int32) (*manifest.Schema2HealthConfig, error) { 505 // Every healthcheck requires a command 506 if len(inCmd) == 0 { 507 return nil, errors.New("Must define a healthcheck command for all healthchecks") 508 } 509 510 // first try to parse option value as JSON array of strings... 511 cmd := []string{} 512 513 if inCmd == "none" { 514 cmd = []string{"NONE"} 515 } else { 516 err := json.Unmarshal([]byte(inCmd), &cmd) 517 if err != nil { 518 // ...otherwise pass it to "/bin/sh -c" inside the container 519 cmd = []string{"CMD-SHELL"} 520 cmd = append(cmd, strings.Split(inCmd, " ")...) 521 } 522 } 523 hc := manifest.Schema2HealthConfig{ 524 Test: cmd, 525 } 526 527 if interval < 1 { 528 // kubernetes interval defaults to 10 sec and cannot be less than 1 529 interval = 10 530 } 531 hc.Interval = (time.Duration(interval) * time.Second) 532 if retries < 1 { 533 // kubernetes retries defaults to 3 534 retries = 3 535 } 536 hc.Retries = int(retries) 537 if timeout < 1 { 538 // kubernetes timeout defaults to 1 539 timeout = 1 540 } 541 timeoutDuration := (time.Duration(timeout) * time.Second) 542 if timeoutDuration < time.Duration(1) { 543 return nil, errors.New("healthcheck-timeout must be at least 1 second") 544 } 545 hc.Timeout = timeoutDuration 546 547 startPeriodDuration := (time.Duration(startPeriod) * time.Second) 548 if startPeriodDuration < time.Duration(0) { 549 return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") 550 } 551 hc.StartPeriod = startPeriodDuration 552 553 return &hc, nil 554 } 555 556 func setupSecurityContext(s *specgen.SpecGenerator, securityContext *v1.SecurityContext, podSecurityContext *v1.PodSecurityContext) { 557 if securityContext == nil { 558 securityContext = &v1.SecurityContext{} 559 } 560 if podSecurityContext == nil { 561 podSecurityContext = &v1.PodSecurityContext{} 562 } 563 564 if securityContext.ReadOnlyRootFilesystem != nil { 565 s.ReadOnlyFilesystem = *securityContext.ReadOnlyRootFilesystem 566 } 567 if securityContext.Privileged != nil { 568 s.Privileged = *securityContext.Privileged 569 } 570 571 if securityContext.AllowPrivilegeEscalation != nil { 572 s.NoNewPrivileges = !*securityContext.AllowPrivilegeEscalation 573 } 574 575 seopt := securityContext.SELinuxOptions 576 if seopt == nil { 577 seopt = podSecurityContext.SELinuxOptions 578 } 579 if seopt != nil { 580 if seopt.User != "" { 581 s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("user:%s", seopt.User)) 582 } 583 if seopt.Role != "" { 584 s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.Role)) 585 } 586 if seopt.Type != "" { 587 s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("type:%s", seopt.Type)) 588 } 589 if seopt.Level != "" { 590 s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("level:%s", seopt.Level)) 591 } 592 } 593 if caps := securityContext.Capabilities; caps != nil { 594 for _, capability := range caps.Add { 595 s.CapAdd = append(s.CapAdd, string(capability)) 596 } 597 for _, capability := range caps.Drop { 598 s.CapDrop = append(s.CapDrop, string(capability)) 599 } 600 } 601 runAsUser := securityContext.RunAsUser 602 if runAsUser == nil { 603 runAsUser = podSecurityContext.RunAsUser 604 } 605 if runAsUser != nil { 606 s.User = fmt.Sprintf("%d", *runAsUser) 607 } 608 609 runAsGroup := securityContext.RunAsGroup 610 if runAsGroup == nil { 611 runAsGroup = podSecurityContext.RunAsGroup 612 } 613 if runAsGroup != nil { 614 if s.User == "" { 615 s.User = "0" 616 } 617 s.User = fmt.Sprintf("%s:%d", s.User, *runAsGroup) 618 } 619 for _, group := range podSecurityContext.SupplementalGroups { 620 s.Groups = append(s.Groups, fmt.Sprintf("%d", group)) 621 } 622 } 623 624 func quantityToInt64(quantity *resource.Quantity) (int64, error) { 625 if i, ok := quantity.AsInt64(); ok { 626 return i, nil 627 } 628 629 if i, ok := quantity.AsDec().Unscaled(); ok { 630 return i, nil 631 } 632 633 return 0, errors.Errorf("Quantity cannot be represented as int64: %v", quantity) 634 } 635 636 // read a k8s secret in JSON format from the secret manager 637 func k8sSecretFromSecretManager(name string, secretsManager *secrets.SecretsManager) (map[string][]byte, error) { 638 _, jsonSecret, err := secretsManager.LookupSecretData(name) 639 if err != nil { 640 return nil, err 641 } 642 643 var secrets map[string][]byte 644 if err := json.Unmarshal(jsonSecret, &secrets); err != nil { 645 return nil, errors.Errorf("Secret %v is not valid JSON: %v", name, err) 646 } 647 return secrets, nil 648 } 649 650 // envVarsFrom returns all key-value pairs as env vars from a configMap or secret that matches the envFrom setting of a container 651 func envVarsFrom(envFrom v1.EnvFromSource, opts *CtrSpecGenOptions) (map[string]string, error) { 652 envs := map[string]string{} 653 654 if envFrom.ConfigMapRef != nil { 655 cmRef := envFrom.ConfigMapRef 656 err := errors.Errorf("Configmap %v not found", cmRef.Name) 657 658 for _, c := range opts.ConfigMaps { 659 if cmRef.Name == c.Name { 660 envs = c.Data 661 err = nil 662 break 663 } 664 } 665 666 if err != nil && (cmRef.Optional == nil || !*cmRef.Optional) { 667 return nil, err 668 } 669 } 670 671 if envFrom.SecretRef != nil { 672 secRef := envFrom.SecretRef 673 secret, err := k8sSecretFromSecretManager(secRef.Name, opts.SecretsManager) 674 if err == nil { 675 for k, v := range secret { 676 envs[k] = string(v) 677 } 678 } else if secRef.Optional == nil || !*secRef.Optional { 679 return nil, err 680 } 681 } 682 683 return envs, nil 684 } 685 686 // envVarValue returns the environment variable value configured within the container's env setting. 687 // It gets the value from a configMap or secret if specified, otherwise returns env.Value 688 func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) { 689 if env.ValueFrom != nil { 690 if env.ValueFrom.ConfigMapKeyRef != nil { 691 cmKeyRef := env.ValueFrom.ConfigMapKeyRef 692 err := errors.Errorf("Cannot set env %v: configmap %v not found", env.Name, cmKeyRef.Name) 693 694 for _, c := range opts.ConfigMaps { 695 if cmKeyRef.Name == c.Name { 696 if value, ok := c.Data[cmKeyRef.Key]; ok { 697 return &value, nil 698 } 699 err = errors.Errorf("Cannot set env %v: key %s not found in configmap %v", env.Name, cmKeyRef.Key, cmKeyRef.Name) 700 break 701 } 702 } 703 if cmKeyRef.Optional == nil || !*cmKeyRef.Optional { 704 return nil, err 705 } 706 return nil, nil 707 } 708 709 if env.ValueFrom.SecretKeyRef != nil { 710 secKeyRef := env.ValueFrom.SecretKeyRef 711 secret, err := k8sSecretFromSecretManager(secKeyRef.Name, opts.SecretsManager) 712 if err == nil { 713 if val, ok := secret[secKeyRef.Key]; ok { 714 value := string(val) 715 return &value, nil 716 } 717 err = errors.Errorf("Secret %v has not %v key", secKeyRef.Name, secKeyRef.Key) 718 } 719 if secKeyRef.Optional == nil || !*secKeyRef.Optional { 720 return nil, errors.Errorf("Cannot set env %v: %v", env.Name, err) 721 } 722 return nil, nil 723 } 724 725 if env.ValueFrom.FieldRef != nil { 726 return envVarValueFieldRef(env, opts) 727 } 728 729 if env.ValueFrom.ResourceFieldRef != nil { 730 return envVarValueResourceFieldRef(env, opts) 731 } 732 } 733 734 return &env.Value, nil 735 } 736 737 func envVarValueFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) { 738 fieldRef := env.ValueFrom.FieldRef 739 740 fieldPathLabelPattern := `^metadata.labels\['(.+)'\]$` 741 fieldPathLabelRegex := regexp.MustCompile(fieldPathLabelPattern) 742 fieldPathAnnotationPattern := `^metadata.annotations\['(.+)'\]$` 743 fieldPathAnnotationRegex := regexp.MustCompile(fieldPathAnnotationPattern) 744 745 fieldPath := fieldRef.FieldPath 746 747 if fieldPath == "metadata.name" { 748 return &opts.PodName, nil 749 } 750 if fieldPath == "metadata.uid" { 751 return &opts.PodID, nil 752 } 753 fieldPathMatches := fieldPathLabelRegex.FindStringSubmatch(fieldPath) 754 if len(fieldPathMatches) == 2 { // 1 for entire regex and 1 for subexp 755 labelValue := opts.Labels[fieldPathMatches[1]] // not existent label is OK 756 return &labelValue, nil 757 } 758 fieldPathMatches = fieldPathAnnotationRegex.FindStringSubmatch(fieldPath) 759 if len(fieldPathMatches) == 2 { // 1 for entire regex and 1 for subexp 760 annotationValue := opts.Annotations[fieldPathMatches[1]] // not existent annotation is OK 761 return &annotationValue, nil 762 } 763 764 return nil, errors.Errorf( 765 "Can not set env %v. Reason: fieldPath %v is either not valid or not supported", 766 env.Name, fieldPath, 767 ) 768 } 769 770 func envVarValueResourceFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) { 771 divisor := env.ValueFrom.ResourceFieldRef.Divisor 772 if divisor.IsZero() { // divisor not set, use default 773 divisor.Set(1) 774 } 775 776 resources, err := getContainerResources(opts.Container) 777 if err != nil { 778 return nil, err 779 } 780 781 var value *resource.Quantity 782 resourceName := env.ValueFrom.ResourceFieldRef.Resource 783 var isValidDivisor bool 784 785 switch resourceName { 786 case "limits.memory": 787 value = resources.Limits.Memory() 788 isValidDivisor = isMemoryDivisor(divisor) 789 case "limits.cpu": 790 value = resources.Limits.Cpu() 791 isValidDivisor = isCPUDivisor(divisor) 792 case "requests.memory": 793 value = resources.Requests.Memory() 794 isValidDivisor = isMemoryDivisor(divisor) 795 case "requests.cpu": 796 value = resources.Requests.Cpu() 797 isValidDivisor = isCPUDivisor(divisor) 798 default: 799 return nil, errors.Errorf( 800 "Can not set env %v. Reason: resource %v is either not valid or not supported", 801 env.Name, resourceName, 802 ) 803 } 804 805 if !isValidDivisor { 806 return nil, errors.Errorf( 807 "Can not set env %s. Reason: divisor value %s is not valid", 808 env.Name, divisor.String(), 809 ) 810 } 811 812 // k8s rounds up the result to the nearest integer 813 intValue := int(math.Ceil(value.AsApproximateFloat64() / divisor.AsApproximateFloat64())) 814 stringValue := strconv.Itoa(intValue) 815 816 return &stringValue, nil 817 } 818 819 func isMemoryDivisor(divisor resource.Quantity) bool { 820 switch divisor.String() { 821 case "1", "1k", "1M", "1G", "1T", "1P", "1E", "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei": 822 return true 823 default: 824 return false 825 } 826 } 827 828 func isCPUDivisor(divisor resource.Quantity) bool { 829 switch divisor.String() { 830 case "1", "1m": 831 return true 832 default: 833 return false 834 } 835 } 836 837 func getContainerResources(container v1.Container) (v1.ResourceRequirements, error) { 838 result := v1.ResourceRequirements{ 839 Limits: v1.ResourceList{}, 840 Requests: v1.ResourceList{}, 841 } 842 843 limits := container.Resources.Limits 844 requests := container.Resources.Requests 845 846 if limits == nil || limits.Memory().IsZero() { 847 mi, err := system.ReadMemInfo() 848 if err != nil { 849 return result, err 850 } 851 result.Limits[v1.ResourceMemory] = *resource.NewQuantity(mi.MemTotal, resource.DecimalSI) 852 } else { 853 result.Limits[v1.ResourceMemory] = limits[v1.ResourceMemory] 854 } 855 856 if limits == nil || limits.Cpu().IsZero() { 857 result.Limits[v1.ResourceCPU] = *resource.NewQuantity(int64(runtime.NumCPU()), resource.DecimalSI) 858 } else { 859 result.Limits[v1.ResourceCPU] = limits[v1.ResourceCPU] 860 } 861 862 if requests == nil || requests.Memory().IsZero() { 863 result.Requests[v1.ResourceMemory] = result.Limits[v1.ResourceMemory] 864 } else { 865 result.Requests[v1.ResourceMemory] = requests[v1.ResourceMemory] 866 } 867 868 if requests == nil || requests.Cpu().IsZero() { 869 result.Requests[v1.ResourceCPU] = result.Limits[v1.ResourceCPU] 870 } else { 871 result.Requests[v1.ResourceCPU] = requests[v1.ResourceCPU] 872 } 873 874 return result, nil 875 } 876 877 // getPodPorts converts a slice of kube container descriptions to an 878 // array of portmapping 879 func getPodPorts(containers []v1.Container) []types.PortMapping { 880 var infraPorts []types.PortMapping 881 for _, container := range containers { 882 for _, p := range container.Ports { 883 if p.HostPort != 0 && p.ContainerPort == 0 { 884 p.ContainerPort = p.HostPort 885 } 886 if p.Protocol == "" { 887 p.Protocol = "tcp" 888 } 889 portBinding := types.PortMapping{ 890 HostPort: uint16(p.HostPort), 891 ContainerPort: uint16(p.ContainerPort), 892 Protocol: strings.ToLower(string(p.Protocol)), 893 HostIP: p.HostIP, 894 } 895 // only hostPort is utilized in podman context, all container ports 896 // are accessible inside the shared network namespace 897 if p.HostPort != 0 { 898 infraPorts = append(infraPorts, portBinding) 899 } 900 } 901 } 902 return infraPorts 903 }