Skip to content

Commit

Permalink
get podman play kube in line with k8s specs and pre-v2.2 behaviour
Browse files Browse the repository at this point in the history
More specifically,
- use both `ENTRYPOINT` and `CMD` from image when neither `command` nor
  `args` have been supplied the pod spec,
- use the `command` and only the `command` when `command` has been
  passed in the pod spec, ignoring both `ENTRYPOINT` and `CMD` from the
  image,
- run the `ENTRYPOINT` from the image with `args` from the pod spec when
  only `args` have been passed and
- override both when both `command` and `args` are present in the spec.

Necessarily,
- adapt pod tests to test the k8s spec documented at the k8s web-docs,
- remove assertions already tested at pod level from deployments and
- shift focus to replica count when testing deployments.

In passing,
- remove unnecessary `s.Command` initialization.

Signed-off-by: Christopher J. Ruwe <[email protected]>
  • Loading branch information
cruwe committed Dec 22, 2020
1 parent c38ae47 commit bcda8e9
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 66 deletions.
33 changes: 26 additions & 7 deletions pkg/specgen/generate/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,20 @@ func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newI

// TODO: We dont understand why specgen does not take of this, but
// integration tests clearly pointed out that it was required.
s.Command = []string{}
// s.Command = []string{}
imageData, err := newImage.Inspect(ctx)
if err != nil {
return nil, err
}
s.WorkDir = "/"

// TODO: determine if `newImage.Inspect(ctx)` can ever return without `err`
// TODO: and have `imageData` or `imageData.Config` equal `nil`
// TODO: if it can, determine how to handle that error generally and globally
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)
Expand All @@ -126,13 +129,29 @@ func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newI
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...)

if len(containerYAML.Command) == 0 && len(containerYAML.Args) == 0 {
s.Entrypoint = imageData.Config.Entrypoint
s.Command = imageData.Config.Cmd
}

if len(containerYAML.Command) != 0 && len(containerYAML.Args) == 0 {
s.Entrypoint = containerYAML.Command
s.Command = nil
}

if len(containerYAML.Command) == 0 && len(containerYAML.Args) != 0 {
s.Entrypoint = imageData.Config.Entrypoint
s.Command = containerYAML.Args
}

if len(containerYAML.Command) != 0 && len(containerYAML.Args) != 0 {
s.Entrypoint = containerYAML.Command
s.Command = containerYAML.Args
}

