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: "a, 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 }