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:
parent
87dd939334
commit
17cebb3ff8
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Reference in New Issue