play kube: add support for configmap binaryData

Signed-off-by: Jakob Ahrer <jakob@ahrer.dev>
This commit is contained in:
Jakob Ahrer
2022-10-11 00:19:22 +02:00
committed by SoMuchForSubtlety
parent bb0b1849d7
commit 2bee2216ce
5 changed files with 288 additions and 6 deletions

View File

@ -155,7 +155,7 @@ Note: **N/A** means that the option cannot be supported in a single-node Podman
| Field | Support | | Field | Support |
|------------|---------| |------------|---------|
| binaryData | | | binaryData | |
| data | ✅ | | data | ✅ |
| immutable | | | immutable | |

View File

@ -486,7 +486,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return nil, fmt.Errorf("cannot create file %q at volume mountpoint %q: %w", k, mountPoint, err) return nil, fmt.Errorf("cannot create file %q at volume mountpoint %q: %w", k, mountPoint, err)
} }
defer f.Close() defer f.Close()
_, err = f.WriteString(v) _, err = f.Write(v)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -71,6 +71,29 @@ data:
"invalid YAML kind", "invalid YAML kind",
v1.ConfigMap{}, v1.ConfigMap{},
}, },
{
"ValidBinaryDataConfigMap",
`
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
binaryData:
data.zip: UEsDBBQACAAIAMm7SlUAAAAAAAAAAAwAAAAIACAAZGF0YS50eHRVVA0AB+qORGM7j0Rj6o5EY3V4CwABBOgDAAAE6AMAAEvKzEssqlRISSxJ5AIAUEsHCN0J2aAOAAAADAAAAFBLAQIUAxQACAAIAMm7SlXdCdmgDgAAAAwAAAAIACAAAAAAAAAAAACkgQAAAABkYXRhLnR4dFVUDQAH6o5EYzuPRGPqjkRjdXgLAAEE6AMAAAToAwAAUEsFBgAAAAABAAEAVgAAAGQAAAAAAA==
`,
false,
"",
v1.ConfigMap{
TypeMeta: v12.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: v12.ObjectMeta{
Name: "foo",
},
BinaryData: map[string][]byte{"data.zip": {0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0xc9, 0xbb, 0x4a, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x20, 0x00, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x0d, 0x00, 0x07, 0xea, 0x8e, 0x44, 0x63, 0x3b, 0x8f, 0x44, 0x63, 0xea, 0x8e, 0x44, 0x63, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x4b, 0xca, 0xcc, 0x4b, 0x2c, 0xaa, 0x54, 0x48, 0x49, 0x2c, 0x49, 0xe4, 0x02, 0x00, 0x50, 0x4b, 0x07, 0x08, 0xdd, 0x09, 0xd9, 0xa0, 0x0e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0xc9, 0xbb, 0x4a, 0x55, 0xdd, 0x09, 0xd9, 0xa0, 0x0e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x00, 0x00, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x0d, 0x00, 0x07, 0xea, 0x8e, 0x44, 0x63, 0x3b, 0x8f, 0x44, 0x63, 0xea, 0x8e, 0x44, 0x63, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x56, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00}},
},
},
} }
for _, test := range tests { for _, test := range tests {

View File

@ -41,6 +41,200 @@ func createSecrets(t *testing.T, d string) *secrets.SecretsManager {
return secretsManager return secretsManager
} }
func TestConfigMapVolumes(t *testing.T) {
yes := true
tests := []struct {
name string
volume v1.Volume
configmaps []v1.ConfigMap
errorMessage string
expectedItems map[string][]byte
}{
{
"VolumeFromConfigmap",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "bar",
},
},
},
},
configMapList,
"",
map[string][]byte{"myvar": []byte("bar")},
},
{
"VolumeFromBinaryConfigmap",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "binary-bar",
},
},
},
},
configMapList,
"",
map[string][]byte{"myvar": []byte("bin-bar")},
},
{
"ConfigmapMissing",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "fizz",
},
},
},
},
configMapList,
`no such ConfigMap "fizz"`,
map[string][]byte{},
},
{
"ConfigmapMissingOptional",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "fizz",
},
Optional: &yes,
},
},
},
configMapList,
"",
map[string][]byte{},
},
{
"MultiValue",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "multi-item",
},
Optional: &yes,
},
},
},
configMapList,
"",
map[string][]byte{"foo": []byte("bar"), "fizz": []byte("buzz")},
},
{
"SpecificValue",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "multi-item",
},
Optional: &yes,
Items: []v1.KeyToPath{{Key: "fizz", Path: "/custom/path"}},
},
},
},
configMapList,
"",
map[string][]byte{"/custom/path": []byte("buzz")},
},
{
"MultiValueBinary",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "multi-binary-item",
},
Optional: &yes,
},
},
},
configMapList,
"",
map[string][]byte{"foo": []byte("bin-bar"), "fizz": []byte("bin-buzz")},
},
{
"SpecificValueBinary",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "multi-binary-item",
},
Optional: &yes,
Items: []v1.KeyToPath{{Key: "fizz", Path: "/custom/path"}},
},
},
},
configMapList,
"",
map[string][]byte{"/custom/path": []byte("bin-buzz")},
},
{
"DuplicateValues",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "dupe",
},
},
},
},
configMapList,
`the ConfigMap "dupe" is invalid: duplicate key "foo" present in data and binaryData`,
map[string][]byte{},
},
{
"DuplicateValuesSpecific",
v1.Volume{
Name: "test-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "dupe",
},
Items: []v1.KeyToPath{{Key: "fizz", Path: "/custom/path"}},
},
},
},
configMapList,
`the ConfigMap "dupe" is invalid: duplicate key "foo" present in data and binaryData`,
map[string][]byte{},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
result, err := VolumeFromConfigMap(test.volume.ConfigMap, test.configmaps)
if test.errorMessage == "" {
assert.NoError(t, err)
assert.Equal(t, test.expectedItems, result.Items)
} else {
assert.Error(t, err)
assert.Equal(t, test.errorMessage, err.Error())
}
})
}
}
func TestEnvVarsFrom(t *testing.T) { func TestEnvVarsFrom(t *testing.T) {
d := t.TempDir() d := t.TempDir()
secretsManager := createSecrets(t, d) secretsManager := createSecrets(t, d)
@ -813,6 +1007,56 @@ var (
"myvar": "foo", "myvar": "foo",
}, },
}, },
{
TypeMeta: v12.TypeMeta{
Kind: "ConfigMap",
},
ObjectMeta: v12.ObjectMeta{
Name: "binary-bar",
},
BinaryData: map[string][]byte{
"myvar": []byte("bin-bar"),
},
},
{
TypeMeta: v12.TypeMeta{
Kind: "ConfigMap",
},
ObjectMeta: v12.ObjectMeta{
Name: "multi-item",
},
Data: map[string]string{
"foo": "bar",
"fizz": "buzz",
},
},
{
TypeMeta: v12.TypeMeta{
Kind: "ConfigMap",
},
ObjectMeta: v12.ObjectMeta{
Name: "multi-binary-item",
},
BinaryData: map[string][]byte{
"foo": []byte("bin-bar"),
"fizz": []byte("bin-buzz"),
},
},
{
TypeMeta: v12.TypeMeta{
Kind: "ConfigMap",
},
ObjectMeta: v12.ObjectMeta{
Name: "dupe",
},
BinaryData: map[string][]byte{
"fiz": []byte("bin-buzz"),
"foo": []byte("bin-bar"),
},
Data: map[string]string{
"foo": "bar",
},
},
} }
optional = true optional = true

