diff --git a/cmd/convert.go b/cmd/convert.go index e022646a..c2972f55 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -47,6 +47,7 @@ var ( ConvertReplicas int ConvertController string ConvertPushImage bool + ConvertNamespace string ConvertPushImageRegistry string ConvertOpt kobject.ConvertOptions ConvertYAMLIndent int @@ -122,6 +123,7 @@ var convertCmd = &cobra.Command{ GenerateNetworkPolicies: GenerateNetworkPolicies, BuildCommand: BuildCommand, PushCommand: PushCommand, + Namespace: ConvertNamespace, } if ServiceGroupMode == "" && MultipleContainerMode { @@ -186,6 +188,7 @@ func init() { 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"|"hostPath" | "configMap")`) convertCmd.Flags().StringVar(&ConvertPVCRequestSize, "pvc-request-size", "", `Specify the size of pvc storage requests in the generated resource spec`) + convertCmd.Flags().StringVarP(&ConvertNamespace, "namespace", "n", "", `Specify the namespace of the generated resources`) convertCmd.Flags().BoolVar(&GenerateNetworkPolicies, "generate-network-policies", false, "Specify whether to generate network policies or not.") convertCmd.Flags().BoolVar(&WithKomposeAnnotation, "with-kompose-annotation", true, "Add kompose annotations to generated resource") diff --git a/pkg/app/app.go b/pkg/app/app.go index 65e600a7..c1752789 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -219,6 +219,8 @@ func Convert(opt kobject.ConvertOptions) { log.Fatalf(err.Error()) } + komposeObject.Namespace = opt.Namespace + // Get a transformer that maps komposeObject to provider's primitives t := getTransformer(opt) diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index d98b7751..525008c5 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -38,6 +38,9 @@ type KomposeObject struct { LoadedFrom string Secrets types.Secrets + + // Namespace is the namespace where all the generated objects would be assigned to + Namespace string } // ConvertOptions holds all options that controls transformation process diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index b46d6f35..6a404cfe 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -1447,6 +1447,12 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. allobjects = append(allobjects, item) } } + + if komposeObject.Namespace != "" { + ns := transformer.CreateNamespace(komposeObject.Namespace) + allobjects = append(allobjects, ns) + } + if opt.ServiceGroupMode != "" { log.Debugf("Service group mode is: %s", opt.ServiceGroupMode) komposeObjectToServiceConfigGroupMapping := KomposeObjectToServiceConfigGroupMapping(&komposeObject, opt) @@ -1596,6 +1602,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. // sort all object so Services are first k.SortServicesFirst(&allobjects) k.RemoveDupObjects(&allobjects) + transformer.AssignNamespaceToObjects(&allobjects, komposeObject.Namespace) // k.FixWorkloadVersion(&allobjects) return allobjects, nil } diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go index a1426a2b..dd3fe448 100644 --- a/pkg/transformer/kubernetes/kubernetes_test.go +++ b/pkg/transformer/kubernetes/kubernetes_test.go @@ -1094,3 +1094,28 @@ func TestServiceGroupModeImagePullSecrets(t *testing.T) { } } } + +func TestNamespaceGeneration(t *testing.T) { + ns := "app" + komposeObject := kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, + Namespace: ns, + } + k := Kubernetes{} + objs, err := k.Transform(komposeObject, kobject.ConvertOptions{}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + for _, obj := range objs { + if namespace, ok := obj.(*api.Namespace); ok { + if strings.ToLower(ns) != strings.ToLower(namespace.ObjectMeta.Name) { + t.Errorf("Expected namespace name %v, got %v", ns, namespace.ObjectMeta.Name) + } + } + if dep, ok := obj.(*appsv1.Deployment); ok { + if dep.ObjectMeta.Namespace != ns { + t.Errorf("Expected deployment namespace %v, got %v", ns, dep.ObjectMeta.Namespace) + } + } + } +} diff --git a/pkg/transformer/openshift/openshift.go b/pkg/transformer/openshift/openshift.go index 780ad4ad..8e77262e 100644 --- a/pkg/transformer/openshift/openshift.go +++ b/pkg/transformer/openshift/openshift.go @@ -259,6 +259,12 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C } // this will hold all the converted data var allobjects []runtime.Object + + if komposeObject.Namespace != "" { + ns := transformer.CreateNamespace(komposeObject.Namespace) + allobjects = append(allobjects, ns) + } + var err error var composeFileDir string buildRepo := opt.BuildRepo @@ -422,6 +428,7 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C // sort all object so Services are first o.SortServicesFirst(&allobjects) o.RemoveDupObjects(&allobjects) + transformer.AssignNamespaceToObjects(&allobjects, komposeObject.Namespace) // o.FixWorkloadVersion(&allobjects) return allobjects, nil diff --git a/pkg/transformer/openshift/openshift_test.go b/pkg/transformer/openshift/openshift_test.go index b3750172..57813ebf 100644 --- a/pkg/transformer/openshift/openshift_test.go +++ b/pkg/transformer/openshift/openshift_test.go @@ -29,6 +29,7 @@ import ( "github.com/kubernetes/kompose/pkg/transformer/kubernetes" deployapi "github.com/openshift/api/apps/v1" "github.com/pkg/errors" + api "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -465,3 +466,28 @@ func TestServiceExternalTrafficPolicy(t *testing.T) { } } } + +func TestNamespaceGeneration(t *testing.T) { + ns := "app" + komposeObject := kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, + Namespace: ns, + } + o := OpenShift{} + objs, err := o.Transform(komposeObject, kobject.ConvertOptions{}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + for _, obj := range objs { + if namespace, ok := obj.(*api.Namespace); ok { + if strings.ToLower(ns) != strings.ToLower(namespace.ObjectMeta.Name) { + t.Errorf("Expected namespace name %v, got %v", ns, namespace.ObjectMeta.Name) + } + } + if dep, ok := obj.(*deployapi.DeploymentConfig); ok { + if dep.ObjectMeta.Namespace != ns { + t.Errorf("Expected deployment namespace %v, got %v", ns, dep.ObjectMeta.Namespace) + } + } + } +} diff --git a/pkg/transformer/utils.go b/pkg/transformer/utils.go index 7561ed8f..7caba1d3 100644 --- a/pkg/transformer/utils.go +++ b/pkg/transformer/utils.go @@ -31,6 +31,8 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" api "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) // Selector used as labels and selector @@ -443,3 +445,33 @@ func PushDockerImageWithOpt(service kobject.ServiceConfig, serviceName string, o return nil } + +// CreateNamespace creates a Kubernetes namespace, which can be used in both: +// Openshift and Kubernetes +func CreateNamespace(namespace string) *api.Namespace { + return &api.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } +} + +// AssignNamespaceToObjects will add the namespace metadata to each object +func AssignNamespaceToObjects(objs *[]runtime.Object, namespace string) { + ns := "default" + if namespace != "" { + ns = namespace + } + var result []runtime.Object + for _, obj := range *objs { + if us, ok := obj.(metav1.Object); ok { + us.SetNamespace(ns) + } + result = append(result, obj) + } + *objs = result +} diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index b44d3ef0..2504a771 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -271,11 +271,19 @@ convert::expect_success "$os_cmd" "$os_output" # Test support for network policies generation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/network-policies/docker-compose.yaml convert --generate-network-policies --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/network-policies/output-k8s.yaml" -convert::expect_success "$os_cmd" "$os_output" +convert::expect_success "$k8s_cmd" "$k8s_output" # Test support for custom build and push images k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/custom-build-push/docker-compose.yaml convert --build-command 'docker build -t ahmedgrati/kompose-test ./script/test/fixtures/custom-build-push' --push-command 'docker push ahmedgrati/kompose-test' --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/custom-build-push/output-k8s.yaml" +convert::expect_success "$k8s_cmd" "$k8s_output" + +# Test support for namespace generation +k8s_cmd="kompose -f ./script/test/fixtures/namespace/docker-compose.yaml convert --stdout --with-kompose-annotation=false -n web" +k8s_output="$KOMPOSE_ROOT/script/test/fixtures/namespace/output-k8s.yaml" +os_cmd="kompose -f ./script/test/fixtures/namespace/docker-compose.yaml convert --stdout --with-kompose-annotation=false -n web --provider openshift" +os_output="$KOMPOSE_ROOT/script/test/fixtures/namespace/output-os.yaml" +convert::expect_success "$k8s_cmd" "$k8s_output" convert::expect_success "$os_cmd" "$os_output" # Test support for read only root fs diff --git a/script/test/fixtures/namespace/docker-compose.yaml b/script/test/fixtures/namespace/docker-compose.yaml new file mode 100644 index 00000000..a7ef6e52 --- /dev/null +++ b/script/test/fixtures/namespace/docker-compose.yaml @@ -0,0 +1,6 @@ +version: '3' +services: + web: + image: nginx + ports: + - 80:80 diff --git a/script/test/fixtures/namespace/output-k8s.yaml b/script/test/fixtures/namespace/output-k8s.yaml new file mode 100644 index 00000000..5d262bfc --- /dev/null +++ b/script/test/fixtures/namespace/output-k8s.yaml @@ -0,0 +1,62 @@ +--- +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + io.kompose.service: web + name: web + namespace: web +spec: + ports: + - name: "80" + port: 80 + targetPort: 80 + selector: + io.kompose.service: web +status: + loadBalancer: {} + +--- +apiVersion: v1 +kind: Namespace +metadata: + creationTimestamp: null + name: web + namespace: web +spec: {} +status: {} + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + io.kompose.service: web + name: web + namespace: web +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: web + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + io.kompose.network/namespace-default: "true" + io.kompose.service: web + spec: + containers: + - image: nginx + name: web + ports: + - containerPort: 80 + hostPort: 80 + protocol: TCP + resources: {} + restartPolicy: Always +status: {} + diff --git a/script/test/fixtures/namespace/output-os.yaml b/script/test/fixtures/namespace/output-os.yaml new file mode 100644 index 00000000..65c15073 --- /dev/null +++ b/script/test/fixtures/namespace/output-os.yaml @@ -0,0 +1,104 @@ +--- +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + io.kompose.service: web + name: web + namespace: web +spec: + ports: + - name: "80" + port: 80 + targetPort: 80 + selector: + io.kompose.service: web +status: + loadBalancer: {} + +--- +apiVersion: v1 +kind: Namespace +metadata: + creationTimestamp: null + name: web + namespace: web +spec: {} +status: {} + +--- +apiVersion: apps.openshift.io/v1 +kind: DeploymentConfig +metadata: + creationTimestamp: null + labels: + io.kompose.service: web + name: web + namespace: web +spec: + replicas: 1 + selector: + io.kompose.service: web + strategy: + resources: {} + template: + metadata: + creationTimestamp: null + labels: + io.kompose.network/namespace-default: "true" + io.kompose.service: web + spec: + containers: + - image: ' ' + name: web + ports: + - containerPort: 80 + hostPort: 80 + protocol: TCP + resources: {} + restartPolicy: Always + test: false + triggers: + - type: ConfigChange + - imageChangeParams: + automatic: true + containerNames: + - web + from: + kind: ImageStreamTag + name: web:latest + type: ImageChange +status: + availableReplicas: 0 + latestVersion: 0 + observedGeneration: 0 + replicas: 0 + unavailableReplicas: 0 + updatedReplicas: 0 + +--- +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + creationTimestamp: null + labels: + io.kompose.service: web + name: web + namespace: web +spec: + lookupPolicy: + local: false + tags: + - annotations: null + from: + kind: DockerImage + name: nginx + generation: null + importPolicy: {} + name: latest + referencePolicy: + type: "" +status: + dockerImageRepository: "" + diff --git a/script/test/fixtures/read-only/output-k8s.yaml b/script/test/fixtures/read-only/output-k8s.yaml index d18582fd..7d7d3fbf 100644 --- a/script/test/fixtures/read-only/output-k8s.yaml +++ b/script/test/fixtures/read-only/output-k8s.yaml @@ -6,6 +6,7 @@ metadata: labels: io.kompose.service: test name: test + namespace: default spec: ports: - name: "80" @@ -24,6 +25,7 @@ metadata: labels: io.kompose.service: test name: test + namespace: default spec: replicas: 1 selector: diff --git a/script/test/fixtures/read-only/output-os.yaml b/script/test/fixtures/read-only/output-os.yaml index 40cc310e..876ce92a 100644 --- a/script/test/fixtures/read-only/output-os.yaml +++ b/script/test/fixtures/read-only/output-os.yaml @@ -6,6 +6,7 @@ metadata: labels: io.kompose.service: test name: test + namespace: default spec: ports: - name: "80" @@ -24,6 +25,7 @@ metadata: labels: io.kompose.service: test name: test + namespace: default spec: replicas: 1 selector: @@ -75,6 +77,7 @@ metadata: labels: io.kompose.service: test name: test + namespace: default spec: lookupPolicy: local: false