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