From 1f0a097836fb4e0ae4a802eb7ab543a4f9493727 Mon Sep 17 00:00:00 2001 From: Hang Yan Date: Thu, 26 Dec 2019 23:45:58 +0800 Subject: [PATCH] Support assign nodeport port in labels (#1210) --- docs/user-guide.md | 2 + pkg/kobject/kobject.go | 1 + pkg/loader/compose/utils.go | 2 + pkg/loader/compose/v1v2.go | 12 ++ pkg/loader/compose/v3.go | 11 ++ pkg/transformer/kubernetes/kubernetes.go | 5 + script/test/cmd/tests.sh | 13 ++ .../service-label/docker-compose.yaml | 13 ++ .../fixtures/service-label/output-k8s.json | 114 +++++++++++++ .../fixtures/service-label/output-oc.json | 152 ++++++++++++++++++ 10 files changed, 325 insertions(+) create mode 100644 script/test/fixtures/service-label/docker-compose.yaml create mode 100644 script/test/fixtures/service-label/output-k8s.json create mode 100644 script/test/fixtures/service-label/output-oc.json diff --git a/docs/user-guide.md b/docs/user-guide.md index d829954c..52de3c0b 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -299,6 +299,7 @@ The currently supported options are: |----------------------|-------------------------------------| | kompose.service.type | nodeport / clusterip / loadbalancer / headless | | kompose.service.expose | true / hostnames (separated by comma) | +| kompose.service.nodeport.port | port value (string) | | kompose.service.expose.tls-secret | secret name | | kompose.volume.size | kubernetes supported volume size | | kompose.controller.type | deployment / daemonset / replicationcontroller | @@ -330,6 +331,7 @@ services: - `kompose.service.expose` defines if the service needs to be made accessible from outside the cluster or not. If the value is set to "true", the provider sets the endpoint automatically, and for any other value, the value is set as the hostname. If multiple ports are defined in a service, the first one is chosen to be the exposed. - For the Kubernetes provider, an ingress resource is created and it is assumed that an ingress controller has already been configured. If the value is set to a comma sepatated list, multiple hostnames are supported. - For the OpenShift provider, a route is created. +- `kompose.service.nodeport.port` defines the port value when service type is `nodeport`, this label should only be set when the service only contains 1 port. Usually kubernetes define a port range for node port values, kompose will not validate this. - `kompose.service.expose.tls-secret` provides the name of the TLS secret to use with the Kubernetes ingress controller. This requires kompose.service.expose to be set. For example: diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 9e08b3cf..f2adc563 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -96,6 +96,7 @@ type ServiceConfig struct { User string `compose:"user"` VolumesFrom []string `compose:"volumes_from"` ServiceType string `compose:"kompose.service.type"` + NodePortPort int32 `compose:"kompose.service.nodeport.port"` StopGracePeriod string `compose:"stop_grace_period"` Build string `compose:"build"` BuildArgs map[string]*string `compose:"build-args"` diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index 2a1bfaed..45f67700 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -31,6 +31,8 @@ import ( const ( // LabelServiceType defines the type of service to be created LabelServiceType = "kompose.service.type" + // LabelNodePortPort defines the port value for NodePort service + LabelNodePortPort = "kompose.service.nodeport.port" // LabelServiceExpose defines if the service needs to be made accessible from outside the cluster or not LabelServiceExpose = "kompose.service.expose" // LabelServiceExposeTLSSecret provides the name of the TLS secret to use with the Kubernetes ingress controller diff --git a/pkg/loader/compose/v1v2.go b/pkg/loader/compose/v1v2.go index 3f1f8823..055ddeea 100644 --- a/pkg/loader/compose/v1v2.go +++ b/pkg/loader/compose/v1v2.go @@ -18,6 +18,7 @@ package compose import ( "fmt" + "github.com/spf13/cast" "net" "os" "path/filepath" @@ -245,6 +246,8 @@ func libComposeToKomposeMapping(composeObject *project.Project) (kobject.Kompose serviceConfig.ExposeService = strings.Trim(strings.ToLower(value), " ,") case LabelServiceExposeTLSSecret: serviceConfig.ExposeServiceTLS = value + case LabelNodePortPort: + serviceConfig.NodePortPort = cast.ToInt32(value) case LabelImagePullSecret: serviceConfig.ImagePullSecret = value case LabelImagePullPolicy: @@ -256,6 +259,15 @@ func libComposeToKomposeMapping(composeObject *project.Project) (kobject.Kompose if serviceConfig.ExposeService == "" && serviceConfig.ExposeServiceTLS != "" { return kobject.KomposeObject{}, errors.New("kompose.service.expose.tls-secret was specified without kompose.service.expose") } + + if serviceConfig.ServiceType != string(api.ServiceTypeNodePort) && serviceConfig.NodePortPort != 0 { + return kobject.KomposeObject{}, errors.New("kompose.service.type must be nodeport when assign node port value") + } + + if len(serviceConfig.Port) > 1 && serviceConfig.NodePortPort != 0 { + return kobject.KomposeObject{}, errors.New("cannnot set kompose.service.nodeport.port when service has multiple ports") + } + err = checkLabelsPorts(len(serviceConfig.Port), composeServiceConfig.Labels[LabelServiceType], name) if err != nil { return kobject.KomposeObject{}, errors.Wrap(err, "kompose.service.type can't be set if service doesn't expose any ports.") diff --git a/pkg/loader/compose/v3.go b/pkg/loader/compose/v3.go index f071a748..922d7c1b 100755 --- a/pkg/loader/compose/v3.go +++ b/pkg/loader/compose/v3.go @@ -17,6 +17,7 @@ limitations under the License. package compose import ( + "github.com/spf13/cast" "strconv" "strings" "time" @@ -424,6 +425,8 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose serviceConfig.ServiceType = serviceType case LabelServiceExpose: serviceConfig.ExposeService = strings.Trim(strings.ToLower(value), " ,") + case LabelNodePortPort: + serviceConfig.NodePortPort = cast.ToInt32(value) case LabelServiceExposeTLSSecret: serviceConfig.ExposeServiceTLS = value case LabelImagePullSecret: @@ -437,6 +440,14 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose return kobject.KomposeObject{}, errors.New("kompose.service.expose.tls-secret was specified without kompose.service.expose") } + if serviceConfig.ServiceType != string(api.ServiceTypeNodePort) && serviceConfig.NodePortPort != 0 { + return kobject.KomposeObject{}, errors.New("kompose.service.type must be nodeport when assign node port value") + } + + if len(serviceConfig.Port) > 1 && serviceConfig.NodePortPort != 0 { + return kobject.KomposeObject{}, errors.New("cannot set kompose.service.nodeport.port when service has multiple ports") + } + // Log if the name will been changed if normalizeServiceNames(name) != name { log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name)) diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 72ac4983..fd6aff3c 100755 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -525,6 +525,11 @@ func (k *Kubernetes) ConfigServicePorts(name string, service kobject.ServiceConf Port: port.HostPort, TargetPort: targetPort, } + + if service.ServiceType == string(api.ServiceTypeNodePort) && service.NodePortPort != 0 { + servicePort.NodePort = service.NodePortPort + } + // If the default is already TCP, no need to include it. if port.Protocol != api.ProtocolTCP { servicePort.Protocol = port.Protocol diff --git a/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 780ff7c1..cf60cb14 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -488,6 +488,19 @@ convert::expect_failure "kompose -f $KOMPOSE_ROOT/script/test/fixtures/dockerfil convert::expect_failure "kompose -f $KOMPOSE_ROOT/script/test/fixtures/label-port/docker-compose.yml convert --stdout" +#### +# test node port with port set +cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/service-label/docker-compose.yaml convert --stdout -j" +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/fixtures/service-label/output-k8s.json > /tmp/output-k8s.json +convert::expect_success "$cmd" "/tmp/output-k8s.json" + +cmd="kompose --provider openshift -f $KOMPOSE_ROOT/script/test/fixtures/service-label/docker-compose.yaml convert --stdout -j" +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/fixtures/service-label/output-oc.json > /tmp/output-oc.json +convert::expect_success "$cmd" "/tmp/output-oc.json" + + + + ###### # Test the output file behavior of kompose convert # Default behavior without -o diff --git a/script/test/fixtures/service-label/docker-compose.yaml b/script/test/fixtures/service-label/docker-compose.yaml new file mode 100644 index 00000000..9ff336d8 --- /dev/null +++ b/script/test/fixtures/service-label/docker-compose.yaml @@ -0,0 +1,13 @@ +version: '2' +services: + mariadb: + ports: + - '3306:3306' + labels: + kompose.service.type: nodeport + kompose.service.nodeport.port: "33111" + image: 'bitnami/mariadb:latest' + environment: + - MARIADB_USER=bn_wordpress + - MARIADB_DATABASE=bitnami_wordpress + - ALLOW_EMPTY_PASSWORD=yes diff --git a/script/test/fixtures/service-label/output-k8s.json b/script/test/fixtures/service-label/output-k8s.json new file mode 100644 index 00000000..77273570 --- /dev/null +++ b/script/test/fixtures/service-label/output-k8s.json @@ -0,0 +1,114 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "mariadb", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mariadb" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.service.nodeport.port": "33111", + "kompose.service.type": "nodeport", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "ports": [ + { + "name": "3306", + "port": 3306, + "targetPort": 3306, + "nodePort": 33111 + } + ], + "selector": { + "io.kompose.service": "mariadb" + }, + "type": "NodePort" + }, + "status": { + "loadBalancer": {} + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.service.nodeport.port": "33111", + "kompose.service.type": "nodeport", + "kompose.version": "%VERSION%" + }, + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mariadb" + }, + "name": "mariadb" + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "io.kompose.service": "mariadb" + } + }, + "strategy": {}, + "template": { + "metadata": { + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.service.nodeport.port": "33111", + "kompose.service.type": "nodeport", + "kompose.version": "%VERSION%" + }, + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mariadb" + } + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "ALLOW_EMPTY_PASSWORD", + "value": "yes" + }, + { + "name": "MARIADB_DATABASE", + "value": "bitnami_wordpress" + }, + { + "name": "MARIADB_USER", + "value": "bn_wordpress" + } + ], + "image": "bitnami/mariadb:latest", + "imagePullPolicy": "", + "name": "mariadb", + "ports": [ + { + "containerPort": 3306 + } + ], + "resources": {} + } + ], + "restartPolicy": "Always", + "serviceAccountName": "", + "volumes": null + } + } + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/service-label/output-oc.json b/script/test/fixtures/service-label/output-oc.json new file mode 100644 index 00000000..a4a73164 --- /dev/null +++ b/script/test/fixtures/service-label/output-oc.json @@ -0,0 +1,152 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "mariadb", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mariadb" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.service.nodeport.port": "33111", + "kompose.service.type": "nodeport", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "ports": [ + { + "name": "3306", + "port": 3306, + "targetPort": 3306, + "nodePort": 33111 + } + ], + "selector": { + "io.kompose.service": "mariadb" + }, + "type": "NodePort" + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "mariadb", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mariadb" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.service.nodeport.port": "33111", + "kompose.service.type": "nodeport", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "mariadb" + ], + "from": { + "kind": "ImageStreamTag", + "name": "mariadb:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "mariadb" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mariadb" + } + }, + "spec": { + "containers": [ + { + "name": "mariadb", + "image": " ", + "ports": [ + { + "containerPort": 3306 + } + ], + "env": [ + { + "name": "ALLOW_EMPTY_PASSWORD", + "value": "yes" + }, + { + "name": "MARIADB_DATABASE", + "value": "bitnami_wordpress" + }, + { + "name": "MARIADB_USER", + "value": "bn_wordpress" + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "mariadb", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mariadb" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "bitnami/mariadb:latest" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + } + ] +}