// FIXME,
// we are currently ignoring imageData.Config.ExposedPorts
if containerYAML.WorkingDir != "" {
Expand Down
235 changes: 176 additions & 59 deletions test/e2e/play_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
Expand Down Expand Up @@ -490,6 +491,12 @@ func getDeployment(options ...deploymentOption) *Deployment {

type deploymentOption func(*Deployment)

func withDeploymentName(name string) deploymentOption {
return func(deployment *Deployment) {
deployment.Name = name
}
}

func withDeploymentLabel(k, v string) deploymentOption {
return func(deployment *Deployment) {
deployment.Labels[k] = v
Expand Down Expand Up @@ -752,6 +759,18 @@ type EnvFrom struct {
From string
}

type ContainerConfig struct {
Cmd []string `json:"Cmd,omitempty"`
Entrypoint string `json:"Entrypoint"`
// we do not yer care for the rest
}

type ContainerState struct {
Args []string `json:"Args,omitempty"`
Config ContainerConfig `json:"Config"`
// we do not yer care for the rest
}

func milliCPUToQuota(milliCPU string) int {
milli, _ := strconv.Atoi(strings.Trim(milliCPU, "m"))
return milli * defaultCPUPeriod
Expand Down Expand Up @@ -803,76 +822,184 @@ var _ = Describe("Podman play kube", func() {

})

It("podman play kube test correct command", func() {
pod := getPod()
// NOTICE: what is called k8s spec in the following section is described at the k8s docs at
// https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#notes

It("podman play kube - k8s spec, 1st item, when you _do not_ supply command _or_ args", func() {
// given an image with `ENTRYPOINT` and `CMD` defined and a pod-spec _without any_,
// when the container is instantiated from the image, it should use `Entrypoint` and `Cmd`
// from the image and `Args` should equal `Cmd`

pod := getPod(withCtr(getCtr(withImage(redis), withCmd(nil), withArg(nil))))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Cmd }}'"})
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod)})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
// Use the defined command to override the image's command
correctCmd := "[" + strings.Join(defaultCtrCmd, " ") + " " + strings.Join(defaultCtrArg, " ")
Expect(inspect.OutputToString()).To(ContainSubstring(correctCmd))

var containerState []ContainerState
err = json.Unmarshal([]byte(inspect.OutputToString()), &containerState)
Expect(err).To(BeNil())

// these we know from having had a look at the redis image
imgEntrypoint := `docker-entrypoint.sh`
imgCmd := []string{`redis-server`}
derivedArgs := imgCmd

containerEntrypoint := containerState[0].Config.Entrypoint
containerCmd := containerState[0].Config.Cmd
containerArgs := containerState[0].Args

Expect(containerEntrypoint).To(Equal(imgEntrypoint))
Expect(containerCmd).To(Equal(imgCmd))
Expect(containerArgs).To(Equal(derivedArgs))
})

It("podman play kube test correct command with only set command in yaml file", func() {
pod := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg(nil))))
It("podman play kube - k8s spec, 2nd item, when you supply command _but no_ args", func() {
// given an image with `ENTRYPOINT` and `CMD` defined and a pod-spec with the `command`
// defined, when the container is instantiated from the image, it should replace any
// `Entrypoint` and `Cmd` and start with `command` _without_ further `Args`.

overrideCmd := []string{"echo", "hello"}
pod := getPod(withCtr(getCtr(withImage(redis), withCmd(overrideCmd), withArg(nil))))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Cmd }}'"})
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod)})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
// Use the defined command to override the image's command, and don't set the args
// so the full command in result should not contains the image's command
Expect(inspect.OutputToString()).To(ContainSubstring(`[echo hello]`))

var containerState []ContainerState
err = json.Unmarshal([]byte(inspect.OutputToString()), &containerState)
Expect(err).To(BeNil())

// these we know from having had a look at the redis image
imgEntrypoint := `docker-entrypoint.sh`
imgCmd := []string{`redis-server`}

containerEntrypoint := containerState[0].Config.Entrypoint
containerCmd := containerState[0].Config.Cmd
containerArgs := containerState[0].Args

Expect(containerEntrypoint).NotTo(Equal(imgEntrypoint))
Expect(containerCmd).NotTo(Equal(imgCmd))

Expect(containerEntrypoint).To(Equal(strings.Join(overrideCmd, " ")))
Expect(len(containerCmd)).To(Equal(0))
Expect(containerArgs).To(Equal(overrideCmd[1:]))
})

It("podman play kube test correct command with only set args in yaml file", func() {
pod := getPod(withCtr(getCtr(withImage(redis), withCmd(nil), withArg([]string{"echo", "hello"}))))
It("podman play kube - k8s spec, 3rd item, when you supply _only_ args", func() {
// given an image with `ENTRYPOINT` and `CMD` defined and a pod-spec with the `command`
// _not_ defined, but `args` given, when the container is instantiated from the image,
// the `Entrypoint` should be taken from the image and `Cmd`/`Args` being replaced by `args`.

args := []string{"echo", "hello"}
pod := getPod(withCtr(getCtr(withImage(redis), withCmd(nil), withArg(args))))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Cmd }}'"})
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod)})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
// this image's ENTRYPOINT is called `docker-entrypoint.sh`
// so result should be `docker-entrypoint.sh + withArg(...)`
Expect(inspect.OutputToString()).To(ContainSubstring(`[docker-entrypoint.sh echo hello]`))

var containerState []ContainerState
err = json.Unmarshal([]byte(inspect.OutputToString()), &containerState)
Expect(err).To(BeNil())

// these we know from having had a look at the redis image
imgEntrypoint := `docker-entrypoint.sh`
imgCmd := []string{`redis-server`}

containerEntrypoint := containerState[0].Config.Entrypoint
containerCmd := containerState[0].Config.Cmd
containerArgs := containerState[0].Args

Expect(containerEntrypoint).To(Equal(imgEntrypoint))
Expect(containerCmd).NotTo(Equal(imgCmd))
Expect(containerCmd).To(Equal(args))
Expect(containerArgs).To(Equal(args))

})

