diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 87df70cc..21b5df56 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -182,6 +182,8 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error { file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) case *api.Service: file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) + case *api.PersistentVolumeClaim: + file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) } files = append(files, file) } @@ -250,12 +252,20 @@ func CreateService(name string, service kobject.ServiceConfig, objects []runtime } // load configurations to k8s objects -func UpdateKubernetesObjects(name string, service kobject.ServiceConfig, objects []runtime.Object) { +func UpdateKubernetesObjects(name string, service kobject.ServiceConfig, objects *[]runtime.Object) { // Configure the environment variables. envs := ConfigEnvs(name, service) // Configure the container volumes. - volumesMount, volumes := ConfigVolumes(service) + volumesMount, volumes, pvc := ConfigVolumes(name, service) + if pvc != nil { + // Looping on the slice pvc instead of `*objects = append(*objects, pvc...)` + // because the type of objects and pvc is different, but when doing append + // one element at a time it gets converted to runtime.Object for objects slice + for _, p := range pvc { + *objects = append(*objects, p) + } + } // Configure the container ports. ports := ConfigPorts(name, service) @@ -301,7 +311,7 @@ func UpdateKubernetesObjects(name string, service kobject.ServiceConfig, objects } // update supported controller - for _, obj := range objects { + for _, obj := range *objects { UpdateController(obj, fillTemplate, fillObjectMeta) } } diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 05f6441b..2f92105e 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -28,6 +28,7 @@ import ( // install kubernetes api "k8s.io/kubernetes/pkg/api" _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" _ "k8s.io/kubernetes/pkg/apis/extensions/install" @@ -146,6 +147,38 @@ func InitDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { return ds } +// Initialize PersistentVolumeClaim +func CreatePVC(name string, mode string) *api.PersistentVolumeClaim { + size, err := resource.ParseQuantity("100Mi") + if err != nil { + logrus.Fatalf("Error parsing size") + } + + pvc := &api.PersistentVolumeClaim{ + TypeMeta: unversioned.TypeMeta{ + Kind: "PersistentVolumeClaim", + APIVersion: "v1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceStorage: size, + }, + }, + }, + } + + if mode == "ro" { + pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{"ReadWriteOnce"} + } else { + pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{"ReadWriteOnce"} + } + return pvc +} + // Configure the container ports. func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { ports := []api.ContainerPort{} @@ -180,34 +213,49 @@ func ConfigServicePorts(name string, service kobject.ServiceConfig) []api.Servic } // Configure the container volumes. -func ConfigVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { +func ConfigVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim) { volumesMount := []api.VolumeMount{} volumes := []api.Volume{} - volumeSource := api.VolumeSource{} + var pvc []*api.PersistentVolumeClaim + + var count int for _, volume := range service.Volumes { - name, host, container, mode, err := transformer.ParseVolume(volume) + volumeName, host, container, mode, err := transformer.ParseVolume(volume) if err != nil { logrus.Warningf("Failed to configure container volume: %v", err) continue } - - // if volume name isn't specified, set it to a random string of 20 chars - if len(name) == 0 { - name = transformer.RandStringBytes(20) + if volumeName == "" { + volumeName = fmt.Sprintf("%s-claim%d", name, count) + count++ } // check if ro/rw mode is defined, default rw readonly := len(mode) > 0 && mode == "ro" - volumesMount = append(volumesMount, api.VolumeMount{Name: name, ReadOnly: readonly, MountPath: container}) + volmount := api.VolumeMount{ + Name: volumeName, + ReadOnly: readonly, + MountPath: container, + } + volumesMount = append(volumesMount, volmount) + + vol := api.Volume{ + Name: volumeName, + VolumeSource: api.VolumeSource{ + PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ + ClaimName: volumeName, + ReadOnly: readonly, + }, + }, + } + volumes = append(volumes, vol) if len(host) > 0 { logrus.Warningf("Volume mount on the host %q isn't supported - ignoring path on the host", host) } - volumeSource = api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}} - - volumes = append(volumes, api.Volume{Name: name, VolumeSource: volumeSource}) + pvc = append(pvc, CreatePVC(volumeName, mode)) } - return volumesMount, volumes + return volumesMount, volumes, pvc } // Configure the environment variables. @@ -255,7 +303,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. objects = append(objects, svc) } - UpdateKubernetesObjects(name, service, objects) + UpdateKubernetesObjects(name, service, &objects) allobjects = append(allobjects, objects...) } diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go index 4b4b9122..6478cbbc 100644 --- a/pkg/transformer/kubernetes/kubernetes_test.go +++ b/pkg/transformer/kubernetes/kubernetes_test.go @@ -152,7 +152,7 @@ func checkPodTemplate(config kobject.ServiceConfig, template api.PodTemplateSpec if !equalStringSlice(config.Args, container.Args) { return fmt.Errorf("Found different container args: %#v vs. %#v", config.Args, container.Args) } - if len(template.Spec.Volumes) == 0 || len(template.Spec.Volumes[0].Name) == 0 || template.Spec.Volumes[0].VolumeSource.EmptyDir == nil { + if len(template.Spec.Volumes) == 0 || len(template.Spec.Volumes[0].Name) == 0 || template.Spec.Volumes[0].VolumeSource.PersistentVolumeClaim == nil { return fmt.Errorf("Found incorrect volumes: %v vs. %#v", config.Volumes, template.Spec.Volumes) } // We only set controller labels here and k8s server will take care of other defaults, such as selectors @@ -202,10 +202,12 @@ func TestKomposeConvert(t *testing.T) { opt kobject.ConvertOptions expectedNumObjs int }{ - "Convert to Deployments (D)": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, Replicas: replicas}, 2}, - "Convert to DaemonSets (DS)": {newKomposeObject(), kobject.ConvertOptions{CreateDS: true}, 2}, - "Convert to ReplicationController (RC)": {newKomposeObject(), kobject.ConvertOptions{CreateRC: true, Replicas: replicas}, 2}, - "Convert to D, DS, and RC": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true, Replicas: replicas}, 4}, + // objects generated are deployment, service and pvc + "Convert to Deployments (D)": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, Replicas: replicas}, 3}, + "Convert to DaemonSets (DS)": {newKomposeObject(), kobject.ConvertOptions{CreateDS: true}, 3}, + "Convert to ReplicationController (RC)": {newKomposeObject(), kobject.ConvertOptions{CreateRC: true, Replicas: replicas}, 3}, + // objects generated are deployment, daemonset, ReplicationController, service and pvc + "Convert to D, DS, and RC": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true, Replicas: replicas}, 5}, // TODO: add more tests } diff --git a/pkg/transformer/openshift/openshift.go b/pkg/transformer/openshift/openshift.go index 9df6859b..492acc62 100644 --- a/pkg/transformer/openshift/openshift.go +++ b/pkg/transformer/openshift/openshift.go @@ -147,7 +147,7 @@ func (k *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C objects = append(objects, svc) } - kubernetes.UpdateKubernetesObjects(name, service, objects) + kubernetes.UpdateKubernetesObjects(name, service, &objects) allobjects = append(allobjects, objects...) } diff --git a/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 9723a83c..33cf5aba 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -51,5 +51,12 @@ convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/ports-wit convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/docker-compose.yml convert --stdout --dc" "$KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/output-os.json" +###### +# Tests related to docker-compose file in /script/test/fixtures/volume-mounts/simple-vol-mounts +# kubernetes test +convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/simple-vol-mounts/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/simple-vol-mounts/output-k8s.json" +# openshift test +convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/simple-vol-mounts/docker-compose.yml convert --stdout --dc" "$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/simple-vol-mounts/output-os.json" + exit $EXIT_STATUS diff --git a/script/test/fixtures/volume-mounts/simple-vol-mounts/docker-compose.yml b/script/test/fixtures/volume-mounts/simple-vol-mounts/docker-compose.yml new file mode 100644 index 00000000..67058c1d --- /dev/null +++ b/script/test/fixtures/volume-mounts/simple-vol-mounts/docker-compose.yml @@ -0,0 +1,9 @@ +version: "2" + +services: + httpd: + image: docker.io/fedora/apache + ports: + - "80" + volumes: + - /var/www/html diff --git a/script/test/fixtures/volume-mounts/simple-vol-mounts/output-k8s.json b/script/test/fixtures/volume-mounts/simple-vol-mounts/output-k8s.json new file mode 100644 index 00000000..fe61e41a --- /dev/null +++ b/script/test/fixtures/volume-mounts/simple-vol-mounts/output-k8s.json @@ -0,0 +1,104 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "httpd", + "creationTimestamp": null, + "labels": { + "service": "httpd" + } + }, + "spec": { + "ports": [ + { + "name": "80", + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "service": "httpd" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "httpd", + "creationTimestamp": null + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "service": "httpd" + } + }, + "spec": { + "volumes": [ + { + "name": "httpd-claim0", + "persistentVolumeClaim": { + "claimName": "httpd-claim0" + } + } + ], + "containers": [ + { + "name": "httpd", + "image": "docker.io/fedora/apache", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "httpd-claim0", + "mountPath": "/var/www/html" + } + ] + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "httpd-claim0", + "creationTimestamp": null + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/volume-mounts/simple-vol-mounts/output-os.json b/script/test/fixtures/volume-mounts/simple-vol-mounts/output-os.json new file mode 100644 index 00000000..3eb4e99c --- /dev/null +++ b/script/test/fixtures/volume-mounts/simple-vol-mounts/output-os.json @@ -0,0 +1,156 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "httpd", + "creationTimestamp": null, + "labels": { + "service": "httpd" + } + }, + "spec": { + "ports": [ + { + "name": "80", + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "service": "httpd" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "httpd", + "creationTimestamp": null, + "labels": { + "service": "httpd" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "httpd" + ], + "from": { + "kind": "ImageStreamTag", + "name": "httpd:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "service": "httpd" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "service": "httpd" + } + }, + "spec": { + "volumes": [ + { + "name": "httpd-claim0", + "persistentVolumeClaim": { + "claimName": "httpd-claim0" + } + } + ], + "containers": [ + { + "name": "httpd", + "image": " ", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "httpd-claim0", + "mountPath": "/var/www/html" + } + ] + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "httpd", + "creationTimestamp": null + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "docker.io/fedora/apache" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "httpd-claim0", + "creationTimestamp": null + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + } + ] +}