summaryrefslogtreecommitdiff
path: root/pkg/specgen/generate/kube/kube.go
diff options
context:
space:
mode:
authorbaude <bbaude@redhat.com>2020-11-05 09:07:03 -0600
committerbaude <bbaude@redhat.com>2020-11-10 07:55:24 -0600
commit72ec8b0aa2d599220b24749c1f4c7a9c42667023 (patch)
tree18bbe29c845250d3c922b7c4be0d82bc0d9b1af4 /pkg/specgen/generate/kube/kube.go
parentda95fb4226c82694af19610578d9c14a6aa964f2 (diff)
downloadpodman-72ec8b0aa2d599220b24749c1f4c7a9c42667023.tar.gz
podman-72ec8b0aa2d599220b24749c1f4c7a9c42667023.tar.bz2
podman-72ec8b0aa2d599220b24749c1f4c7a9c42667023.zip
migrate play kube to spec gen
we need to migrate play kube away from using the old container creation method. the new approach is specgen and this aligns play kube with container creation in the rest of podman. Signed-off-by: baude <bbaude@redhat.com>
Diffstat (limited to 'pkg/specgen/generate/kube/kube.go')
-rw-r--r--pkg/specgen/generate/kube/kube.go312
1 files changed, 312 insertions, 0 deletions
diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go
new file mode 100644
index 000000000..e1202956c
--- /dev/null
+++ b/pkg/specgen/generate/kube/kube.go
@@ -0,0 +1,312 @@
+package kube
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/podman/v2/libpod/image"
+ ann "github.com/containers/podman/v2/pkg/annotations"
+ "github.com/containers/podman/v2/pkg/specgen"
+ "github.com/containers/podman/v2/pkg/util"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+)
+
+func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec) (*specgen.PodSpecGenerator, error) {
+ p := specgen.NewPodSpecGenerator()
+ p.Name = podName
+ p.Labels = podYAML.ObjectMeta.Labels
+ // TODO we only configure Process namespace. We also need to account for Host{IPC,Network,PID}
+ // which is not currently possible with pod create
+ if podYAML.Spec.ShareProcessNamespace != nil && *podYAML.Spec.ShareProcessNamespace {
+ p.SharedNamespaces = append(p.SharedNamespaces, "pid")
+ }
+ p.Hostname = podYAML.Spec.Hostname
+ if p.Hostname == "" {
+ p.Hostname = podName
+ }
+ if podYAML.Spec.HostNetwork {
+ p.NetNS.Value = "host"
+ }
+ if podYAML.Spec.HostAliases != nil {
+ hosts := make([]string, 0, len(podYAML.Spec.HostAliases))
+ for _, hostAlias := range podYAML.Spec.HostAliases {
+ for _, host := range hostAlias.Hostnames {
+ hosts = append(hosts, host+":"+hostAlias.IP)
+ }
+ }
+ p.HostAdd = hosts
+ }
+ podPorts := getPodPorts(podYAML.Spec.Containers)
+ p.PortMappings = podPorts
+
+ return p, nil
+}
+
+func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newImage *image.Image, volumes map[string]string, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *KubeSeccompPaths, restartPolicy string) (*specgen.SpecGenerator, error) {
+ s := specgen.NewSpecGenerator(iid, false)
+
+ // podName should be non-empty for Deployment objects to be able to create
+ // multiple pods having containers with unique names
+ if len(podName) < 1 {
+ return nil, errors.Errorf("kubeContainerToCreateConfig got empty podName")
+ }
+
+ s.Name = fmt.Sprintf("%s-%s", podName, containerYAML.Name)
+
+ s.Terminal = containerYAML.TTY
+
+ s.Pod = podID
+
+ setupSecurityContext(s, containerYAML)
+
+ // Since we prefix the container name with pod name to work-around the uniqueness requirement,
+ // the seccomp profile should reference the actual container name from the YAML
+ // but apply to the containers with the prefixed name
+ s.SeccompProfilePath = seccompPaths.FindForContainer(containerYAML.Name)
+
+ s.ResourceLimits = &spec.LinuxResources{}
+ milliCPU, err := quantityToInt64(containerYAML.Resources.Limits.Cpu())
+ if err != nil {
+ return nil, errors.Wrap(err, "Failed to set CPU quota")
+ }
+ if milliCPU > 0 {
+ period, quota := util.CoresToPeriodAndQuota(float64(milliCPU) / 1000)
+ s.ResourceLimits.CPU = &spec.LinuxCPU{
+ Quota: &quota,
+ Period: &period,
+ }
+ }
+
+ limit, err := quantityToInt64(containerYAML.Resources.Limits.Memory())
+ if err != nil {
+ return nil, errors.Wrap(err, "Failed to set memory limit")
+ }
+
+ memoryRes, err := quantityToInt64(containerYAML.Resources.Requests.Memory())
+ if err != nil {
+ return nil, errors.Wrap(err, "Failed to set memory reservation")
+ }
+
+ if limit > 0 || memoryRes > 0 {
+ s.ResourceLimits.Memory = &spec.LinuxMemory{}
+ }
+
+ if limit > 0 {
+ s.ResourceLimits.Memory.Limit = &limit
+ }
+
+ if memoryRes > 0 {
+ s.ResourceLimits.Memory.Reservation = &memoryRes
+ }
+
+ // TODO: We dont understand why specgen does not take of this, but
+ // integration tests clearly pointed out that it was required.
+ s.Command = []string{}
+ imageData, err := newImage.Inspect(ctx)
+ if err != nil {
+ return nil, err
+ }
+ s.WorkDir = "/"
+ if imageData != nil && imageData.Config != nil {
+ if imageData.Config.WorkingDir != "" {
+ s.WorkDir = imageData.Config.WorkingDir
+ }
+ s.Command = imageData.Config.Entrypoint
+ s.Labels = imageData.Config.Labels
+ if len(imageData.Config.StopSignal) > 0 {
+ stopSignal, err := util.ParseSignal(imageData.Config.StopSignal)
+ if err != nil {
+ return nil, err
+ }
+ s.StopSignal = &stopSignal
+ }
+ }
+ if len(containerYAML.Command) != 0 {
+ s.Command = containerYAML.Command
+ }
+ // doc https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#notes
+ if len(containerYAML.Args) != 0 {
+ s.Command = append(s.Command, containerYAML.Args...)
+ }
+ // FIXME,
+ // we are currently ignoring imageData.Config.ExposedPorts
+ if containerYAML.WorkingDir != "" {
+ s.WorkDir = containerYAML.WorkingDir
+ }
+
+ annotations := make(map[string]string)
+ if infraID != "" {
+ annotations[ann.SandboxID] = infraID
+ annotations[ann.ContainerType] = ann.ContainerTypeContainer
+ }
+ s.Annotations = annotations
+
+ // Environment Variables
+ envs := map[string]string{}
+ for _, env := range containerYAML.Env {
+ value := envVarValue(env, configMaps)
+
+ envs[env.Name] = value
+ }
+ for _, envFrom := range containerYAML.EnvFrom {
+ cmEnvs := envVarsFromConfigMap(envFrom, configMaps)
+
+ for k, v := range cmEnvs {
+ envs[k] = v
+ }
+ }
+ s.Env = envs
+
+ for _, volume := range containerYAML.VolumeMounts {
+ hostPath, exists := volumes[volume.Name]
+ if !exists {
+ return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
+ }
+ if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
+ return nil, errors.Wrapf(err, "error in parsing MountPath")
+ }
+ mount := spec.Mount{
+ Destination: volume.MountPath,
+ Source: hostPath,
+ Type: "bind",
+ }
+ if volume.ReadOnly {
+ mount.Options = []string{"ro"}
+ }
+ s.Mounts = append(s.Mounts, mount)
+ }
+
+ s.RestartPolicy = restartPolicy
+
+ return s, nil
+}
+
+func setupSecurityContext(s *specgen.SpecGenerator, containerYAML v1.Container) {
+ if containerYAML.SecurityContext == nil {
+ return
+ }
+ if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil {
+ s.ReadOnlyFilesystem = *containerYAML.SecurityContext.ReadOnlyRootFilesystem
+ }
+ if containerYAML.SecurityContext.Privileged != nil {
+ s.Privileged = *containerYAML.SecurityContext.Privileged
+ }
+
+ if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil {
+ s.NoNewPrivileges = !*containerYAML.SecurityContext.AllowPrivilegeEscalation
+ }
+
+ if seopt := containerYAML.SecurityContext.SELinuxOptions; seopt != nil {
+ if seopt.User != "" {
+ s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.User))
+ }
+ if seopt.Role != "" {
+ s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.Role))
+ }
+ if seopt.Type != "" {
+ s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.Type))
+ }
+ if seopt.Level != "" {
+ s.SelinuxOpts = append(s.SelinuxOpts, fmt.Sprintf("role:%s", seopt.Level))
+ }
+ }
+ if caps := containerYAML.SecurityContext.Capabilities; caps != nil {
+ for _, capability := range caps.Add {
+ s.CapAdd = append(s.CapAdd, string(capability))
+ }
+ for _, capability := range caps.Drop {
+ s.CapDrop = append(s.CapDrop, string(capability))
+ }
+ }
+ if containerYAML.SecurityContext.RunAsUser != nil {
+ s.User = fmt.Sprintf("%d", *containerYAML.SecurityContext.RunAsUser)
+ }
+ if containerYAML.SecurityContext.RunAsGroup != nil {
+ if s.User == "" {
+ s.User = "0"
+ }
+ s.User = fmt.Sprintf("%s:%d", s.User, *containerYAML.SecurityContext.RunAsGroup)
+ }
+}
+
+func quantityToInt64(quantity *resource.Quantity) (int64, error) {
+ if i, ok := quantity.AsInt64(); ok {
+ return i, nil
+ }
+
+ if i, ok := quantity.AsDec().Unscaled(); ok {
+ return i, nil
+ }
+
+ return 0, errors.Errorf("Quantity cannot be represented as int64: %v", quantity)
+}
+
+// envVarsFromConfigMap returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container
+func envVarsFromConfigMap(envFrom v1.EnvFromSource, configMaps []v1.ConfigMap) map[string]string {
+ envs := map[string]string{}
+
+ if envFrom.ConfigMapRef != nil {
+ cmName := envFrom.ConfigMapRef.Name
+
+ for _, c := range configMaps {
+ if cmName == c.Name {
+ envs = c.Data
+ break
+ }
+ }
+ }
+
+ return envs
+}
+
+// envVarValue returns the environment variable value configured within the container's env setting.
+// It gets the value from a configMap if specified, otherwise returns env.Value
+func envVarValue(env v1.EnvVar, configMaps []v1.ConfigMap) string {
+ for _, c := range configMaps {
+ if env.ValueFrom != nil {
+ if env.ValueFrom.ConfigMapKeyRef != nil {
+ if env.ValueFrom.ConfigMapKeyRef.Name == c.Name {
+ if value, ok := c.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok {
+ return value
+ }
+ }
+ }
+ }
+ }
+
+ return env.Value
+}
+
+// getPodPorts converts a slice of kube container descriptions to an
+// array of portmapping
+func getPodPorts(containers []v1.Container) []specgen.PortMapping {
+ var infraPorts []specgen.PortMapping
+ for _, container := range containers {
+ for _, p := range container.Ports {
+ if p.HostPort != 0 && p.ContainerPort == 0 {
+ p.ContainerPort = p.HostPort
+ }
+ if p.Protocol == "" {
+ p.Protocol = "tcp"
+ }
+ portBinding := specgen.PortMapping{
+ HostPort: uint16(p.HostPort),
+ ContainerPort: uint16(p.ContainerPort),
+ Protocol: strings.ToLower(string(p.Protocol)),
+ HostIP: p.HostIP,
+ }
+ // only hostPort is utilized in podman context, all container ports
+ // are accessible inside the shared network namespace
+ if p.HostPort != 0 {
+ infraPorts = append(infraPorts, portBinding)
+ }
+
+ }
+ }
+ return infraPorts
+}