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>
This commit is contained in:
jose luis 2024-04-07 21:18:01 +02:00
parent 14152d4a81
commit 4cd0b268fc
No known key found for this signature in database
GPG Key ID: 6D23FAD11F88081A
9 changed files with 375 additions and 6 deletions

View File

@ -985,3 +985,67 @@ func reformatSecretConfigUnderscoreWithDash(secretConfig types.ServiceSecretConf
return newSecretConfig 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
}

View File

@ -29,6 +29,7 @@ import (
"github.com/kubernetes/kompose/pkg/testutils" "github.com/kubernetes/kompose/pkg/testutils"
"github.com/pkg/errors" "github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
api "k8s.io/api/core/v1"
corev1 "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)
}
})
}
}

View File

@ -635,11 +635,7 @@ func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorVa
} }
} }
if mode == "ro" { pvc.Spec.AccessModes = setVolumeAccessMode(mode, pvc.Spec.AccessModes)
pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadOnlyMany}
} else {
pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce}
}
if len(storageClassName) > 0 { if len(storageClassName) > 0 {
pvc.Spec.StorageClassName = &storageClassName 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 //iterating over array of `Vols` struct as it contains all necessary information about volumes
for _, volume := range service.Volumes { for _, volume := range service.Volumes {
// check if ro/rw mode is defined, default rw // 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 volume.VolumeName == "" {
if useEmptyVolumes { if useEmptyVolumes {
volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1) volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1)

View File

@ -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" os_output="$KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/output-os.yaml"
convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1
convert::expect_success "$os_cmd" "$os_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

View File

@ -0,0 +1 @@
content from file auth.txt

View File

@ -0,0 +1 @@
content of file cert1.pem

View File

@ -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"
]

View File

@ -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

View File

@ -0,0 +1 @@
content from file users.php