It("podman play kube test correct output", func() {
p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"}))))
It("podman play kube - k8s spec, 4th item, when you supply command _and_ args", func() {
// given any image, irrespective of `ENTRYPOINT` and `CMD` being defined in the image or not,
// both `command` and `args` from the pod spec should _replace_ the images' `ENTRYPOINT` and `CMD`
// and the container's `Args` should equal `args`.
pod := getPod(withCtr(getCtr(withImage(redis), withCmd(defaultCtrCmd), withArg(defaultCtrArg))))
//
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod)})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))

var containerState []ContainerState
err = json.Unmarshal([]byte(inspect.OutputToString()), &containerState)
Expect(err).To(BeNil())

// these we know from having had a look at the redis image
imgEntrypoint := `docker-entrypoint.sh`
imgCmd := []string{`redis-server`}

err := generateKubeYaml("pod", p, kubeYaml)
containerEntrypoint := containerState[0].Config.Entrypoint
containerCmd := containerState[0].Config.Cmd
containerArgs := containerState[0].Args

Expect(containerEntrypoint).NotTo(Equal(imgEntrypoint))
Expect(containerCmd).NotTo(Equal(imgCmd))

Expect(containerEntrypoint).To(Equal(strings.Join(defaultCtrCmd, " ")))
Expect(containerCmd).To(Equal(defaultCtrArg))
Expect(containerArgs).To(Equal(defaultCtrArg))
})

It("podman play kube - test output being logged", func() {
pod := getPod(withCtr(getCtr(withCmd([]string{"echo"}), withArg([]string{"hello", "world"}))))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(p)})
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
})

inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(p), "--format", "'{{ .Config.Cmd }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`[echo hello world]`))
It("podman play kube - test output being logged with strange, but legal `command`/`args` invocation", func() {
pod := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"}))))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
})

It("podman play kube test restartPolicy", func() {
Expand Down Expand Up @@ -1226,43 +1353,33 @@ spec:
Expect(ctr[0].Config.StopSignal).To(Equal(uint(51)))
})

// Deployment related tests
It("podman play kube deployment 1 replica test correct command", func() {
deployment := getDeployment()
err := generateKubeYaml("deployment", deployment, kubeYaml)
Expect(err).To(BeNil())
It("podman play kube deployment - test deployments' replica count", func() {
numReplicas := []int{1, 2, 5}
ctrOptions := getCtr(withCmd([]string{"echo"}), withArg([]string{"hello", "world"}))

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))
for _, replicaCount := range numReplicas {

podNames := getPodNamesInDeployment(deployment)
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[0]), "--format", "'{{ .Config.Cmd }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
// yaml's command shuold override the image's Entrypoint
correctCmd := "[" + strings.Join(defaultCtrCmd, " ") + " " + strings.Join(defaultCtrArg, " ")
Expect(inspect.OutputToString()).To(ContainSubstring(correctCmd))
})

It("podman play kube deployment more than 1 replica test correct command", func() {
var i, numReplicas int32
numReplicas = 5
deployment := getDeployment(withReplicas(numReplicas))
err := generateKubeYaml("deployment", deployment, kubeYaml)
Expect(err).To(BeNil())
pod := getPod(withCtr(ctrOptions))
deployment := getDeployment(withReplicas(int32(replicaCount)), withPod(pod), withDeploymentName(fmt.Sprintf("with-%v-repl", replicaCount)))
err := generateKubeYaml("deployment", deployment, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))

podNames := getPodNamesInDeployment(deployment)
correctCmd := "[" + strings.Join(defaultCtrCmd, " ") + " " + strings.Join(defaultCtrArg, " ")
for i = 0; i < numReplicas; i++ {
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[i]), "--format", "'{{ .Config.Cmd }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
Expect(inspect.OutputToString()).To(ContainSubstring(correctCmd))
podNames := getPodNamesInDeployment(deployment)
Expect(len(podNames)).To(Equal(replicaCount))

for idx, podName := range podNames {
println(getCtrNameInPod(&podName))
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[idx])})
println(inspect)
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(&podNames[idx])})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
}
}
})

Expand Down

0 comments on commit bcda8e9

Please sign in to comment.