diff --git a/devfile.yaml b/devfile.yaml index 866caad9..b08f18de 100644 --- a/devfile.yaml +++ b/devfile.yaml @@ -1,106 +1,121 @@ -schemaVersion: 2.2.0-latest -metadata: - name: nodejs - version: 1.0.0 - attributes: - alpha.build-dockerfile: /relative/path/to/Dockerfile -variables: - test: testValue -parent: - # uri: https://raw.githubusercontent.com/odo-devfiles/registry/master/devfiles/nodejs/devfile.yaml - id: nodejs - registryUrl: "https://registry.devfile.io" - version: latest - commands: - - id: install - exec: - component: runtime - commandLine: npm install - workingDir: /project-starter - group: - kind: build - isDefault: true -starterProjects: -- name: nodejs-starter2 - git: - remotes: - origin: https://github.com/odo-devfiles/nodejs-ex.git components: -- name: runtime2 - attributes: - tool: console-import - import: - strategy: Dockerfile - container: - endpoints: - - name: http-8888 - targetPort: 8888 - image: registry.access.redhat.com/ubi8/nodejs-12:1-45 - memoryLimit: 1024Mi - mountSources: true - sourceMapping: /project - command: - - npm install -- name: runtime3 - attributes: - tool: odo - cli: - usage: deploy - container: - endpoints: - - name: http-8080 - targetPort: 8080 - image: registry.access.redhat.com/ubi8/nodejs-12:1-45 - memoryLimit: 1024Mi - mountSources: true - sourceMapping: /project -- name: runtime4 - attributes: - tool: workspace-operator - container: - endpoints: - - name: http-9090 - targetPort: 9090 - image: "{{invalid-var}}" - memoryLimit: 1024Mi - mountSources: true - sourceMapping: /project -commands: -- exec: - commandLine: npm install - component: runtime2 - group: - isDefault: false - kind: build - workingDir: "{{test}}" - id: install2 - attributes: - tool: odo - mandatory: false -- exec: - commandLine: npm start - component: runtime2 - group: - isDefault: false - kind: run - workingDir: /project - id: run2 - attributes: - tool: odo - mandatory: true -- exec: - commandLine: npm run debug - component: runtime2 - group: - isDefault: false - kind: debug - workingDir: /project - id: debug2 -- exec: - commandLine: npm test - component: runtime2 - group: - isDefault: false - kind: test - workingDir: /project - id: test2 + - kubernetes: + endpoints: + - name: jsct05850 + secure: false + targetPort: 4061 + - exposure: internal + name: fttrf05851 + secure: false + targetPort: 2794 + - name: pediv05852 + path: /Path_GpjjJ + secure: false + targetPort: 4061 + - name: w05853 + protocol: tcp + secure: false + targetPort: 4061 + - name: ve05854 + path: /Path_rUnibQXIL + secure: true + targetPort: 4061 + name: sxi05849 + - kubernetes: + endpoints: + - name: bj05856 + path: /Path_QEqKHLrqVSS + secure: false + targetPort: 1985 + - exposure: internal + name: udbeo05857 + secure: true + targetPort: 1985 + - exposure: internal + name: yol05858 + protocol: wss + secure: false + targetPort: 1985 + name: lsk05855 + - kubernetes: {} + name: shf05859 + - kubernetes: + endpoints: + - name: grmex05861 + protocol: tcp + secure: true + targetPort: 429 + - name: ork05862 + path: /Path_MofEhbd + protocol: wss + secure: false + targetPort: 3214 + name: zrk05860 + - kubernetes: {} + name: igh05863 + - kubernetes: + endpoints: + - name: emf05865 + path: /Path_ZkPBw + protocol: wss + secure: true + targetPort: 970 + - name: wqnaf05866 + path: /Path_UpxBmbMJDr + secure: true + targetPort: 1863 + - exposure: internal + name: rbk05867 + path: /Path_TTlXWYESKJwelJ + protocol: udp + secure: true + targetPort: 2706 + - exposure: none + name: zjiv05868 + path: /Path_JUQlS + protocol: udp + secure: true + targetPort: 970 + name: ecw05864 + - kubernetes: + endpoints: + - exposure: none + name: i05870 + protocol: tcp + secure: true + targetPort: 565 + - name: yln05871 + protocol: https + secure: true + targetPort: 4386 + - name: qyrrk05872 + path: /Path_gtsayNeeuFJ + secure: false + targetPort: 3546 + - exposure: none + name: vv05873 + secure: false + targetPort: 2295 + - name: dat05874 + path: /Path_kWxdWGLbCmZYf + protocol: wss + secure: false + targetPort: 2474 + name: lgv05869 + - kubernetes: + endpoints: + - name: koac05876 + secure: false + targetPort: 4318 + - name: lelw05877 + path: /Path_EhnGCYPmFHrZPA + protocol: ws + secure: true + targetPort: 592 + - name: j05878 + protocol: tcp + secure: false + targetPort: 4318 + name: qcu05875 +metadata: {} +schemaVersion: 2.2.0 diff --git a/go.mod b/go.mod index 2f88954f..fcce6dc0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/devfile/library go 1.15 require ( - github.com/devfile/api/v2 v2.0.0-20220309195345-48ebbf1e51cf + github.com/devfile/api/v2 v2.0.0-20220614133608-351f05b7c2b1 github.com/fatih/color v1.7.0 github.com/fsnotify/fsnotify v1.4.9 github.com/gobwas/glob v0.2.3 diff --git a/go.sum b/go.sum index a1db5810..dd457e98 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devfile/api/v2 v2.0.0-20220309195345-48ebbf1e51cf h1:FkwAOQtepscB5B0j++9S/eoicXj707MaP5HPIScz0sA= -github.com/devfile/api/v2 v2.0.0-20220309195345-48ebbf1e51cf/go.mod h1:kLX/nW93gigOHXK3NLeJL2fSS/sgEe+OHu8bo3aoOi4= +github.com/devfile/api/v2 v2.0.0-20220614133608-351f05b7c2b1 h1:rwhw0TQUCS/qT4iDsoOPV/AaopUiYYYXs5zXeX1n6Ts= +github.com/devfile/api/v2 v2.0.0-20220614133608-351f05b7c2b1/go.mod h1:dN7xFrOVG+iPqn4UKGibXLd5oVsdE8XyK9OEb5JL3aI= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= diff --git a/pkg/devfile/parse_test.go b/pkg/devfile/parse_test.go index 4fe85602..7c3efed5 100644 --- a/pkg/devfile/parse_test.go +++ b/pkg/devfile/parse_test.go @@ -1,7 +1,12 @@ package devfile import ( + v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "net" + "net/http" + "net/http/httptest" "reflect" + "strings" "testing" "github.com/devfile/api/v2/pkg/validation/variables" @@ -10,6 +15,81 @@ import ( ) func TestParseDevfileAndValidate(t *testing.T) { + convertUriToInline := false + K8sLikeComponentOriginalURIKey := "api.devfile.io/k8sLikeComponent-originalURI" + outerloopDeployContent := ` +kind: Deployment +apiVersion: apps/v1 +metadata: + name: my-python +spec: + replicas: 1 + selector: + matchLabels: + app: python-app + template: + metadata: + labels: + app: python-app + spec: + containers: + - name: my-python + image: my-python-image:{{ PARAMS }} + ports: + - name: http + containerPort: 8081 + protocol: TCP + resources: + limits: + memory: "128Mi" + cpu: "500m" +` + outerloopServiceContent := ` +apiVersion: v1 +kind: Service +metadata: + labels: + app: python-app + name: python-app-svc +spec: + ports: + - name: http-8081 + port: 8081 + protocol: TCP + targetPort: 8081 + selector: + app: python-app + variable: {{ PARAMS }} + type: LoadBalancer +` + uri := "127.0.0.1:8080" + var testServer *httptest.Server + testServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + if strings.Contains(r.URL.Path, "/outerloop-deploy.yaml") { + _, err = w.Write([]byte(outerloopDeployContent)) + } else if strings.Contains(r.URL.Path, "/outerloop-service.yaml") { + _, err = w.Write([]byte(outerloopServiceContent)) + } + if err != nil { + t.Errorf("unexpected error while writing yaml: %v", err) + } + + })) + // create a listener with the desired port. + l, err := net.Listen("tcp", uri) + if err != nil { + t.Errorf("Test_parseParentAndPluginFromURI() unexpected error while creating listener: %v", err) + } + + // NewUnstartedServer creates a listener. Close that listener and replace + // with the one we created. + testServer.Listener.Close() + testServer.Listener = l + + testServer.Start() + defer testServer.Close() + devfileContent := `commands: - exec: commandLine: ./main {{ PARAMS }} @@ -28,6 +108,12 @@ components: memoryLimit: 1024Mi mountSources: true name: runtime +- kubernetes: + uri: http://127.0.0.1:8080/outerloop-deploy.yaml + name: outerloop-deploy +- openshift: + uri: http://127.0.0.1:8080/outerloop-service.yaml + name: outerloop-deploy2 metadata: description: Stack with the latest Go version displayName: Go Runtime @@ -38,7 +124,7 @@ metadata: tags: - Go version: 1.0.0 -schemaVersion: 2.1.0 +schemaVersion: 2.2.0 ` devfileContentWithVariable := devfileContent + `variables: @@ -47,11 +133,13 @@ schemaVersion: 2.1.0 args parser.ParserArgs } tests := []struct { - name string - args args - wantVarWarning variables.VariableWarning - wantCommandLine string - wantVariables map[string]string + name string + args args + wantVarWarning variables.VariableWarning + wantCommandLine string + wantKubernetesInline string + wantOpenshiftInline string + wantVariables map[string]string }{ { name: "with external overriding variables", @@ -63,8 +151,9 @@ schemaVersion: 2.1.0 Data: []byte(devfileContentWithVariable), }, }, - - wantCommandLine: "./main bar", + wantKubernetesInline: "image: my-python-image:bar", + wantOpenshiftInline: "variable: bar", + wantCommandLine: "./main bar", wantVariables: map[string]string{ "PARAMS": "bar", }, @@ -85,8 +174,9 @@ schemaVersion: 2.1.0 Data: []byte(devfileContentWithVariable), }, }, - - wantCommandLine: "./main foo", + wantKubernetesInline: "image: my-python-image:foo", + wantOpenshiftInline: "variable: foo", + wantCommandLine: "./main foo", wantVariables: map[string]string{ "PARAMS": "foo", "OTHER": "other", @@ -107,7 +197,29 @@ schemaVersion: 2.1.0 Data: []byte(devfileContent), }, }, - + wantKubernetesInline: "image: my-python-image:baz", + wantOpenshiftInline: "variable: baz", + wantCommandLine: "./main baz", + wantVariables: map[string]string{ + "PARAMS": "baz", + }, + wantVarWarning: variables.VariableWarning{ + Commands: map[string][]string{}, + Components: map[string][]string{}, + Projects: map[string][]string{}, + StarterProjects: map[string][]string{}, + }, + }, { + name: "with external variables and covertUriToInline is false", + args: args{ + args: parser.ParserArgs{ + ExternalVariables: map[string]string{ + "PARAMS": "baz", + }, + ConvertKubernetesContentInUri: &convertUriToInline, + Data: []byte(devfileContent), + }, + }, wantCommandLine: "./main baz", wantVariables: map[string]string{ "PARAMS": "baz", @@ -135,6 +247,76 @@ schemaVersion: 2.1.0 if expectedCommandLine != tt.wantCommandLine { t.Errorf("command line is %q, should be %q", expectedCommandLine, tt.wantCommandLine) } + + getKubeCompOptions := common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ + ComponentType: v1.KubernetesComponentType, + }, + } + kubeComponents, err := gotD.Data.GetComponents(getKubeCompOptions) + if err != nil { + t.Errorf("unexpected error getting kubernetes component") + } + kubenetesComponent := kubeComponents[0] + + // check openshift component uri -> inline conversion and value substitution + getOpenshiftCompOptions := common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ + ComponentType: v1.OpenshiftComponentType, + }, + } + openshiftComponents, err := gotD.Data.GetComponents(getOpenshiftCompOptions) + if err != nil { + t.Errorf("unexpected error getting openshift component") + } + openshiftComponent := openshiftComponents[0] + + if tt.args.args.ConvertKubernetesContentInUri == nil || *tt.args.args.ConvertKubernetesContentInUri != false { + // check kubernetes component uri -> inline conversion and value substitution + if kubenetesComponent.Kubernetes.Uri != "" || kubenetesComponent.Kubernetes.Inlined == "" || + !strings.Contains(kubenetesComponent.Kubernetes.Inlined, tt.wantKubernetesInline) { + t.Errorf("unexpected kubenetes component inlined, got %s, want include %s", kubenetesComponent.Kubernetes.Inlined, tt.wantKubernetesInline) + } + + if kubenetesComponent.Attributes != nil { + if originalUri := kubenetesComponent.Attributes.GetString(K8sLikeComponentOriginalURIKey, &err); err != nil || originalUri != "http://127.0.0.1:8080/outerloop-deploy.yaml" { + t.Errorf("ParseDevfileAndValidate() should set kubenetesComponent.Attributes, '%s', expected http://127.0.0.1:8080/outerloop-deploy.yaml, got %s", + K8sLikeComponentOriginalURIKey, originalUri) + } + } else { + t.Error("ParseDevfileAndValidate() should set kubenetesComponent.Attributes, but got empty Attributes") + } + + // check openshift component uri -> inline conversion and value substitution + if openshiftComponent.Openshift.Uri != "" || openshiftComponent.Openshift.Inlined == "" || + !strings.Contains(openshiftComponent.Openshift.Inlined, tt.wantOpenshiftInline) { + t.Errorf("unexpected openshift component inlined, got %s, want include %s", openshiftComponent.Openshift.Inlined, tt.wantOpenshiftInline) + } + + if openshiftComponent.Attributes != nil { + if originalUri := openshiftComponent.Attributes.GetString(K8sLikeComponentOriginalURIKey, &err); err != nil || originalUri != "http://127.0.0.1:8080/outerloop-service.yaml" { + t.Errorf("ParseDevfileAndValidate() should set openshiftComponent.Attributes, '%s', expected http://127.0.0.1:8080/outerloop-service.yaml, got %s", + K8sLikeComponentOriginalURIKey, originalUri) + } + } else { + t.Error("ParseDevfileAndValidate() should set openshiftComponent.Attributes, but got empty Attributes") + } + } else { + if kubenetesComponent.Kubernetes.Uri == "" || kubenetesComponent.Kubernetes.Inlined != "" { + t.Errorf("unexpected Kubernetes component inlined, got %s, want empty", kubenetesComponent.Kubernetes.Inlined) + } + if kubenetesComponent.Attributes != nil { + t.Errorf("unexpected Kubernetes component attribute, got %v, want empty", kubenetesComponent.Attributes) + } + + if openshiftComponent.Openshift.Uri == "" || openshiftComponent.Openshift.Inlined != "" { + t.Errorf("unexpected Openshift component inlined, got %s, want empty", openshiftComponent.Openshift.Inlined) + } + if kubenetesComponent.Attributes != nil { + t.Errorf("unexpected Openshift component attribute, got %v, want empty", openshiftComponent.Attributes) + } + } + if !reflect.DeepEqual(gotVarWarning, tt.wantVarWarning) { t.Errorf("ParseDevfileAndValidate() gotVarWarning = %v, want %v", gotVarWarning, tt.wantVarWarning) } diff --git a/pkg/devfile/parser/context/context.go b/pkg/devfile/parser/context/context.go index 64e3b5d4..92c27f00 100644 --- a/pkg/devfile/parser/context/context.go +++ b/pkg/devfile/parser/context/context.go @@ -35,6 +35,9 @@ type DevfileCtx struct { // filesystem for devfile fs filesystem.Filesystem + + // devfile kubernetes components has been coverted from uri to inlined in memory + convertUriToInlined bool } // NewDevfileCtx returns a new DevfileCtx type object @@ -143,3 +146,13 @@ func (d *DevfileCtx) SetAbsPath() (err error) { return nil } + +// GetConvertUriToInlined func returns if the devfile kubernetes comp has been converted from uri to inlined +func (d *DevfileCtx) GetConvertUriToInlined() bool { + return d.convertUriToInlined +} + +// SetConvertUriToInlined sets if the devfile kubernetes comp has been converted from uri to inlined +func (d *DevfileCtx) SetConvertUriToInlined(value bool) { + d.convertUriToInlined = value +} diff --git a/pkg/devfile/parser/data/v2/2.2.0/devfileJsonSchema220.go b/pkg/devfile/parser/data/v2/2.2.0/devfileJsonSchema220.go index d729f7cf..1fa44489 100644 --- a/pkg/devfile/parser/data/v2/2.2.0/devfileJsonSchema220.go +++ b/pkg/devfile/parser/data/v2/2.2.0/devfileJsonSchema220.go @@ -372,7 +372,7 @@ const JsonSchema220 = `{ "type": "boolean" }, "targetPort": { - "description": "The port number should be unique.", + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", "type": "integer" } }, @@ -491,7 +491,7 @@ const JsonSchema220 = `{ } }, "buildContext": { - "description": "Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container", + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", "type": "string" }, "devfileRegistry": { @@ -644,7 +644,7 @@ const JsonSchema220 = `{ "type": "boolean" }, "targetPort": { - "description": "The port number should be unique.", + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", "type": "integer" } }, @@ -746,7 +746,7 @@ const JsonSchema220 = `{ "type": "boolean" }, "targetPort": { - "description": "The port number should be unique.", + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", "type": "integer" } }, @@ -1258,7 +1258,7 @@ const JsonSchema220 = `{ "type": "boolean" }, "targetPort": { - "description": "The port number should be unique.", + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", "type": "integer" } }, @@ -1377,7 +1377,7 @@ const JsonSchema220 = `{ } }, "buildContext": { - "description": "Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container", + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", "type": "string" }, "devfileRegistry": { @@ -1521,7 +1521,7 @@ const JsonSchema220 = `{ "type": "boolean" }, "targetPort": { - "description": "The port number should be unique.", + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", "type": "integer" } }, @@ -1620,7 +1620,7 @@ const JsonSchema220 = `{ "type": "boolean" }, "targetPort": { - "description": "The port number should be unique.", + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", "type": "integer" } }, diff --git a/pkg/devfile/parser/devfileobj.go b/pkg/devfile/parser/devfileobj.go index 65c669c8..cb076493 100644 --- a/pkg/devfile/parser/devfileobj.go +++ b/pkg/devfile/parser/devfileobj.go @@ -7,7 +7,8 @@ import ( // Default filenames for create devfile const ( - OutputDevfileYamlPath = "devfile.yaml" + OutputDevfileYamlPath = "devfile.yaml" + K8sLikeComponentOriginalURIKey = "api.devfile.io/k8sLikeComponent-originalURI" ) // DevfileObj is the runtime devfile object diff --git a/pkg/devfile/parser/parse.go b/pkg/devfile/parser/parse.go index a4ba3820..3e4863f0 100644 --- a/pkg/devfile/parser/parse.go +++ b/pkg/devfile/parser/parse.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/devfile/api/v2/pkg/attributes" "net/url" "path" "strings" @@ -72,6 +73,9 @@ type ParserArgs struct { // FlattenedDevfile defines if the returned devfileObj is flattened content (true) or raw content (false). // The value is default to be true. FlattenedDevfile *bool + // ConvertKubernetesContentInUri defines if the kubernetes resources definition from uri will be converted to inlined in devObj(true) or not (false). + // The value is default to be true. + ConvertKubernetesContentInUri *bool // RegistryURLs is a list of registry hosts which parser should pull parent devfile from. // If registryUrl is defined in devfile, this list will be ignored. RegistryURLs []string @@ -127,6 +131,19 @@ func ParseDevfile(args ParserArgs) (d DevfileObj, err error) { } } + convertUriToInlined := true + if args.ConvertKubernetesContentInUri != nil { + convertUriToInlined = *args.ConvertKubernetesContentInUri + } + + if convertUriToInlined { + d.Ctx.SetConvertUriToInlined(true) + err = parseKubeResourceFromURI(d) + if err != nil { + return d, errors.Wrapf(err, "failed to parse kubernetes/openshift component from uri to inlined") + } + } + return d, err } @@ -599,3 +616,117 @@ func setEndpoints(endpoints []v1.Endpoint) { endpoints[i].Secure = &val } } + +//parseKubeResourceFromURI iterate through all kubernetes & openshift components, and parse from uri and update the content to inlined field in devfileObj +func parseKubeResourceFromURI(devObj DevfileObj) error { + getKubeCompOptions := common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ + ComponentType: v1.KubernetesComponentType, + }, + } + getOpenshiftCompOptions := common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ + ComponentType: v1.OpenshiftComponentType, + }, + } + kubeComponents, err := devObj.Data.GetComponents(getKubeCompOptions) + if err != nil { + return err + } + openshiftComponents, err := devObj.Data.GetComponents(getOpenshiftCompOptions) + if err != nil { + return err + } + for _, kubeComp := range kubeComponents { + if kubeComp.Kubernetes != nil && kubeComp.Kubernetes.Uri != "" { + err := convertK8sLikeCompUriToInlined(&kubeComp, devObj.Ctx) + if err != nil { + return errors.Wrapf(err, "failed to convert Kubernetes Uri to inlined for component '%s'", kubeComp.Name) + } + err = devObj.Data.UpdateComponent(kubeComp) + if err != nil { + return err + } + } + } + for _, openshiftComp := range openshiftComponents { + if openshiftComp.Openshift != nil && openshiftComp.Openshift.Uri != "" { + err := convertK8sLikeCompUriToInlined(&openshiftComp, devObj.Ctx) + if err != nil { + return errors.Wrapf(err, "failed to convert Openshift Uri to inlined for component '%s'", openshiftComp.Name) + } + err = devObj.Data.UpdateComponent(openshiftComp) + if err != nil { + return err + } + } + } + return nil +} + +//convertK8sLikeCompUriToInlined read in kubernetes resources definition from uri and converts to kubernetest inlined field +func convertK8sLikeCompUriToInlined(component *v1.Component, d devfileCtx.DevfileCtx) error { + var uri string + if component.Kubernetes != nil { + uri = component.Kubernetes.Uri + } else if component.Openshift != nil { + uri = component.Openshift.Uri + } + data, err := getKubernetesDefinitionFromUri(uri, d) + if err != nil { + return err + } + if component.Kubernetes != nil { + component.Kubernetes.Inlined = string(data) + component.Kubernetes.Uri = "" + } else if component.Openshift != nil { + component.Openshift.Inlined = string(data) + component.Openshift.Uri = "" + } + if component.Attributes == nil { + component.Attributes = attributes.Attributes{} + } + component.Attributes.PutString(K8sLikeComponentOriginalURIKey, uri) + + return nil +} + +//getKubernetesDefinitionFromUri read in kubernetes resources definition from uri and returns the raw content +func getKubernetesDefinitionFromUri(uri string, d devfileCtx.DevfileCtx) ([]byte, error) { + // validate URI + err := validation.ValidateURI(uri) + if err != nil { + return nil, err + } + + absoluteURL := strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://") + var newUri string + var data []byte + // relative path on disk + if !absoluteURL && d.GetAbsPath() != "" { + newUri = path.Join(path.Dir(d.GetAbsPath()), uri) + fs := d.GetFs() + data, err = fs.ReadFile(newUri) + if err != nil { + return nil, errors.Wrapf(err, "failed to read kubernetes resources definition from path '%s'", newUri) + } + } else if absoluteURL || d.GetURL() != "" { + if d.GetURL() != "" { + // relative path to a URL + u, err := url.Parse(d.GetURL()) + if err != nil { + return nil, err + } + u.Path = path.Join(path.Dir(u.Path), uri) + newUri = u.String() + } else { + // absolute URL address + newUri = uri + } + data, err = util.DownloadFileInMemory(newUri) + if err != nil { + return nil, errors.Wrapf(err, "error getting kubernetes resources definition info from url '%s'", newUri) + } + } + return data, nil +} diff --git a/pkg/devfile/parser/writer.go b/pkg/devfile/parser/writer.go index 78abd8f9..45d371dd 100644 --- a/pkg/devfile/parser/writer.go +++ b/pkg/devfile/parser/writer.go @@ -1,6 +1,9 @@ package parser import ( + v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/devfile/library/pkg/devfile/parser/data/v2/common" "sigs.k8s.io/yaml" "github.com/devfile/library/pkg/testingutil/filesystem" @@ -11,6 +14,13 @@ import ( // WriteYamlDevfile creates a devfile.yaml file func (d *DevfileObj) WriteYamlDevfile() error { + // Check kubernetes components, and restore original uri content + if d.Ctx.GetConvertUriToInlined() { + err := restoreK8sCompURI(d) + if err != nil { + return errors.Wrapf(err, "failed to restore kubernetes component uri field") + } + } // Encode data into YAML format yamlData, err := yaml.Marshal(d.Data) if err != nil { @@ -30,3 +40,61 @@ func (d *DevfileObj) WriteYamlDevfile() error { klog.V(2).Infof("devfile yaml created at: '%s'", OutputDevfileYamlPath) return nil } + +func restoreK8sCompURI(devObj *DevfileObj) error { + getKubeCompOptions := common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ + ComponentType: v1.KubernetesComponentType, + }, + } + getOpenshiftCompOptions := common.DevfileOptions{ + ComponentOptions: common.ComponentOptions{ + ComponentType: v1.OpenshiftComponentType, + }, + } + kubeComponents, err := devObj.Data.GetComponents(getKubeCompOptions) + if err != nil { + return err + } + openshiftComponents, err := devObj.Data.GetComponents(getOpenshiftCompOptions) + if err != nil { + return err + } + + for _, kubeComp := range kubeComponents { + uri := kubeComp.Attributes.GetString(K8sLikeComponentOriginalURIKey, &err) + if err != nil { + if _, ok := err.(*apiAttributes.KeyNotFoundError); !ok { + return err + } + } + if uri != "" { + kubeComp.Kubernetes.Uri = uri + kubeComp.Kubernetes.Inlined = "" + delete(kubeComp.Attributes, K8sLikeComponentOriginalURIKey) + err = devObj.Data.UpdateComponent(kubeComp) + if err != nil { + return err + } + } + } + + for _, openshiftComp := range openshiftComponents { + uri := openshiftComp.Attributes.GetString(K8sLikeComponentOriginalURIKey, &err) + if err != nil { + if _, ok := err.(*apiAttributes.KeyNotFoundError); !ok { + return err + } + } + if uri != "" { + openshiftComp.Openshift.Uri = uri + openshiftComp.Openshift.Inlined = "" + delete(openshiftComp.Attributes, K8sLikeComponentOriginalURIKey) + err = devObj.Data.UpdateComponent(openshiftComp) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/devfile/parser/writer_test.go b/pkg/devfile/parser/writer_test.go index 9601a253..e7828279 100644 --- a/pkg/devfile/parser/writer_test.go +++ b/pkg/devfile/parser/writer_test.go @@ -1,20 +1,26 @@ package parser import ( - "testing" - + "fmt" v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" devfilepkg "github.com/devfile/api/v2/pkg/devfile" devfileCtx "github.com/devfile/library/pkg/devfile/parser/context" v2 "github.com/devfile/library/pkg/devfile/parser/data/v2" "github.com/devfile/library/pkg/testingutil/filesystem" + "strings" + "testing" ) func TestWriteYamlDevfile(t *testing.T) { var ( - schemaVersion = "2.0.0" + schemaVersion = "2.2.0" testName = "TestName" + uri = "./relative/path/deploy.yaml" + uri2 = "./relative/path/deploy2.yaml" + attributes = apiAttributes.Attributes{}.PutString(K8sLikeComponentOriginalURIKey, uri) + attributes2 = apiAttributes.Attributes{}.PutString(K8sLikeComponentOriginalURIKey, uri2) ) t.Run("write yaml devfile", func(t *testing.T) { @@ -33,9 +39,42 @@ func TestWriteYamlDevfile(t *testing.T) { Name: testName, }, }, + DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ + Components: []v1.Component{ + { + Name: "kubeComp", + Attributes: attributes, + ComponentUnion: v1.ComponentUnion{ + Kubernetes: &v1.KubernetesComponent{ + K8sLikeComponent: v1.K8sLikeComponent{ + K8sLikeComponentLocation: v1.K8sLikeComponentLocation{ + Inlined: "placeholder", + }, + }, + }, + }, + }, + { + Name: "openshiftComp", + Attributes: attributes2, + ComponentUnion: v1.ComponentUnion{ + Openshift: &v1.OpenshiftComponent{ + K8sLikeComponent: v1.K8sLikeComponent{ + K8sLikeComponentLocation: v1.K8sLikeComponentLocation{ + Inlined: "placeholder", + }, + }, + }, + }, + }, + }, + }, + }, }, }, } + devfileObj.Ctx.SetConvertUriToInlined(true) // test func() err := devfileObj.WriteYamlDevfile() @@ -46,5 +85,21 @@ func TestWriteYamlDevfile(t *testing.T) { if _, err := fs.Stat(OutputDevfileYamlPath); err != nil { t.Errorf("TestWriteYamlDevfile() unexpected error: '%v'", err) } + + data, err := fs.ReadFile(OutputDevfileYamlPath) + if err != nil { + t.Errorf("TestWriteYamlDevfile() unexpected error: '%v'", err) + } else { + content := string(data) + if strings.Contains(content, "inlined") || strings.Contains(content, K8sLikeComponentOriginalURIKey) { + t.Errorf("TestWriteYamlDevfile() failed: kubernetes component should not contain inlined or %s", K8sLikeComponentOriginalURIKey) + } + if !strings.Contains(content, fmt.Sprintf("uri: %s", uri)) { + t.Errorf("TestWriteYamlDevfile() failed: kubernetes component does not contain uri") + } + if !strings.Contains(content, fmt.Sprintf("uri: %s", uri2)) { + t.Errorf("TestWriteYamlDevfile() failed: openshift component does not contain uri") + } + } }) } diff --git a/tests/v2/utils/library/test_utils.go b/tests/v2/utils/library/test_utils.go index d762331d..e93f97ad 100644 --- a/tests/v2/utils/library/test_utils.go +++ b/tests/v2/utils/library/test_utils.go @@ -179,8 +179,9 @@ func validateDevfile(devfile *commonUtils.TestDevfile) error { var err error commonUtils.LogInfoMessage(fmt.Sprintf("Parse and Validate %s : ", devfile.FileName)) - + parseK8sDefinitionFromURI := false parserArgs.Path = devfile.FileName + parserArgs.ConvertKubernetesContentInUri = &parseK8sDefinitionFromURI libraryObj, warning, err := devfilepkg.ParseDevfileAndValidate(parserArgs) if len(warning.Commands) > 0 || len(warning.Components) > 0 || len(warning.Projects) > 0 || len(warning.StarterProjects) > 0 { @@ -189,6 +190,7 @@ func validateDevfile(devfile *commonUtils.TestDevfile) error { if err != nil { commonUtils.LogErrorMessage(fmt.Sprintf("From ParseDevfileAndValidate %v : ", err)) + return err } else { follower := devfile.Follower.(DevfileFollower) follower.LibraryData = libraryObj.Data