mirror of
https://github.com/containers/podman.git
synced 2025-06-24 03:08:13 +08:00
play kube: add support for env vars defined from secrets
Add support for secretRef and secretKeyRef to allow env vars to be set from a secret. As K8S secrets are dictionaries the secret value must be a JSON dictionary compatible with the data field of a K8S secret object. The keys must consist of alphanumeric characters, '-', '_' or '.', and the values must be base64 encoded strings. Signed-off-by: Alban Bedel <albeu@free.fr>
This commit is contained in:
@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/secrets"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v3/libpod"
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
@ -135,6 +136,12 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
report entities.PlayKubeReport
|
||||
)
|
||||
|
||||
// Create the secret manager before hand
|
||||
secretsManager, err := secrets.NewManager(ic.Libpod.GetSecretsStorageDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for name collision between pod and container
|
||||
if podName == "" {
|
||||
return nil, errors.Errorf("pod does not have a name")
|
||||
@ -261,16 +268,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
}
|
||||
|
||||
specgenOpts := kube.CtrSpecGenOptions{
|
||||
Container: container,
|
||||
Image: newImage,
|
||||
Volumes: volumes,
|
||||
PodID: pod.ID(),
|
||||
PodName: podName,
|
||||
PodInfraID: podInfraID,
|
||||
ConfigMaps: configMaps,
|
||||
SeccompPaths: seccompPaths,
|
||||
RestartPolicy: ctrRestartPolicy,
|
||||
NetNSIsHost: p.NetNS.IsHost(),
|
||||
Container: container,
|
||||
Image: newImage,
|
||||
Volumes: volumes,
|
||||
PodID: pod.ID(),
|
||||
PodName: podName,
|
||||
PodInfraID: podInfraID,
|
||||
ConfigMaps: configMaps,
|
||||
SeccompPaths: seccompPaths,
|
||||
RestartPolicy: ctrRestartPolicy,
|
||||
NetNSIsHost: p.NetNS.IsHost(),
|
||||
SecretsManager: secretsManager,
|
||||
}
|
||||
specGen, err := kube.ToSpecGen(ctx, &specgenOpts)
|
||||
if err != nil {
|
||||
|
@ -2,11 +2,13 @@ package kube
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/parse"
|
||||
"github.com/containers/common/pkg/secrets"
|
||||
"github.com/containers/podman/v3/libpod/image"
|
||||
ann "github.com/containers/podman/v3/pkg/annotations"
|
||||
"github.com/containers/podman/v3/pkg/specgen"
|
||||
@ -94,6 +96,8 @@ type CtrSpecGenOptions struct {
|
||||
RestartPolicy string
|
||||
// NetNSIsHost tells the container to use the host netns
|
||||
NetNSIsHost bool
|
||||
// SecretManager to access the secrets
|
||||
SecretsManager *secrets.SecretsManager
|
||||
}
|
||||
|
||||
func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGenerator, error) {
|
||||
@ -331,7 +335,21 @@ func quantityToInt64(quantity *resource.Quantity) (int64, error) {
|
||||
return 0, errors.Errorf("Quantity cannot be represented as int64: %v", quantity)
|
||||
}
|
||||
|
||||
// envVarsFrom returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container
|
||||
// read a k8s secret in JSON format from the secret manager
|
||||
func k8sSecretFromSecretManager(name string, secretsManager *secrets.SecretsManager) (map[string][]byte, error) {
|
||||
_, jsonSecret, err := secretsManager.LookupSecretData(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var secrets map[string][]byte
|
||||
if err := json.Unmarshal(jsonSecret, &secrets); err != nil {
|
||||
return nil, errors.Errorf("Secret %v is not valid JSON: %v", name, err)
|
||||
}
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
// envVarsFrom returns all key-value pairs as env vars from a configMap or secret that matches the envFrom setting of a container
|
||||
func envVarsFrom(envFrom v1.EnvFromSource, opts *CtrSpecGenOptions) (map[string]string, error) {
|
||||
envs := map[string]string{}
|
||||
|
||||
@ -352,11 +370,23 @@ func envVarsFrom(envFrom v1.EnvFromSource, opts *CtrSpecGenOptions) (map[string]
|
||||
}
|
||||
}
|
||||
|
||||
if envFrom.SecretRef != nil {
|
||||
secRef := envFrom.SecretRef
|
||||
secret, err := k8sSecretFromSecretManager(secRef.Name, opts.SecretsManager)
|
||||
if err == nil {
|
||||
for k, v := range secret {
|
||||
envs[k] = string(v)
|
||||
}
|
||||
} else if secRef.Optional == nil || !*secRef.Optional {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return envs, nil
|
||||
}
|
||||
|
||||
// envVarValue returns the environment variable value configured within the container's env setting.
|
||||
// It gets the value from a configMap if specified, otherwise returns env.Value
|
||||
// It gets the value from a configMap or secret if specified, otherwise returns env.Value
|
||||
func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) {
|
||||
if env.ValueFrom != nil {
|
||||
if env.ValueFrom.ConfigMapKeyRef != nil {
|
||||
@ -377,6 +407,21 @@ func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) {
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if env.ValueFrom.SecretKeyRef != nil {
|
||||
secKeyRef := env.ValueFrom.SecretKeyRef
|
||||
secret, err := k8sSecretFromSecretManager(secKeyRef.Name, opts.SecretsManager)
|
||||
if err == nil {
|
||||
if val, ok := secret[secKeyRef.Key]; ok {
|
||||
return string(val), nil
|
||||
}
|
||||
err = errors.Errorf("Secret %v has not %v key", secKeyRef.Name, secKeyRef.Key)
|
||||
}
|
||||
if secKeyRef.Optional == nil || !*secKeyRef.Optional {
|
||||
return "", errors.Errorf("Cannot set env %v: %v", env.Name, err)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
return env.Value, nil
|
||||
|
@ -1,14 +1,43 @@
|
||||
package kube
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/common/pkg/secrets"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func createSecrets(t *testing.T, d string) *secrets.SecretsManager {
|
||||
secretsManager, err := secrets.NewManager(d)
|
||||
assert.NoError(t, err)
|
||||
|
||||
driver := "file"
|
||||
driverOpts := map[string]string{
|
||||
"path": d,
|
||||
}
|
||||
|
||||
for _, s := range k8sSecrets {
|
||||
data, err := json.Marshal(s.Data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = secretsManager.Store(s.ObjectMeta.Name, data, driver, driverOpts)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
return secretsManager
|
||||
}
|
||||
|
||||
func TestEnvVarsFrom(t *testing.T) {
|
||||
d, err := ioutil.TempDir("", "secrets")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(d)
|
||||
secretsManager := createSecrets(t, d)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
envFrom v1.EnvFromSource
|
||||
@ -95,6 +124,54 @@ func TestEnvVarsFrom(t *testing.T) {
|
||||
true,
|
||||
map[string]string{},
|
||||
},
|
||||
{
|
||||
"SecretExists",
|
||||
v1.EnvFromSource{
|
||||
SecretRef: &v1.SecretEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
true,
|
||||
map[string]string{
|
||||
"myvar": "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
"SecretDoesNotExist",
|
||||
v1.EnvFromSource{
|
||||
SecretRef: &v1.SecretEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"OptionalSecretDoesNotExist",
|
||||
v1.EnvFromSource{
|
||||
SecretRef: &v1.SecretEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
true,
|
||||
map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@ -108,6 +185,11 @@ func TestEnvVarsFrom(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarValue(t *testing.T) {
|
||||
d, err := ioutil.TempDir("", "secrets")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(d)
|
||||
secretsManager := createSecrets(t, d)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
envVar v1.EnvVar
|
||||
@ -251,6 +333,103 @@ func TestEnvVarValue(t *testing.T) {
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"SecretExists",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "foo",
|
||||
},
|
||||
Key: "myvar",
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
true,
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
"ContainerKeyDoesNotExistInSecret",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "foo",
|
||||
},
|
||||
Key: "doesnotexist",
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"OptionalContainerKeyDoesNotExistInSecret",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "foo",
|
||||
},
|
||||
Key: "doesnotexist",
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"SecretDoesNotExist",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
Key: "myvar",
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"OptionalSecretDoesNotExist",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
Key: "myvar",
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
SecretsManager: secretsManager,
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@ -289,3 +468,28 @@ var configMapList = []v1.ConfigMap{
|
||||
}
|
||||
|
||||
var optional = true
|
||||
|
||||
var k8sSecrets = []v1.Secret{
|
||||
{
|
||||
TypeMeta: v12.TypeMeta{
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: v12.ObjectMeta{
|
||||
Name: "bar",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"myvar": []byte("bar"),
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: v12.TypeMeta{
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: v12.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"myvar": []byte("foo"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -142,7 +142,15 @@ spec:
|
||||
name: {{ .RefName }}
|
||||
key: {{ .RefKey }}
|
||||
optional: {{ .Optional }}
|
||||
{{ else }}
|
||||
{{ end }}
|
||||
{{ if (eq .ValueFrom "secret") }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .RefName }}
|
||||
key: {{ .RefKey }}
|
||||
optional: {{ .Optional }}
|
||||
{{ end }}
|
||||
{{ if (eq .ValueFrom "") }}
|
||||
value: {{ .Value }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@ -154,6 +162,11 @@ spec:
|
||||
name: {{ .Name }}
|
||||
optional: {{ .Optional }}
|
||||
{{ end }}
|
||||
{{ if (eq .From "secret") }}
|
||||
- secretRef:
|
||||
name: {{ .Name }}
|
||||
optional: {{ .Optional }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
image: {{ .Image }}
|
||||
@ -342,6 +355,8 @@ var (
|
||||
seccompPwdEPERM = []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`)
|
||||
// CPU Period in ms
|
||||
defaultCPUPeriod = 100
|
||||
// Default secret in JSON. Note that the values ("foo" and "bar") are base64 encoded.
|
||||
defaultSecret = []byte(`{"FOO":"Zm9v","BAR":"YmFy"}`)
|
||||
)
|
||||
|
||||
func writeYaml(content string, fileName string) error {
|
||||
@ -409,6 +424,16 @@ func generateMultiDocKubeYaml(kubeObjects []string, pathname string) error {
|
||||
return writeYaml(multiKube, pathname)
|
||||
}
|
||||
|
||||
func createSecret(podmanTest *PodmanTestIntegration, name string, value []byte) {
|
||||
secretFilePath := filepath.Join(podmanTest.TempDir, "secret")
|
||||
err := ioutil.WriteFile(secretFilePath, value, 0755)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
secret := podmanTest.Podman([]string{"secret", "create", name, secretFilePath})
|
||||
secret.WaitWithDefaultTimeout()
|
||||
Expect(secret.ExitCode()).To(Equal(0))
|
||||
}
|
||||
|
||||
// ConfigMap describes the options a kube yaml can be configured at configmap level
|
||||
type ConfigMap struct {
|
||||
Name string
|
||||
@ -1186,6 +1211,111 @@ var _ = Describe("Podman play kube", func() {
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman play kube test env value from secret", func() {
|
||||
createSecret(podmanTest, "foo", defaultSecret)
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "secret", "foo", "FOO", false))))
|
||||
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.Env }}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO=foo`))
|
||||
})
|
||||
|
||||
It("podman play kube test required env value from missing secret", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "secret", "foo", "FOO", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman play kube test required env value from secret with missing key", func() {
|
||||
createSecret(podmanTest, "foo", defaultSecret)
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "secret", "foo", "MISSING", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman play kube test optional env value from missing secret", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "secret", "foo", "FOO", true))))
|
||||
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", "'{{ range .Config.Env }}[{{ . }}]{{end}}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`[FOO=]`))
|
||||
})
|
||||
|
||||
It("podman play kube test optional env value from secret with missing key", func() {
|
||||
createSecret(podmanTest, "foo", defaultSecret)
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "secret", "foo", "MISSING", true))))
|
||||
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", "'{{ range .Config.Env }}[{{ . }}]{{end}}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`[FOO=]`))
|
||||
})
|
||||
|
||||
It("podman play kube test get all key-value pairs from secret as envs", func() {
|
||||
createSecret(podmanTest, "foo", defaultSecret)
|
||||
pod := getPod(withCtr(getCtr(withEnvFrom("foo", "secret", false))))
|
||||
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.Env }}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO=foo`))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`BAR=bar`))
|
||||
})
|
||||
|
||||
It("podman play kube test get all key-value pairs from required secret as envs", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnvFrom("missing_secret", "secret", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman play kube test get all key-value pairs from optional secret as envs", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnvFrom("missing_secret", "secret", true))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman play kube test hostname", func() {
|
||||
pod := getPod()
|
||||
err := generateKubeYaml("pod", pod, kubeYaml)
|
||||
|
Reference in New Issue
Block a user