From cf833c8818220d69fc12dceead6b4c1581031b00 Mon Sep 17 00:00:00 2001 From: Hang Yan Date: Fri, 16 Mar 2018 10:40:25 +0800 Subject: [PATCH] Support hostpath volume --- cmd/convert.go | 2 +- cmd/up.go | 2 +- pkg/app/app.go | 2 +- pkg/transformer/kubernetes/kubernetes.go | 30 +++- script/test/cmd/tests.sh | 14 ++ .../volume-mounts/hostpath/docker-compose.yml | 8 + .../hostpath/output-k8s-template.json | 96 ++++++++++++ .../hostpath/output-os-template.json | 147 ++++++++++++++++++ 8 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 script/test/fixtures/volume-mounts/hostpath/docker-compose.yml create mode 100644 script/test/fixtures/volume-mounts/hostpath/output-k8s-template.json create mode 100644 script/test/fixtures/volume-mounts/hostpath/output-os-template.json diff --git a/cmd/convert.go b/cmd/convert.go index 767ca819..c3e5a942 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -135,7 +135,7 @@ func init() { convertCmd.Flags().BoolVar(&ConvertStdout, "stdout", false, "Print converted objects to stdout") convertCmd.Flags().StringVarP(&ConvertOut, "out", "o", "", "Specify a file name to save objects to") convertCmd.Flags().IntVar(&ConvertReplicas, "replicas", 1, "Specify the number of replicas in the generated resource spec") - convertCmd.Flags().StringVar(&ConvertVolumes, "volumes", "persistentVolumeClaim", `Volumes to be generated ("persistentVolumeClaim"|"emptyDir")`) + convertCmd.Flags().StringVar(&ConvertVolumes, "volumes", "persistentVolumeClaim", `Volumes to be generated ("persistentVolumeClaim"|"emptyDir"|"hostPath")`) // Deprecated commands convertCmd.Flags().BoolVar(&ConvertEmptyVols, "emptyvols", false, "Use Empty Volumes. Do not generate PVCs") diff --git a/cmd/up.go b/cmd/up.go index 91984389..e9eae851 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -76,7 +76,7 @@ var upCmd = &cobra.Command{ func init() { upCmd.Flags().IntVar(&UpReplicas, "replicas", 1, "Specify the number of replicas generated") - upCmd.Flags().StringVar(&UpVolumes, "volumes", "persistentVolumeClaim", `Volumes to be generated ("persistentVolumeClaim"|"emptyDir")`) + upCmd.Flags().StringVar(&UpVolumes, "volumes", "persistentVolumeClaim", `Volumes to be generated ("persistentVolumeClaim"|"emptyDir"|"hostPath")`) upCmd.Flags().BoolVar(&UpInsecureRepo, "insecure-repository", false, "Use an insecure Docker repository for OpenShift ImageStream") upCmd.Flags().StringVar(&UpNamespace, "namespace", "default", "Specify Namespace to deploy your application") upCmd.Flags().StringVar(&UpBuild, "build", "local", `Set the type of build ("local"|"build-config" (OpenShift only)|"none")`) diff --git a/pkg/app/app.go b/pkg/app/app.go index f14e1e9e..cb255e08 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -149,7 +149,7 @@ func ValidateFlags(bundle string, args []string, cmd *cobra.Command, opt *kobjec log.Fatalf("YAML and JSON format cannot be provided at the same time") } - if opt.Volumes != "persistentVolumeClaim" && opt.Volumes != "emptyDir" { + if opt.Volumes != "persistentVolumeClaim" && opt.Volumes != "emptyDir" && opt.Volumes != "hostPath" { log.Fatal("Unknown Volume type: ", opt.Volumes, ", possible values are: persistentVolumeClaim and emptyDir") } } diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index c74954eb..1326f173 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -51,6 +51,7 @@ import ( "github.com/pkg/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/labels" + "path/filepath" ) // Kubernetes implements Transformer interface and represents Kubernetes transformer @@ -431,11 +432,16 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( // Set a var based on if the user wants to use empty volumes // as opposed to persistent volumes and volume claims useEmptyVolumes := k.Opt.EmptyVols + useHostPath := false if k.Opt.Volumes == "emptyDir" { useEmptyVolumes = true } + if k.Opt.Volumes == "hostPath" { + useHostPath = true + } + var count int //interating over array of `Vols` struct as it contains all necessary information about volumes for _, volume := range service.Volumes { @@ -446,6 +452,8 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( if volume.VolumeName == "" { if useEmptyVolumes { volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1) + } else if useHostPath { + volumeName = strings.Replace(volume.PVCName, "claim", "hostpath", 1) } else { volumeName = volume.PVCName } @@ -465,8 +473,13 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( if useEmptyVolumes { volsource = k.ConfigEmptyVolumeSource("volume") + } else if useHostPath { + source, err := k.ConfigHostPathVolumeSource(volume.Host) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "k.ConfigHostPathVolumeSource failed") + } + volsource = source } else { - volsource = k.ConfigPVCVolumeSource(volumeName, readonly) if volume.VFrom == "" { defaultSize := PVCRequestSize @@ -485,6 +498,7 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( PVCs = append(PVCs, createdPVC) } + } // create a new volume object using the volsource and add to list @@ -494,7 +508,7 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( } volumes = append(volumes, vol) - if len(volume.Host) > 0 { + if len(volume.Host) > 0 && !useHostPath { log.Warningf("Volume mount on the host %q isn't supported - ignoring path on the host", volume.Host) } @@ -521,6 +535,18 @@ func (k *Kubernetes) ConfigEmptyVolumeSource(key string) *api.VolumeSource { } +// ConfigHostPathVolumeSource is a helper function to create a HostPath api.VolumeSource +func (k *Kubernetes) ConfigHostPathVolumeSource(path string) (*api.VolumeSource, error) { + dir, err := transformer.GetComposeFileDir(k.Opt.InputFiles) + if err != nil { + return nil, err + } + absPath := filepath.Join(dir, path) + return &api.VolumeSource{ + HostPath: &api.HostPathVolumeSource{Path: absPath}, + }, nil +} + // ConfigPVCVolumeSource is helper function to create an api.VolumeSource with a PVC func (k *Kubernetes) ConfigPVCVolumeSource(name string, readonly bool) *api.VolumeSource { return &api.VolumeSource{ diff --git a/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 9c66a56c..e39ed97c 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -175,6 +175,20 @@ sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/f convert::expect_success "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/named-volume/docker-compose.yml convert --stdout -j" "/tmp/output-os.json" +###### +# Tests related to docker-compose file in /script/test/fixtures/volume-mounts/hostpath +# kubernetes test +cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/hostpath/docker-compose.yml convert --stdout -j --volumes hostPath" +hostpath=$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/hostpath/data +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" -e "s;%HOSTPATH%;$hostpath;g" $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/hostpath/output-k8s-template.json > /tmp/output-k8s.json +convert::expect_success "$cmd" "/tmp/output-k8s.json" + +# openshift test +cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/hostpath/docker-compose.yml convert --stdout -j --volumes hostPath" +hostpath=$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/hostpath/data +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" -e "s;%HOSTPATH%;$hostpath;g" $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/hostpath/output-os-template.json > /tmp/output-os.json +convert::expect_success "$cmd" "/tmp/output-os.json" + ###### # Tests related to docker-compose file in /script/test/fixtures/volume-mounts/volumes-from # kubernetes test diff --git a/script/test/fixtures/volume-mounts/hostpath/docker-compose.yml b/script/test/fixtures/volume-mounts/hostpath/docker-compose.yml new file mode 100644 index 00000000..d626ca92 --- /dev/null +++ b/script/test/fixtures/volume-mounts/hostpath/docker-compose.yml @@ -0,0 +1,8 @@ +version: '2' +services: + db: + image: postgres:10.1 + ports: + - "5432" + volumes: + - ./data:/var/lib/postgresql/data \ No newline at end of file diff --git a/script/test/fixtures/volume-mounts/hostpath/output-k8s-template.json b/script/test/fixtures/volume-mounts/hostpath/output-k8s-template.json new file mode 100644 index 00000000..efb23c99 --- /dev/null +++ b/script/test/fixtures/volume-mounts/hostpath/output-k8s-template.json @@ -0,0 +1,96 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "db", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "db" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "ports": [ + { + "name": "5432", + "port": 5432, + "targetPort": 5432 + } + ], + "selector": { + "io.kompose.service": "db" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "db", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "db" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "db" + } + }, + "spec": { + "volumes": [ + { + "name": "db-hostpath0", + "hostPath": { + "path": "%HOSTPATH%" + } + } + ], + "containers": [ + { + "name": "db", + "image": "postgres:10.1", + "ports": [ + { + "containerPort": 5432 + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "db-hostpath0", + "mountPath": "/var/lib/postgresql/data" + } + ] + } + ], + "restartPolicy": "Always" + } + }, + "strategy": { + "type": "Recreate" + } + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/volume-mounts/hostpath/output-os-template.json b/script/test/fixtures/volume-mounts/hostpath/output-os-template.json new file mode 100644 index 00000000..2dc25c87 --- /dev/null +++ b/script/test/fixtures/volume-mounts/hostpath/output-os-template.json @@ -0,0 +1,147 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "db", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "db" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "ports": [ + { + "name": "5432", + "port": 5432, + "targetPort": 5432 + } + ], + "selector": { + "io.kompose.service": "db" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "db", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "db" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "strategy": { + "type": "Recreate", + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "db" + ], + "from": { + "kind": "ImageStreamTag", + "name": "db:10.1" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "db" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "db" + } + }, + "spec": { + "volumes": [ + { + "name": "db-hostpath0", + "hostPath": { + "path": "%HOSTPATH%" + } + } + ], + "containers": [ + { + "name": "db", + "image": " ", + "ports": [ + { + "containerPort": 5432 + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "db-hostpath0", + "mountPath": "/var/lib/postgresql/data" + } + ] + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "db", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "db" + } + }, + "spec": { + "tags": [ + { + "name": "10.1", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "postgres:10.1" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + } + ] +}