View File

@ -45,7 +45,7 @@ type KubeVolume struct {
// This is only used when there are volumes in the yaml that refer to a configmap // This is only used when there are volumes in the yaml that refer to a configmap
// Example: if configmap has data "SPECIAL_LEVEL: very" then the file name is "SPECIAL_LEVEL" and the // Example: if configmap has data "SPECIAL_LEVEL: very" then the file name is "SPECIAL_LEVEL" and the
// data in that file is "very". // data in that file is "very".
Items map[string]string Items map[string][]byte
// If the volume is optional, we can move on if it is not found // 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 // Only used when there are volumes in a yaml that refer to a configmap
Optional bool Optional bool
@ -163,11 +163,11 @@ func VolumeFromSecret(secretSource *v1.SecretVolumeSource, secretsManager *secre
kv.Type = KubeVolumeTypeSecret kv.Type = KubeVolumeTypeSecret
kv.Source = secretSource.SecretName kv.Source = secretSource.SecretName
kv.Optional = *secretSource.Optional kv.Optional = *secretSource.Optional
kv.Items = make(map[string]string) kv.Items = make(map[string][]byte)
// add key: value pairs to the items array // add key: value pairs to the items array
for key, entry := range data.Data { for key, entry := range data.Data {
kv.Items[key] = entry kv.Items[key] = []byte(entry)
} }
return kv, nil return kv, nil
} }
@ -182,7 +182,10 @@ func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource
func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) { func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) {
var configMap *v1.ConfigMap var configMap *v1.ConfigMap
kv := &KubeVolume{Type: KubeVolumeTypeConfigMap, Items: map[string]string{}} kv := &KubeVolume{
Type: KubeVolumeTypeConfigMap,
Items: map[string][]byte{},
}
for _, cm := range configMaps { for _, cm := range configMaps {
if cm.Name == configMapVolumeSource.Name { if cm.Name == configMapVolumeSource.Name {
matchedCM := cm matchedCM := cm
@ -203,15 +206,27 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config
return nil, fmt.Errorf("no such ConfigMap %q", configMapVolumeSource.Name) return nil, fmt.Errorf("no such ConfigMap %q", configMapVolumeSource.Name)
} }
// don't allow keys from "data" and "binaryData" to overlap
for k := range configMap.Data {
if _, ok := configMap.BinaryData[k]; ok {
return nil, fmt.Errorf("the ConfigMap %q is invalid: duplicate key %q present in data and binaryData", configMap.Name, k)
}
}
// If there are Items specified in the volumeSource, that overwrites the Data from the configmap // If there are Items specified in the volumeSource, that overwrites the Data from the configmap
if len(configMapVolumeSource.Items) > 0 { if len(configMapVolumeSource.Items) > 0 {
for _, item := range configMapVolumeSource.Items { for _, item := range configMapVolumeSource.Items {
if val, ok := configMap.Data[item.Key]; ok { if val, ok := configMap.Data[item.Key]; ok {
kv.Items[item.Path] = []byte(val)
} else if val, ok := configMap.BinaryData[item.Key]; ok {
kv.Items[item.Path] = val kv.Items[item.Path] = val
} }
} }
} else { } else {
for k, v := range configMap.Data { for k, v := range configMap.Data {
kv.Items[k] = []byte(v)
}
for k, v := range configMap.BinaryData {
kv.Items[k] = v kv.Items[k] = v
} }
} }