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:  &quota,
   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  }