From 55dfa7fad205d811b4ade9a2424c49f7e2f7588f Mon Sep 17 00:00:00 2001
From: Jordan Christiansen <xordspar0@gmail.com>
Date: Tue, 15 Sep 2020 21:09:08 -0500
Subject: [PATCH] Add labels to a pod created via play kube

When using `podman play kube` with a YAML file that has pod labels,
apply those labels to the pods that podman makes.

For example, this Deployment spec has labels on a pod:

	apiVersion: apps/v1
	kind: Deployment
	metadata:
	  name: myapp
	  labels:
	    app: myapp
	spec:
	  selector:
	    matchLabels:
	      app: myapp
	  template:
	    metadata:
	      labels:
		app: myapp
	    spec:
	      containers:
	      - name: web
		image: nginx
		ports:
		- containerPort: 80

The pods that podman creates will have the label "app" set to "myapp" so
that these pods can be found with `podman pods ps --filter label=app`.

Signed-off-by: Jordan Christiansen <xordspar0@gmail.com>
---
 pkg/domain/infra/abi/play.go |  5 +++
 test/e2e/play_kube_test.go   | 74 ++++++++++++++++++++++++++++++++++--
 2 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 6dfb52c63b..659cc799c7 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -132,6 +132,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
 		libpod.WithInfraContainer(),
 		libpod.WithPodName(podName),
 	}
+
+	if podYAML.ObjectMeta.Labels != nil {
+		podOptions = append(podOptions, libpod.WithPodLabels(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 {
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 5e01971cb5..87de92357a 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -30,9 +30,14 @@ apiVersion: v1
 kind: Pod
 metadata:
   creationTimestamp: "2019-07-17T14:44:08Z"
+  name: {{ .Name }}
   labels:
     app: {{ .Name }}
-  name: {{ .Name }}
+{{ with .Labels }}
+  {{ range $key, $value := . }}
+    {{ $key }}: {{ $value }}
+  {{ end }}
+{{ end }}
 {{ with .Annotations }}
   annotations:
   {{ range $key, $value := . }}
@@ -125,9 +130,14 @@ apiVersion: v1
 kind: Deployment
 metadata:
   creationTimestamp: "2019-07-17T14:44:08Z"
+  name: {{ .Name }}
   labels:
     app: {{ .Name }}
-  name: {{ .Name }}
+{{ with .Labels }}
+  {{ range $key, $value := . }}
+    {{ $key }}: {{ $value }}
+  {{ end }}
+{{ end }}
 {{ with .Annotations }}
   annotations:
   {{ range $key, $value := . }}
@@ -145,6 +155,9 @@ spec:
     metadata:
       labels:
         app: {{ .Name }}
+        {{- with .Labels }}{{ range $key, $value := . }}
+        {{ $key }}: {{ $value }}
+        {{- end }}{{ end }}
       {{ with .Annotations }}
         annotations:
         {{ range $key, $value := . }}
@@ -266,6 +279,7 @@ type Pod struct {
 	HostAliases []HostAlias
 	Ctrs        []*Ctr
 	Volumes     []*Volume
+	Labels      map[string]string
 	Annotations map[string]string
 }
 
@@ -278,7 +292,15 @@ type HostAlias struct {
 // and the configured options
 // if no containers are added, it will add the default container
 func getPod(options ...podOption) *Pod {
-	p := Pod{defaultPodName, "", nil, make([]*Ctr, 0), make([]*Volume, 0), make(map[string]string)}
+	p := Pod{
+		Name:        defaultPodName,
+		Hostname:    "",
+		HostAliases: nil,
+		Ctrs:        make([]*Ctr, 0),
+		Volumes:     make([]*Volume, 0),
+		Labels:      make(map[string]string),
+		Annotations: make(map[string]string),
+	}
 	for _, option := range options {
 		option(&p)
 	}
@@ -311,6 +333,12 @@ func withCtr(c *Ctr) podOption {
 	}
 }
 
+func withLabel(k, v string) podOption {
+	return func(pod *Pod) {
+		pod.Labels[k] = v
+	}
+}
+
 func withAnnotation(k, v string) podOption {
 	return func(pod *Pod) {
 		pod.Annotations[k] = v
@@ -327,12 +355,19 @@ func withVolume(v *Volume) podOption {
 type Deployment struct {
 	Name        string
 	Replicas    int32
+	Labels      map[string]string
 	Annotations map[string]string
 	PodTemplate *Pod
 }
 
 func getDeployment(options ...deploymentOption) *Deployment {
-	d := Deployment{defaultDeploymentName, 1, make(map[string]string), getPod()}
+	d := Deployment{
+		Name:        defaultDeploymentName,
+		Replicas:    1,
+		Labels:      make(map[string]string),
+		Annotations: make(map[string]string),
+		PodTemplate: getPod(),
+	}
 	for _, option := range options {
 		option(&d)
 	}
@@ -342,6 +377,12 @@ func getDeployment(options ...deploymentOption) *Deployment {
 
 type deploymentOption func(*Deployment)
 
+func withDeploymentLabel(k, v string) deploymentOption {
+	return func(deployment *Deployment) {
+		deployment.Labels[k] = v
+	}
+}
+
 func withDeploymentAnnotation(k, v string) deploymentOption {
 	return func(deployment *Deployment) {
 		deployment.Annotations[k] = v
@@ -1077,4 +1118,29 @@ spec:
 		correct := fmt.Sprintf("%s:%s:%s", hostPathLocation, hostPathLocation, "ro")
 		Expect(inspect.OutputToString()).To(ContainSubstring(correct))
 	})
+
+	It("podman play kube applies labels to pods", func() {
+		SkipIfRemote()
+		var numReplicas int32 = 5
+		expectedLabelKey := "key1"
+		expectedLabelValue := "value1"
+		deployment := getDeployment(
+			withReplicas(numReplicas),
+			withPod(getPod(withLabel(expectedLabelKey, expectedLabelValue))),
+		)
+		err := generateDeploymentKubeYaml(deployment, kubeYaml)
+		Expect(err).To(BeNil())
+
+		kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+		kube.WaitWithDefaultTimeout()
+		Expect(kube.ExitCode()).To(Equal(0))
+
+		correctLabels := expectedLabelKey + ":" + expectedLabelValue
+		for _, pod := range getPodNamesInDeployment(deployment) {
+			inspect := podmanTest.Podman([]string{"pod", "inspect", pod.Name, "--format", "'{{ .Labels }}'"})
+			inspect.WaitWithDefaultTimeout()
+			Expect(inspect.ExitCode()).To(Equal(0))
+			Expect(inspect.OutputToString()).To(ContainSubstring(correctLabels))
+		}
+	})
 })