From f5e4ffb5e46be03a81b4425d3fe080543fca7035 Mon Sep 17 00:00:00 2001
From: Urvashi Mohnani <umohnani@redhat.com>
Date: Wed, 25 Aug 2021 12:37:51 -0400
Subject: Add init containers to generate and play kube

Kubernetes has a concept of init containers that run and exit before
the regular containers in a pod are started. We added init containers
to podman pods as well. This patch adds support for generating init
containers in the kube yaml when a pod we are converting had init
containers. When playing a kube yaml, it detects an init container
and creates such a container in podman accordingly.
Note, only init containers created with the init type set to "always"
will be generated as the "once" option deletes the init container after
it has run and exited. Play kube will always creates init containers
with the "always" init container type.

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>
---
 test/e2e/generate_kube_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++
 test/e2e/play_kube_test.go     | 55 ++++++++++++++++++++++++++++++++
 2 files changed, 127 insertions(+)

(limited to 'test/e2e')

diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
index 8996bf6a0..bf89a0708 100644
--- a/test/e2e/generate_kube_test.go
+++ b/test/e2e/generate_kube_test.go
@@ -174,6 +174,78 @@ var _ = Describe("Podman generate kube", func() {
 		Expect(string(kube.Out.Contents())).To(ContainSubstring(`name: pod2`))
 	})
 
