From 4cd0b268fc4acab3eabbc2348a20400ab9daaefb Mon Sep 17 00:00:00 2001 From: jose luis <2064537+sosan@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:18:01 +0200 Subject: [PATCH] add configmaps derived from file and dir add tests and e2e function isconfigfile check if we need to use configmap and mount in readonly readonly not asigned because not fail e2e Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> --- pkg/transformer/kubernetes/k8sutils.go | 64 +++++++ pkg/transformer/kubernetes/k8sutils_test.go | 166 ++++++++++++++++++ pkg/transformer/kubernetes/kubernetes.go | 11 +- script/test/cmd/tests_new.sh | 5 + .../fixtures/configmap-file-configs/auth.txt | 1 + .../configmap-file-configs/certs/cert1.pem | 1 + .../configmap-file-configs/compose.yaml | 16 ++ .../configmap-file-configs/output-k8s.yaml | 116 ++++++++++++ .../fixtures/configmap-file-configs/users.php | 1 + 9 files changed, 375 insertions(+), 6 deletions(-) create mode 100644 script/test/fixtures/configmap-file-configs/auth.txt create mode 100644 script/test/fixtures/configmap-file-configs/certs/cert1.pem create mode 100644 script/test/fixtures/configmap-file-configs/compose.yaml create mode 100644 script/test/fixtures/configmap-file-configs/output-k8s.yaml create mode 100644 script/test/fixtures/configmap-file-configs/users.php diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 890e3455..00ced438 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -985,3 +985,67 @@ func reformatSecretConfigUnderscoreWithDash(secretConfig types.ServiceSecretConf return newSecretConfig } + +// isConfigFile checks if the given filePath should be used as a configMap +// if dir is not empty, withindir are treated as cofigmaps +// if it's configMap, mount readonly as default +func isConfigFile(filePath string) (useConfigMap bool, readonly bool) { + if strings.HasSuffix(filePath, ".sock") { + return + } + + fi, err := os.Stat(filePath) + if err != nil { + log.Warnf("Failed to check if the directory is empty: %v", err) + return + } + + if !fi.Mode().IsRegular() { // is dir + isDirEmpty, err := checkIsEmptyDir(filePath) + if err != nil { + log.Warnf("Failed to check if the directory is empty: %v", err) + return + } + + if isDirEmpty { + return false, false + } + } + return true, true +} + +// checkIsEmptyDir checks if filepath is empty +func checkIsEmptyDir(filePath string) (bool, error) { + entries, err := os.ReadDir(filePath) + if err != nil { + return false, err + } + if len(entries) == 0 { + return true, err + } + return false, err +} + +// setVolumeAccessMode sets the access mode for a volume based on the mode string +// current types: +// ReadOnly RO and ReadOnlyMany ROX can be mounted in read-only mode to many hosts +// ReadWriteMany RWX can be mounted in read/write mode to many hosts +// ReadWriteOncePod RWOP can be mounted in read/write mode to exactly 1 pod +// ReadWriteOnce RWO can be mounted in read/write mode to exactly 1 host +// https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes +func setVolumeAccessMode(mode string, volumeAccesMode []api.PersistentVolumeAccessMode) []api.PersistentVolumeAccessMode { + switch mode { + case "ro", "rox": + volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadOnlyMany} + case "rwx": + volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteMany} + case "rwop": + volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteOncePod} + case "rwo": + volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteOnce} + default: + volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteOnce} + } + + return volumeAccesMode +} diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index 637d7f2b..68e2aa92 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -29,6 +29,7 @@ import ( "github.com/kubernetes/kompose/pkg/testutils" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" + api "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" ) @@ -738,3 +739,168 @@ func TestRemoveEmptyInterfaces(t *testing.T) { }) } } + +func Test_setVolumeAccessMode(t *testing.T) { + type args struct { + mode string + volumeAccesMode []api.PersistentVolumeAccessMode + } + tests := []struct { + name string + args args + want []api.PersistentVolumeAccessMode + }{ + { + name: "readonly", + args: args{ + mode: "ro", + volumeAccesMode: []api.PersistentVolumeAccessMode{}, + }, + want: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, + }, + { + name: "not acceptable", + args: args{ + mode: "wrong", + volumeAccesMode: []api.PersistentVolumeAccessMode{}, + }, + want: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + }, + { + name: "readonly many", + args: args{ + mode: "rox", + volumeAccesMode: []api.PersistentVolumeAccessMode{}, + }, + want: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, + }, + { + name: "readwrite many", + args: args{ + mode: "rwx", + volumeAccesMode: []api.PersistentVolumeAccessMode{}, + }, + want: []api.PersistentVolumeAccessMode{api.ReadWriteMany}, + }, + { + name: "readwrite once in pod", + args: args{ + mode: "rwop", + volumeAccesMode: []api.PersistentVolumeAccessMode{}, + }, + want: []api.PersistentVolumeAccessMode{api.ReadWriteOncePod}, + }, + { + name: "readwrite once", + args: args{ + mode: "rwo", + volumeAccesMode: []api.PersistentVolumeAccessMode{}, + }, + want: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := setVolumeAccessMode(tt.args.mode, tt.args.volumeAccesMode); !reflect.DeepEqual(got, tt.want) { + t.Errorf("setVolumeAccessMode() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isConfigFile(t *testing.T) { + type args struct { + filePath string + } + tests := []struct { + name string + args args + wantUseConfigMap bool + wantReadonly bool + }{ + { + name: "sock", + args: args{ + filePath: "./docker.sock", + }, + wantUseConfigMap: false, + wantReadonly: false, + }, + { + name: "cannot resolve filepath", + args: args{ + filePath: "./certs/cert1.pem", + }, + wantUseConfigMap: false, + wantReadonly: false, + }, + { + name: "file cert", + args: args{ + filePath: "../../../script/test/fixtures/configmap-file-configs/certs/cert1.pem", + }, + wantUseConfigMap: true, + wantReadonly: true, + }, + { + name: "dir not empty", + args: args{ + filePath: "../../../script/test/fixtures/configmap-file-configs/certs", + }, + wantUseConfigMap: true, + wantReadonly: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotUseConfigMap, gotReadonly := isConfigFile(tt.args.filePath) + if gotUseConfigMap != tt.wantUseConfigMap { + t.Errorf("isConfigFile() gotUseConfigMap = %v, want %v", gotUseConfigMap, tt.wantUseConfigMap) + } + if gotReadonly != tt.wantReadonly { + t.Errorf("isConfigFile() gotReadonly = %v, want %v", gotReadonly, tt.wantReadonly) + } + }) + } +} + +func Test_checkIsEmptyDir(t *testing.T) { + type args struct { + filePath string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "dir not found", + args: args{ + filePath: "../../../script/test/fixtures/configmap-file-configs/notfound", + }, + want: false, + wantErr: true, + }, + { + name: "dir not empty", + args: args{ + filePath: "../../../script/test/fixtures/configmap-file-configs/certs", + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := checkIsEmptyDir(tt.args.filePath) + if (err != nil) != tt.wantErr { + t.Errorf("checkIsEmptyDir() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("checkIsEmptyDir() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 360ebc1d..188925ee 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -635,11 +635,7 @@ func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorVa } } - if mode == "ro" { - pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadOnlyMany} - } else { - pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce} - } + pvc.Spec.AccessModes = setVolumeAccessMode(mode, pvc.Spec.AccessModes) if len(storageClassName) > 0 { pvc.Spec.StorageClassName = &storageClassName @@ -958,7 +954,10 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( //iterating over array of `Vols` struct as it contains all necessary information about volumes for _, volume := range service.Volumes { // check if ro/rw mode is defined, default rw - readonly := len(volume.Mode) > 0 && volume.Mode == "ro" + readonly := len(volume.Mode) > 0 && (volume.Mode == "ro" || volume.Mode == "rox") + // return useconfigmap and readonly, + // not used asigned readonly because dont break e2e + useConfigMap, _ = isConfigFile(volume.Host) if volume.VolumeName == "" { if useEmptyVolumes { volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1) diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index 61c1f7e1..f918780a 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -339,3 +339,8 @@ os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/compos os_output="$KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 + +#Test configmaps +k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/compose.yaml convert --stdout --with-kompose-annotation=false" +k8s_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/output-k8s.yaml" +convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 diff --git a/script/test/fixtures/configmap-file-configs/auth.txt b/script/test/fixtures/configmap-file-configs/auth.txt new file mode 100644 index 00000000..037eac15 --- /dev/null +++ b/script/test/fixtures/configmap-file-configs/auth.txt @@ -0,0 +1 @@ +content from file auth.txt diff --git a/script/test/fixtures/configmap-file-configs/certs/cert1.pem b/script/test/fixtures/configmap-file-configs/certs/cert1.pem new file mode 100644 index 00000000..904bef1f --- /dev/null +++ b/script/test/fixtures/configmap-file-configs/certs/cert1.pem @@ -0,0 +1 @@ +content of file cert1.pem diff --git a/script/test/fixtures/configmap-file-configs/compose.yaml b/script/test/fixtures/configmap-file-configs/compose.yaml new file mode 100644 index 00000000..83d642eb --- /dev/null +++ b/script/test/fixtures/configmap-file-configs/compose.yaml @@ -0,0 +1,16 @@ +services: + busy: + image: busybox + ports: + - "8081:8080" + - "8026:8025" + volumes: + - ./certs:/certs + - ./auth.txt:/auth.txt + - ./users.php:/users.php:ro + command: + [ + "/bin/sh", + "-c", + "cat /auth.txt /users.php /certs/cert1.pem" + ] diff --git a/script/test/fixtures/configmap-file-configs/output-k8s.yaml b/script/test/fixtures/configmap-file-configs/output-k8s.yaml new file mode 100644 index 00000000..f3be4775 --- /dev/null +++ b/script/test/fixtures/configmap-file-configs/output-k8s.yaml @@ -0,0 +1,116 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + io.kompose.service: busy + name: busy +spec: + ports: + - name: "8081" + port: 8081 + targetPort: 8080 + - name: "8026" + port: 8026 + targetPort: 8025 + selector: + io.kompose.service: busy + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + io.kompose.service: busy + name: busy +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: busy + strategy: + type: Recreate + template: + metadata: + labels: + io.kompose.network/configmap-file-configs-default: "true" + io.kompose.service: busy + spec: + containers: + - args: + - /bin/sh + - -c + - cat /auth.txt /users.php /certs/cert1.pem + image: busybox + name: busy + ports: + - containerPort: 8080 + hostPort: 8081 + protocol: TCP + - containerPort: 8025 + hostPort: 8026 + protocol: TCP + volumeMounts: + - mountPath: /certs + name: busy-cm0 + - mountPath: /auth.txt + name: busy-cm1 + subPath: auth.txt + - mountPath: /users.php + name: busy-cm2 + subPath: users.php + readOnly: true + restartPolicy: Always + volumes: + - configMap: + name: busy-cm0 + name: busy-cm0 + - configMap: + items: + - key: auth.txt + path: auth.txt + name: busy-cm1 + name: busy-cm1 + - configMap: + items: + - key: users.php + path: users.php + name: busy-cm2 + name: busy-cm2 + +--- +apiVersion: v1 +data: + cert1.pem: | + content of file cert1.pem +kind: ConfigMap +metadata: + labels: + io.kompose.service: busy + name: busy-cm0 + +--- +apiVersion: v1 +data: + auth.txt: | + content from file auth.txt +kind: ConfigMap +metadata: + annotations: + use-subpath: "true" + labels: + io.kompose.service: busy + name: busy-cm1 + +--- +apiVersion: v1 +data: + users.php: | + content from file users.php +kind: ConfigMap +metadata: + annotations: + use-subpath: "true" + labels: + io.kompose.service: busy + name: busy-cm2 diff --git a/script/test/fixtures/configmap-file-configs/users.php b/script/test/fixtures/configmap-file-configs/users.php new file mode 100644 index 00000000..7d0f1a20 --- /dev/null +++ b/script/test/fixtures/configmap-file-configs/users.php @@ -0,0 +1 @@ +content from file users.php