github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/domain/infra/abi/play.go (about) 1 package abi 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strconv" 12 "strings" 13 14 buildahDefine "github.com/containers/buildah/define" 15 "github.com/containers/common/libimage" 16 nettypes "github.com/containers/common/libnetwork/types" 17 "github.com/containers/common/pkg/config" 18 "github.com/containers/image/v5/types" 19 "github.com/hanks177/podman/v4/libpod" 20 "github.com/hanks177/podman/v4/libpod/define" 21 "github.com/hanks177/podman/v4/pkg/autoupdate" 22 "github.com/hanks177/podman/v4/pkg/domain/entities" 23 v1apps "github.com/hanks177/podman/v4/pkg/k8s.io/api/apps/v1" 24 v1 "github.com/hanks177/podman/v4/pkg/k8s.io/api/core/v1" 25 "github.com/hanks177/podman/v4/pkg/specgen" 26 "github.com/hanks177/podman/v4/pkg/specgen/generate" 27 "github.com/hanks177/podman/v4/pkg/specgen/generate/kube" 28 "github.com/hanks177/podman/v4/pkg/specgenutil" 29 "github.com/hanks177/podman/v4/pkg/util" 30 "github.com/ghodss/yaml" 31 "github.com/opencontainers/go-digest" 32 "github.com/pkg/errors" 33 "github.com/sirupsen/logrus" 34 yamlv2 "gopkg.in/yaml.v2" 35 ) 36 37 // createServiceContainer creates a container that can later on 38 // be associated with the pods of a K8s yaml. It will be started along with 39 // the first pod. 40 func (ic *ContainerEngine) createServiceContainer(ctx context.Context, name string, options entities.PlayKubeOptions) (*libpod.Container, error) { 41 // Make sure to replace the service container as well if requested by 42 // the user. 43 if options.Replace { 44 if _, err := ic.ContainerRm(ctx, []string{name}, entities.RmOptions{Force: true, Ignore: true}); err != nil { 45 return nil, fmt.Errorf("replacing service container: %w", err) 46 } 47 } 48 49 // Similar to infra containers, a service container is using the pause image. 50 image, err := generate.PullOrBuildInfraImage(ic.Libpod, "") 51 if err != nil { 52 return nil, fmt.Errorf("image for service container: %w", err) 53 } 54 55 ctrOpts := entities.ContainerCreateOptions{ 56 // Inherited from infra containers 57 ImageVolume: "bind", 58 IsInfra: false, 59 MemorySwappiness: -1, 60 // No need to spin up slirp etc. 61 Net: &entities.NetOptions{Network: specgen.Namespace{NSMode: specgen.NoNetwork}}, 62 } 63 64 // Create and fill out the runtime spec. 65 s := specgen.NewSpecGenerator(image, false) 66 if err := specgenutil.FillOutSpecGen(s, &ctrOpts, []string{}); err != nil { 67 return nil, fmt.Errorf("completing spec for service container: %w", err) 68 } 69 s.Name = name 70 71 runtimeSpec, spec, opts, err := generate.MakeContainer(ctx, ic.Libpod, s, false, nil) 72 if err != nil { 73 return nil, fmt.Errorf("creating runtime spec for service container: %w", err) 74 } 75 opts = append(opts, libpod.WithIsService()) 76 opts = append(opts, libpod.WithSdNotifyMode(define.SdNotifyModeConmon)) 77 78 // Create a new libpod container based on the spec. 79 ctr, err := ic.Libpod.NewContainer(ctx, runtimeSpec, spec, false, opts...) 80 if err != nil { 81 return nil, fmt.Errorf("creating service container: %w", err) 82 } 83 84 return ctr, nil 85 } 86 87 // Creates the name for a service container based on the provided content of a 88 // K8s yaml file. 89 func serviceContainerName(content []byte) string { 90 // The name of the service container is the first 12 91 // characters of the yaml file's hash followed by the 92 // '-service' suffix to guarantee a predictable and 93 // discoverable name. 94 hash := digest.FromBytes(content).Encoded() 95 return hash[0:12] + "-service" 96 } 97 98 func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options entities.PlayKubeOptions) (_ *entities.PlayKubeReport, finalErr error) { 99 report := &entities.PlayKubeReport{} 100 validKinds := 0 101 102 // read yaml document 103 content, err := ioutil.ReadAll(body) 104 if err != nil { 105 return nil, err 106 } 107 108 // split yaml document 109 documentList, err := splitMultiDocYAML(content) 110 if err != nil { 111 return nil, err 112 } 113 114 // sort kube kinds 115 documentList, err = sortKubeKinds(documentList) 116 if err != nil { 117 return nil, errors.Wrap(err, "unable to sort kube kinds") 118 } 119 120 ipIndex := 0 121 122 var configMaps []v1.ConfigMap 123 124 // create pod on each document if it is a pod or deployment 125 // any other kube kind will be skipped 126 for _, document := range documentList { 127 kind, err := getKubeKind(document) 128 if err != nil { 129 return nil, errors.Wrap(err, "unable to read kube YAML") 130 } 131 132 // TODO: create constants for the various "kinds" of yaml files. 133 var serviceContainer *libpod.Container 134 if options.ServiceContainer && (kind == "Pod" || kind == "Deployment") { 135 ctr, err := ic.createServiceContainer(ctx, serviceContainerName(content), options) 136 if err != nil { 137 return nil, err 138 } 139 serviceContainer = ctr 140 // Make sure to remove the container in case something goes wrong below. 141 defer func() { 142 if finalErr == nil { 143 return 144 } 145 if err := ic.Libpod.RemoveContainer(ctx, ctr, true, false, nil); err != nil { 146 logrus.Errorf("Cleaning up service container after failure: %v", err) 147 } 148 }() 149 } 150 151 switch kind { 152 case "Pod": 153 var podYAML v1.Pod 154 var podTemplateSpec v1.PodTemplateSpec 155 156 if err := yaml.Unmarshal(document, &podYAML); err != nil { 157 return nil, errors.Wrap(err, "unable to read YAML as Kube Pod") 158 } 159 160 podTemplateSpec.ObjectMeta = podYAML.ObjectMeta 161 podTemplateSpec.Spec = podYAML.Spec 162 for name, val := range podYAML.Annotations { 163 if len(val) > define.MaxKubeAnnotation { 164 return nil, errors.Errorf("invalid annotation %q=%q value length exceeds Kubernetetes max %d", name, val, define.MaxKubeAnnotation) 165 } 166 } 167 for name, val := range options.Annotations { 168 if podYAML.Annotations == nil { 169 podYAML.Annotations = make(map[string]string) 170 } 171 podYAML.Annotations[name] = val 172 } 173 174 r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex, podYAML.Annotations, configMaps, serviceContainer) 175 if err != nil { 176 return nil, err 177 } 178 179 report.Pods = append(report.Pods, r.Pods...) 180 validKinds++ 181 case "Deployment": 182 var deploymentYAML v1apps.Deployment 183 184 if err := yaml.Unmarshal(document, &deploymentYAML); err != nil { 185 return nil, errors.Wrap(err, "unable to read YAML as Kube Deployment") 186 } 187 188 r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options, &ipIndex, configMaps, serviceContainer) 189 if err != nil { 190 return nil, err 191 } 192 193 report.Pods = append(report.Pods, r.Pods...) 194 validKinds++ 195 case "PersistentVolumeClaim": 196 var pvcYAML v1.PersistentVolumeClaim 197 198 if err := yaml.Unmarshal(document, &pvcYAML); err != nil { 199 return nil, errors.Wrap(err, "unable to read YAML as Kube PersistentVolumeClaim") 200 } 201 202 r, err := ic.playKubePVC(ctx, &pvcYAML) 203 if err != nil { 204 return nil, err 205 } 206 207 report.Volumes = append(report.Volumes, r.Volumes...) 208 validKinds++ 209 case "ConfigMap": 210 var configMap v1.ConfigMap 211 212 if err := yaml.Unmarshal(document, &configMap); err != nil { 213 return nil, errors.Wrap(err, "unable to read YAML as Kube ConfigMap") 214 } 215 configMaps = append(configMaps, configMap) 216 default: 217 logrus.Infof("Kube kind %s not supported", kind) 218 continue 219 } 220 } 221 222 if validKinds == 0 { 223 if len(configMaps) > 0 { 224 return nil, fmt.Errorf("ConfigMaps in podman are not a standalone object and must be used in a container") 225 } 226 return nil, fmt.Errorf("YAML document does not contain any supported kube kind") 227 } 228 229 return report, nil 230 } 231 232 func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions, ipIndex *int, configMaps []v1.ConfigMap, serviceContainer *libpod.Container) (*entities.PlayKubeReport, error) { 233 var ( 234 deploymentName string 235 podSpec v1.PodTemplateSpec 236 numReplicas int32 237 i int32 238 report entities.PlayKubeReport 239 ) 240 241 deploymentName = deploymentYAML.ObjectMeta.Name 242 if deploymentName == "" { 243 return nil, errors.Errorf("Deployment does not have a name") 244 } 245 numReplicas = 1 246 if deploymentYAML.Spec.Replicas != nil { 247 numReplicas = *deploymentYAML.Spec.Replicas 248 } 249 podSpec = deploymentYAML.Spec.Template 250 251 // create "replicas" number of pods 252 for i = 0; i < numReplicas; i++ { 253 podName := fmt.Sprintf("%s-pod-%d", deploymentName, i) 254 podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex, deploymentYAML.Annotations, configMaps, serviceContainer) 255 if err != nil { 256 return nil, errors.Wrapf(err, "error encountered while bringing up pod %s", podName) 257 } 258 report.Pods = append(report.Pods, podReport.Pods...) 259 } 260 return &report, nil 261 } 262 263 func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int, annotations map[string]string, configMaps []v1.ConfigMap, serviceContainer *libpod.Container) (*entities.PlayKubeReport, error) { 264 var ( 265 writer io.Writer 266 playKubePod entities.PlayKubePod 267 report entities.PlayKubeReport 268 ) 269 270 // Create the secret manager before hand 271 secretsManager, err := ic.Libpod.SecretsManager() 272 if err != nil { 273 return nil, err 274 } 275 276 // Assert the pod has a name 277 if podName == "" { 278 return nil, errors.Errorf("pod does not have a name") 279 } 280 281 podOpt := entities.PodCreateOptions{ 282 Infra: true, 283 Net: &entities.NetOptions{NoHosts: options.NoHosts}, 284 ExitPolicy: string(config.PodExitPolicyStop), 285 } 286 podOpt, err = kube.ToPodOpt(ctx, podName, podOpt, podYAML) 287 if err != nil { 288 return nil, err 289 } 290 291 if len(options.Networks) > 0 { 292 ns, networks, netOpts, err := specgen.ParseNetworkFlag(options.Networks) 293 if err != nil { 294 return nil, err 295 } 296 297 if (ns.IsBridge() && len(networks) == 0) || ns.IsHost() { 298 return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML") 299 } 300 301 podOpt.Net.Network = ns 302 podOpt.Net.Networks = networks 303 podOpt.Net.NetworkOptions = netOpts 304 } 305 306 if options.Userns == "" { 307 options.Userns = "host" 308 } 309 310 // Validate the userns modes supported. 311 podOpt.Userns, err = specgen.ParseUserNamespace(options.Userns) 312 if err != nil { 313 return nil, err 314 } 315 316 // FIXME This is very hard to support properly with a good ux 317 if len(options.StaticIPs) > *ipIndex { 318 if !podOpt.Net.Network.IsBridge() { 319 return nil, errors.Wrap(define.ErrInvalidArg, "static ip addresses can only be set when the network mode is bridge") 320 } 321 if len(podOpt.Net.Networks) != 1 { 322 return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static ip addresses for more than network, use netname:ip=<ip> syntax to specify ips for more than network") 323 } 324 for name, netOpts := range podOpt.Net.Networks { 325 netOpts.StaticIPs = append(netOpts.StaticIPs, options.StaticIPs[*ipIndex]) 326 podOpt.Net.Networks[name] = netOpts 327 } 328 } else if len(options.StaticIPs) > 0 { 329 // only warn if the user has set at least one ip 330 logrus.Warn("No more static ips left using a random one") 331 } 332 if len(options.StaticMACs) > *ipIndex { 333 if !podOpt.Net.Network.IsBridge() { 334 return nil, errors.Wrap(define.ErrInvalidArg, "static mac address can only be set when the network mode is bridge") 335 } 336 if len(podOpt.Net.Networks) != 1 { 337 return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static mac address for more than network, use netname:mac=<mac> syntax to specify mac for more than network") 338 } 339 for name, netOpts := range podOpt.Net.Networks { 340 netOpts.StaticMAC = nettypes.HardwareAddr(options.StaticMACs[*ipIndex]) 341 podOpt.Net.Networks[name] = netOpts 342 } 343 } else if len(options.StaticIPs) > 0 { 344 // only warn if the user has set at least one mac 345 logrus.Warn("No more static macs left using a random one") 346 } 347 *ipIndex++ 348 349 p := specgen.NewPodSpecGenerator() 350 if err != nil { 351 return nil, err 352 } 353 354 p, err = entities.ToPodSpecGen(*p, &podOpt) 355 if err != nil { 356 return nil, err 357 } 358 podSpec := entities.PodSpec{PodSpecGen: *p} 359 360 configMapIndex := make(map[string]struct{}) 361 for _, configMap := range configMaps { 362 configMapIndex[configMap.Name] = struct{}{} 363 } 364 for _, p := range options.ConfigMaps { 365 f, err := os.Open(p) 366 if err != nil { 367 return nil, err 368 } 369 defer f.Close() 370 371 cm, err := readConfigMapFromFile(f) 372 if err != nil { 373 return nil, errors.Wrapf(err, "%q", p) 374 } 375 376 if _, present := configMapIndex[cm.Name]; present { 377 return nil, errors.Errorf("ambiguous configuration: the same config map %s is present in YAML and in --configmaps %s file", cm.Name, p) 378 } 379 380 configMaps = append(configMaps, cm) 381 } 382 383 volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps) 384 if err != nil { 385 return nil, err 386 } 387 388 // Go through the volumes and create a podman volume for all volumes that have been 389 // defined by a configmap 390 for _, v := range volumes { 391 if v.Type == kube.KubeVolumeTypeConfigMap && !v.Optional { 392 vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source)) 393 if err != nil { 394 if errors.Is(err, define.ErrVolumeExists) { 395 // Volume for this configmap already exists do not 396 // error out instead reuse the current volume. 397 vol, err = ic.Libpod.GetVolume(v.Source) 398 if err != nil { 399 return nil, errors.Wrapf(err, "cannot re-use local volume for volume from configmap %q", v.Source) 400 } 401 } else { 402 return nil, errors.Wrapf(err, "cannot create a local volume for volume from configmap %q", v.Source) 403 } 404 } 405 mountPoint, err := vol.MountPoint() 406 if err != nil || mountPoint == "" { 407 return nil, errors.Wrapf(err, "unable to get mountpoint of volume %q", vol.Name()) 408 } 409 // Create files and add data to the volume mountpoint based on the Items in the volume 410 for k, v := range v.Items { 411 dataPath := filepath.Join(mountPoint, k) 412 f, err := os.Create(dataPath) 413 if err != nil { 414 return nil, errors.Wrapf(err, "cannot create file %q at volume mountpoint %q", k, mountPoint) 415 } 416 defer f.Close() 417 _, err = f.WriteString(v) 418 if err != nil { 419 return nil, err 420 } 421 } 422 } 423 } 424 425 seccompPaths, err := kube.InitializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot) 426 if err != nil { 427 return nil, err 428 } 429 430 var ctrRestartPolicy string 431 switch podYAML.Spec.RestartPolicy { 432 case v1.RestartPolicyAlways: 433 ctrRestartPolicy = define.RestartPolicyAlways 434 case v1.RestartPolicyOnFailure: 435 ctrRestartPolicy = define.RestartPolicyOnFailure 436 case v1.RestartPolicyNever: 437 ctrRestartPolicy = define.RestartPolicyNo 438 default: // Default to Always 439 ctrRestartPolicy = define.RestartPolicyAlways 440 } 441 442 if podOpt.Infra { 443 infraImage := util.DefaultContainerConfig().Engine.InfraImage 444 infraOptions := entities.NewInfraContainerCreateOptions() 445 infraOptions.Hostname = podSpec.PodSpecGen.PodBasicConfig.Hostname 446 infraOptions.UserNS = options.Userns 447 podSpec.PodSpecGen.InfraImage = infraImage 448 podSpec.PodSpecGen.NoInfra = false 449 podSpec.PodSpecGen.InfraContainerSpec = specgen.NewSpecGenerator(infraImage, false) 450 podSpec.PodSpecGen.InfraContainerSpec.NetworkOptions = p.NetworkOptions 451 podSpec.PodSpecGen.InfraContainerSpec.SdNotifyMode = define.SdNotifyModeIgnore 452 453 err = specgenutil.FillOutSpecGen(podSpec.PodSpecGen.InfraContainerSpec, &infraOptions, []string{}) 454 if err != nil { 455 return nil, err 456 } 457 } 458 459 if serviceContainer != nil { 460 podSpec.PodSpecGen.ServiceContainerID = serviceContainer.ID() 461 } 462 463 // Create the Pod 464 pod, err := generate.MakePod(&podSpec, ic.Libpod) 465 if err != nil { 466 return nil, err 467 } 468 469 podInfraID, err := pod.InfraContainerID() 470 if err != nil { 471 return nil, err 472 } 473 474 if !options.Quiet { 475 writer = os.Stderr 476 } 477 478 containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers)) 479 initContainers := make([]*libpod.Container, 0, len(podYAML.Spec.InitContainers)) 480 481 var cwd string 482 if options.ContextDir != "" { 483 cwd = options.ContextDir 484 } else { 485 cwd, err = os.Getwd() 486 if err != nil { 487 return nil, err 488 } 489 } 490 491 ctrNames := make(map[string]string) 492 for _, initCtr := range podYAML.Spec.InitContainers { 493 // Error out if same name is used for more than one container 494 if _, ok := ctrNames[initCtr.Name]; ok { 495 return nil, errors.Errorf("the pod %q is invalid; duplicate container name %q detected", podName, initCtr.Name) 496 } 497 ctrNames[initCtr.Name] = "" 498 // Init containers cannot have either of lifecycle, livenessProbe, readinessProbe, or startupProbe set 499 if initCtr.Lifecycle != nil || initCtr.LivenessProbe != nil || initCtr.ReadinessProbe != nil || initCtr.StartupProbe != nil { 500 return nil, errors.Errorf("cannot create an init container that has either of lifecycle, livenessProbe, readinessProbe, or startupProbe set") 501 } 502 pulledImage, labels, err := ic.getImageAndLabelInfo(ctx, cwd, annotations, writer, initCtr, options) 503 if err != nil { 504 return nil, err 505 } 506 507 for k, v := range podSpec.PodSpecGen.Labels { // add podYAML labels 508 labels[k] = v 509 } 510 511 specgenOpts := kube.CtrSpecGenOptions{ 512 Annotations: annotations, 513 ConfigMaps: configMaps, 514 Container: initCtr, 515 Image: pulledImage, 516 InitContainerType: define.AlwaysInitContainer, 517 Labels: labels, 518 LogDriver: options.LogDriver, 519 LogOptions: options.LogOptions, 520 NetNSIsHost: p.NetNS.IsHost(), 521 PodID: pod.ID(), 522 PodInfraID: podInfraID, 523 PodName: podName, 524 PodSecurityContext: podYAML.Spec.SecurityContext, 525 RestartPolicy: ctrRestartPolicy, 526 SeccompPaths: seccompPaths, 527 SecretsManager: secretsManager, 528 UserNSIsHost: p.Userns.IsHost(), 529 Volumes: volumes, 530 } 531 specGen, err := kube.ToSpecGen(ctx, &specgenOpts) 532 if err != nil { 533 return nil, err 534 } 535 specGen.SdNotifyMode = define.SdNotifyModeIgnore 536 rtSpec, spec, opts, err := generate.MakeContainer(ctx, ic.Libpod, specGen, false, nil) 537 if err != nil { 538 return nil, err 539 } 540 opts = append(opts, libpod.WithSdNotifyMode(define.SdNotifyModeIgnore)) 541 ctr, err := generate.ExecuteCreate(ctx, ic.Libpod, rtSpec, spec, false, opts...) 542 if err != nil { 543 return nil, err 544 } 545 546 initContainers = append(initContainers, ctr) 547 } 548 for _, container := range podYAML.Spec.Containers { 549 // Error out if the same name is used for more than one container 550 if _, ok := ctrNames[container.Name]; ok { 551 return nil, errors.Errorf("the pod %q is invalid; duplicate container name %q detected", podName, container.Name) 552 } 553 ctrNames[container.Name] = "" 554 pulledImage, labels, err := ic.getImageAndLabelInfo(ctx, cwd, annotations, writer, container, options) 555 if err != nil { 556 return nil, err 557 } 558 559 for k, v := range podSpec.PodSpecGen.Labels { // add podYAML labels 560 labels[k] = v 561 } 562 563 specgenOpts := kube.CtrSpecGenOptions{ 564 Annotations: annotations, 565 ConfigMaps: configMaps, 566 Container: container, 567 Image: pulledImage, 568 Labels: labels, 569 LogDriver: options.LogDriver, 570 LogOptions: options.LogOptions, 571 NetNSIsHost: p.NetNS.IsHost(), 572 PodID: pod.ID(), 573 PodInfraID: podInfraID, 574 PodName: podName, 575 PodSecurityContext: podYAML.Spec.SecurityContext, 576 RestartPolicy: ctrRestartPolicy, 577 SeccompPaths: seccompPaths, 578 SecretsManager: secretsManager, 579 UserNSIsHost: p.Userns.IsHost(), 580 Volumes: volumes, 581 } 582 specGen, err := kube.ToSpecGen(ctx, &specgenOpts) 583 if err != nil { 584 return nil, err 585 } 586 specGen.RawImageName = container.Image 587 rtSpec, spec, opts, err := generate.MakeContainer(ctx, ic.Libpod, specGen, false, nil) 588 if err != nil { 589 return nil, err 590 } 591 opts = append(opts, libpod.WithSdNotifyMode(define.SdNotifyModeIgnore)) 592 ctr, err := generate.ExecuteCreate(ctx, ic.Libpod, rtSpec, spec, false, opts...) 593 if err != nil { 594 return nil, err 595 } 596 containers = append(containers, ctr) 597 } 598 599 if options.Start != types.OptionalBoolFalse { 600 // Start the containers 601 podStartErrors, err := pod.Start(ctx) 602 if err != nil && errors.Cause(err) != define.ErrPodPartialFail { 603 return nil, err 604 } 605 for id, err := range podStartErrors { 606 playKubePod.ContainerErrors = append(playKubePod.ContainerErrors, errors.Wrapf(err, "error starting container %s", id).Error()) 607 fmt.Println(playKubePod.ContainerErrors) 608 } 609 } 610 611 playKubePod.ID = pod.ID() 612 for _, ctr := range containers { 613 playKubePod.Containers = append(playKubePod.Containers, ctr.ID()) 614 } 615 for _, initCtr := range initContainers { 616 playKubePod.InitContainers = append(playKubePod.InitContainers, initCtr.ID()) 617 } 618 619 report.Pods = append(report.Pods, playKubePod) 620 621 return &report, nil 622 } 623 624 // getImageAndLabelInfo returns the image information and how the image should be pulled plus as well as labels to be used for the container in the pod. 625 // Moved this to a separate function so that it can be used for both init and regular containers when playing a kube yaml. 626 func (ic *ContainerEngine) getImageAndLabelInfo(ctx context.Context, cwd string, annotations map[string]string, writer io.Writer, container v1.Container, options entities.PlayKubeOptions) (*libimage.Image, map[string]string, error) { 627 // Contains all labels obtained from kube 628 labels := make(map[string]string) 629 var pulledImage *libimage.Image 630 buildFile, err := getBuildFile(container.Image, cwd) 631 if err != nil { 632 return nil, nil, err 633 } 634 existsLocally, err := ic.Libpod.LibimageRuntime().Exists(container.Image) 635 if err != nil { 636 return nil, nil, err 637 } 638 if (len(buildFile) > 0) && ((!existsLocally && options.Build != types.OptionalBoolFalse) || (options.Build == types.OptionalBoolTrue)) { 639 buildOpts := new(buildahDefine.BuildOptions) 640 commonOpts := new(buildahDefine.CommonBuildOptions) 641 buildOpts.ConfigureNetwork = buildahDefine.NetworkDefault 642 buildOpts.Isolation = buildahDefine.IsolationChroot 643 buildOpts.CommonBuildOpts = commonOpts 644 buildOpts.Output = container.Image 645 buildOpts.ContextDirectory = filepath.Dir(buildFile) 646 if _, _, err := ic.Libpod.Build(ctx, *buildOpts, []string{buildFile}...); err != nil { 647 return nil, nil, err 648 } 649 i, _, err := ic.Libpod.LibimageRuntime().LookupImage(container.Image, new(libimage.LookupImageOptions)) 650 if err != nil { 651 return nil, nil, err 652 } 653 pulledImage = i 654 } else { 655 // NOTE: set the pull policy to "newer". This will cover cases 656 // where the "latest" tag requires a pull and will also 657 // transparently handle "localhost/" prefixed files which *may* 658 // refer to a locally built image OR an image running a 659 // registry on localhost. 660 pullPolicy := config.PullPolicyNewer 661 if len(container.ImagePullPolicy) > 0 { 662 // Make sure to lower the strings since K8s pull policy 663 // may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905). 664 rawPolicy := string(container.ImagePullPolicy) 665 pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy)) 666 if err != nil { 667 return nil, nil, err 668 } 669 } 670 // This ensures the image is the image store 671 pullOptions := &libimage.PullOptions{} 672 pullOptions.AuthFilePath = options.Authfile 673 pullOptions.CertDirPath = options.CertDir 674 pullOptions.SignaturePolicyPath = options.SignaturePolicy 675 pullOptions.Writer = writer 676 pullOptions.Username = options.Username 677 pullOptions.Password = options.Password 678 pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify 679 680 pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions) 681 if err != nil { 682 return nil, nil, err 683 } 684 pulledImage = pulledImages[0] 685 } 686 687 // Handle kube annotations 688 for k, v := range annotations { 689 switch k { 690 // Auto update annotation without container name will apply to 691 // all containers within the pod 692 case autoupdate.Label, autoupdate.AuthfileLabel: 693 labels[k] = v 694 // Auto update annotation with container name will apply only 695 // to the specified container 696 case fmt.Sprintf("%s/%s", autoupdate.Label, container.Name), 697 fmt.Sprintf("%s/%s", autoupdate.AuthfileLabel, container.Name): 698 prefixAndCtr := strings.Split(k, "/") 699 labels[prefixAndCtr[0]] = v 700 } 701 } 702 703 return pulledImage, labels, nil 704 } 705 706 // playKubePVC creates a podman volume from a kube persistent volume claim. 707 func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.PersistentVolumeClaim) (*entities.PlayKubeReport, error) { 708 var report entities.PlayKubeReport 709 opts := make(map[string]string) 710 711 // Get pvc name. 712 // This is the only required pvc attribute to create a podman volume. 713 name := pvcYAML.Name 714 if strings.TrimSpace(name) == "" { 715 return nil, fmt.Errorf("persistent volume claim name can not be empty") 716 } 717 718 // Create podman volume options. 719 volOptions := []libpod.VolumeCreateOption{ 720 libpod.WithVolumeName(name), 721 libpod.WithVolumeLabels(pvcYAML.Labels), 722 } 723 724 // Get pvc annotations and create remaining podman volume options if available. 725 // These are podman volume options that do not match any of the persistent volume claim 726 // attributes, so they can be configured using annotations since they will not affect k8s. 727 for k, v := range pvcYAML.Annotations { 728 switch k { 729 case util.VolumeDriverAnnotation: 730 volOptions = append(volOptions, libpod.WithVolumeDriver(v)) 731 case util.VolumeDeviceAnnotation: 732 opts["device"] = v 733 case util.VolumeTypeAnnotation: 734 opts["type"] = v 735 case util.VolumeUIDAnnotation: 736 uid, err := strconv.Atoi(v) 737 if err != nil { 738 return nil, errors.Wrapf(err, "cannot convert uid %s to integer", v) 739 } 740 volOptions = append(volOptions, libpod.WithVolumeUID(uid)) 741 opts["UID"] = v 742 case util.VolumeGIDAnnotation: 743 gid, err := strconv.Atoi(v) 744 if err != nil { 745 return nil, errors.Wrapf(err, "cannot convert gid %s to integer", v) 746 } 747 volOptions = append(volOptions, libpod.WithVolumeGID(gid)) 748 opts["GID"] = v 749 case util.VolumeMountOptsAnnotation: 750 opts["o"] = v 751 } 752 } 753 volOptions = append(volOptions, libpod.WithVolumeOptions(opts)) 754 755 // Create volume. 756 vol, err := ic.Libpod.NewVolume(ctx, volOptions...) 757 if err != nil { 758 return nil, err 759 } 760 761 report.Volumes = append(report.Volumes, entities.PlayKubeVolume{ 762 Name: vol.Name(), 763 }) 764 765 return &report, nil 766 } 767 768 // readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag 769 func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) { 770 var cm v1.ConfigMap 771 772 content, err := ioutil.ReadAll(r) 773 if err != nil { 774 return cm, errors.Wrapf(err, "unable to read ConfigMap YAML content") 775 } 776 777 if err := yaml.Unmarshal(content, &cm); err != nil { 778 return cm, errors.Wrapf(err, "unable to read YAML as Kube ConfigMap") 779 } 780 781 if cm.Kind != "ConfigMap" { 782 return cm, errors.Errorf("invalid YAML kind: %q. [ConfigMap] is the only supported by --configmap", cm.Kind) 783 } 784 785 return cm, nil 786 } 787 788 // splitMultiDocYAML reads multiple documents in a YAML file and 789 // returns them as a list. 790 func splitMultiDocYAML(yamlContent []byte) ([][]byte, error) { 791 var documentList [][]byte 792 793 d := yamlv2.NewDecoder(bytes.NewReader(yamlContent)) 794 for { 795 var o interface{} 796 // read individual document 797 err := d.Decode(&o) 798 if err == io.EOF { 799 break 800 } 801 if err != nil { 802 return nil, errors.Wrapf(err, "multi doc yaml could not be split") 803 } 804 805 if o != nil { 806 // back to bytes 807 document, err := yamlv2.Marshal(o) 808 if err != nil { 809 return nil, errors.Wrapf(err, "individual doc yaml could not be marshalled") 810 } 811 812 documentList = append(documentList, document) 813 } 814 } 815 816 return documentList, nil 817 } 818 819 // getKubeKind unmarshals a kube YAML document and returns its kind. 820 func getKubeKind(obj []byte) (string, error) { 821 var kubeObject v1.ObjectReference 822 823 if err := yaml.Unmarshal(obj, &kubeObject); err != nil { 824 return "", err 825 } 826 827 return kubeObject.Kind, nil 828 } 829 830 // sortKubeKinds adds the correct creation order for the kube kinds. 831 // Any pod dependency will be created first like volumes, secrets, etc. 832 func sortKubeKinds(documentList [][]byte) ([][]byte, error) { 833 var sortedDocumentList [][]byte 834 835 for _, document := range documentList { 836 kind, err := getKubeKind(document) 837 if err != nil { 838 return nil, err 839 } 840 841 switch kind { 842 case "Pod", "Deployment": 843 sortedDocumentList = append(sortedDocumentList, document) 844 default: 845 sortedDocumentList = append([][]byte{document}, sortedDocumentList...) 846 } 847 } 848 849 return sortedDocumentList, nil 850 } 851 func imageNamePrefix(imageName string) string { 852 prefix := imageName 853 s := strings.Split(prefix, ":") 854 if len(s) > 0 { 855 prefix = s[0] 856 } 857 s = strings.Split(prefix, "/") 858 if len(s) > 0 { 859 prefix = s[len(s)-1] 860 } 861 s = strings.Split(prefix, "@") 862 if len(s) > 0 { 863 prefix = s[0] 864 } 865 return prefix 866 } 867 868 func getBuildFile(imageName string, cwd string) (string, error) { 869 buildDirName := imageNamePrefix(imageName) 870 containerfilePath := filepath.Join(cwd, buildDirName, "Containerfile") 871 dockerfilePath := filepath.Join(cwd, buildDirName, "Dockerfile") 872 873 _, err := os.Stat(containerfilePath) 874 if err == nil { 875 logrus.Debugf("Building %s with %s", imageName, containerfilePath) 876 return containerfilePath, nil 877 } 878 // If the error is not because the file does not exist, take 879 // a mulligan and try Dockerfile. If that also fails, return that 880 // error 881 if err != nil && !os.IsNotExist(err) { 882 logrus.Error(err.Error()) 883 } 884 885 _, err = os.Stat(dockerfilePath) 886 if err == nil { 887 logrus.Debugf("Building %s with %s", imageName, dockerfilePath) 888 return dockerfilePath, nil 889 } 890 // Strike two 891 if os.IsNotExist(err) { 892 return "", nil 893 } 894 return "", err 895 } 896 897 func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, _ entities.PlayKubeDownOptions) (*entities.PlayKubeReport, error) { 898 var ( 899 podNames []string 900 ) 901 reports := new(entities.PlayKubeReport) 902 903 // read yaml document 904 content, err := ioutil.ReadAll(body) 905 if err != nil { 906 return nil, err 907 } 908 909 // split yaml document 910 documentList, err := splitMultiDocYAML(content) 911 if err != nil { 912 return nil, err 913 } 914 915 // sort kube kinds 916 documentList, err = sortKubeKinds(documentList) 917 if err != nil { 918 return nil, errors.Wrap(err, "unable to sort kube kinds") 919 } 920 921 for _, document := range documentList { 922 kind, err := getKubeKind(document) 923 if err != nil { 924 return nil, errors.Wrap(err, "unable to read as kube YAML") 925 } 926 927 switch kind { 928 case "Pod": 929 var podYAML v1.Pod 930 if err := yaml.Unmarshal(document, &podYAML); err != nil { 931 return nil, errors.Wrap(err, "unable to read YAML as Kube Pod") 932 } 933 podNames = append(podNames, podYAML.ObjectMeta.Name) 934 case "Deployment": 935 var deploymentYAML v1apps.Deployment 936 937 if err := yaml.Unmarshal(document, &deploymentYAML); err != nil { 938 return nil, errors.Wrap(err, "unable to read YAML as Kube Deployment") 939 } 940 var numReplicas int32 = 1 941 deploymentName := deploymentYAML.ObjectMeta.Name 942 if deploymentYAML.Spec.Replicas != nil { 943 numReplicas = *deploymentYAML.Spec.Replicas 944 } 945 for i := 0; i < int(numReplicas); i++ { 946 podName := fmt.Sprintf("%s-pod-%d", deploymentName, i) 947 podNames = append(podNames, podName) 948 } 949 default: 950 continue 951 } 952 } 953 954 // Add the reports 955 reports.StopReport, err = ic.PodStop(ctx, podNames, entities.PodStopOptions{}) 956 if err != nil { 957 return nil, err 958 } 959 960 reports.RmReport, err = ic.PodRm(ctx, podNames, entities.PodRmOptions{}) 961 if err != nil { 962 return nil, err 963 } 964 965 return reports, nil 966 }