+	It("podman generate kube on pod with init containers", func() {
+		session := podmanTest.Podman([]string{"create", "--pod", "new:toppod", "--init-ctr", "always", ALPINE, "echo", "hello"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		session = podmanTest.Podman([]string{"create", "--pod", "toppod", "--init-ctr", "always", ALPINE, "echo", "world"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		session = podmanTest.Podman([]string{"create", "--pod", "toppod", ALPINE, "top"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		kube := podmanTest.Podman([]string{"generate", "kube", "toppod"})
+		kube.WaitWithDefaultTimeout()
+		Expect(kube).Should(Exit(0))
+
+		pod := new(v1.Pod)
+		err := yaml.Unmarshal(kube.Out.Contents(), pod)
+		Expect(err).To(BeNil())
+		Expect(pod.Spec.HostNetwork).To(Equal(false))
+
+		numContainers := len(pod.Spec.Containers) + len(pod.Spec.InitContainers)
+		Expect(numContainers).To(Equal(3))
+
+		// Init container should be in the generated kube yaml if created with "once" type and the pod has not been started
+		session = podmanTest.Podman([]string{"create", "--pod", "new:toppod-2", "--init-ctr", "once", ALPINE, "echo", "using once type"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		session = podmanTest.Podman([]string{"create", "--pod", "toppod-2", ALPINE, "top"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		kube = podmanTest.Podman([]string{"generate", "kube", "toppod-2"})
+		kube.WaitWithDefaultTimeout()
+		Expect(kube).Should(Exit(0))
+
+		pod = new(v1.Pod)
+		err = yaml.Unmarshal(kube.Out.Contents(), pod)
+		Expect(err).To(BeNil())
+		Expect(pod.Spec.HostNetwork).To(Equal(false))
+
+		numContainers = len(pod.Spec.Containers) + len(pod.Spec.InitContainers)
+		Expect(numContainers).To(Equal(2))
+
+		// Init container should not be in the generated kube yaml if created with "once" type and the pod has been started
+		session = podmanTest.Podman([]string{"create", "--pod", "new:toppod-3", "--init-ctr", "once", ALPINE, "echo", "using once type"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		session = podmanTest.Podman([]string{"create", "--pod", "toppod-3", ALPINE, "top"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		session = podmanTest.Podman([]string{"pod", "start", "toppod-3"})
+		session.WaitWithDefaultTimeout()
+		Expect(session).Should(Exit(0))
+
+		kube = podmanTest.Podman([]string{"generate", "kube", "toppod-3"})
+		kube.WaitWithDefaultTimeout()
+		Expect(kube).Should(Exit(0))
+
+		pod = new(v1.Pod)
+		err = yaml.Unmarshal(kube.Out.Contents(), pod)
+		Expect(err).To(BeNil())
+		Expect(pod.Spec.HostNetwork).To(Equal(false))
+
+		numContainers = len(pod.Spec.Containers) + len(pod.Spec.InitContainers)
+		Expect(numContainers).To(Equal(1))
+	})
+
 	It("podman generate kube on pod with host network", func() {
 		podSession := podmanTest.Podman([]string{"pod", "create", "--name", "testHostNetwork", "--network", "host"})
 		podSession.WaitWithDefaultTimeout()
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index fa30f068c..15bd10616 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/containers/common/pkg/config"
+	"github.com/containers/podman/v3/libpod/define"
 	"github.com/containers/podman/v3/pkg/util"
 	. "github.com/containers/podman/v3/test/utils"
 	"github.com/containers/storage/pkg/stringid"
@@ -262,6 +263,17 @@ spec:
     - {{ . }}
   {{ end }}
     ip: {{ .IP }}
+{{ end }}
+  initContainers:
+{{ with .InitCtrs }}
+  {{ range . }}
+  - command:
+    {{ range .Cmd }}
+    - {{.}}
+    {{ end }}
+    image: {{ .Image }}
+    name: {{ .Name }}
+  {{ end }}
 {{ end }}
   containers:
 {{ with .Ctrs }}
@@ -665,6 +677,7 @@ type Pod struct {
 	HostNetwork   bool
 	HostAliases   []HostAlias
 	Ctrs          []*Ctr
+	InitCtrs      []*Ctr
 	Volumes       []*Volume
 	Labels        map[string]string
 	Annotations   map[string]string
@@ -686,6 +699,7 @@ func getPod(options ...podOption) *Pod {
 		HostNetwork:   false,
 		HostAliases:   nil,
 		Ctrs:          make([]*Ctr, 0),
+		InitCtrs:      make([]*Ctr, 0),
 		Volumes:       make([]*Volume, 0),
 		Labels:        make(map[string]string),
 		Annotations:   make(map[string]string),
@@ -728,6 +742,12 @@ func withCtr(c *Ctr) podOption {
 	}
 }
 
+func withPodInitCtr(ic *Ctr) podOption {
+	return func(pod *Pod) {
+		pod.InitCtrs = append(pod.InitCtrs, ic)
+	}
+}
+
 func withRestartPolicy(policy string) podOption {
 	return func(pod *Pod) {
 		pod.RestartPolicy = policy
@@ -847,6 +867,7 @@ type Ctr struct {
 	VolumeReadOnly  bool
 	Env             []Env
 	EnvFrom         []EnvFrom
+	InitCtrType     string
 }
 
 // getCtr takes a list of ctrOptions and returns a Ctr with sane defaults
@@ -870,6 +891,7 @@ func getCtr(options ...ctrOption) *Ctr {
 		VolumeReadOnly:  false,
 		Env:             []Env{},
 		EnvFrom:         []EnvFrom{},
+		InitCtrType:     "",
 	}
 	for _, option := range options {
 		option(&c)
@@ -885,6 +907,12 @@ func withName(name string) ctrOption {
 	}
 }
 
+func withInitCtr() ctrOption {
+	return func(c *Ctr) {
+		c.InitCtrType = define.AlwaysInitContainer
+	}
+}
+
 func withCmd(cmd []string) ctrOption {
 	return func(c *Ctr) {
 		c.Cmd = cmd
@@ -1294,6 +1322,33 @@ var _ = Describe("Podman play kube", func() {
 		Expect(inspect.OutputToString()).To(ContainSubstring(`[]`))
 	})
 
+	// If you have an init container in the pod yaml, podman should create and run the init container with play kube
+	It("podman play kube test with init containers", func() {
+		pod := getPod(withPodInitCtr(getCtr(withImage(ALPINE), withCmd([]string{"echo", "hello"}), withInitCtr(), withName("init-test"))), withCtr(getCtr(withImage(ALPINE), withCmd([]string{"top"}))))
+		err := generateKubeYaml("pod", pod, kubeYaml)
+		Expect(err).To(BeNil())
+
+		kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+		kube.WaitWithDefaultTimeout()
+		Expect(kube).Should(Exit(0))
+
+		// Expect the number of containers created to be 3, one init, infra, and regular container
+		numOfCtrs := podmanTest.NumberOfContainers()
+		Expect(numOfCtrs).To(Equal(3))
+
+		// Init container should have exited after running
+		inspect := podmanTest.Podman([]string{"inspect", "--format", "{{.State.Status}}", "testPod-init-test"})
+		inspect.WaitWithDefaultTimeout()
+		Expect(inspect).Should(Exit(0))
+		Expect(inspect.OutputToString()).To(ContainSubstring("exited"))
+
+		// Regular container should be in running state
+		inspect = podmanTest.Podman([]string{"inspect", "--format", "{{.State.Status}}", "testPod-" + defaultCtrName})
+		inspect.WaitWithDefaultTimeout()
+		Expect(inspect).Should(Exit(0))
+		Expect(inspect.OutputToString()).To(ContainSubstring("running"))
+	})
+
 	// If you supply only args for a Container, the default Entrypoint defined in the Docker image is run with the args that you supplied.
 	It("podman play kube test correct command with only set args in yaml file", func() {
 		pod := getPod(withCtr(getCtr(withImage(registry), withCmd(nil), withArg([]string{"echo", "hello"}))))
-- 
cgit v1.2.3-54-g00ecf