github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/specgen/generate/kube/kube.go (about)

     1  package kube
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/containers/buildah/pkg/parse"
     9  	"github.com/containers/podman/v2/libpod/image"
    10  	ann "github.com/containers/podman/v2/pkg/annotations"
    11  	"github.com/containers/podman/v2/pkg/specgen"
    12  	"github.com/containers/podman/v2/pkg/util"
    13  	spec "github.com/opencontainers/runtime-spec/specs-go"
    14  	"github.com/pkg/errors"
    15  	v1 "k8s.io/api/core/v1"
    16  	"k8s.io/apimachinery/pkg/api/resource"
    17  )
    18  
    19  func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec) (*specgen.PodSpecGenerator, error) {
    20  	p := specgen.NewPodSpecGenerator()
    21  	p.Name = podName
    22  	p.Labels = podYAML.ObjectMeta.Labels
    23  	// TODO we only configure Process namespace. We also need to account for Host{IPC,Network,PID}
    24  	// which is not currently possible with pod create
    25  	if podYAML.Spec.ShareProcessNamespace != nil && *podYAML.Spec.ShareProcessNamespace {
    26  		p.SharedNamespaces = append(p.SharedNamespaces, "pid")
    27  	}
    28  	p.Hostname = podYAML.Spec.Hostname
    29  	if p.Hostname == "" {
    30  		p.Hostname = podName
    31  	}
    32  	if podYAML.Spec.HostNetwork {
    33  		p.NetNS.Value = "host"
    34  	}
    35  	if podYAML.Spec.HostAliases != nil {
    36  		hosts := make([]string, 0, len(podYAML.Spec.HostAliases))
    37  		for _, hostAlias := range podYAML.Spec.HostAliases {
    38  			for _, host := range hostAlias.Hostnames {
    39  				hosts = append(hosts, host+":"+hostAlias.IP)
    40  			}
    41  		}
    42  		p.HostAdd = hosts
    43  	}
    44  	podPorts := getPodPorts(podYAML.Spec.Containers)
    45  	p.PortMappings = podPorts
    46  
    47  	return p, nil
    48  }
    49  
    50  func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newImage *image.Image, volumes map[string]*KubeVolume, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *KubeSeccompPaths, restartPolicy string) (*specgen.SpecGenerator, error) {
    51  	s := specgen.NewSpecGenerator(iid, false)
    52  
    53  	// podName should be non-empty for Deployment objects to be able to create
    54  	// multiple pods having containers with unique names
    55  	if len(podName) < 1 {
    56  		return nil, errors.Errorf("kubeContainerToCreateConfig got empty podName")
    57  	}
    58  
    59  	s.Name = fmt.Sprintf("%s-%s", podName, containerYAML.Name)
    60  
    61  	s.Terminal = containerYAML.TTY
    62  
    63  	s.Pod = podID
    64  
    65  	setupSecurityContext(s, containerYAML)
    66  
    67  	// Since we prefix the container name with pod name to work-around the uniqueness requirement,
    68  	// the seccomp profile should reference the actual container name from the YAML
    69  	// but apply to the containers with the prefixed name
    70  	s.SeccompProfilePath = seccompPaths.FindForContainer(containerYAML.Name)
    71  
    72  	s.ResourceLimits = &spec.LinuxResources{}
    73  	milliCPU, err := quantityToInt64(containerYAML.Resources.Limits.Cpu())
    74  	if err != nil {
    75  		return nil, errors.Wrap(err, "Failed to set CPU quota")
    76  	}
    77  	if milliCPU > 0 {
    78  		period, quota := util.CoresToPeriodAndQuota(float64(milliCPU) / 1000)
    79  		s.ResourceLimits.CPU = &spec.LinuxCPU{
    80  			Quota:  &quota,
    81  			Period: &period,
    82  		}
    83  	}
    84  
    85  	limit, err := quantityToInt64(containerYAML.Resources.Limits.Memory())
    86  	if err != nil {
    87  		return nil, errors.Wrap(err, "Failed to set memory limit")
    88  	}
    89  
    90  	memoryRes, err := quantityToInt64(containerYAML.Resources.Requests.Memory())
    91  	if err != nil {
    92  		return nil, errors.Wrap(err, "Failed to set memory reservation")
    93  	}
    94  
    95  	if limit > 0 || memoryRes > 0 {
    96  		s.ResourceLimits.Memory = &spec.LinuxMemory{}
    97  	}
    98  
    99  	if limit > 0 {
   100  		s.ResourceLimits.Memory.Limit = &limit
   101  	}
   102  
   103  	if memoryRes > 0 {
   104  		s.ResourceLimits.Memory.Reservation = &memoryRes
   105  	}
   106  
   107  	// TODO: We dont understand why specgen does not take of this, but
   108  	// integration tests clearly pointed out that it was required.
   109  	s.Command = []string{}
   110  	imageData, err := newImage.Inspect(ctx)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	s.WorkDir = "/"
   115  	if imageData != nil && imageData.Config != nil {
   116  		if imageData.Config.WorkingDir != "" {
   117  			s.WorkDir = imageData.Config.WorkingDir
   118  		}
   119  		s.Command = imageData.Config.Entrypoint
   120  		s.Labels = imageData.Config.Labels
   121  		if len(imageData.Config.StopSignal) > 0 {
   122  			stopSignal, err := util.ParseSignal(imageData.Config.StopSignal)
   123  			if err != nil {
   124  				return nil, err
   125  			}
   126  			s.StopSignal = &stopSignal
   127  		}
   128  	}
   129  	if len(containerYAML.Command) != 0 {
   130  		s.Command = containerYAML.Command
   131  	}
   132  	// doc https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#notes
   133  	if len(containerYAML.Args) != 0 {
   134  		s.Command = append(s.Command, containerYAML.Args...)
   135  	}
   136  	// FIXME,
   137  	// we are currently ignoring imageData.Config.ExposedPorts
   138  	if containerYAML.WorkingDir != "" {
   139  		s.WorkDir = containerYAML.WorkingDir
   140  	}
   141  
   142  	annotations := make(map[string]string)
   143  	if infraID != "" {
   144  		annotations[ann.SandboxID] = infraID
   145  		annotations[ann.ContainerType] = ann.ContainerTypeContainer
   146  	}
   147  	s.Annotations = annotations
   148  
   149  	// Environment Variables
   150  	envs := map[string]string{}
   151  	for _, env := range containerYAML.Env {
   152  		value := envVarValue(env, configMaps)
   153  
   154  		envs[env.Name] = value
   155  	}
   156  	for _, envFrom := range containerYAML.EnvFrom {
   157  		cmEnvs := envVarsFromConfigMap(envFrom, configMaps)
   158  
   159  		for k, v := range cmEnvs {
   160  			envs[k] = v
   161  		}
   162  	}
   163  	s.Env = envs
   164  
   165  	for _, volume := range containerYAML.VolumeMounts {
   166  		volumeSource, exists := volumes[volume.Name]
   167  		if !exists {
   168  			return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
   169  		}
   170  		switch volumeSource.Type {
   171  		case KubeVolumeTypeBindMount:
   172  			if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
   173  				return nil, errors.Wrapf(err, "error in parsing MountPath")
   174  			}
   175  			mount := spec.Mount{
   176  				Destination: volume.MountPath,
   177  				Source:      volumeSource.Source,
   178  				Type:        "bind",
   179  			}
   180  			if volume.ReadOnly {
   181  				mount.Options = []string{"ro"}
   182  			}
   183  			s.Mounts = append(s.Mounts, mount)
   184  		case KubeVolumeTypeNamed:
   185  			namedVolume := specgen.NamedVolume{
   186  				Dest: volume.MountPath,
   187  				Name: volumeSource.Source,
   188  			}
   189  			if volume.ReadOnly {
   190  				namedVolume.Options = []string{"ro"}
   191  			}
   192  			s.Volumes = append(s.Volumes, &namedVolume)
   193  		default:
   194  			return nil, errors.Errorf("Unsupported volume source type")
   195  		}
   196  	}
   197  
   198  	s.RestartPolicy = restartPolicy
   199  
   200  	return s, nil
   201  }
   202  
   203  func setupSecurityContext(s *specgen.SpecGenerator, containerYAML v1.Container) {
   204  	if containerYAML.SecurityContext == nil {
   205  		return
   206  	}
   207  	if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil {
   208  		s.ReadOnlyFilesystem = *containerYAML.SecurityContext.ReadOnlyRootFilesystem
   209  	}
   210  	if containerYAML.SecurityContext.Privileged != nil {
   211  		s.Privileged = *containerYAML.SecurityContext.Privileged
   212  	}
   213  
   214  	if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil {
   215  		s.NoNewPrivileges = !*containerYAML.SecurityContext.AllowPrivilegeEscalation
   216  	}
   217  
   218  	if seopt := containerYAML.SecurityContext.SELinuxOptions; seopt != nil {
   219  		if seopt.User != "" {
   220  			s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.User))
   221  		}
   222  		if seopt.Role != "" {
   223  			s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.Role))
   224  		}
   225  		if seopt.Type != "" {
   226  			s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.Type))
   227  		}
   228  		if seopt.Level != "" {
   229  			s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.Level))
   230  		}
   231  	}
   232  	if caps := containerYAML.SecurityContext.Capabilities; caps != nil {
   233  		for _, capability := range caps.Add {
   234  			s.CapAdd = append(s.CapAdd, string(capability))
   235  		}
   236  		for _, capability := range caps.Drop {
   237  			s.CapDrop = append(s.CapDrop, string(capability))
   238  		}
   239  	}
   240  	if containerYAML.SecurityContext.RunAsUser != nil {
   241  		s.User = fmt.Sprintf("%d", *containerYAML.SecurityContext.RunAsUser)
   242  	}
   243  	if containerYAML.SecurityContext.RunAsGroup != nil {
   244  		if s.User == "" {
   245  			s.User = "0"
   246  		}
   247  		s.User = fmt.Sprintf("%s:%d", s.User, *containerYAML.SecurityContext.RunAsGroup)
   248  	}
   249  }
   250  
   251  func quantityToInt64(quantity *resource.Quantity) (int64, error) {
   252  	if i, ok := quantity.AsInt64(); ok {
   253  		return i, nil
   254  	}
   255  
   256  	if i, ok := quantity.AsDec().Unscaled(); ok {
   257  		return i, nil
   258  	}
   259  
   260  	return 0, errors.Errorf("Quantity cannot be represented as int64: %v", quantity)
   261  }
   262  
   263  // envVarsFromConfigMap returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container
   264  func envVarsFromConfigMap(envFrom v1.EnvFromSource, configMaps []v1.ConfigMap) map[string]string {
   265  	envs := map[string]string{}
   266  
   267  	if envFrom.ConfigMapRef != nil {
   268  		cmName := envFrom.ConfigMapRef.Name
   269  
   270  		for _, c := range configMaps {
   271  			if cmName == c.Name {
   272  				envs = c.Data
   273  				break
   274  			}
   275  		}
   276  	}
   277  
   278  	return envs
   279  }
   280  
   281  // envVarValue returns the environment variable value configured within the container's env setting.
   282  // It gets the value from a configMap if specified, otherwise returns env.Value
   283  func envVarValue(env v1.EnvVar, configMaps []v1.ConfigMap) string {
   284  	for _, c := range configMaps {
   285  		if env.ValueFrom != nil {
   286  			if env.ValueFrom.ConfigMapKeyRef != nil {
   287  				if env.ValueFrom.ConfigMapKeyRef.Name == c.Name {
   288  					if value, ok := c.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok {
   289  						return value
   290  					}
   291  				}
   292  			}
   293  		}
   294  	}
   295  
   296  	return env.Value
   297  }
   298  
   299  // getPodPorts converts a slice of kube container descriptions to an
   300  // array of portmapping
   301  func getPodPorts(containers []v1.Container) []specgen.PortMapping {
   302  	var infraPorts []specgen.PortMapping
   303  	for _, container := range containers {
   304  		for _, p := range container.Ports {
   305  			if p.HostPort != 0 && p.ContainerPort == 0 {
   306  				p.ContainerPort = p.HostPort
   307  			}
   308  			if p.Protocol == "" {
   309  				p.Protocol = "tcp"
   310  			}
   311  			portBinding := specgen.PortMapping{
   312  				HostPort:      uint16(p.HostPort),
   313  				ContainerPort: uint16(p.ContainerPort),
   314  				Protocol:      strings.ToLower(string(p.Protocol)),
   315  				HostIP:        p.HostIP,
   316  			}
   317  			// only hostPort is utilized in podman context, all container ports
   318  			// are accessible inside the shared network namespace
   319  			if p.HostPort != 0 {
   320  				infraPorts = append(infraPorts, portBinding)
   321  			}
   322  
   323  		}
   324  	}
   325  	return infraPorts
   326  }