diff --git a/docs/kubernetes_support.md b/docs/kubernetes_support.md index 851e692cb2..7a050df1ce 100644 --- a/docs/kubernetes_support.md +++ b/docs/kubernetes_support.md @@ -155,7 +155,7 @@ Note: **N/A** means that the option cannot be supported in a single-node Podman | Field | Support | |------------|---------| -| binaryData | | +| binaryData | ✅ | | data | ✅ | | immutable | | diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index bec5c4cb5e..d97cda7c16 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -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) } defer f.Close() - _, err = f.WriteString(v) + _, err = f.Write(v) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/play_test.go b/pkg/domain/infra/abi/play_test.go index e11581fa21..bdc81ada85 100644 --- a/pkg/domain/infra/abi/play_test.go +++ b/pkg/domain/infra/abi/play_test.go @@ -71,6 +71,29 @@ data: "invalid YAML kind", 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 { diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go index adf9b979a8..6bb6e7d9e6 100644 --- a/pkg/specgen/generate/kube/play_test.go +++ b/pkg/specgen/generate/kube/play_test.go @@ -41,6 +41,200 @@ func createSecrets(t *testing.T, d string) *secrets.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) { d := t.TempDir() secretsManager := createSecrets(t, d) @@ -813,6 +1007,56 @@ var ( "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 diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index beb460d684..4e98305c5d 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -45,7 +45,7 @@ type KubeVolume struct { // 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 // 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 // Only used when there are volumes in a yaml that refer to a configmap Optional bool @@ -163,11 +163,11 @@ func VolumeFromSecret(secretSource *v1.SecretVolumeSource, secretsManager *secre kv.Type = KubeVolumeTypeSecret kv.Source = secretSource.SecretName kv.Optional = *secretSource.Optional - kv.Items = make(map[string]string) + kv.Items = make(map[string][]byte) // add key: value pairs to the items array for key, entry := range data.Data { - kv.Items[key] = entry + kv.Items[key] = []byte(entry) } return kv, nil } @@ -182,7 +182,10 @@ 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]string{}} + kv := &KubeVolume{ + Type: KubeVolumeTypeConfigMap, + Items: map[string][]byte{}, + } for _, cm := range configMaps { if cm.Name == configMapVolumeSource.Name { matchedCM := cm @@ -203,15 +206,27 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config 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 len(configMapVolumeSource.Items) > 0 { for _, item := range configMapVolumeSource.Items { 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 } } } else { for k, v := range configMap.Data { + kv.Items[k] = []byte(v) + } + for k, v := range configMap.BinaryData { kv.Items[k] = v } }