Add DefaultMode to kube play

Add support for DefaultMode for configMaps and secrets.
This allows users to set the file permissions for files
created with their volume mounts. Adheres to k8s defaults.

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>
This commit is contained in:
Urvashi Mohnani 2023-09-28 15:22:33 -04:00
parent 87dd939334
commit 17cebb3ff8
3 changed files with 252 additions and 27 deletions

View File

@ -628,6 +628,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
if err != nil || mountPoint == "" {
return nil, nil, fmt.Errorf("unable to get mountpoint of volume %q: %w", vol.Name(), err)
}
defaultMode := v.DefaultMode
// Create files and add data to the volume mountpoint based on the Items in the volume
for k, v := range v.Items {
dataPath := filepath.Join(mountPoint, k)
@ -640,6 +641,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
if err != nil {
return nil, nil, err
}
// Set file permissions
if err := os.Chmod(f.Name(), os.FileMode(defaultMode)); err != nil {
return nil, nil, err
}
}
}
}

View File

@ -51,6 +51,9 @@ type KubeVolume struct {
// If the volume is optional, we can move on if it is not found
// Only used when there are volumes in a yaml that refer to a configmap
Optional bool
// DefaultMode sets the permissions on files created for the volume
// This is optional and defaults to 0644
DefaultMode int32
}
// Create a KubeVolume from an HostPathVolumeSource
@ -135,9 +138,18 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource, mountLabel string) (*
// VolumeFromSecret creates a new kube volume from a kube secret.
func VolumeFromSecret(secretSource *v1.SecretVolumeSource, secretsManager *secrets.SecretsManager) (*KubeVolume, error) {
kv := &KubeVolume{
Type: KubeVolumeTypeSecret,
Source: secretSource.SecretName,
Items: map[string][]byte{},
Type: KubeVolumeTypeSecret,
Source: secretSource.SecretName,
Items: map[string][]byte{},
DefaultMode: v1.SecretVolumeSourceDefaultMode,
}
// Set the defaultMode if set in the kube yaml
validMode, err := isValidDefaultMode(secretSource.DefaultMode)
if err != nil {
return nil, fmt.Errorf("invalid DefaultMode for secret %q: %w", secretSource.SecretName, err)
}
if validMode {
kv.DefaultMode = *secretSource.DefaultMode
}
// returns a byte array of a kube secret data, meaning this needs to go into a string map
@ -191,8 +203,9 @@ func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource
func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) {
var configMap *v1.ConfigMap
kv := &KubeVolume{
Type: KubeVolumeTypeConfigMap,
Items: map[string][]byte{},
Type: KubeVolumeTypeConfigMap,
Items: map[string][]byte{},
DefaultMode: v1.ConfigMapVolumeSourceDefaultMode,
}
for _, cm := range configMaps {
if cm.Name == configMapVolumeSource.Name {
@ -203,6 +216,14 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config
break
}
}
// Set the defaultMode if set in the kube yaml
validMode, err := isValidDefaultMode(configMapVolumeSource.DefaultMode)
if err != nil {
return nil, fmt.Errorf("invalid DefaultMode for configMap %q: %w", configMapVolumeSource.Name, err)
}
if validMode {
kv.DefaultMode = *configMapVolumeSource.DefaultMode
}
if configMap == nil {
// If the volumeSource was optional, move on even if a matching configmap wasn't found
@ -279,3 +300,14 @@ func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secre
return volumes, nil
}
// isValidDefaultMode returns true if mode is between 0 and 0777
func isValidDefaultMode(mode *int32) (bool, error) {
if mode == nil {
return false, nil
}
if *mode >= 0 && *mode <= int32(os.ModePerm) {
return true, nil
}
return false, errors.New("must be between 0000 and 0777")
}

View File

@ -22,6 +22,7 @@ import (
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/bindings"
"github.com/containers/podman/v4/pkg/bindings/play"
v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1"
"github.com/containers/podman/v4/pkg/util"
. "github.com/containers/podman/v4/test/utils"
"github.com/containers/podman/v4/utils"
@ -710,6 +711,7 @@ spec:
configMap:
name: {{ .ConfigMap.Name }}
optional: {{ .ConfigMap.Optional }}
defaultMode: {{ .ConfigMap.DefaultMode }}
{{- with .ConfigMap.Items }}
items:
{{- range . }}
@ -722,6 +724,7 @@ spec:
secret:
secretName: {{ .SecretVol.SecretName }}
optional: {{ .SecretVol.Optional }}
defaultMode: {{ .SecretVol.DefaultMode }}
{{- with .SecretVol.Items }}
items:
{{- range . }}
@ -1858,15 +1861,17 @@ type PersistentVolumeClaim struct {
}
type ConfigMap struct {
Name string
Items []map[string]string
Optional bool
Name string
Items []map[string]string
Optional bool
DefaultMode int32
}
type SecretVol struct {
SecretName string
Items []map[string]string
Optional bool
SecretName string
Items []map[string]string
Optional bool
DefaultMode int32
}
type EmptyDir struct{}
@ -1908,28 +1913,38 @@ func getPersistentVolumeClaimVolume(vName string) *Volume {
// getConfigMap returns a new ConfigMap Volume given the name and items
// of the ConfigMap.
func getConfigMapVolume(vName string, items []map[string]string, optional bool) *Volume { //nolint:unparam
return &Volume{
func getConfigMapVolume(vName string, items []map[string]string, optional bool, defaultMode *int32) *Volume { //nolint:unparam
vol := &Volume{
VolumeType: "ConfigMap",
Name: defaultVolName,
ConfigMap: ConfigMap{
Name: vName,
Items: items,
Optional: optional,
Name: vName,
Items: items,
Optional: optional,
DefaultMode: v1.ConfigMapVolumeSourceDefaultMode,
},
}
if defaultMode != nil {
vol.ConfigMap.DefaultMode = *defaultMode
}
return vol
}
func getSecretVolume(vName string, items []map[string]string, optional bool) *Volume {
return &Volume{
func getSecretVolume(vName string, items []map[string]string, optional bool, defaultMode *int32) *Volume {
vol := &Volume{
VolumeType: "Secret",
Name: defaultVolName,
SecretVol: SecretVol{
SecretName: vName,
Items: items,
Optional: optional,
SecretName: vName,
Items: items,
Optional: optional,
DefaultMode: v1.SecretVolumeSourceDefaultMode,
},
}
if defaultMode != nil {
vol.SecretVol.DefaultMode = *defaultMode
}
return vol
}
func getEmptyDirVolume() *Volume {
@ -3584,7 +3599,7 @@ VOLUME %s`, CITEST_IMAGE, hostPathDir+"/")
Expect(err).ToNot(HaveOccurred())
ctr := getCtr(withVolumeMount("/test", "", false), withImage(CITEST_IMAGE))
pod := getPod(withVolume(getConfigMapVolume(volumeName, []map[string]string{}, false)), withCtr(ctr))
pod := getPod(withVolume(getConfigMapVolume(volumeName, []map[string]string{}, false, nil)), withCtr(ctr))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).ToNot(HaveOccurred())
yamls := []string{cmYaml, podYaml}
@ -3612,7 +3627,7 @@ VOLUME %s`, CITEST_IMAGE, hostPathDir+"/")
}}
ctr := getCtr(withVolumeMount("/test", "", false), withImage(CITEST_IMAGE))
pod := getPod(withVolume(getConfigMapVolume(volumeName, volumeContents, false)), withCtr(ctr))
pod := getPod(withVolume(getConfigMapVolume(volumeName, volumeContents, false, nil)), withCtr(ctr))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).ToNot(HaveOccurred())
yamls := []string{cmYaml, podYaml}
@ -3637,7 +3652,7 @@ VOLUME %s`, CITEST_IMAGE, hostPathDir+"/")
volumeName := "cmVol"
ctr := getCtr(withVolumeMount("/test", "", false), withImage(CITEST_IMAGE))
pod := getPod(withVolume(getConfigMapVolume(volumeName, []map[string]string{}, true)), withCtr(ctr))
pod := getPod(withVolume(getConfigMapVolume(volumeName, []map[string]string{}, true, nil)), withCtr(ctr))
err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).ToNot(HaveOccurred())
@ -3646,6 +3661,94 @@ VOLUME %s`, CITEST_IMAGE, hostPathDir+"/")
Expect(kube).Should(ExitCleanly())
})
It("ConfigMap volume with defaultMode set", func() {
volumeName := "cmVol"
cm := getConfigMap(withConfigMapName(volumeName), withConfigMapData("FOO", "foobar"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).ToNot(HaveOccurred())
ctr := getCtr(withVolumeMount("/test", "", false), withImage(CITEST_IMAGE))
defaultMode := int32(0777)
pod := getPod(withVolume(getConfigMapVolume(volumeName, []map[string]string{}, false, &defaultMode)), withCtr(ctr))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).ToNot(HaveOccurred())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).ToNot(HaveOccurred())
kube := podmanTest.Podman([]string{"kube", "play", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(ExitCleanly())
cmData := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/FOO"})
cmData.WaitWithDefaultTimeout()
Expect(cmData).Should(ExitCleanly())
Expect(cmData.OutputToString()).To(Equal("foobar"))
inspect := podmanTest.Podman([]string{"volume", "inspect", volumeName, "--format", "{{.Mountpoint}}"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(ExitCleanly())
Expect(inspect.OutputToStringArray()).To(HaveLen(1))
path := inspect.OutputToString()
permData := SystemExec("stat", []string{"-c", "%a", path + "/FOO"})
permData.WaitWithDefaultTimeout()
Expect(permData).Should(ExitCleanly())
Expect(permData.OutputToString()).To(Equal("777"))
})
It("configMap as volume with no defaultMode set", func() {
cmYaml := `
kind: ConfigMap
apiVersion: v1
metadata:
name: example-configmap
data:
foo: bar
---
apiVersion: v1
kind: Pod
metadata:
name: youthfulshaw-pod
spec:
containers:
- command:
- sleep
- "1000"
image: alpine
name: youthfulshaw
volumeMounts:
- name: cm-volume
mountPath: /test
volumes:
- name: cm-volume
configMap:
name: example-configmap
`
err := writeYaml(cmYaml, kubeYaml)
Expect(err).ToNot(HaveOccurred())
kube := podmanTest.Podman([]string{"kube", "play", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(ExitCleanly())
cmData := podmanTest.Podman([]string{"exec", "youthfulshaw-pod-youthfulshaw", "cat", "/test/foo"})
cmData.WaitWithDefaultTimeout()
Expect(cmData).Should(ExitCleanly())
Expect(cmData.OutputToString()).To(Equal("bar"))
inspect := podmanTest.Podman([]string{"volume", "inspect", "example-configmap", "--format", "{{.Mountpoint}}"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(ExitCleanly())
Expect(inspect.OutputToStringArray()).To(HaveLen(1))
path := inspect.OutputToString()
permData := SystemExec("stat", []string{"-c", "%a", path + "/foo"})
permData.WaitWithDefaultTimeout()
Expect(permData).Should(ExitCleanly())
Expect(permData.OutputToString()).To(Equal("644"))
})
It("with emptyDir volume", func() {
podName := "test-pod"
ctrName1 := "vol-test-ctr"
@ -5101,7 +5204,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q
Expect(err).ToNot(HaveOccurred())
ctr := getCtr(withVolumeMount("/test", "", false), withImage(CITEST_IMAGE))
pod := getPod(withVolume(getSecretVolume(volumeName, []map[string]string{}, false)), withCtr(ctr))
pod := getPod(withVolume(getSecretVolume(volumeName, []map[string]string{}, false, nil)), withCtr(ctr))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).ToNot(HaveOccurred())
yamls := []string{secretYaml, podYaml}
@ -5129,7 +5232,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q
}}
ctr := getCtr(withVolumeMount("/test", "", false), withImage(CITEST_IMAGE))
pod := getPod(withVolume(getSecretVolume(volumeName, volumeContents, false)), withCtr(ctr))
pod := getPod(withVolume(getSecretVolume(volumeName, volumeContents, false, nil)), withCtr(ctr))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).ToNot(HaveOccurred())
yamls := []string{secretYaml, podYaml}
@ -5148,6 +5251,91 @@ ENV OPENJ9_JAVA_OPTIONS=%q
secretData = podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/FOO"})
secretData.WaitWithDefaultTimeout()
Expect(secretData).Should(Not(ExitCleanly()))
})
It("secret as volume with defaultMode set", func() {
volumeName := "secretVol"
secret := getSecret(withSecretName(volumeName), withSecretData("FOO", "testuser"))
secretYaml, err := getKubeYaml("secret", secret)
Expect(err).ToNot(HaveOccurred())
ctr := getCtr(withVolumeMount("/test", "", false), withImage(CITEST_IMAGE))
defaultMode := int32(0777)
pod := getPod(withVolume(getSecretVolume(volumeName, []map[string]string{}, false, &defaultMode)), withCtr(ctr))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).ToNot(HaveOccurred())
yamls := []string{secretYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).ToNot(HaveOccurred())
kube := podmanTest.Podman([]string{"kube", "play", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(ExitCleanly())
secretData := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/FOO"})
secretData.WaitWithDefaultTimeout()
Expect(secretData).Should(ExitCleanly())
Expect(secretData.OutputToString()).To(Equal("testuser"))
inspect := podmanTest.Podman([]string{"volume", "inspect", volumeName, "--format", "{{.Mountpoint}}"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(ExitCleanly())
Expect(inspect.OutputToStringArray()).To(HaveLen(1))
path := inspect.OutputToString()
permData := SystemExec("stat", []string{"-c", "%a", path + "/FOO"})
permData.WaitWithDefaultTimeout()
Expect(permData).Should(ExitCleanly())
Expect(permData.OutputToString()).To(Equal("777"))
})
It("secret as volume with no defaultMode set", func() {
secretYaml := `
apiVersion: v1
kind: Secret
metadata:
name: newsecret
type: Opaque
data:
foo: dXNlcg==
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- command:
- sleep
- "1000"
image: alpine
name: test
volumeMounts:
- name: secret-volume
mountPath: /test
volumes:
- name: secret-volume
secret:
secretName: newsecret
`
err := writeYaml(secretYaml, kubeYaml)
Expect(err).ToNot(HaveOccurred())
kube := podmanTest.Podman([]string{"kube", "play", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(ExitCleanly())
inspect := podmanTest.Podman([]string{"volume", "inspect", "newsecret", "--format", "{{.Mountpoint}}"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(ExitCleanly())
Expect(inspect.OutputToStringArray()).To(HaveLen(1))
path := inspect.OutputToString()
permData := SystemExec("stat", []string{"-c", "%a", path + "/foo"})
permData.WaitWithDefaultTimeout()
Expect(permData).Should(ExitCleanly())
Expect(permData.OutputToString()).To(Equal("644"))
})
It("with disabled cgroup", func() {
@ -5374,7 +5562,7 @@ spec:
}}
ctr := getCtr(withPullPolicy("always"), withName("testctr"), withCmd([]string{"top"}), withVolumeMount("/etc/BAR", "BAR", false), withImage(CITEST_IMAGE))
pod := getPod(withPodName("testpod"), withVolume(getConfigMapVolume(volumeName, volumeContents, false)), withCtr(ctr))
pod := getPod(withPodName("testpod"), withVolume(getConfigMapVolume(volumeName, volumeContents, false, nil)), withCtr(ctr))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).ToNot(HaveOccurred())