├── OWNERS ├── .vscode └── launch.json ├── tests ├── dockerfiles │ ├── Dockerfile │ ├── DockerfileInvalidFROM │ ├── DockerfileInvalid │ ├── DockerfileWithWhitespace │ └── DockerfileWithComment ├── v2 │ ├── devfiles │ │ └── samples │ │ │ ├── Test_Parent_RegistryURL.yaml │ │ │ ├── Test_Parent_KubeCRD.yaml │ │ │ ├── Test_200.yaml │ │ │ ├── Test_210.yaml │ │ │ ├── Test_220.yaml │ │ │ ├── Parent.yaml │ │ │ ├── Test_Parent_LocalURI.yaml │ │ │ └── testParent.yaml │ └── utils │ │ └── library │ │ └── command_test_utils.go └── README.md ├── license_header.txt ├── .gitignore ├── pkg ├── devfile │ ├── testdata │ │ ├── .devfile.yml │ │ ├── devfile.yml │ │ ├── valid-devfile.yaml.txt │ │ ├── priority-for-devfile_yml │ │ │ ├── .devfile.yml │ │ │ └── devfile.yml │ │ ├── priority-for-dot_devfile_yaml │ │ │ ├── .devfile.yml │ │ │ ├── devfile.yml │ │ │ └── .devfile.yaml │ │ ├── priority-for-dot_devfile_yml │ │ │ └── .devfile.yml │ │ ├── .devfile.yaml │ │ └── devfile.yaml │ ├── parser │ │ ├── context │ │ │ ├── fs.go │ │ │ ├── fakecontext.go │ │ │ ├── schema.go │ │ │ ├── location.go │ │ │ ├── apiVersion.go │ │ │ ├── context_test.go │ │ │ ├── content.go │ │ │ ├── apiVersion_test.go │ │ │ └── schema_test.go │ │ ├── data │ │ │ ├── v2 │ │ │ │ ├── types.go │ │ │ │ ├── parent.go │ │ │ │ ├── header.go │ │ │ │ ├── common │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── component_helper.go │ │ │ │ │ ├── project_helper.go │ │ │ │ │ ├── options_test.go │ │ │ │ │ ├── options.go │ │ │ │ │ └── command_helper.go │ │ │ │ ├── workspace.go │ │ │ │ ├── parent_test.go │ │ │ │ ├── attributes.go │ │ │ │ ├── events.go │ │ │ │ ├── workspace_test.go │ │ │ │ ├── commands.go │ │ │ │ ├── volumes.go │ │ │ │ └── components.go │ │ │ ├── helper.go │ │ │ ├── helper_test.go │ │ │ ├── versions.go │ │ │ └── interface.go │ │ ├── util │ │ │ ├── interface.go │ │ │ ├── utils.go │ │ │ └── mock.go │ │ ├── errors │ │ │ └── errors.go │ │ ├── devfileobj.go │ │ ├── resolutionContext.go │ │ ├── writer.go │ │ ├── utils.go │ │ ├── configurables.go │ │ └── writer_test.go │ ├── validate │ │ └── validate.go │ └── parse.go ├── testingutil │ ├── filesystem │ │ ├── singleton.go │ │ ├── filesystem.go │ │ ├── watcher.go │ │ └── fake_fs.go │ ├── containers.go │ ├── resources.go │ └── k8sClient.go └── util │ └── httpcache.go ├── .github ├── workflows │ ├── proxy-warming.yml │ ├── codecov.yml │ ├── go.yml │ └── scorecard.yml └── PULL_REQUEST_TEMPLATE.md ├── .clomonitor.yml ├── DCO ├── replaceSchemaFile.go ├── SECURITY-INSIGHTS.yml ├── .codecov.yaml ├── Makefile ├── scripts ├── changelog-script.sh └── updateApi.sh ├── devfile.yaml ├── CONTRIBUTING.md └── main.go /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md 2 | 3 | approvers: 4 | - elsony 5 | - Jdubrick 6 | - michael-valdron 7 | - thepetk 8 | 9 | reviewers: 10 | - elsony 11 | - Jdubrick 12 | - michael-valdron 13 | - thepetk -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "useApiV1": false, 6 | "name": "Debug parser", 7 | "type": "go", 8 | "request": "launch", 9 | "mode": "auto", 10 | "program": "${workspaceFolder}", 11 | "cwd": "${workspaceFolder}", 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tests/dockerfiles/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | COPY . /project/ 4 | 5 | # Removing node_modules 6 | RUN rm -rf /project/node_modules 7 | 8 | # Install user-app dependencies 9 | WORKDIR /project 10 | RUN npm install --production 11 | 12 | # Copy the dependencies into a slim Node docker image 13 | FROM node:12-slim 14 | 15 | # Copy project with dependencies 16 | COPY --chown=node:node --from=0 /project /project 17 | 18 | WORKDIR /project 19 | 20 | ENV NODE_ENV production 21 | 22 | USER node 23 | 24 | CMD ["npm", "start"] 25 | -------------------------------------------------------------------------------- /tests/dockerfiles/DockerfileInvalidFROM: -------------------------------------------------------------------------------- 1 | FR0M node:12 2 | 3 | COPY . /project/ 4 | 5 | # Removing node_modules 6 | RUN rm -rf /project/node_modules 7 | 8 | # Install user-app dependencies 9 | WORKDIR /project 10 | RUN npm install --production 11 | 12 | # Copy the dependencies into a slim Node docker image 13 | FROM node:12-slim 14 | 15 | # Copy project with dependencies 16 | COPY --chown=node:node --from=0 /project /project 17 | 18 | WORKDIR /project 19 | 20 | ENV NODE_ENV production 21 | 22 | USER node 23 | 24 | CMD ["npm", "start"] 25 | -------------------------------------------------------------------------------- /tests/dockerfiles/DockerfileInvalid: -------------------------------------------------------------------------------- 1 | Illegal Line 2 | 3 | FROM node:12 4 | 5 | COPY . /project/ 6 | 7 | # Removing node_modules 8 | RUN rm -rf /project/node_modules 9 | 10 | # Install user-app dependencies 11 | WORKDIR /project 12 | RUN npm install --production 13 | 14 | # Copy the dependencies into a slim Node docker image 15 | FROM node:12-slim 16 | 17 | # Copy project with dependencies 18 | COPY --chown=node:node --from=0 /project /project 19 | 20 | WORKDIR /project 21 | 22 | ENV NODE_ENV production 23 | 24 | USER node 25 | 26 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /tests/dockerfiles/DockerfileWithWhitespace: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FROM node:12 6 | 7 | COPY . /project/ 8 | 9 | # Removing node_modules 10 | RUN rm -rf /project/node_modules 11 | 12 | # Install user-app dependencies 13 | WORKDIR /project 14 | RUN npm install --production 15 | 16 | # Copy the dependencies into a slim Node docker image 17 | FROM node:12-slim 18 | 19 | # Copy project with dependencies 20 | COPY --chown=node:node --from=0 /project /project 21 | 22 | WORKDIR /project 23 | 24 | ENV NODE_ENV production 25 | 26 | USER node 27 | 28 | CMD ["npm", "start"] 29 | -------------------------------------------------------------------------------- /license_header.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright Red Hat 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. -------------------------------------------------------------------------------- /tests/dockerfiles/DockerfileWithComment: -------------------------------------------------------------------------------- 1 | # Install the app dependencies in a full Node docker image 2 | FROM node:12 3 | 4 | COPY . /project/ 5 | 6 | # Removing node_modules 7 | RUN rm -rf /project/node_modules 8 | 9 | # Install user-app dependencies 10 | WORKDIR /project 11 | RUN npm install --production 12 | 13 | # Copy the dependencies into a slim Node docker image 14 | FROM node:12-slim 15 | 16 | # Copy project with dependencies 17 | COPY --chown=node:node --from=0 /project /project 18 | 19 | WORKDIR /project 20 | 21 | ENV NODE_ENV production 22 | 23 | USER node 24 | 25 | CMD ["npm", "start"] 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | main 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # Files used for debugging 19 | .vscode/ 20 | 21 | # Files created by GoLand 22 | .idea/ 23 | 24 | # File created running tests 25 | tests/**/tmp/ 26 | tests/v2/lib-test-coverage.* 27 | *resource.file* 28 | 29 | 30 | # Mac related files 31 | .DS_Store 32 | 33 | # Release utility changelog files 34 | CHANGELOG.md 35 | -------------------------------------------------------------------------------- /pkg/devfile/testdata/.devfile.yml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (.devfile.yml) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/devfile.yml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (devfile.yml) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/valid-devfile.yaml.txt: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (valid-devfile.yaml.txt) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/priority-for-devfile_yml/.devfile.yml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (.devfile.yml) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/priority-for-devfile_yml/devfile.yml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (devfile.yml) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/priority-for-dot_devfile_yaml/.devfile.yml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (.devfile.yml) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/priority-for-dot_devfile_yaml/devfile.yml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (devfile.yml) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/priority-for-dot_devfile_yml/.devfile.yml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | image: busybox:latest 13 | command: [tail] 14 | args: [ -f, /dev/null ] 15 | mountSources: true 16 | name: runtime 17 | - kubernetes: 18 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 19 | name: outerloop-deploy 20 | - openshift: 21 | uri: http://127.0.0.1:8080/outerloop-service.yaml 22 | name: outerloop-deploy2 23 | metadata: 24 | description: Test stack (Busybox) 25 | displayName: Test stack (.devfile.yml) 26 | name: my-test-app 27 | version: 0.1.0 28 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/testingutil/filesystem/singleton.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package filesystem 17 | 18 | var singleFs Filesystem 19 | 20 | func Get() Filesystem { 21 | if singleFs == nil { 22 | singleFs = &DefaultFs{} 23 | } 24 | return singleFs 25 | } 26 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/fs.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import "github.com/devfile/library/v2/pkg/testingutil/filesystem" 19 | 20 | // GetFs returns the filesystem object 21 | func (d *DevfileCtx) GetFs() filesystem.Filesystem { 22 | return d.fs 23 | } 24 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/types.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 20 | ) 21 | 22 | // DevfileV2 is the devfile go struct from devfile/api 23 | type DevfileV2 struct { 24 | v1.Devfile 25 | } 26 | -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/Test_Parent_RegistryURL.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 2.2.2 2 | parent: 3 | id: nodejs 4 | registryUrl: "https://registry.stage.devfile.io" 5 | version: 2.2.1 6 | commands: 7 | - id: run 8 | exec: 9 | component: runtime 10 | commandLine: npm install #override 11 | workingDir: /project2 #override 12 | components: 13 | - name: runtime 14 | container: 15 | image: registry.access.redhat.com/ubi8/nodejs-14:dev #override 16 | memoryLimit: 2048Mi #override 17 | mountSources: false #override 18 | sourceMapping: /project2 #override 19 | endpoints: 20 | - name: http-9080 #this will result in a second endpoint 21 | targetPort: 9080 22 | starterProjects: 23 | - name: nodejs-starter 24 | git: 25 | remotes: 26 | origin: "https://github.com/odo-devfiles/nodejs-ex2.git" #override 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/fakecontext.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import "github.com/devfile/library/v2/pkg/testingutil/filesystem" 19 | 20 | func FakeContext(fs filesystem.Filesystem, absPath string) DevfileCtx { 21 | return DevfileCtx{ 22 | fs: fs, 23 | absPath: absPath, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/devfile/parser/util/interface.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package util 17 | 18 | import "github.com/devfile/library/v2/pkg/util" 19 | 20 | type DevfileUtils interface { 21 | DownloadGitRepoResources(url string, destDir string, token string) error 22 | DownloadInMemory(params util.HTTPRequestParams) ([]byte, error) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/testingutil/containers.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package testingutil 17 | 18 | import corev1 "k8s.io/api/core/v1" 19 | 20 | // CreateFakeContainer creates a container with the given containerName 21 | func CreateFakeContainer(containerName string) corev1.Container { 22 | return corev1.Container{ 23 | Name: containerName, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/devfile/testdata/.devfile.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | endpoints: 13 | - name: http 14 | targetPort: 8080 15 | image: golang:latest 16 | memoryLimit: 1024Mi 17 | mountSources: true 18 | name: runtime 19 | - kubernetes: 20 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 21 | name: outerloop-deploy 22 | - openshift: 23 | uri: http://127.0.0.1:8080/outerloop-service.yaml 24 | name: outerloop-deploy2 25 | metadata: 26 | description: Stack with the latest Go version 27 | displayName: Go Runtime (.devfile.yaml) 28 | icon: https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg 29 | language: go 30 | name: my-go-app 31 | projectType: go 32 | tags: 33 | - Go 34 | version: 1.0.0 35 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/devfile.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | endpoints: 13 | - name: http 14 | targetPort: 8080 15 | image: golang:latest 16 | memoryLimit: 1024Mi 17 | mountSources: true 18 | name: runtime 19 | - kubernetes: 20 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 21 | name: outerloop-deploy 22 | - openshift: 23 | uri: http://127.0.0.1:8080/outerloop-service.yaml 24 | name: outerloop-deploy2 25 | metadata: 26 | description: Stack with the latest Go version 27 | displayName: Go Runtime (devfile.yaml) 28 | icon: https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg 29 | language: go 30 | name: my-go-app 31 | projectType: go 32 | tags: 33 | - Go 34 | version: 1.0.0 35 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/testdata/priority-for-dot_devfile_yaml/.devfile.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: ./main {{ PARAMS }} 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: run 8 | workingDir: ${PROJECT_SOURCE} 9 | id: run 10 | components: 11 | - container: 12 | endpoints: 13 | - name: http 14 | targetPort: 8080 15 | image: golang:latest 16 | memoryLimit: 1024Mi 17 | mountSources: true 18 | name: runtime 19 | - kubernetes: 20 | uri: http://127.0.0.1:8080/outerloop-deploy.yaml 21 | name: outerloop-deploy 22 | - openshift: 23 | uri: http://127.0.0.1:8080/outerloop-service.yaml 24 | name: outerloop-deploy2 25 | metadata: 26 | description: Stack with the latest Go version 27 | displayName: Go Runtime (.devfile.yaml) 28 | icon: https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg 29 | language: go 30 | name: my-go-app 31 | projectType: go 32 | tags: 33 | - Go 34 | version: 1.0.0 35 | schemaVersion: 2.2.0 -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/parent.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 20 | ) 21 | 22 | // GetParent returns the Parent object parsed from devfile 23 | func (d *DevfileV2) GetParent() *v1.Parent { 24 | return d.Parent 25 | } 26 | 27 | // SetParent sets the parent for the devfile 28 | func (d *DevfileV2) SetParent(parent *v1.Parent) { 29 | d.Parent = parent 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/proxy-warming.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | name: Renew documentation 16 | 17 | on: 18 | release: 19 | types: 20 | - created 21 | tags: 22 | - 'v[0-9]+.[0-9]+.[0-9]+' 23 | 24 | # Declare default permissions as read only. 25 | permissions: read-all 26 | 27 | jobs: 28 | build: 29 | name: Renew documentation 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Pull new module version 33 | uses: andrewslotin/go-proxy-pull-action@master -------------------------------------------------------------------------------- /pkg/devfile/parser/errors/errors.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package errors 17 | 18 | import "fmt" 19 | 20 | // NonCompliantDevfile returns an error if devfile parsing failed due to Non-Compliant Devfile 21 | type NonCompliantDevfile struct { 22 | Err string 23 | } 24 | 25 | func (e *NonCompliantDevfile) Error() string { 26 | errMsg := "error parsing devfile because of non-compliant data" 27 | if e.Err != "" { 28 | errMsg = fmt.Sprintf("%s due to %v", errMsg, e.Err) 29 | } 30 | return errMsg 31 | } 32 | -------------------------------------------------------------------------------- /.clomonitor.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # CLOMonitor metadata file 17 | # This file must be located at the root of the repository 18 | 19 | # Checks exemptions 20 | exemptions: 21 | - check: license_scanning # Check identifier (see https://github.com/cncf/clomonitor/blob/main/docs/checks.md#exemptions) 22 | reason: "There are currently no plans moving forward to implement FOSSA or Snyk for scanning purposes." # Justification of this exemption (mandatory, it will be displayed on the UI) 23 | - check: artifacthub_badge 24 | reason: "This repository has no items that should be added to Artifact Hub." -------------------------------------------------------------------------------- /pkg/devfile/parser/devfileobj.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | devfileCtx "github.com/devfile/library/v2/pkg/devfile/parser/context" 20 | "github.com/devfile/library/v2/pkg/devfile/parser/data" 21 | ) 22 | 23 | // Default filenames for create devfile 24 | const ( 25 | OutputDevfileYamlPath = "devfile.yaml" 26 | K8sLikeComponentOriginalURIKey = "api.devfile.io/k8sLikeComponent-originalURI" 27 | ) 28 | 29 | // DevfileObj is the runtime devfile object 30 | type DevfileObj struct { 31 | 32 | // Ctx has devfile context info 33 | Ctx devfileCtx.DevfileCtx 34 | 35 | // Data has the devfile data 36 | Data data.DevfileData 37 | } 38 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/header.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | devfilepkg "github.com/devfile/api/v2/pkg/devfile" 20 | ) 21 | 22 | // GetSchemaVersion gets devfile schema version 23 | func (d *DevfileV2) GetSchemaVersion() string { 24 | return d.SchemaVersion 25 | } 26 | 27 | // SetSchemaVersion sets devfile schema version 28 | func (d *DevfileV2) SetSchemaVersion(version string) { 29 | d.SchemaVersion = version 30 | } 31 | 32 | // GetMetadata returns the DevfileMetadata Object parsed from devfile 33 | func (d *DevfileV2) GetMetadata() devfilepkg.DevfileMetadata { 34 | return d.Metadata 35 | } 36 | 37 | // SetMetadata sets the metadata for devfile 38 | func (d *DevfileV2) SetMetadata(metadata devfilepkg.DevfileMetadata) { 39 | d.Metadata = metadata 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | name: Code Coverage Report 16 | on: 17 | push: 18 | branches: 19 | - main 20 | 21 | # Declare default permissions as read only. 22 | permissions: read-all 23 | 24 | jobs: 25 | build-and-deploy: 26 | runs-on: ubuntu-20.04 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 30 | with: 31 | persist-credentials: false 32 | - name: Set up Go 1.x 33 | uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 34 | with: 35 | go-version-file: 'go.mod' 36 | - name: Run tests 37 | run: make test 38 | - name: Codecov 39 | uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 40 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/common/errors.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package common 17 | 18 | import "fmt" 19 | 20 | // FieldAlreadyExistError error returned if tried to add already exisitng field 21 | type FieldAlreadyExistError struct { 22 | // field which already exist 23 | Field string 24 | // field name 25 | Name string 26 | } 27 | 28 | func (e *FieldAlreadyExistError) Error() string { 29 | return fmt.Sprintf("%s %s already exists in devfile", e.Field, e.Name) 30 | } 31 | 32 | // FieldNotFoundError error returned if the field with the name is not found 33 | type FieldNotFoundError struct { 34 | // field which doesn't exist 35 | Field string 36 | // field name 37 | Name string 38 | } 39 | 40 | func (e *FieldNotFoundError) Error() string { 41 | return fmt.Sprintf("%s %s is not found in the devfile", e.Field, e.Name) 42 | } 43 | -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/Test_Parent_KubeCRD.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 2.1.0 2 | parent: 3 | kubernetes: 4 | name: testkubeparent1 5 | namespace: default 6 | commands: 7 | - apply: 8 | component: devbuild 9 | group: 10 | kind: build #override 11 | isDefault: false #override 12 | label: testcontainerparent1 #override 13 | id: applycommand 14 | components: 15 | - container: 16 | image: updatedimage #override 17 | name: devbuild 18 | projects: 19 | - name: parentproject 20 | git: 21 | remotes: 22 | neworigin: "https://github.com/spring-projects/spring-petclinic2.git" #override, should result in 2 remotes in flattened file 23 | checkoutFrom: 24 | remote: neworigin #override 25 | revision: main #override 26 | - name: parentproject2 27 | zip: 28 | location: "https://github.com/spring-projects/spring-petclinic2.zip" #override 29 | starterProjects: 30 | - name: parentstarterproject 31 | git: 32 | remotes: 33 | origin: "https://github.com/spring-projects/spring-petclinic2.git" #override 34 | checkoutFrom: 35 | remote: origin 36 | revision: master #override 37 | attributes: # only applicable to v2.1.0 38 | category: mainDevfile #override 39 | title: This is a main devfile #override 40 | variables: #only applicable to v2.1.0 41 | version: 2.1.0 #override 42 | tag: main #override 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/workspace.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 20 | ) 21 | 22 | // GetDevfileWorkspaceSpecContent returns the workspace spec content for the devfile 23 | func (d *DevfileV2) GetDevfileWorkspaceSpecContent() *v1.DevWorkspaceTemplateSpecContent { 24 | 25 | return &d.DevWorkspaceTemplateSpecContent 26 | } 27 | 28 | // SetDevfileWorkspaceSpecContent sets the workspace spec content 29 | func (d *DevfileV2) SetDevfileWorkspaceSpecContent(content v1.DevWorkspaceTemplateSpecContent) { 30 | d.DevWorkspaceTemplateSpecContent = content 31 | } 32 | 33 | func (d *DevfileV2) GetDevfileWorkspaceSpec() *v1.DevWorkspaceTemplateSpec { 34 | return &d.DevWorkspaceTemplateSpec 35 | } 36 | 37 | // SetDevfileWorkspaceSpec sets the workspace spec 38 | func (d *DevfileV2) SetDevfileWorkspaceSpec(spec v1.DevWorkspaceTemplateSpec) { 39 | d.DevWorkspaceTemplateSpec = spec 40 | } 41 | -------------------------------------------------------------------------------- /pkg/util/httpcache.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package util 17 | 18 | import ( 19 | "os" 20 | "path/filepath" 21 | "time" 22 | 23 | "k8s.io/klog" 24 | ) 25 | 26 | // cleanHttpCache checks cacheDir and deletes all files that were modified more than cacheTime back 27 | func cleanHttpCache(cacheDir string, cacheTime time.Duration) error { 28 | cacheEntries, err := os.ReadDir(cacheDir) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | cacheFiles := make([]os.FileInfo, 0, len(cacheEntries)) 34 | for _, cacheEntry := range cacheEntries { 35 | info, err := cacheEntry.Info() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | cacheFiles = append(cacheFiles, info) 41 | } 42 | 43 | for _, f := range cacheFiles { 44 | if f.ModTime().Add(cacheTime).Before(time.Now()) { 45 | klog.V(4).Infof("Removing cache file %s, because it is older than %s", f.Name(), cacheTime.String()) 46 | err := os.Remove(filepath.Join(cacheDir, f.Name())) 47 | if err != nil { 48 | return err 49 | } 50 | } 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/testingutil/resources.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package testingutil 17 | 18 | import ( 19 | corev1 "k8s.io/api/core/v1" 20 | "k8s.io/apimachinery/pkg/api/resource" 21 | ) 22 | 23 | // FakeResourceRequirements creates a fake resource requirements from cpu and memory 24 | func FakeResourceRequirements(cpu, memory string) (corev1.ResourceRequirements, error) { 25 | var resReq corev1.ResourceRequirements 26 | 27 | limits := make(corev1.ResourceList) 28 | var err error 29 | limits[corev1.ResourceCPU], err = resource.ParseQuantity(cpu) 30 | if err != nil { 31 | return resReq, err 32 | } 33 | limits[corev1.ResourceMemory], err = resource.ParseQuantity(memory) 34 | if err != nil { 35 | return resReq, err 36 | } 37 | resReq.Limits = limits 38 | 39 | requests := make(corev1.ResourceList) 40 | requests[corev1.ResourceCPU], err = resource.ParseQuantity(cpu) 41 | if err != nil { 42 | return resReq, err 43 | } 44 | requests[corev1.ResourceMemory], err = resource.ParseQuantity(memory) 45 | if err != nil { 46 | return resReq, err 47 | } 48 | 49 | resReq.Requests = requests 50 | 51 | return resReq, nil 52 | } 53 | -------------------------------------------------------------------------------- /replaceSchemaFile.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | "strings" 22 | ) 23 | 24 | func ReplaceSchemaFile() { 25 | if len(os.Args) != 7 { 26 | printErr(fmt.Errorf("ReplaceSchemaFile() expect 7 arguments")) 27 | os.Exit(1) 28 | } 29 | originalSchema := os.Args[2] 30 | schemaURL := os.Args[3] 31 | packageVersion := os.Args[4] 32 | jsonSchemaVersion := os.Args[5] 33 | filePath := os.Args[6] 34 | 35 | // replace all ` with ' to convert schema content from json file format to json format in golang 36 | newSchema := strings.ReplaceAll(originalSchema, "`", "'") 37 | fmt.Printf("Writing to file: %s\n", filePath) 38 | fileContent := fmt.Sprintf("package %s\n\n// %s\nconst %s = `%s\n`\n", packageVersion, schemaURL, jsonSchemaVersion, newSchema) 39 | 40 | if err := os.WriteFile(filePath, []byte(fileContent), 0600); err != nil { 41 | printErr(err) 42 | os.Exit(1) 43 | } 44 | } 45 | 46 | func printErr(err error) { 47 | // prints error in red 48 | colorRed := "\033[31m" 49 | colorReset := "\033[0m" 50 | 51 | fmt.Println(string(colorRed), err, string(colorReset)) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/common/component_helper.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package common 17 | 18 | import ( 19 | "fmt" 20 | 21 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 22 | ) 23 | 24 | // IsContainer checks if the component is a container 25 | func IsContainer(component v1.Component) bool { 26 | return component.Container != nil 27 | } 28 | 29 | // IsVolume checks if the component is a volume 30 | func IsVolume(component v1.Component) bool { 31 | return component.Volume != nil 32 | } 33 | 34 | // GetComponentType returns the component type of a given component 35 | func GetComponentType(component v1.Component) (v1.ComponentType, error) { 36 | switch { 37 | case component.Container != nil: 38 | return v1.ContainerComponentType, nil 39 | case component.Volume != nil: 40 | return v1.VolumeComponentType, nil 41 | case component.Plugin != nil: 42 | return v1.PluginComponentType, nil 43 | case component.Kubernetes != nil: 44 | return v1.KubernetesComponentType, nil 45 | case component.Openshift != nil: 46 | return v1.OpenshiftComponentType, nil 47 | case component.Image != nil: 48 | return v1.ImageComponentType, nil 49 | case component.Custom != nil: 50 | return v1.CustomComponentType, nil 51 | 52 | default: 53 | return "", fmt.Errorf("unknown component type") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/Test_200.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: npm install 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: build 8 | hotReloadCapable: false 9 | workingDir: /project 10 | id: install 11 | - exec: 12 | commandLine: npm start 13 | component: runtime 14 | group: 15 | isDefault: true 16 | kind: run 17 | hotReloadCapable: false 18 | workingDir: /project 19 | id: run 20 | - exec: 21 | commandLine: npm run debug 22 | component: runtime 23 | group: 24 | isDefault: true 25 | kind: debug 26 | hotReloadCapable: false 27 | workingDir: /project 28 | id: debug 29 | - exec: 30 | commandLine: npm test 31 | component: runtime 32 | group: 33 | isDefault: true 34 | kind: test 35 | hotReloadCapable: false 36 | workingDir: /project 37 | id: test 38 | components: 39 | - container: 40 | dedicatedPod: true 41 | endpoints: 42 | - name: http-3000 43 | secure: false 44 | targetPort: 3000 45 | image: registry.access.redhat.com/ubi8/nodejs-14:latest 46 | memoryLimit: 1024Mi 47 | mountSources: true 48 | sourceMapping: /project 49 | volumeMounts: 50 | - name: v1 51 | path: /v1 52 | - name: v2 53 | path: /v2 54 | name: runtime 55 | - name: v1 56 | volume: 57 | size: 1Gi 58 | - name: v2 59 | volume: 60 | size: 1Gi 61 | metadata: 62 | description: Stack with Node.js 14 63 | displayName: Node.js Runtime 64 | icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg 65 | language: javascript 66 | name: nodejs-defect-pcbx 67 | projectType: nodejs 68 | tags: 69 | - NodeJS 70 | - Express 71 | - ubi8 72 | version: 1.0.1 73 | schemaVersion: 2.0.0 74 | starterProjects: 75 | - git: 76 | remotes: 77 | origin: https://github.com/odo-devfiles/nodejs-ex.git 78 | name: nodejs-starter 79 | -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/Test_210.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: npm install 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: build 8 | hotReloadCapable: false 9 | workingDir: /project 10 | id: install 11 | - exec: 12 | commandLine: npm start 13 | component: runtime 14 | group: 15 | isDefault: true 16 | kind: run 17 | hotReloadCapable: false 18 | workingDir: /project 19 | id: run 20 | - exec: 21 | commandLine: npm run debug 22 | component: runtime 23 | group: 24 | isDefault: true 25 | kind: debug 26 | hotReloadCapable: false 27 | workingDir: /project 28 | id: debug 29 | - exec: 30 | commandLine: npm test 31 | component: runtime 32 | group: 33 | isDefault: true 34 | kind: test 35 | hotReloadCapable: false 36 | workingDir: /project 37 | id: test 38 | components: 39 | - container: 40 | dedicatedPod: true 41 | endpoints: 42 | - name: http-3000 43 | secure: false 44 | targetPort: 3000 45 | image: registry.access.redhat.com/ubi8/nodejs-14:latest 46 | memoryLimit: 1024Mi 47 | mountSources: true 48 | sourceMapping: /project 49 | volumeMounts: 50 | - name: v1 51 | path: /v1 52 | - name: v2 53 | path: /v2 54 | name: runtime 55 | - name: v1 56 | volume: 57 | size: 1Gi 58 | - name: v2 59 | volume: 60 | size: 1Gi 61 | metadata: 62 | description: Stack with Node.js 14 63 | displayName: Node.js Runtime 64 | icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg 65 | language: javascript 66 | name: nodejs-defect-pcbx 67 | projectType: nodejs 68 | tags: 69 | - NodeJS 70 | - Express 71 | - ubi8 72 | version: 1.0.1 73 | schemaVersion: 2.1.0 74 | starterProjects: 75 | - git: 76 | remotes: 77 | origin: https://github.com/odo-devfiles/nodejs-ex.git 78 | name: nodejs-starter 79 | -------------------------------------------------------------------------------- /SECURITY-INSIGHTS.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | header: 16 | schema-version: 1.0.0 17 | last-updated: '2024-03-01' 18 | last-reviewed: '2024-03-01' 19 | expiration-date: '2025-03-01T10:00:00.000Z' 20 | project-url: https://github.com/devfile/library 21 | project-release: 'v2.2.1' 22 | commit-hash: '6aa1b8339d39e9c2db403d3817cb552d428d19e4' 23 | license: 'https://raw.githubusercontent.com/devfile/library/main/LICENSE' 24 | project-lifecycle: 25 | status: active 26 | bug-fixes-only: false 27 | core-maintainers: 28 | - github:michael-valdron 29 | - github:Jdubrick 30 | - github:thepetk 31 | release-cycle: https://github.com/devfile/library/blob/main/README.md#releases 32 | security-testing: 33 | - tool-type: sca 34 | tool-name: Dependabot 35 | comment: | 36 | Dependabot is enabled for this repo. 37 | distribution-points: 38 | - https://github.com/devfile/library 39 | contribution-policy: 40 | accepts-pull-requests: true 41 | accepts-automated-pull-requests: true 42 | contributing-policy: https://github.com/devfile/library/blob/main/CONTRIBUTING.md 43 | code-of-conduct: https://github.com/devfile/api/blob/main/CODE_OF_CONDUCT.md 44 | documentation: 45 | - https://github.com/devfile/library/blob/main/README.md#usage 46 | dependencies: 47 | third-party-packages: true 48 | dependencies-lists: 49 | - https://github.com/devfile/library/blob/main/go.mod -------------------------------------------------------------------------------- /pkg/testingutil/k8sClient.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package testingutil 17 | 18 | import ( 19 | "context" 20 | "errors" 21 | "fmt" 22 | 23 | "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | ) 26 | 27 | type FakeK8sClient struct { 28 | client.Client // To satisfy interface; override all used methods 29 | DevWorkspaceResources map[string]v1alpha2.DevWorkspaceTemplate 30 | Errors map[string]string 31 | ExpectedNamespace string 32 | } 33 | 34 | func (client *FakeK8sClient) Get(_ context.Context, namespacedName client.ObjectKey, obj client.Object, _ ...client.GetOption) error { 35 | if client.ExpectedNamespace != "" && client.ExpectedNamespace != namespacedName.Namespace { 36 | return fmt.Errorf("expected namespace %s, got %s", client.ExpectedNamespace, namespacedName.Namespace) 37 | } 38 | template, ok := obj.(*v1alpha2.DevWorkspaceTemplate) 39 | if !ok { 40 | return fmt.Errorf("called Get() in fake client with non-DevWorkspaceTemplate") 41 | } 42 | if element, ok := client.DevWorkspaceResources[namespacedName.Name]; ok { 43 | *template = element 44 | return nil 45 | } 46 | 47 | if err, ok := client.Errors[namespacedName.Name]; ok { 48 | return errors.New(err) 49 | } 50 | return fmt.Errorf("test does not define an entry for %s", namespacedName.Name) 51 | } 52 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/parent_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | "reflect" 20 | "testing" 21 | 22 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 23 | ) 24 | 25 | func TestDevfile200_SetParent(t *testing.T) { 26 | 27 | tests := []struct { 28 | name string 29 | parent *v1.Parent 30 | devfilev2 *DevfileV2 31 | expectedDevfilev2 *DevfileV2 32 | }{ 33 | { 34 | name: "set parent", 35 | devfilev2: &DevfileV2{ 36 | v1.Devfile{}, 37 | }, 38 | parent: &v1.Parent{ 39 | ImportReference: v1.ImportReference{ 40 | RegistryUrl: "testRegistryUrl", 41 | }, 42 | ParentOverrides: v1.ParentOverrides{}, 43 | }, 44 | expectedDevfilev2: &DevfileV2{ 45 | v1.Devfile{ 46 | DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ 47 | Parent: &v1.Parent{ 48 | ImportReference: v1.ImportReference{ 49 | RegistryUrl: "testRegistryUrl", 50 | }, 51 | ParentOverrides: v1.ParentOverrides{}, 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | } 58 | for _, tt := range tests { 59 | t.Run(tt.name, func(t *testing.T) { 60 | tt.devfilev2.SetParent(tt.parent) 61 | if !reflect.DeepEqual(tt.devfilev2, tt.expectedDevfilev2) { 62 | t.Errorf("TestDevfile200_SetParent() error: expected %v, got %v", tt.expectedDevfilev2, tt.devfilev2) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/Test_220.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | - exec: 3 | commandLine: npm install 4 | component: runtime 5 | group: 6 | isDefault: true 7 | kind: build 8 | hotReloadCapable: false 9 | workingDir: /project 10 | id: install 11 | - exec: 12 | commandLine: npm start 13 | component: runtime 14 | group: 15 | isDefault: true 16 | kind: run 17 | hotReloadCapable: false 18 | workingDir: /project 19 | id: run 20 | - exec: 21 | commandLine: npm run debug 22 | component: runtime 23 | group: 24 | isDefault: true 25 | kind: debug 26 | hotReloadCapable: false 27 | workingDir: /project 28 | id: debug 29 | - exec: 30 | commandLine: npm test 31 | component: runtime 32 | group: 33 | isDefault: true 34 | kind: test 35 | hotReloadCapable: false 36 | workingDir: /project 37 | id: test 38 | components: 39 | - name: outerloop-build 40 | image: 41 | imageName: nodejs-image:latest 42 | dockerfile: 43 | uri: Dockerfile 44 | - container: 45 | dedicatedPod: true 46 | endpoints: 47 | - name: http-3000 48 | secure: false 49 | targetPort: 3000 50 | image: registry.access.redhat.com/ubi8/nodejs-14:latest 51 | memoryLimit: 1024Mi 52 | mountSources: true 53 | sourceMapping: /project 54 | volumeMounts: 55 | - name: v1 56 | path: /v1 57 | - name: v2 58 | path: /v2 59 | name: runtime 60 | - name: v1 61 | volume: 62 | size: 1Gi 63 | - name: v2 64 | volume: 65 | size: 1Gi 66 | metadata: 67 | description: Stack with Node.js 14 68 | displayName: Node.js Runtime 69 | icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg 70 | language: javascript 71 | name: nodejs-defect-pcbx 72 | projectType: nodejs 73 | tags: 74 | - NodeJS 75 | - Express 76 | - ubi8 77 | version: 1.0.1 78 | schemaVersion: 2.2.0 79 | starterProjects: 80 | - git: 81 | remotes: 82 | origin: https://github.com/odo-devfiles/nodejs-ex.git 83 | name: nodejs-starter 84 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description of Changes 2 | _Summarize the changes you made as part of this pull request._ 3 | 4 | # Related Issue(s) 5 | _Link the GitHub/GitLab/JIRA issues that are related to this PR._ 6 | 7 | # Acceptance Criteria 8 | 9 | _Testing and documentation do not need to be complete in order for this PR to be approved. However, tracking issues must be opened for missing testing/documentation._ 10 | 11 | New testing and documentation issues can be opened under [`devfile/api/issues`](https://github.com/devfile/api/issues). 12 | 13 | You can check the respective criteria below if either of the following is true: 14 | - There is a separate tracking issue opened and that issue is linked in this PR. 15 | - Testing/documentation updates are contained within this PR. 16 | 17 | If criteria is left unchecked please provide an explanation why. 18 | 19 | 20 | - [ ] Unit/Functional tests 21 | 22 | 23 | 24 | - [ ] [QE Integration test](https://github.com/devfile/integration-tests) 25 | 26 | 27 | 28 | - [ ] Documentation (READMEs, Product Docs, Blogs, Education Modules, etc.) 29 | 30 | 31 | 32 | - [ ] Client Impact 33 | 34 | 35 | 36 | - [ ] Gosec scans 37 | 38 | 39 | 40 | # Tests Performed 41 | _Explain what tests you personally ran to ensure the changes are functioning as expected._ 42 | 43 | # How To Test 44 | _Instructions for the reviewer on how to test your changes._ 45 | 46 | # Notes To Reviewer 47 | _Any notes you would like to include for the reviewer._ 48 | -------------------------------------------------------------------------------- /.codecov.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # See http://docs.codecov.io/docs/coverage-configuration 17 | coverage: 18 | precision: 2 # 2 = xx.xx%, 0 = xx% 19 | round: down 20 | # For example: 20...60 would result in any coverage less than 20% 21 | # would have a red background. The color would gradually change to 22 | # green approaching 60%. Any coverage over 60% would result in a 23 | # solid green color. 24 | range: "20...60" 25 | 26 | status: 27 | # project will give us the diff in the total code coverage between a commit 28 | # and its parent 29 | project: yes 30 | # Patch gives just the coverage of the patch 31 | patch: yes 32 | # changes tells us if there are unexpected code coverage changes in other files 33 | # which were not changed by the diff 34 | changes: yes 35 | 36 | # See http://docs.codecov.io/docs/ignoring-paths 37 | ignore: 38 | - "assets/*" 39 | - "build/*" 40 | - "deploy/*" 41 | - "hack/*" 42 | - "manifests/*" 43 | - "openshift-ci/*" 44 | - "vendor/*" 45 | - "Makefile" 46 | - ".travis.yml" 47 | - "pkg/devfile/parser/util/mock.go" 48 | - "pkg/util/mock.go" 49 | 50 | # See http://docs.codecov.io/docs/pull-request-comments-1 51 | comment: 52 | layout: "diff, files" 53 | behavior: "" 54 | # default = posts once then update, posts new if delete 55 | # once = post once then updates 56 | # new = delete old, post new 57 | # spammy = post new 58 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/schema.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/devfile/library/v2/pkg/devfile/parser/data" 22 | errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors" 23 | "github.com/pkg/errors" 24 | "github.com/xeipuuv/gojsonschema" 25 | "k8s.io/klog" 26 | ) 27 | 28 | // SetDevfileJSONSchema returns the JSON schema for the given devfile apiVersion 29 | func (d *DevfileCtx) SetDevfileJSONSchema() error { 30 | 31 | // Check if json schema is present for the given apiVersion 32 | jsonSchema, err := data.GetDevfileJSONSchema(d.apiVersion) 33 | if err != nil { 34 | return &errPkg.NonCompliantDevfile{Err: err.Error()} 35 | } 36 | d.jsonSchema = jsonSchema 37 | return nil 38 | } 39 | 40 | // ValidateDevfileSchema validate JSON schema of the provided devfile 41 | func (d *DevfileCtx) ValidateDevfileSchema() error { 42 | var ( 43 | schemaLoader = gojsonschema.NewStringLoader(d.jsonSchema) 44 | documentLoader = gojsonschema.NewStringLoader(string(d.rawContent)) 45 | ) 46 | 47 | // Validate devfile with JSON schema 48 | result, err := gojsonschema.Validate(schemaLoader, documentLoader) 49 | if err != nil { 50 | return errors.Wrapf(err, "failed to validate devfile schema") 51 | } 52 | 53 | if !result.Valid() { 54 | errMsg := "invalid devfile schema. errors :\n" 55 | for _, desc := range result.Errors() { 56 | errMsg = errMsg + fmt.Sprintf("- %s\n", desc) 57 | } 58 | return &errPkg.NonCompliantDevfile{Err: errMsg} 59 | } 60 | 61 | // Sucessful 62 | klog.V(4).Info("validated devfile schema") 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | FILES := main 17 | 18 | default: bin 19 | 20 | .PHONY: all 21 | all: gomod_tidy gofmt bin test 22 | 23 | .PHONY: gomod_tidy 24 | gomod_tidy: 25 | go mod tidy 26 | 27 | .PHONY: gofmt 28 | gofmt: 29 | go fmt -x ./... 30 | 31 | .PHONY: bin 32 | bin: 33 | go build *.go 34 | 35 | .PHONY: test 36 | test: 37 | go test -coverprofile cover.out -v ./... 38 | 39 | .PHONY: clean 40 | clean: 41 | @rm -rf $(FILES) 42 | 43 | ### fmt_license: ensure license header is set on all files 44 | fmt_license: 45 | ifneq ($(shell command -v addlicense 2> /dev/null),) 46 | @echo 'addlicense -v -f license_header.txt **/*.go' 47 | @addlicense -v -f license_header.txt $$(find . -name '*.go') 48 | else 49 | $(error "addlicense must be installed for this command: go install github.com/google/addlicense@latest") 50 | endif 51 | 52 | ### check_fmt: Checks for missing licenses on files in repo 53 | check_license: 54 | ifeq ($(shell command -v addlicense 2> /dev/null),) 55 | $(error "error addlicense must be installed for this command: go install github.com/google/addlicense@latest") 56 | endif 57 | 58 | if ! addlicense -check -f license_header.txt $$(find . -not -path '*/\.*' -name '*.go'); then \ 59 | echo "Licenses are not formatted; run 'make fmt_license'"; exit 1 ;\ 60 | fi \ 61 | 62 | 63 | 64 | ### gosec - runs the gosec scanner for non-test files in this repo 65 | .PHONY: gosec 66 | gosec: 67 | # Run this command to install gosec, if not installed: 68 | # go install github.com/securego/gosec/v2/cmd/gosec@latest 69 | gosec -no-fail -fmt=sarif -out=gosec.sarif -exclude-dir pkg/testingutil -exclude-dir tests ./... -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/Parent.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 2.1.0 2 | commands: 3 | - apply: 4 | component: testcontainerparent1 5 | group: 6 | kind: test 7 | isDefault: true 8 | label: JXTVtfYNNsaiQcqFSwTavCaBlRGMaBOXaxXsgDRxFxsNxbuHfGQuQjBwJWJVmHd 9 | id: testapplyparentcommand1 10 | - id: run 11 | exec: 12 | component: testcontainerparent1 13 | commandLine: npm start 14 | workingDir: /project 15 | group: 16 | kind: run 17 | isDefault: true 18 | hotReloadCapable: true 19 | - id: test 20 | composite: 21 | commands: [testapplyparentcommand1] 22 | group: 23 | kind: debug 24 | label: testcompositeparent1 25 | parallel: true 26 | components: 27 | - container: 28 | image: mKrpiOQnyGZ00003 29 | name: testcontainerparent1 30 | - kubernetes: 31 | inlined: | 32 | apiVersion: batch/v1 33 | kind: Job 34 | metadata: 35 | name: pi 36 | spec: 37 | template: 38 | spec: 39 | containers: 40 | - name: job 41 | image: myimage 42 | command: ["some", "command"] 43 | restartPolicy: Never 44 | name: testkubeparent1 45 | - openshift: 46 | uri: openshift.yaml 47 | name: openshiftcomponent1 48 | projects: 49 | - name: petclinic 50 | git: 51 | remotes: 52 | origin: "https://github.com/spring-projects/spring-petclinic.git" 53 | checkoutFrom: 54 | remote: origin 55 | revision: main 56 | - name: petclinic-dev 57 | zip: 58 | location: https://github.com/spring-projects/spring-petclinic/archive/refs/heads/main.zip 59 | attributes: 60 | editorFree: true 61 | user: default 62 | starterProjects: 63 | - name: user-app 64 | git: 65 | remotes: 66 | origin: 'https://github.com/OpenLiberty/application-stack-starters.git' 67 | description: An Open Liberty Starter project 68 | subDir: /app 69 | attributes: 70 | workingDir: /home 71 | - name: user-app2 72 | zip: 73 | location: 'https://github.com/OpenLiberty/application-stack-starters.zip' 74 | attributes: #only applicable to v2.1.0 75 | category: parentdevfile 76 | title: This is a parent devfile 77 | variables: #only applicable to v2.1.0 78 | version: 2.0.0 79 | tag: parent 80 | lastUpdated: "2020" 81 | 82 | -------------------------------------------------------------------------------- /pkg/testingutil/filesystem/filesystem.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | This package is a FORK of https://github.com/kubernetes/kubernetes/blob/master/pkg/util/filesystem/filesystem.go 19 | See above license 20 | */ 21 | 22 | package filesystem 23 | 24 | import ( 25 | "os" 26 | "path/filepath" 27 | "time" 28 | ) 29 | 30 | // Filesystem is an interface that we can use to mock various filesystem operations 31 | type Filesystem interface { 32 | // from "os" 33 | Stat(name string) (os.FileInfo, error) 34 | Create(name string) (File, error) 35 | Open(name string) (File, error) 36 | OpenFile(name string, flag int, perm os.FileMode) (File, error) 37 | Rename(oldpath, newpath string) error 38 | MkdirAll(path string, perm os.FileMode) error 39 | Chtimes(name string, atime time.Time, mtime time.Time) error 40 | RemoveAll(path string) error 41 | Remove(name string) error 42 | Chmod(name string, mode os.FileMode) error 43 | Getwd() (dir string, err error) 44 | ReadFile(filename string) ([]byte, error) 45 | WriteFile(filename string, data []byte, perm os.FileMode) error 46 | TempDir(dir, prefix string) (string, error) 47 | TempFile(dir, prefix string) (File, error) 48 | ReadDir(dirname string) ([]os.FileInfo, error) 49 | 50 | // from "filepath" 51 | Walk(root string, walkFn filepath.WalkFunc) error 52 | } 53 | 54 | // File is an interface that we can use to mock various filesystem operations typically 55 | // accessed through the File object from the "os" package 56 | type File interface { 57 | // for now, the only os.File methods used are those below, add more as necessary 58 | Name() string 59 | Write(b []byte) (n int, err error) 60 | WriteString(s string) (n int, err error) 61 | Sync() error 62 | Close() error 63 | Read(b []byte) (n int, err error) 64 | Readdir(n int) ([]os.FileInfo, error) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/location.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "io/fs" 22 | "path/filepath" 23 | "strings" 24 | 25 | "github.com/devfile/library/v2/pkg/testingutil/filesystem" 26 | ) 27 | 28 | // possibleDevfileNames contains possible filenames for a devfile. 29 | // Those are checked in this priority order from a given context dir. 30 | var possibleDevfileNames = []string{ 31 | "devfile.yaml", 32 | ".devfile.yaml", 33 | "devfile.yml", 34 | ".devfile.yml", 35 | } 36 | 37 | // lookupDevfileFromPath returns the file path to use as devfile filename, by looking at the relative path specified in relPath. 38 | // If relPath is not a directory, it is returned as is. 39 | // For backward compatibility, if relPath is a directory, it will try to detect the first existing devfile filename under relPath, 40 | // based on the list of possible devfile filenames defined in the sorted possibleDevfileNames. 41 | // It returns any error found while interacting with the filesystem, or if no file was found from the list of possible devfile names. 42 | func lookupDevfileFromPath(fsys filesystem.Filesystem, relPath string) (string, error) { 43 | stat, err := fsys.Stat(relPath) 44 | if err != nil { 45 | return "", err 46 | } 47 | 48 | if !stat.IsDir() { 49 | return relPath, nil 50 | } 51 | 52 | for _, possibleDevfileName := range possibleDevfileNames { 53 | p := filepath.Join(relPath, possibleDevfileName) 54 | if _, err = fsys.Stat(p); errors.Is(err, fs.ErrNotExist) { 55 | continue 56 | } 57 | return p, nil 58 | } 59 | 60 | return "", fmt.Errorf( 61 | "the provided path is not a valid yaml filepath, and no possible devfile could be found in the provided path : %s. Possible filenames for a devfile: %s", 62 | relPath, 63 | strings.Join(possibleDevfileNames, ", ")) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/helper.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package data 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | "sort" 22 | "strings" 23 | 24 | "k8s.io/klog" 25 | ) 26 | 27 | // String converts supportedApiVersion type to string type 28 | func (s supportedApiVersion) String() string { 29 | return string(s) 30 | } 31 | 32 | // NewDevfileData returns relevant devfile struct for the provided API version 33 | func NewDevfileData(version string) (obj DevfileData, err error) { 34 | 35 | // Fetch devfile struct type from map 36 | devfileType, ok := apiVersionToDevfileStruct[supportedApiVersion(version)] 37 | if !ok { 38 | return obj, fmt.Errorf("devfile type not present for apiVersion '%s'", version) 39 | } 40 | 41 | return reflect.New(devfileType).Interface().(DevfileData), nil 42 | } 43 | 44 | // GetDevfileJSONSchema returns the devfile JSON schema of the supported apiVersion 45 | func GetDevfileJSONSchema(version string) (string, error) { 46 | 47 | // Fetch json schema from the devfileApiVersionToJSONSchema map 48 | schema, ok := devfileApiVersionToJSONSchema[supportedApiVersion(version)] 49 | if !ok { 50 | var supportedVersions []string 51 | for version := range devfileApiVersionToJSONSchema { 52 | supportedVersions = append(supportedVersions, string(version)) 53 | } 54 | sort.Strings(supportedVersions) 55 | return "", fmt.Errorf("unable to find schema for version %q. The parser supports devfile schema for version %s", version, strings.Join(supportedVersions, ", ")) 56 | } 57 | klog.V(4).Infof("devfile apiVersion '%s' is supported", version) 58 | 59 | // Successful 60 | return schema, nil 61 | } 62 | 63 | // IsApiVersionSupported returns true if the API version is supported 64 | func IsApiVersionSupported(version string) bool { 65 | return apiVersionToDevfileStruct[supportedApiVersion(version)] != nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/attributes.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/devfile/api/v2/pkg/attributes" 22 | ) 23 | 24 | // GetAttributes gets the devfile top level attributes 25 | func (d *DevfileV2) GetAttributes() (attributes.Attributes, error) { 26 | // This feature was introduced in 2.1.0; so any version 2.1.0 and up should use the 2.1.0 implementation 27 | switch d.SchemaVersion { 28 | case "2.0.0": 29 | return attributes.Attributes{}, fmt.Errorf("top-level attributes is not supported in devfile schema version 2.0.0") 30 | default: 31 | return d.Attributes, nil 32 | } 33 | } 34 | 35 | // UpdateAttributes updates the devfile top level attribute for the specific key, err out if key is absent 36 | func (d *DevfileV2) UpdateAttributes(key string, value interface{}) error { 37 | var err error 38 | 39 | // This feature was introduced in 2.1.0; so any version 2.1.0 and up should use the 2.1.0 implementation 40 | switch d.SchemaVersion { 41 | case "2.0.0": 42 | return fmt.Errorf("top-level attributes is not supported in devfile schema version 2.0.0") 43 | default: 44 | if d.Attributes.Exists(key) { 45 | d.Attributes.Put(key, value, &err) 46 | } else { 47 | return fmt.Errorf("cannot update top-level attribute, key %s is not present", key) 48 | } 49 | } 50 | 51 | return err 52 | } 53 | 54 | // AddAttributes adds to the devfile top level attributes, value will be overwritten if key is already present 55 | func (d *DevfileV2) AddAttributes(key string, value interface{}) error { 56 | var err error 57 | 58 | // This feature was introduced in 2.1.0; so any version 2.1.0 and up should use the 2.1.0 implementation 59 | switch d.SchemaVersion { 60 | case "2.0.0": 61 | return fmt.Errorf("top-level attributes is not supported in devfile schema version 2.0.0") 62 | default: 63 | d.Attributes.Put(key, value, &err) 64 | } 65 | 66 | return err 67 | } 68 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/apiVersion.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "encoding/json" 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/devfile/library/v2/pkg/devfile/parser/data" 24 | errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors" 25 | "k8s.io/klog" 26 | ) 27 | 28 | // SetDevfileAPIVersion returns the devfile APIVersion 29 | func (d *DevfileCtx) SetDevfileAPIVersion() error { 30 | 31 | // Unmarshal JSON into map 32 | var r map[string]interface{} 33 | err := json.Unmarshal(d.rawContent, &r) 34 | if err != nil { 35 | return &errPkg.NonCompliantDevfile{Err: err.Error()} 36 | } 37 | 38 | // Get "schemaVersion" value from map for devfile V2 39 | schemaVersion, okSchema := r["schemaVersion"] 40 | var devfilePath string 41 | if d.GetAbsPath() != "" { 42 | devfilePath = d.GetAbsPath() 43 | } else if d.GetURL() != "" { 44 | devfilePath = d.GetURL() 45 | } 46 | 47 | if okSchema { 48 | // SchemaVersion cannot be empty 49 | if schemaVersion.(string) == "" { 50 | return &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("schemaVersion in devfile: %s cannot be empty", devfilePath)} 51 | } 52 | } else { 53 | return &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("schemaVersion not present in devfile: %s", devfilePath)} 54 | } 55 | 56 | // Successful 57 | // split by `-` and get the first substring as schema version, schemaVersion without `-` won't get affected 58 | // e.g. 2.2.0-latest => 2.2.0, 2.2.0 => 2.2.0 59 | d.apiVersion = strings.Split(schemaVersion.(string), "-")[0] 60 | klog.V(4).Infof("devfile schemaVersion: '%s'", d.apiVersion) 61 | return nil 62 | } 63 | 64 | // GetApiVersion returns apiVersion stored in devfile context 65 | func (d *DevfileCtx) GetApiVersion() string { 66 | return d.apiVersion 67 | } 68 | 69 | // IsApiVersionSupported return true if the apiVersion in DevfileCtx is supported 70 | func (d *DevfileCtx) IsApiVersionSupported() bool { 71 | return data.IsApiVersionSupported(d.apiVersion) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/testingutil/filesystem/watcher.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | This package is a FORK of https://github.com/kubernetes/kubernetes/blob/master/pkg/util/filesystem/watcher.go 19 | See above license 20 | */ 21 | 22 | package filesystem 23 | 24 | import ( 25 | "github.com/fsnotify/fsnotify" 26 | ) 27 | 28 | // FSWatcher is a callback-based filesystem watcher abstraction for fsnotify. 29 | type FSWatcher interface { 30 | // Initializes the watcher with the given watch handlers. 31 | // Called before all other methods. 32 | Init(FSEventHandler, FSErrorHandler) error 33 | 34 | // Starts listening for events and errors. 35 | // When an event or error occurs, the corresponding handler is called. 36 | Run() 37 | 38 | // Add a filesystem path to watch 39 | AddWatch(path string) error 40 | } 41 | 42 | // FSEventHandler is called when a fsnotify event occurs. 43 | type FSEventHandler func(event fsnotify.Event) 44 | 45 | // FSErrorHandler is called when a fsnotify error occurs. 46 | type FSErrorHandler func(err error) 47 | 48 | type fsnotifyWatcher struct { 49 | watcher *fsnotify.Watcher 50 | eventHandler FSEventHandler 51 | errorHandler FSErrorHandler 52 | } 53 | 54 | var _ FSWatcher = &fsnotifyWatcher{} 55 | 56 | func (w *fsnotifyWatcher) AddWatch(path string) error { 57 | return w.watcher.Add(path) 58 | } 59 | 60 | func (w *fsnotifyWatcher) Init(eventHandler FSEventHandler, errorHandler FSErrorHandler) error { 61 | var err error 62 | w.watcher, err = fsnotify.NewWatcher() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | w.eventHandler = eventHandler 68 | w.errorHandler = errorHandler 69 | return nil 70 | } 71 | 72 | func (w *fsnotifyWatcher) Run() { 73 | go func() { 74 | defer w.watcher.Close() 75 | for { 76 | select { 77 | case event := <-w.watcher.Events: 78 | if w.eventHandler != nil { 79 | w.eventHandler(event) 80 | } 81 | case err := <-w.watcher.Errors: 82 | if w.errorHandler != nil { 83 | w.errorHandler(err) 84 | } 85 | } 86 | } 87 | }() 88 | } 89 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/common/project_helper.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package common 17 | 18 | import ( 19 | "fmt" 20 | 21 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 22 | ) 23 | 24 | // GetDefaultSource get information about primary source 25 | // returns 3 strings: remote name, remote URL, reference(revision) 26 | func GetDefaultSource(ps v1.GitLikeProjectSource) (remoteName string, remoteURL string, revision string, err error) { 27 | // get git checkout information 28 | // if there are multiple remotes we are ignoring them, as we don't need to setup git repository as it is defined here, 29 | // the only thing that we need is to download the content 30 | 31 | if ps.CheckoutFrom != nil && ps.CheckoutFrom.Revision != "" { 32 | revision = ps.CheckoutFrom.Revision 33 | } 34 | if len(ps.Remotes) > 1 { 35 | if ps.CheckoutFrom == nil { 36 | err = fmt.Errorf("there are multiple git remotes but no checkoutFrom information") 37 | return "", "", "", err 38 | } 39 | remoteName = ps.CheckoutFrom.Remote 40 | if val, ok := ps.Remotes[remoteName]; ok { 41 | remoteURL = val 42 | } else { 43 | err = fmt.Errorf("checkoutFrom.Remote is not defined in Remotes") 44 | return "", "", "", err 45 | 46 | } 47 | } else { 48 | // there is only one remote, using range to get it as there are not indexes 49 | for name, url := range ps.Remotes { 50 | remoteName = name 51 | remoteURL = url 52 | } 53 | 54 | } 55 | 56 | return remoteName, remoteURL, revision, err 57 | 58 | } 59 | 60 | // GetProjectSourceType returns the source type of a given project source 61 | func GetProjectSourceType(projectSrc v1.ProjectSource) (v1.ProjectSourceType, error) { 62 | switch { 63 | case projectSrc.Git != nil: 64 | return v1.GitProjectSourceType, nil 65 | case projectSrc.Zip != nil: 66 | return v1.ZipProjectSourceType, nil 67 | case projectSrc.Custom != nil: 68 | return v1.CustomProjectSourceType, nil 69 | 70 | default: 71 | return "", fmt.Errorf("unknown project source type") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /scripts/changelog-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright Red Hat 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | # This script uses github_changelog_generator to generate a changelog and requires: 19 | # 20 | # 1. set an env GITHUB_TOKEN for the Github token 21 | # 2. previous release as an arg, to generate changelog since the mentioned release 22 | # 3. github_changelog_generator be installed where the script is being executed 23 | # 24 | # A CHANGELOG.md is generated and it's contents can be copy-pasted on the Github release 25 | 26 | #TODO: Since issue tracking happens in devfile/api, github_changelog_generator cannot 27 | # detect the issues from a different repository. Need to check if this is achievable. 28 | 29 | BLUE='\033[1;34m' 30 | GREEN='\033[0;32m' 31 | RED='\033[0;31m' 32 | NC='\033[0m' 33 | BOLD='\033[1m' 34 | 35 | # Ensure the github token is set 36 | if [ -z "$GITHUB_TOKEN" ] 37 | then 38 | echo -e "${RED}GITHUB_TOKEN env variable is empty..\nGet your GitHub token from https://github.com/settings/tokens and export GITHUB_TOKEN=${NC}" 39 | exit 1 40 | fi 41 | 42 | # Ensure there is a release version passed in 43 | if [ -z "$1" ] 44 | then 45 | echo -e "${RED}The last release version needs to be provided. Changelog will be generated since that release..${NC}" 46 | echo -e "${RED}Example: ./changelog-script.sh v1.0.0-alpha.2 will generate a changelog for all the changes since release v1.0.0-alpha.2${NC}" 47 | exit 1 48 | fi 49 | 50 | # Ensure github_changelog_generator is installed 51 | if ! command -v github_changelog_generator &> /dev/null 52 | then 53 | echo -e "${RED}The command github_changelog_generator could not be found, please install the command to generate a changelog${NC}" 54 | exit 1 55 | fi 56 | 57 | 58 | github_changelog_generator \ 59 | -u devfile \ 60 | -p library \ 61 | -t $GITHUB_TOKEN \ 62 | --since-tag $1 \ 63 | 64 | RESULT=$? 65 | 66 | if [ $RESULT -eq 0 ]; then 67 | echo -e "${GREEN}Changelog since release $1 generated at $PWD/CHANGELOG.md${NC}" 68 | else 69 | echo -e "${RED}Unable to generate changelog using github_changelog_generator${NC}" 70 | exit 1 71 | fi 72 | -------------------------------------------------------------------------------- /pkg/devfile/validate/validate.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package validate 17 | 18 | import ( 19 | "fmt" 20 | 21 | v2Validation "github.com/devfile/api/v2/pkg/validation" 22 | devfileData "github.com/devfile/library/v2/pkg/devfile/parser/data" 23 | v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" 24 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 25 | "github.com/hashicorp/go-multierror" 26 | ) 27 | 28 | // ValidateDevfileData validates whether sections of devfile are compatible 29 | func ValidateDevfileData(data devfileData.DevfileData) error { 30 | 31 | commands, err := data.GetCommands(common.DevfileOptions{}) 32 | if err != nil { 33 | return err 34 | } 35 | components, err := data.GetComponents(common.DevfileOptions{}) 36 | if err != nil { 37 | return err 38 | } 39 | projects, err := data.GetProjects(common.DevfileOptions{}) 40 | if err != nil { 41 | return err 42 | } 43 | starterProjects, err := data.GetStarterProjects(common.DevfileOptions{}) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | var returnedErr error 49 | switch d := data.(type) { 50 | case *v2.DevfileV2: 51 | // validate components 52 | err = v2Validation.ValidateComponents(components) 53 | if err != nil { 54 | returnedErr = multierror.Append(returnedErr, err) 55 | } 56 | 57 | // validate commands 58 | err = v2Validation.ValidateCommands(commands, components) 59 | if err != nil { 60 | returnedErr = multierror.Append(returnedErr, err) 61 | } 62 | 63 | err = v2Validation.ValidateEvents(data.GetEvents(), commands) 64 | if err != nil { 65 | returnedErr = multierror.Append(returnedErr, err) 66 | } 67 | 68 | err = v2Validation.ValidateProjects(projects) 69 | if err != nil { 70 | returnedErr = multierror.Append(returnedErr, err) 71 | } 72 | 73 | err = v2Validation.ValidateStarterProjects(starterProjects) 74 | if err != nil { 75 | returnedErr = multierror.Append(returnedErr, err) 76 | } 77 | 78 | return returnedErr 79 | 80 | default: 81 | return fmt.Errorf("unknown devfile type %T", d) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/common/options_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package common 17 | 18 | import ( 19 | "testing" 20 | 21 | "github.com/devfile/api/v2/pkg/attributes" 22 | ) 23 | 24 | func TestFilterDevfileObject(t *testing.T) { 25 | 26 | tests := []struct { 27 | name string 28 | attributes attributes.Attributes 29 | options DevfileOptions 30 | wantFilter bool 31 | }{ 32 | { 33 | name: "Filter with one key", 34 | attributes: attributes.Attributes{}.FromStringMap(map[string]string{ 35 | "firstString": "firstStringValue", 36 | "secondString": "secondStringValue", 37 | }), 38 | options: DevfileOptions{ 39 | Filter: map[string]interface{}{ 40 | "firstString": "firstStringValue", 41 | }, 42 | }, 43 | wantFilter: true, 44 | }, 45 | { 46 | name: "Filter with two keys", 47 | attributes: attributes.Attributes{}.FromStringMap(map[string]string{ 48 | "firstString": "firstStringValue", 49 | "secondString": "secondStringValue", 50 | }), 51 | options: DevfileOptions{ 52 | Filter: map[string]interface{}{ 53 | "firstString": "firstStringValue", 54 | "secondString": "secondStringValue", 55 | }, 56 | }, 57 | wantFilter: true, 58 | }, 59 | { 60 | name: "Filter with missing key", 61 | attributes: attributes.Attributes{}.FromStringMap(map[string]string{ 62 | "firstString": "firstStringValue", 63 | "secondString": "secondStringValue", 64 | }), 65 | options: DevfileOptions{ 66 | Filter: map[string]interface{}{ 67 | "missingkey": "firstStringValue", 68 | }, 69 | }, 70 | wantFilter: false, 71 | }, 72 | } 73 | 74 | for _, tt := range tests { 75 | t.Run(tt.name, func(t *testing.T) { 76 | filterIn, err := FilterDevfileObject(tt.attributes, tt.options) 77 | // Unexpected error 78 | if err != nil { 79 | t.Errorf("TestFilterDevfileObject() unexpected error: %v", err) 80 | } else if filterIn != tt.wantFilter { 81 | t.Errorf("TestFilterDevfileObject() error: expected %v got %v", tt.wantFilter, filterIn) 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pkg/devfile/parser/resolutionContext.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | 22 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 23 | ) 24 | 25 | // resolutionContextTree is a recursive structure representing information about the devfile that is 26 | // lost when flattening (e.g. plugins, parents) 27 | type resolutionContextTree struct { 28 | importReference v1.ImportReference 29 | parentNode *resolutionContextTree 30 | } 31 | 32 | // appendNode adds a new node to the resolution context. 33 | func (t *resolutionContextTree) appendNode(importReference v1.ImportReference) *resolutionContextTree { 34 | newNode := &resolutionContextTree{ 35 | importReference: importReference, 36 | parentNode: t, 37 | } 38 | return newNode 39 | } 40 | 41 | // hasCycle checks if the current resolutionContextTree has a cycle 42 | func (t *resolutionContextTree) hasCycle() error { 43 | var seenRefs []v1.ImportReference 44 | currNode := t 45 | hasCycle := false 46 | cycle := resolveImportReference(t.importReference) 47 | 48 | for currNode.parentNode != nil { 49 | for _, seenRef := range seenRefs { 50 | if reflect.DeepEqual(seenRef, currNode.importReference) { 51 | hasCycle = true 52 | } 53 | } 54 | seenRefs = append(seenRefs, currNode.importReference) 55 | currNode = currNode.parentNode 56 | cycle = fmt.Sprintf("%s -> %s", resolveImportReference(currNode.importReference), cycle) 57 | } 58 | 59 | if hasCycle { 60 | return fmt.Errorf("devfile has an cycle in references: %v", cycle) 61 | } 62 | return nil 63 | } 64 | 65 | func resolveImportReference(importReference v1.ImportReference) string { 66 | if !reflect.DeepEqual(importReference, v1.ImportReference{}) { 67 | switch { 68 | case importReference.Uri != "": 69 | return fmt.Sprintf("uri: %s", importReference.Uri) 70 | case importReference.Id != "": 71 | return fmt.Sprintf("id: %s, registryURL: %s", importReference.Id, importReference.RegistryUrl) 72 | case importReference.Kubernetes != nil: 73 | return fmt.Sprintf("name: %s, namespace: %s", importReference.Kubernetes.Name, importReference.Kubernetes.Namespace) 74 | } 75 | 76 | } 77 | // the first node 78 | return "main devfile" 79 | } 80 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | name: Validate PRs 16 | 17 | on: 18 | push: 19 | branches: [ main ] 20 | pull_request: 21 | branches: [ main ] 22 | 23 | # Declare default permissions as read only. 24 | permissions: read-all 25 | 26 | jobs: 27 | 28 | build: 29 | name: Build 30 | runs-on: ubuntu-latest 31 | 32 | permissions: 33 | security-events: write 34 | 35 | steps: 36 | 37 | - name: Check out code into the Go module directory 38 | uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 39 | 40 | - name: Setup Go environment 41 | uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 42 | with: 43 | go-version-file: 'go.mod' 44 | id: go 45 | 46 | - name: Check go mod status 47 | run: | 48 | make gomod_tidy 49 | if [[ ! -z $(git status -s) ]] 50 | then 51 | echo "Go mod state is not clean" 52 | git diff "$GITHUB_SHA" 53 | exit 1 54 | fi 55 | - name: Build Binary 56 | run: make bin 57 | 58 | - name: Check format 59 | run: | 60 | make gofmt 61 | if [[ ! -z $(git status -s) ]] 62 | then 63 | echo "not well formatted sources are found : $(git status -s)" 64 | exit 1 65 | fi 66 | 67 | - name: Check license 68 | run: | 69 | go install github.com/google/addlicense@latest 70 | git reset HEAD --hard 71 | make check_license 72 | if [[ $? != 0 ]] 73 | then 74 | echo "not well formatted sources are found:" 75 | git --no-pager diff 76 | exit 1 77 | fi 78 | 79 | - name: Run Go Tests 80 | run: make test 81 | 82 | - name: Run Gosec Security Scanner 83 | run: | 84 | go install github.com/securego/gosec/v2/cmd/gosec@v2.22.7 85 | make gosec 86 | if [[ $? != 0 ]] 87 | then 88 | echo "gosec scanner failed to run " 89 | exit 1 90 | fi 91 | 92 | - name: Upload SARIF file 93 | uses: github/codeql-action/upload-sarif@v2 94 | with: 95 | # Path to SARIF file relative to the root of the repository 96 | sarif_file: gosec.sarif 97 | 98 | - name: Upload coverage to Codecov 99 | uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/Test_Parent_LocalURI.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 2.1.0 2 | parent: 3 | uri: "Parent.yaml" 4 | commands: 5 | - apply: 6 | component: testcontainer1 #override, point to a container in the main devfile 7 | group: 8 | kind: test 9 | isDefault: false #override 10 | label: testcontainerparent #override 11 | id: testapplyparentcommand1 12 | - id: run 13 | exec: 14 | component: testcontainerparent1 15 | commandLine: npm install #override 16 | workingDir: /project2 #override 17 | env: #addition, does not exist in parent 18 | - name: PATH 19 | value: /dir 20 | - name: USER 21 | value: user1 22 | group: 23 | kind: build #override 24 | isDefault: false #override 25 | hotReloadCapable: false #override 26 | - id: test 27 | composite: 28 | commands: [testapplyparentcommand1, run] #override 29 | group: 30 | kind: debug 31 | label: testcompositeparent1 32 | parallel: false #override 33 | components: 34 | - kubernetes: 35 | inlined: | #override 36 | apiVersion: batch/v1 37 | kind: Pod 38 | metadata: 39 | name: pi 40 | namespace: dev 41 | spec: 42 | template: 43 | spec: 44 | containers: 45 | - name: newJob 46 | image: myimage 47 | command: ["some", "command"] 48 | restartPolicy: Never 49 | name: testkubeparent1 50 | - openshift: 51 | uri: openshift2.yaml #override 52 | name: openshiftcomponent1 53 | - container: 54 | image: updatedimage #override 55 | name: testcontainerparent1 56 | projects: 57 | - name: petclinic 58 | git: 59 | remotes: 60 | neworigin: "https://github.com/spring-projects/spring-petclinic2.git" #override, should result in 2 remotes in flattened file 61 | checkoutFrom: 62 | remote: neworigin #override 63 | revision: master #override 64 | - name: petclinic-dev 65 | zip: 66 | location: https://github.com/spring-projects/spring-petclinic/petclinic.zip #override 67 | clonePath: /petclinic #overrides the default 68 | attributes: 69 | editorFree: false #override 70 | user: user1 #override 71 | starterProjects: 72 | - name: user-app 73 | git: 74 | remotes: 75 | origin: 'https://github.com/OpenLiberty/application-stack-starters-new.git' #override 76 | description: An Open Liberty Starter project override #override 77 | subDir: /newapp #override 78 | attributes: #add additional attributes 79 | env: test 80 | user: user1 81 | attributes: # only applicable to v2.1.0 82 | category: mainDevfile #override 83 | title: This is a main devfile #override 84 | variables: #only applicable to v2.1.0 85 | version: 2.1.0 #override 86 | tag: main #override 87 | lastUpdated: "2021" #override 88 | components: 89 | - container: 90 | image: mKrpiOQnyGZ00003 91 | name: testcontainer1 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/common/options.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package common 17 | 18 | import ( 19 | "reflect" 20 | 21 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 22 | apiAttributes "github.com/devfile/api/v2/pkg/attributes" 23 | ) 24 | 25 | // DevfileOptions provides options for Devfile operations 26 | type DevfileOptions struct { 27 | // Filter is a map that lets filter devfile object against their attributes. Interface can be string, float, boolean or a map 28 | Filter map[string]interface{} 29 | 30 | // CommandOptions specifies the various options available to filter commands 31 | CommandOptions CommandOptions 32 | 33 | // ComponentOptions specifies the various options available to filter components 34 | ComponentOptions ComponentOptions 35 | 36 | // ProjectOptions specifies the various options available to filter projects/starterProjects 37 | ProjectOptions ProjectOptions 38 | 39 | // FilterByName specifies the name for the particular devfile object that's been looking for 40 | FilterByName string 41 | } 42 | 43 | // CommandOptions specifies the various options available to filter commands 44 | type CommandOptions struct { 45 | // CommandGroupKind is an option that allows to filter command based on their kind 46 | CommandGroupKind v1.CommandGroupKind 47 | 48 | // CommandType is an option that allows to filter command based on their type 49 | CommandType v1.CommandType 50 | } 51 | 52 | // ComponentOptions specifies the various options available to filter components 53 | type ComponentOptions struct { 54 | 55 | // ComponentType is an option that allows to filter component based on their type 56 | ComponentType v1.ComponentType 57 | } 58 | 59 | // ProjectOptions specifies the various options available to filter projects/starterProjects 60 | type ProjectOptions struct { 61 | 62 | // ProjectSourceType is an option that allows to filter project based on their source type 63 | ProjectSourceType v1.ProjectSourceType 64 | } 65 | 66 | // FilterDevfileObject filters devfile attributes with the given options 67 | func FilterDevfileObject(attributes apiAttributes.Attributes, options DevfileOptions) (bool, error) { 68 | filterIn := true 69 | for key, value := range options.Filter { 70 | var err error 71 | currentFilterIn := false 72 | attrValue := attributes.Get(key, &err) 73 | var keyNotFoundErr = &apiAttributes.KeyNotFoundError{Key: key} 74 | if err != nil && err.Error() != keyNotFoundErr.Error() { 75 | return false, err 76 | } else if reflect.DeepEqual(attrValue, value) { 77 | currentFilterIn = true 78 | } 79 | 80 | filterIn = filterIn && currentFilterIn 81 | } 82 | 83 | return filterIn, nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/devfile/parser/util/utils.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package util 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | "path" 22 | "strings" 23 | 24 | "github.com/devfile/library/v2/pkg/util" 25 | "github.com/hashicorp/go-multierror" 26 | ) 27 | 28 | // Contains common naming conventions for devfiles to look for when downloading resources 29 | var DevfilePossibilities = [...]string{"devfile.yaml", ".devfile.yaml", "devfile.yml", ".devfile.yml"} 30 | 31 | type DevfileUtilsClient struct { 32 | } 33 | 34 | func NewDevfileUtilsClient() DevfileUtilsClient { 35 | return DevfileUtilsClient{} 36 | } 37 | 38 | // DownloadInMemory is a wrapper to the util.DownloadInMemory() call. 39 | // This is done to help devfile/library clients invoke this function with a client. 40 | func (c DevfileUtilsClient) DownloadInMemory(params util.HTTPRequestParams) ([]byte, error) { 41 | return util.DownloadInMemory(params) 42 | } 43 | 44 | // DownloadGitRepoResources downloads the git repository resources 45 | func (c DevfileUtilsClient) DownloadGitRepoResources(url string, destDir string, token string) error { 46 | var returnedErr error 47 | if util.IsGitProviderRepo(url) { 48 | gitUrl, err := util.NewGitURL(url, token) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | if !gitUrl.IsFile || gitUrl.Revision == "" || !ValidateDevfileExistence((gitUrl.Path)) { 54 | return fmt.Errorf("error getting devfile from url: failed to retrieve %s", url) 55 | } 56 | 57 | stackDir, err := os.MkdirTemp("", "git-resources") 58 | if err != nil { 59 | return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err) 60 | } 61 | 62 | defer func(path string) { 63 | err := os.RemoveAll(path) 64 | if err != nil { 65 | returnedErr = multierror.Append(returnedErr, err) 66 | } 67 | }(stackDir) 68 | 69 | gitUrl.Token = token 70 | 71 | err = gitUrl.CloneGitRepo(stackDir) 72 | if err != nil { 73 | returnedErr = multierror.Append(returnedErr, err) 74 | return returnedErr 75 | } 76 | 77 | dir := path.Dir(path.Join(stackDir, gitUrl.Path)) 78 | err = util.CopyAllDirFiles(dir, destDir) 79 | if err != nil { 80 | returnedErr = multierror.Append(returnedErr, err) 81 | return returnedErr 82 | } 83 | } else { 84 | return fmt.Errorf("failed to download resources from parent devfile. Unsupported Git Provider for %s ", url) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | // ValidateDevfileExistence verifies if any of the naming possibilities for devfile are present in the url path 91 | func ValidateDevfileExistence(path string) bool { 92 | for _, devfile := range DevfilePossibilities { 93 | if strings.Contains(path, devfile) { 94 | return true 95 | } 96 | } 97 | return false 98 | } 99 | -------------------------------------------------------------------------------- /tests/v2/devfiles/samples/testParent.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: "2.0.0" 2 | commands: 3 | - id: testexecparent1 4 | exec: 5 | commandLine: 'echo "Hello ${GREETING} ${USER}"' 6 | component: parserTest-tests 7 | group: 8 | isDefault: false 9 | kind: run 10 | hotReloadCapable: false 11 | label: "Command Exec run" 12 | env: 13 | - name: "USER" 14 | value: "Test Tester" 15 | - name: "GREETING" 16 | value: "Hello" 17 | workingDir: This Directory 18 | - id: testcompositeparent1 19 | attributes: 20 | test: Composite Test test 21 | scope: Api 22 | composite: 23 | label: Composite Test 24 | commands: 25 | - runTest1 26 | - runTest2 27 | parallel: false 28 | group: 29 | isDefault: true 30 | kind: test 31 | components: 32 | - container: 33 | args: [ Arg1,Arg2 ] 34 | command: [ run1,run2 ] 35 | dedicatedPod: true 36 | image: "tester" 37 | memoryLimit: "128M" 38 | mountSources: false 39 | endpoints: 40 | - name: test-endpoint 41 | attributes: 42 | test: Apply Test 43 | scope: Api 44 | exposure: public 45 | path: test-path 46 | protocol: http 47 | secure: false 48 | targetPort: 1234 49 | volumeMounts: 50 | - name: volume 51 | path: mount 52 | sourceMapping: sourceMapping 53 | env: 54 | - name: envName 55 | value: envValue 56 | name: "testcontainerparent1" 57 | - name: "testopenshiftparent1" 58 | openshift: 59 | uri: test-uri 60 | endpoints: 61 | - name: test-endpoint 62 | attributes: 63 | test: Apply Test 64 | scope: Api 65 | exposure: public 66 | path: test-path 67 | protocol: http 68 | secure: false 69 | targetPort: 1234 70 | projects: 71 | - name: testparentproject1 72 | git: 73 | checkoutFrom: 74 | remote: test-branch 75 | remotes: 76 | origin: test-origin 77 | clonePath: /Users/test/projects 78 | sparseCheckoutDirs: [thisDir, thatDir] 79 | - name: testparentproject2 80 | github: 81 | checkoutFrom: 82 | remote: test-branch 83 | remotes: 84 | origin: test-origin 85 | clonePath: /Users/test/projects 86 | sparseCheckoutDirs: [thisDir, thatDir] 87 | - name: testparentproject3 88 | zip: 89 | location: git-repo.zip 90 | clonePath: /Users/test/projects 91 | sparseCheckoutDirs: [thisDir, thatDir] 92 | starterProjects: 93 | - name: testparentstarterproject1 94 | git: 95 | checkoutFrom: 96 | remote: test-branch 97 | remotes: 98 | origin: test-origin 99 | description: Test starter project 100 | subDir: test-subdir 101 | - name: testparentstarterproject2 102 | github: 103 | checkoutFrom: 104 | remote: test-branch 105 | remotes: 106 | origin: test-origin 107 | description: Test starter project 108 | subDir: test-subdir 109 | - name: testparentstarterproject3 110 | zip: 111 | location: git-repo.zip 112 | description: Test starter project 113 | subDir: test-subdir 114 | 115 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/events.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | "fmt" 20 | "strings" 21 | 22 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 23 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 24 | ) 25 | 26 | // GetEvents returns the Events Object parsed from devfile 27 | func (d *DevfileV2) GetEvents() v1.Events { 28 | if d.Events != nil { 29 | return *d.Events 30 | } 31 | return v1.Events{} 32 | } 33 | 34 | // AddEvents adds the Events Object to the devfile's events 35 | // an event field is considered as invalid if it is already defined 36 | // all event fields will be checked and processed, and returns a total error of all event fields 37 | func (d *DevfileV2) AddEvents(events v1.Events) error { 38 | 39 | if d.Events == nil { 40 | d.Events = &v1.Events{} 41 | } 42 | var errorsList []string 43 | if len(events.PreStop) > 0 { 44 | if len(d.Events.PreStop) > 0 { 45 | errorsList = append(errorsList, (&common.FieldAlreadyExistError{Field: "event field", Name: "pre stop"}).Error()) 46 | } else { 47 | d.Events.PreStop = events.PreStop 48 | } 49 | } 50 | 51 | if len(events.PreStart) > 0 { 52 | if len(d.Events.PreStart) > 0 { 53 | errorsList = append(errorsList, (&common.FieldAlreadyExistError{Field: "event field", Name: "pre start"}).Error()) 54 | } else { 55 | d.Events.PreStart = events.PreStart 56 | } 57 | } 58 | 59 | if len(events.PostStop) > 0 { 60 | if len(d.Events.PostStop) > 0 { 61 | errorsList = append(errorsList, (&common.FieldAlreadyExistError{Field: "event field", Name: "post stop"}).Error()) 62 | } else { 63 | d.Events.PostStop = events.PostStop 64 | } 65 | } 66 | 67 | if len(events.PostStart) > 0 { 68 | if len(d.Events.PostStart) > 0 { 69 | errorsList = append(errorsList, (&common.FieldAlreadyExistError{Field: "event field", Name: "post start"}).Error()) 70 | } else { 71 | d.Events.PostStart = events.PostStart 72 | } 73 | } 74 | if len(errorsList) > 0 { 75 | return fmt.Errorf("errors while adding events:\n%s", strings.Join(errorsList, "\n")) 76 | } 77 | return nil 78 | } 79 | 80 | // UpdateEvents updates the devfile's events 81 | // it only updates the events passed to it 82 | func (d *DevfileV2) UpdateEvents(postStart, postStop, preStart, preStop []string) { 83 | 84 | if d.Events == nil { 85 | d.Events = &v1.Events{} 86 | } 87 | 88 | if postStart != nil { 89 | d.Events.PostStart = postStart 90 | } 91 | if postStop != nil { 92 | d.Events.PostStop = postStop 93 | } 94 | if preStart != nil { 95 | d.Events.PreStart = preStart 96 | } 97 | if preStop != nil { 98 | d.Events.PreStop = preStop 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/helper_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package data 17 | 18 | import ( 19 | "reflect" 20 | "strings" 21 | "testing" 22 | 23 | v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" 24 | v200 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.0.0" 25 | ) 26 | 27 | func TestNewDevfileData(t *testing.T) { 28 | 29 | t.Run("valid devfile apiVersion", func(t *testing.T) { 30 | 31 | var ( 32 | version = APISchemaVersion200 33 | want = reflect.TypeOf(&v2.DevfileV2{}) 34 | obj, err = NewDevfileData(string(version)) 35 | got = reflect.TypeOf(obj) 36 | ) 37 | 38 | // got and want should be equal 39 | if !reflect.DeepEqual(got, want) { 40 | t.Errorf("got: '%v', want: '%s'", got, want) 41 | } 42 | 43 | // no error should be received 44 | if err != nil { 45 | t.Errorf("did not expect an error '%v'", err) 46 | } 47 | }) 48 | 49 | t.Run("invalid devfile apiVersion", func(t *testing.T) { 50 | 51 | var ( 52 | version = "invalidVersion" 53 | _, err = NewDevfileData(string(version)) 54 | ) 55 | 56 | // no error should be received 57 | if err == nil { 58 | t.Errorf("did not expect an error '%v'", err) 59 | } 60 | }) 61 | } 62 | 63 | func TestGetDevfileJSONSchema(t *testing.T) { 64 | 65 | t.Run("valid devfile apiVersion", func(t *testing.T) { 66 | 67 | var ( 68 | version = APISchemaVersion200 69 | want = v200.JsonSchema200 70 | got, err = GetDevfileJSONSchema(string(version)) 71 | ) 72 | 73 | if err != nil { 74 | t.Errorf("did not expect an error '%v'", err) 75 | } 76 | 77 | if strings.Compare(got, want) != 0 { 78 | t.Errorf("incorrect json schema") 79 | } 80 | }) 81 | 82 | t.Run("invalid devfile apiVersion", func(t *testing.T) { 83 | 84 | var ( 85 | version = "invalidVersion" 86 | _, err = GetDevfileJSONSchema(string(version)) 87 | ) 88 | 89 | if err == nil { 90 | t.Errorf("expected an error, didn't get one") 91 | } 92 | }) 93 | } 94 | 95 | func TestIsApiVersionSupported(t *testing.T) { 96 | 97 | t.Run("valid devfile apiVersion", func(t *testing.T) { 98 | 99 | var ( 100 | version = APISchemaVersion200 101 | want = true 102 | got = IsApiVersionSupported(string(version)) 103 | ) 104 | 105 | if got != want { 106 | t.Errorf("want: '%t', got: '%t'", want, got) 107 | } 108 | }) 109 | 110 | t.Run("invalid devfile apiVersion", func(t *testing.T) { 111 | 112 | var ( 113 | version = "invalidVersion" 114 | want = false 115 | got = IsApiVersionSupported(string(version)) 116 | ) 117 | 118 | if got != want { 119 | t.Errorf("expected an error, didn't get one") 120 | } 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/context_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "github.com/stretchr/testify/assert" 20 | 21 | parserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util" 22 | 23 | "net/http" 24 | "net/http/httptest" 25 | "testing" 26 | ) 27 | 28 | func TestPopulateFromBytes(t *testing.T) { 29 | failedToConvertYamlErr := "mapping values are not allowed in this context" 30 | 31 | tests := []struct { 32 | name string 33 | dataFunc func() []byte 34 | expectError *string 35 | }{ 36 | { 37 | name: "valid data passed", 38 | dataFunc: validJsonRawContent200, 39 | }, 40 | { 41 | name: "invalid data passed", 42 | dataFunc: invalidJsonRawContent200, 43 | expectError: &failedToConvertYamlErr, 44 | }, 45 | } 46 | for _, tt := range tests { 47 | t.Run(tt.name, func(t *testing.T) { 48 | testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 | _, err := w.Write(tt.dataFunc()) 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | })) 54 | var ( 55 | d = DevfileCtx{ 56 | url: testServer.URL, 57 | } 58 | ) 59 | defer testServer.Close() 60 | err := d.PopulateFromURL(parserUtil.NewDevfileUtilsClient()) 61 | if (tt.expectError != nil) != (err != nil) { 62 | t.Errorf("TestPopulateFromBytes(): unexpected error: %v, wantErr: %v", err, tt.expectError) 63 | } else if tt.expectError != nil { 64 | assert.Regexp(t, *tt.expectError, err.Error(), "TestPopulateFromBytes(): Error message should match") 65 | } 66 | }) 67 | } 68 | } 69 | 70 | func TestPopulateFromInvalidURL(t *testing.T) { 71 | expectError := ".*invalid URI for request" 72 | t.Run("Populate from invalid URL", func(t *testing.T) { 73 | var ( 74 | d = DevfileCtx{ 75 | url: "blah", 76 | } 77 | ) 78 | 79 | err := d.PopulateFromURL(parserUtil.NewDevfileUtilsClient()) 80 | 81 | if err == nil { 82 | t.Errorf("TestPopulateFromInvalidURL(): expected an error, didn't get one") 83 | } else { 84 | assert.Regexp(t, expectError, err.Error(), "TestPopulateFromInvalidURL(): Error message should match") 85 | } 86 | }) 87 | } 88 | 89 | func TestNewURLDevfileCtx(t *testing.T) { 90 | var ( 91 | token = "fake-token" 92 | url = "https://github.com/devfile/registry/blob/main/stacks/go/2.0.0/devfile.yaml" 93 | ) 94 | { 95 | d := NewURLDevfileCtx(url) 96 | assert.Equal(t, "https://github.com/devfile/registry/blob/main/stacks/go/2.0.0/devfile.yaml", d.GetURL()) 97 | assert.Equal(t, "", d.GetToken()) 98 | d.SetToken(token) 99 | assert.Equal(t, "fake-token", d.GetToken()) 100 | } 101 | } 102 | 103 | func invalidJsonRawContent200() []byte { 104 | return []byte(InvalidDevfileContent) 105 | } 106 | -------------------------------------------------------------------------------- /devfile.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | schemaVersion: 2.2.0-latest 17 | metadata: 18 | name: nodejs 19 | version: 1.0.0 20 | attributes: 21 | alpha.build-dockerfile: /relative/path/to/Dockerfile 22 | variables: 23 | test: testValue 24 | parent: 25 | # uri: https://raw.githubusercontent.com/odo-devfiles/registry/master/devfiles/nodejs/devfile.yaml 26 | id: nodejs 27 | registryUrl: "https://registry.devfile.io" 28 | version: latest 29 | commands: 30 | - id: install 31 | exec: 32 | component: runtime 33 | commandLine: npm install 34 | workingDir: /project-starter 35 | group: 36 | kind: build 37 | isDefault: true 38 | starterProjects: 39 | - name: nodejs-starter2 40 | git: 41 | remotes: 42 | origin: https://github.com/odo-devfiles/nodejs-ex.git 43 | components: 44 | - name: runtime2 45 | attributes: 46 | tool: console-import 47 | import: 48 | strategy: Dockerfile 49 | container: 50 | endpoints: 51 | - name: http-8888 52 | targetPort: 8888 53 | image: registry.access.redhat.com/ubi8/nodejs-12:1-45 54 | memoryLimit: 1024Mi 55 | mountSources: true 56 | sourceMapping: /project 57 | command: 58 | - npm install 59 | - name: runtime3 60 | attributes: 61 | tool: odo 62 | cli: 63 | usage: deploy 64 | container: 65 | endpoints: 66 | - name: http-8080 67 | targetPort: 8080 68 | image: registry.access.redhat.com/ubi8/nodejs-12:1-45 69 | memoryLimit: 1024Mi 70 | mountSources: true 71 | sourceMapping: /project 72 | - name: runtime4 73 | attributes: 74 | tool: workspace-operator 75 | container: 76 | endpoints: 77 | - name: http-9090 78 | targetPort: 9090 79 | image: "{{invalid-var}}" 80 | memoryLimit: 1024Mi 81 | mountSources: true 82 | sourceMapping: /project 83 | commands: 84 | - exec: 85 | commandLine: npm install 86 | component: runtime2 87 | group: 88 | isDefault: false 89 | kind: build 90 | workingDir: "{{test}}" 91 | id: install2 92 | attributes: 93 | tool: odo 94 | mandatory: false 95 | - exec: 96 | commandLine: npm start 97 | component: runtime2 98 | group: 99 | isDefault: false 100 | kind: run 101 | workingDir: /project 102 | id: run2 103 | attributes: 104 | tool: odo 105 | mandatory: true 106 | - exec: 107 | commandLine: npm run debug 108 | component: runtime2 109 | group: 110 | isDefault: false 111 | kind: debug 112 | workingDir: /project 113 | id: debug2 114 | - exec: 115 | commandLine: npm test 116 | component: runtime2 117 | group: 118 | isDefault: false 119 | kind: test 120 | workingDir: /project 121 | id: test2 122 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Devfile Library 2 | 3 | This document is a new contributor guide, which outlines the requirements for contributing to this repository. 4 | 5 | To get an overview of the project, read the [README](README.md). For more information on devfiles, check the official [devfile docs](https://devfile.io/docs/2.2.0/what-is-a-devfile). 6 | 7 | ## Certificate of Origin 8 | 9 | By contributing to this project you agree to the Developer Certificate of 10 | Origin (DCO). This document was created by the Linux Kernel community and is a 11 | simple statement that you, as a contributor, have the legal right to make the 12 | contribution. See the [DCO](./DCO) file for details. 13 | 14 | ## Prerequisites 15 | 16 | The following are required to work on devfile library: 17 | 18 | - Git 19 | - Go 1.24 or later 20 | 21 | ## Code of Conduct 22 | Before contributing to this repository, see [contributor code of conduct](https://github.com/devfile/api/blob/main/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct) 23 | 24 | ## How to Contribute 25 | 26 | ### Issues 27 | 28 | If you spot a problem with devfile library, [search if an issue already exists](https://github.com/devfile/api/issues). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/devfile/api/issues/new/choose). 29 | 30 | ### Writing Code 31 | 32 | For writing the code just follow [Go guide](https://go.dev/doc/effective_go), and also test with [testing](https://pkg.go.dev/testing). Remember to add new unit tests if new features have been introducted, or changes have been made to existing code. If there is something unclear of the style, just look at existing code which might help you to understand it better. 33 | 34 | ### Testing Changes 35 | To run unit tests and api tests. Visit [library tests](tests/README.md) to find out more information on tests 36 | ``` 37 | make test 38 | ``` 39 | 40 | ### Submitting Pull Request 41 | 42 | **Note:** All commits must be signed off with the footer: 43 | ``` 44 | Signed-off-by: First Lastname 45 | ``` 46 | 47 | You can easily add this footer to your commits by adding `-s` when running `git commit`. 48 | 49 | When you think the code is ready for review, create a pull request and link the issue associated with it. 50 | Owners of the repository will watch out for and review new PR‘s. 51 | By default for each change in the PR, Travis CI runs all the tests against it. If tests are failing make sure to address the failures. 52 | If comments have been given in a review, they have to get integrated. 53 | After addressing review comments, don’t forget to add a comment in the PR afterward, so everyone gets notified by Github. 54 | 55 | 56 | ## Managing the Repository 57 | 58 | ### Updating Devfile Schema Files in Library 59 | 60 | Executing `./scripts/updateApi.sh` fetches the latest `github.com/devfile/api` go mod and updates the schema saved under `pkg/devfile/parser/data` 61 | 62 | The script also accepts a version number as an argument to update the devfile schema for a specific devfile version. 63 | For example, running the following command will update the devfile schema for 2.0.0 64 | ``` 65 | ./scripts/updateApi.sh 2.0.0 66 | ``` 67 | Running the script with no arguments will default to update the latest devfile version. 68 | 69 | 70 | ### Releases 71 | 72 | Currently devfile library publish new releases annually. A new version can also be generated and released on demand. 73 | A new branch is expected to be created for a new release. 74 | To generate a changelog for a new release, execute `./scripts/changelog-script.sh v2.x.y` for all the changes since the release v2.x.y 75 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/content.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "bytes" 20 | "unicode" 21 | 22 | errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors" 23 | parserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util" 24 | "github.com/devfile/library/v2/pkg/util" 25 | "github.com/pkg/errors" 26 | "k8s.io/klog" 27 | "sigs.k8s.io/yaml" 28 | ) 29 | 30 | // Every JSON document starts with "{" 31 | var jsonPrefix = []byte("{") 32 | 33 | // YAMLToJSON converts a single YAML document into a JSON document 34 | // or returns an error. If the document appears to be JSON the 35 | // YAML decoding path is not used. 36 | func YAMLToJSON(data []byte) ([]byte, error) { 37 | 38 | // Is already JSON 39 | if hasJSONPrefix(data) { 40 | return data, nil 41 | } 42 | 43 | // Is YAML, convert to JSON 44 | data, err := yaml.YAMLToJSON(data) 45 | if err != nil { 46 | return data, &errPkg.NonCompliantDevfile{Err: err.Error()} 47 | } 48 | 49 | // Successful 50 | klog.V(4).Infof("converted devfile YAML to JSON") 51 | return data, nil 52 | } 53 | 54 | // hasJSONPrefix returns true if the provided buffer appears to start with 55 | // a JSON open brace. 56 | func hasJSONPrefix(buf []byte) bool { 57 | return hasPrefix(buf, jsonPrefix) 58 | } 59 | 60 | // hasPrefix returns true if the first non-whitespace bytes in buf is prefix. 61 | func hasPrefix(buf []byte, prefix []byte) bool { 62 | trim := bytes.TrimLeftFunc(buf, unicode.IsSpace) 63 | return bytes.HasPrefix(trim, prefix) 64 | } 65 | 66 | // SetDevfileContent reads devfile and if devfile is in YAML format converts it to JSON 67 | func (d *DevfileCtx) SetDevfileContent(devfileUtilsClient parserUtil.DevfileUtils) error { 68 | 69 | var err error 70 | var data []byte 71 | if d.url != "" { 72 | // set the client identifier for telemetry 73 | params := util.HTTPRequestParams{URL: d.url, TelemetryClientName: util.TelemetryClientName} 74 | if d.token != "" { 75 | params.Token = d.token 76 | } 77 | data, err = devfileUtilsClient.DownloadInMemory(params) 78 | if err != nil { 79 | return errors.Wrap(err, "error getting devfile info from url") 80 | } 81 | } else if d.absPath != "" { 82 | // Read devfile 83 | fs := d.GetFs() 84 | data, err = fs.ReadFile(d.absPath) 85 | if err != nil { 86 | return errors.Wrapf(err, "failed to read devfile from path '%s'", d.absPath) 87 | } 88 | } 89 | 90 | // set devfile content 91 | return d.SetDevfileContentFromBytes(data) 92 | } 93 | 94 | // SetDevfileContentFromBytes sets devfile content from byte input 95 | func (d *DevfileCtx) SetDevfileContentFromBytes(data []byte) error { 96 | // If YAML file convert it to JSON 97 | var err error 98 | d.rawContent, err = YAMLToJSON(data) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | // Successful 104 | return nil 105 | } 106 | 107 | // GetDevfileContent returns the devfile content 108 | func (d *DevfileCtx) GetDevfileContent() []byte { 109 | return d.rawContent 110 | } 111 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Red Hat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This workflow uses actions that are not certified by GitHub. They are provided 17 | # by a third-party and are governed by separate terms of service, privacy 18 | # policy, and support documentation. 19 | 20 | name: Scorecard supply-chain security 21 | on: 22 | # For Branch-Protection check. Only the default branch is supported. See 23 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 24 | branch_protection_rule: 25 | pull_request: 26 | branches: [ "main" ] 27 | 28 | # Declare default permissions as read only. 29 | permissions: read-all 30 | 31 | jobs: 32 | analysis: 33 | name: Scorecard analysis 34 | runs-on: ubuntu-latest 35 | permissions: 36 | # Needed to upload the results to code-scanning dashboard. 37 | security-events: write 38 | # Needed to publish results and get a badge (see publish_results below). 39 | id-token: write 40 | # Uncomment the permissions below if installing in a private repository. 41 | # contents: read 42 | # actions: read 43 | 44 | steps: 45 | - name: "Checkout code" 46 | uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 47 | with: 48 | persist-credentials: false 49 | 50 | - name: "Run analysis" 51 | uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2 52 | with: 53 | results_file: results.sarif 54 | results_format: sarif 55 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 56 | # - you want to enable the Branch-Protection check on a *public* repository, or 57 | # - you are installing Scorecard on a *private* repository 58 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 59 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 60 | 61 | # Public repositories: 62 | # - Publish results to OpenSSF REST API for easy access by consumers 63 | # - Allows the repository to include the Scorecard badge. 64 | # - See https://github.com/ossf/scorecard-action#publishing-results. 65 | # For private repositories: 66 | # - `publish_results` will always be set to `false`, regardless 67 | # of the value entered here. 68 | publish_results: true 69 | 70 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 71 | # format to the repository Actions tab. 72 | - name: "Upload artifact" 73 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 74 | with: 75 | name: SARIF file 76 | path: results.sarif 77 | retention-days: 5 78 | 79 | # Upload the results to GitHub's code scanning dashboard. 80 | - name: "Upload to code-scanning" 81 | uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 82 | with: 83 | sarif_file: results.sarif 84 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/common/command_helper.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package common 17 | 18 | import ( 19 | "fmt" 20 | 21 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 22 | ) 23 | 24 | // GetGroup returns the group the command belongs to 25 | func GetGroup(dc v1.Command) *v1.CommandGroup { 26 | switch { 27 | case dc.Composite != nil: 28 | return dc.Composite.Group 29 | case dc.Exec != nil: 30 | return dc.Exec.Group 31 | case dc.Apply != nil: 32 | return dc.Apply.Group 33 | case dc.Custom != nil: 34 | return dc.Custom.Group 35 | 36 | default: 37 | return nil 38 | } 39 | } 40 | 41 | // GetExecComponent returns the component of the exec command 42 | func GetExecComponent(dc v1.Command) string { 43 | if dc.Exec != nil { 44 | return dc.Exec.Component 45 | } 46 | 47 | return "" 48 | } 49 | 50 | // GetExecCommandLine returns the command line of the exec command 51 | func GetExecCommandLine(dc v1.Command) string { 52 | if dc.Exec != nil { 53 | return dc.Exec.CommandLine 54 | } 55 | 56 | return "" 57 | } 58 | 59 | // GetExecWorkingDir returns the working dir of the exec command 60 | func GetExecWorkingDir(dc v1.Command) string { 61 | if dc.Exec != nil { 62 | return dc.Exec.WorkingDir 63 | } 64 | 65 | return "" 66 | } 67 | 68 | // GetApplyComponent returns the component of the apply command 69 | func GetApplyComponent(dc v1.Command) string { 70 | if dc.Apply != nil { 71 | return dc.Apply.Component 72 | } 73 | 74 | return "" 75 | } 76 | 77 | // GetCommandType returns the command type of a given command 78 | func GetCommandType(command v1.Command) (v1.CommandType, error) { 79 | switch { 80 | case command.Apply != nil: 81 | return v1.ApplyCommandType, nil 82 | case command.Composite != nil: 83 | return v1.CompositeCommandType, nil 84 | case command.Exec != nil: 85 | return v1.ExecCommandType, nil 86 | case command.Custom != nil: 87 | return v1.CustomCommandType, nil 88 | 89 | default: 90 | return "", fmt.Errorf("unknown command type") 91 | } 92 | } 93 | 94 | // GetCommandsMap returns a map of the command Id to the command 95 | func GetCommandsMap(commands []v1.Command) map[string]v1.Command { 96 | commandMap := make(map[string]v1.Command, len(commands)) 97 | for _, command := range commands { 98 | commandMap[command.Id] = command 99 | } 100 | return commandMap 101 | } 102 | 103 | // GetCommandsFromEvent returns the list of commands from the event name. 104 | // If the event is a composite command, it returns the sub-commands from the tree 105 | func GetCommandsFromEvent(commandsMap map[string]v1.Command, eventName string) []string { 106 | var commands []string 107 | 108 | if command, ok := commandsMap[eventName]; ok { 109 | if command.Composite != nil { 110 | for _, compositeSubCmd := range command.Composite.Commands { 111 | subCommands := GetCommandsFromEvent(commandsMap, compositeSubCmd) 112 | commands = append(commands, subCommands...) 113 | } 114 | } else { 115 | commands = append(commands, command.Id) 116 | } 117 | } 118 | 119 | return commands 120 | } 121 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/workspace_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | "reflect" 20 | "testing" 21 | 22 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 23 | ) 24 | 25 | var devworkspaceContent = v1.DevWorkspaceTemplateSpecContent{ 26 | Components: []v1.Component{ 27 | { 28 | Name: "component1", 29 | ComponentUnion: v1.ComponentUnion{ 30 | Container: &v1.ContainerComponent{}, 31 | }, 32 | }, 33 | { 34 | Name: "component2", 35 | ComponentUnion: v1.ComponentUnion{ 36 | Volume: &v1.VolumeComponent{}, 37 | }, 38 | }, 39 | }, 40 | } 41 | 42 | func TestDevfile200_SetDevfileWorkspaceSpecContent(t *testing.T) { 43 | 44 | devfilev2 := &DevfileV2{ 45 | v1.Devfile{}, 46 | } 47 | 48 | tests := []struct { 49 | name string 50 | workspaceSpecContent v1.DevWorkspaceTemplateSpecContent 51 | expectedDevfilev2 *DevfileV2 52 | }{ 53 | { 54 | name: "set workspace", 55 | workspaceSpecContent: devworkspaceContent, 56 | expectedDevfilev2: &DevfileV2{ 57 | v1.Devfile{ 58 | DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ 59 | DevWorkspaceTemplateSpecContent: devworkspaceContent, 60 | }, 61 | }, 62 | }, 63 | }, 64 | } 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | devfilev2.SetDevfileWorkspaceSpecContent(tt.workspaceSpecContent) 68 | if !reflect.DeepEqual(devfilev2, tt.expectedDevfilev2) { 69 | t.Errorf("TestDevfile200_SetDevfileWorkspaceSpecContent() error: expected %v, got %v", tt.expectedDevfilev2, devfilev2) 70 | } 71 | }) 72 | } 73 | } 74 | 75 | func TestDevfile200_SetDevfileWorkspaceSpec(t *testing.T) { 76 | 77 | devfilev2 := &DevfileV2{ 78 | v1.Devfile{}, 79 | } 80 | 81 | tests := []struct { 82 | name string 83 | workspaceSpec v1.DevWorkspaceTemplateSpec 84 | expectedDevfilev2 *DevfileV2 85 | }{ 86 | { 87 | name: "set workspace spec", 88 | workspaceSpec: v1.DevWorkspaceTemplateSpec{ 89 | Parent: &v1.Parent{ 90 | ImportReference: v1.ImportReference{ 91 | ImportReferenceUnion: v1.ImportReferenceUnion{ 92 | Uri: "uri", 93 | }, 94 | }, 95 | }, 96 | DevWorkspaceTemplateSpecContent: devworkspaceContent, 97 | }, 98 | expectedDevfilev2: &DevfileV2{ 99 | v1.Devfile{ 100 | DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ 101 | Parent: &v1.Parent{ 102 | ImportReference: v1.ImportReference{ 103 | ImportReferenceUnion: v1.ImportReferenceUnion{ 104 | Uri: "uri", 105 | }, 106 | }, 107 | }, 108 | DevWorkspaceTemplateSpecContent: devworkspaceContent, 109 | }, 110 | }, 111 | }, 112 | }, 113 | } 114 | for _, tt := range tests { 115 | t.Run(tt.name, func(t *testing.T) { 116 | devfilev2.SetDevfileWorkspaceSpec(tt.workspaceSpec) 117 | if !reflect.DeepEqual(devfilev2, tt.expectedDevfilev2) { 118 | t.Errorf("TestDevfile200_SetDevfileWorkspaceSpec() error: expected %v, got %v", tt.expectedDevfilev2, devfilev2) 119 | } 120 | }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/versions.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package data 17 | 18 | import ( 19 | "reflect" 20 | 21 | v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" 22 | v200 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.0.0" 23 | v210 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.1.0" 24 | v220 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.2.0" 25 | v221 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.2.1" 26 | v222 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.2.2" 27 | v230 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.3.0" 28 | ) 29 | 30 | // SupportedApiVersions stores the supported devfile API versions 31 | type supportedApiVersion string 32 | 33 | // Supported devfile API versions 34 | const ( 35 | APISchemaVersion200 supportedApiVersion = "2.0.0" 36 | APISchemaVersion210 supportedApiVersion = "2.1.0" 37 | APISchemaVersion220 supportedApiVersion = "2.2.0" 38 | APISchemaVersion221 supportedApiVersion = "2.2.1" 39 | APISchemaVersion222 supportedApiVersion = "2.2.2" 40 | APISchemaVersion230 supportedApiVersion = "2.3.0" 41 | APIVersionAlpha2 supportedApiVersion = "v1alpha2" 42 | ) 43 | 44 | // ------------- Init functions ------------- // 45 | 46 | // apiVersionToDevfileStruct maps supported devfile API versions to their corresponding devfile structs 47 | var apiVersionToDevfileStruct map[supportedApiVersion]reflect.Type 48 | 49 | // Initializes a map of supported devfile api versions and devfile structs 50 | func init() { 51 | apiVersionToDevfileStruct = make(map[supportedApiVersion]reflect.Type) 52 | apiVersionToDevfileStruct[APISchemaVersion200] = reflect.TypeOf(v2.DevfileV2{}) 53 | apiVersionToDevfileStruct[APISchemaVersion210] = reflect.TypeOf(v2.DevfileV2{}) 54 | apiVersionToDevfileStruct[APISchemaVersion220] = reflect.TypeOf(v2.DevfileV2{}) 55 | apiVersionToDevfileStruct[APISchemaVersion221] = reflect.TypeOf(v2.DevfileV2{}) 56 | apiVersionToDevfileStruct[APISchemaVersion222] = reflect.TypeOf(v2.DevfileV2{}) 57 | apiVersionToDevfileStruct[APISchemaVersion230] = reflect.TypeOf(v2.DevfileV2{}) 58 | apiVersionToDevfileStruct[APIVersionAlpha2] = reflect.TypeOf(v2.DevfileV2{}) 59 | } 60 | 61 | // Map to store mappings between supported devfile API versions and respective devfile JSON schemas 62 | var devfileApiVersionToJSONSchema map[supportedApiVersion]string 63 | 64 | // init initializes a map of supported devfile apiVersions with it's respective devfile JSON schema 65 | func init() { 66 | devfileApiVersionToJSONSchema = make(map[supportedApiVersion]string) 67 | devfileApiVersionToJSONSchema[APISchemaVersion200] = v200.JsonSchema200 68 | devfileApiVersionToJSONSchema[APISchemaVersion210] = v210.JsonSchema210 69 | devfileApiVersionToJSONSchema[APISchemaVersion220] = v220.JsonSchema220 70 | devfileApiVersionToJSONSchema[APISchemaVersion221] = v221.JsonSchema221 71 | devfileApiVersionToJSONSchema[APISchemaVersion222] = v222.JsonSchema222 72 | devfileApiVersionToJSONSchema[APISchemaVersion230] = v230.JsonSchema230 73 | // should use hightest v2 schema version since it is expected to be backward compatible with the same api version 74 | devfileApiVersionToJSONSchema[APIVersionAlpha2] = v230.JsonSchema230 75 | } 76 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Devfile Parser Library Tests 2 | 3 | ## About 4 | 5 | The tests use the go language and are intended to test every aspect of the parser for every schema attribute. Some basic aspects of the tests: 6 | 7 | * A first test (parser_v200_schema_test.go) feeds pre-created devfiles to the parser to ensure the parser can parse all attribues and return an appropriate error when the devfile contains an error. This test is not currently available. 8 | * A second set of tests (parser_v200_verify_test.go) create devfile content at runtime: 9 | * Devfile content is randomly generated and as a result the tests are designed to run multiple times. 10 | * Parser functions covered: 11 | * Read an existing devfile. 12 | * Write a new devfile. 13 | * Modify content of a devfile. 14 | * Multi-threaded access to the parser. 15 | * The tests use the devfile schema to create a structure containing expected content for a devfile. These structures are compared with those returned by the parser. 16 | * sigs.k8s.io/yaml is used to write out devfiles. 17 | * github.com/google/go-cmp/cmp is used to compare structures. 18 | 19 | ## Current tests: 20 | 21 | The tests using pre-created devfiles are not currently available (update in progress due to schema changes) 22 | 23 | The tests which generate devfiles with random content at run time currently cover the following properties and items. 24 | 25 | * Commands: 26 | * Exec 27 | * Composite 28 | * Apply 29 | * Components 30 | * Container 31 | * Volume 32 | * Projects 33 | * Starter Projects 34 | 35 | ## Test structure 36 | 37 | * From this repository 38 | - `tests/v2/libraryTest/library-test.go`: The go unit test program 39 | - `tests/v2/utils/library/*-utils.go` : utilites, used by the test, which contain functions uniqiue to the library tests. Mostly contain the code to modify and check devfile content. 40 | * From the [api respository](https://github.com/devfile/api/tree/main/test/v200/utils/common) 41 | - `tests/v200/utils/common/*-utils.go` : utilites, used by the test, which are also used by the api tests. Mostly contain the code to generate valid devfile content. 42 | 43 | ## Running the tests locally 44 | 45 | 1. Go to the ```/library``` directory 46 | 1. Run ```Make test``` 47 | 1. The test creates the following files: 48 | 1. ```./tmp/test.log``` contains log output from the tests. 49 | 1. ```./tmp/library_test/Test_*.yaml``` are the devfiles which are randomly generated at runtime. The file name matches the name of the test function which resulted in them being created. 50 | 1. If a test detects an error when comparing properties returned by the parser with expected properties 51 | * ```./tmp/library_test/Test_*__Parser.yaml``` - property as returned by the parser 52 | * ```./tmp/library_test/Test_*__Test.yaml``` - property as expected by the test 53 | 1. ```tests/v2/lib-test-coverage.html``` which is the test coverage report for the test run. You can open this file up in a browser and inspect the results to determine the gaps you may have in testing 54 | 55 | Note: each run of the test removes the existing contents of the ```./tmp``` directory 56 | 57 | ## Viewing test results from a workflow 58 | 59 | The tests run automatically with every PR or Push action. You can see the results in the `devfile/library` repo's `Actions` view: 60 | 61 | 1. Select the `Validate PRs` workflow and click on the PR you're interested in 62 | 1. To view the console output, select the `Build` job and expand the `Run Go Tests` step. This will give you a summary of the tests that were executed and their respective status 63 | 1. To view the test coverage report, click on the `Summary` page and you should see an `Artifacts` section with the `lib-test-coverage-html` file available for download. 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/apiVersion_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | "testing" 22 | 23 | errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors" 24 | ) 25 | 26 | func TestSetDevfileAPIVersion(t *testing.T) { 27 | 28 | const ( 29 | schemaVersion = "2.2.0" 30 | validJson = `{"schemaVersion": "2.2.0"}` 31 | concreteSchema = `{"schemaVersion": "2.2.0-latest"}` 32 | emptyJson = "{}" 33 | emptySchemaVersionJson = `{"schemaVersion": ""}` 34 | badJson = `{"name": "Joe", "age": null]` 35 | devfilePath = "/testpath/devfile.yaml" 36 | devfileURL = "http://server/devfile.yaml" 37 | ) 38 | 39 | // test table 40 | tests := []struct { 41 | name string 42 | devfileCtx DevfileCtx 43 | want string 44 | wantErr error 45 | }{ 46 | { 47 | name: "valid schemaVersion", 48 | devfileCtx: DevfileCtx{rawContent: []byte(validJson), absPath: devfilePath}, 49 | want: schemaVersion, 50 | wantErr: nil, 51 | }, 52 | { 53 | name: "concrete schemaVersion", 54 | devfileCtx: DevfileCtx{rawContent: []byte(concreteSchema), absPath: devfilePath}, 55 | want: schemaVersion, 56 | wantErr: nil, 57 | }, 58 | { 59 | name: "schemaVersion not present", 60 | devfileCtx: DevfileCtx{rawContent: []byte(emptyJson), absPath: devfilePath}, 61 | want: "", 62 | wantErr: &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("schemaVersion not present in devfile: %s", devfilePath)}, 63 | }, 64 | { 65 | name: "schemaVersion empty", 66 | devfileCtx: DevfileCtx{rawContent: []byte(emptySchemaVersionJson), url: devfileURL}, 67 | want: "", 68 | wantErr: &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("schemaVersion in devfile: %s cannot be empty", devfileURL)}, 69 | }, 70 | { 71 | name: "unmarshal error", 72 | devfileCtx: DevfileCtx{rawContent: []byte(badJson), url: devfileURL}, 73 | want: "", 74 | wantErr: &errPkg.NonCompliantDevfile{Err: "invalid character ']' after object key:value pair"}, 75 | }, 76 | } 77 | 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | 81 | // new devfile context object 82 | d := tt.devfileCtx 83 | 84 | // SetDevfileAPIVersion 85 | gotErr := d.SetDevfileAPIVersion() 86 | got := d.apiVersion 87 | 88 | if !reflect.DeepEqual(gotErr, tt.wantErr) { 89 | t.Errorf("TestSetDevfileAPIVersion() unexpected error: '%v', wantErr: '%v'", gotErr, tt.wantErr) 90 | } 91 | 92 | if got != tt.want { 93 | t.Errorf("TestSetDevfileAPIVersion() want: '%v', got: '%v'", tt.want, got) 94 | } 95 | }) 96 | } 97 | } 98 | 99 | func TestGetApiVersion(t *testing.T) { 100 | 101 | const ( 102 | apiVersion = "2.0.0" 103 | ) 104 | 105 | t.Run("get apiVersion", func(t *testing.T) { 106 | 107 | var ( 108 | d = DevfileCtx{apiVersion: apiVersion} 109 | want = apiVersion 110 | got = d.GetApiVersion() 111 | ) 112 | 113 | if got != want { 114 | t.Errorf("TestGetApiVersion() want: '%v', got: '%v'", want, got) 115 | } 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /pkg/devfile/parser/writer.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 20 | apiAttributes "github.com/devfile/api/v2/pkg/attributes" 21 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 22 | "sigs.k8s.io/yaml" 23 | 24 | "github.com/devfile/library/v2/pkg/testingutil/filesystem" 25 | "github.com/pkg/errors" 26 | "k8s.io/klog" 27 | ) 28 | 29 | // WriteYamlDevfile writes the content of the Devfile data to its absolute path on the filesystem. 30 | func (d *DevfileObj) WriteYamlDevfile() error { 31 | 32 | // Check kubernetes components, and restore original uri content 33 | if d.Ctx.GetConvertUriToInlined() { 34 | err := restoreK8sCompURI(d) 35 | if err != nil { 36 | return errors.Wrapf(err, "failed to restore kubernetes component uri field") 37 | } 38 | } 39 | // Encode data into YAML format 40 | yamlData, err := yaml.Marshal(d.Data) 41 | if err != nil { 42 | return errors.Wrapf(err, "failed to marshal devfile object into yaml") 43 | } 44 | // Write to the absolute path 45 | fs := d.Ctx.GetFs() 46 | if fs == nil { 47 | fs = filesystem.DefaultFs{} 48 | } 49 | err = fs.WriteFile(d.Ctx.GetAbsPath(), yamlData, 0644) 50 | if err != nil { 51 | return errors.Wrapf(err, "failed to create devfile yaml file") 52 | } 53 | 54 | // Successful 55 | klog.V(2).Infof("devfile written to: '%s'", d.Ctx.GetAbsPath()) 56 | return nil 57 | } 58 | 59 | func restoreK8sCompURI(devObj *DevfileObj) error { 60 | getKubeCompOptions := common.DevfileOptions{ 61 | ComponentOptions: common.ComponentOptions{ 62 | ComponentType: v1.KubernetesComponentType, 63 | }, 64 | } 65 | getOpenshiftCompOptions := common.DevfileOptions{ 66 | ComponentOptions: common.ComponentOptions{ 67 | ComponentType: v1.OpenshiftComponentType, 68 | }, 69 | } 70 | kubeComponents, err := devObj.Data.GetComponents(getKubeCompOptions) 71 | if err != nil { 72 | return err 73 | } 74 | openshiftComponents, err := devObj.Data.GetComponents(getOpenshiftCompOptions) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | for _, kubeComp := range kubeComponents { 80 | uri := kubeComp.Attributes.GetString(K8sLikeComponentOriginalURIKey, &err) 81 | if err != nil { 82 | if _, ok := err.(*apiAttributes.KeyNotFoundError); !ok { 83 | return err 84 | } 85 | } 86 | if uri != "" { 87 | kubeComp.Kubernetes.Uri = uri 88 | kubeComp.Kubernetes.Inlined = "" 89 | delete(kubeComp.Attributes, K8sLikeComponentOriginalURIKey) 90 | err = devObj.Data.UpdateComponent(kubeComp) 91 | if err != nil { 92 | return err 93 | } 94 | } 95 | } 96 | 97 | for _, openshiftComp := range openshiftComponents { 98 | uri := openshiftComp.Attributes.GetString(K8sLikeComponentOriginalURIKey, &err) 99 | if err != nil { 100 | if _, ok := err.(*apiAttributes.KeyNotFoundError); !ok { 101 | return err 102 | } 103 | } 104 | if uri != "" { 105 | openshiftComp.Openshift.Uri = uri 106 | openshiftComp.Openshift.Inlined = "" 107 | delete(openshiftComp.Attributes, K8sLikeComponentOriginalURIKey) 108 | err = devObj.Data.UpdateComponent(openshiftComp) 109 | if err != nil { 110 | return err 111 | } 112 | } 113 | } 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/interface.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package data 17 | 18 | import ( 19 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 20 | "github.com/devfile/api/v2/pkg/attributes" 21 | devfilepkg "github.com/devfile/api/v2/pkg/devfile" 22 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 23 | ) 24 | 25 | // Generate mock interfaces for DevfileData by executing the following cmd in pkg/devfile/parser/data 26 | // mockgen -package=data -source=interface.go DevfileData > /tmp/mock_interface.go ; cp /tmp/mock_interface.go ./mock_interface.go 27 | 28 | // DevfileData is an interface that defines functions for Devfile data operations 29 | type DevfileData interface { 30 | 31 | // header related methods 32 | 33 | GetSchemaVersion() string 34 | SetSchemaVersion(version string) 35 | GetMetadata() devfilepkg.DevfileMetadata 36 | SetMetadata(metadata devfilepkg.DevfileMetadata) 37 | 38 | // top-level attributes related method 39 | 40 | GetAttributes() (attributes.Attributes, error) 41 | AddAttributes(key string, value interface{}) error 42 | UpdateAttributes(key string, value interface{}) error 43 | 44 | // parent related methods 45 | 46 | GetParent() *v1.Parent 47 | SetParent(parent *v1.Parent) 48 | 49 | // event related methods 50 | 51 | GetEvents() v1.Events 52 | AddEvents(events v1.Events) error 53 | UpdateEvents(postStart, postStop, preStart, preStop []string) 54 | 55 | // component related methods 56 | 57 | GetComponents(common.DevfileOptions) ([]v1.Component, error) 58 | AddComponents(components []v1.Component) error 59 | UpdateComponent(component v1.Component) error 60 | DeleteComponent(name string) error 61 | 62 | // project related methods 63 | 64 | GetProjects(common.DevfileOptions) ([]v1.Project, error) 65 | AddProjects(projects []v1.Project) error 66 | UpdateProject(project v1.Project) error 67 | DeleteProject(name string) error 68 | 69 | // starter projects related commands 70 | 71 | GetStarterProjects(common.DevfileOptions) ([]v1.StarterProject, error) 72 | AddStarterProjects(projects []v1.StarterProject) error 73 | UpdateStarterProject(project v1.StarterProject) error 74 | DeleteStarterProject(name string) error 75 | 76 | // command related methods 77 | 78 | GetCommands(common.DevfileOptions) ([]v1.Command, error) 79 | AddCommands(commands []v1.Command) error 80 | UpdateCommand(command v1.Command) error 81 | DeleteCommand(id string) error 82 | 83 | // volume mount related methods 84 | 85 | AddVolumeMounts(containerName string, volumeMounts []v1.VolumeMount) error 86 | DeleteVolumeMount(name string) error 87 | GetVolumeMountPaths(mountName, containerName string) ([]string, error) 88 | 89 | // workspace related methods 90 | 91 | GetDevfileWorkspaceSpecContent() *v1.DevWorkspaceTemplateSpecContent 92 | SetDevfileWorkspaceSpecContent(content v1.DevWorkspaceTemplateSpecContent) 93 | GetDevfileWorkspaceSpec() *v1.DevWorkspaceTemplateSpec 94 | SetDevfileWorkspaceSpec(spec v1.DevWorkspaceTemplateSpec) 95 | 96 | // utils 97 | 98 | GetDevfileContainerComponents(common.DevfileOptions) ([]v1.Component, error) 99 | GetDevfileVolumeComponents(common.DevfileOptions) ([]v1.Component, error) 100 | 101 | // containers 102 | RemoveEnvVars(containerEnvMap map[string][]string) error 103 | SetPorts(containerPortsMap map[string][]string) error 104 | AddEnvVars(containerEnvMap map[string][]v1.EnvVar) error 105 | RemovePorts(containerPortsMap map[string][]string) error 106 | } 107 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/commands.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | "strings" 22 | 23 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 24 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 25 | ) 26 | 27 | // GetCommands returns the slice of Command objects parsed from the Devfile 28 | func (d *DevfileV2) GetCommands(options common.DevfileOptions) ([]v1.Command, error) { 29 | 30 | if reflect.DeepEqual(options, common.DevfileOptions{}) { 31 | return d.Commands, nil 32 | } 33 | 34 | var commands []v1.Command 35 | for _, command := range d.Commands { 36 | // Filter Command Attributes 37 | filterIn, err := common.FilterDevfileObject(command.Attributes, options) 38 | if err != nil { 39 | return nil, err 40 | } else if !filterIn { 41 | continue 42 | } 43 | 44 | // Filter Command Type - Exec, Composite, etc. 45 | commandType, err := common.GetCommandType(command) 46 | if err != nil { 47 | return nil, err 48 | } 49 | if options.CommandOptions.CommandType != "" && commandType != options.CommandOptions.CommandType { 50 | continue 51 | } 52 | 53 | // Filter Command Group Kind - Run, Build, etc. 54 | commandGroup := common.GetGroup(command) 55 | // exclude conditions: 56 | // 1. options group is present and command group is present but does not match 57 | // 2. options group is present and command group is not present 58 | if options.CommandOptions.CommandGroupKind != "" && ((commandGroup != nil && options.CommandOptions.CommandGroupKind != commandGroup.Kind) || commandGroup == nil) { 59 | continue 60 | } 61 | 62 | if options.FilterByName == "" || command.Id == options.FilterByName { 63 | commands = append(commands, command) 64 | } 65 | } 66 | 67 | return commands, nil 68 | } 69 | 70 | // AddCommands adds the slice of Command objects to the Devfile's commands 71 | // a command is considered as invalid if it is already defined 72 | // command list passed in will be all processed, and returns a total error of all invalid commands 73 | func (d *DevfileV2) AddCommands(commands []v1.Command) error { 74 | var errorsList []string 75 | for _, command := range commands { 76 | var err error 77 | for _, devfileCommand := range d.Commands { 78 | if command.Id == devfileCommand.Id { 79 | err = &common.FieldAlreadyExistError{Name: command.Id, Field: "command"} 80 | errorsList = append(errorsList, err.Error()) 81 | break 82 | } 83 | } 84 | if err == nil { 85 | d.Commands = append(d.Commands, command) 86 | } 87 | } 88 | if len(errorsList) > 0 { 89 | return fmt.Errorf("errors while adding commands:\n%s", strings.Join(errorsList, "\n")) 90 | } 91 | return nil 92 | } 93 | 94 | // UpdateCommand updates the command with the given id 95 | // return an error if the command is not found 96 | func (d *DevfileV2) UpdateCommand(command v1.Command) error { 97 | for i := range d.Commands { 98 | if d.Commands[i].Id == command.Id { 99 | d.Commands[i] = command 100 | return nil 101 | } 102 | } 103 | return fmt.Errorf("update command failed: command %s not found", command.Id) 104 | } 105 | 106 | // DeleteCommand removes the specified command 107 | func (d *DevfileV2) DeleteCommand(id string) error { 108 | 109 | for i := range d.Commands { 110 | if d.Commands[i].Id == id { 111 | d.Commands = append(d.Commands[:i], d.Commands[i+1:]...) 112 | return nil 113 | } 114 | } 115 | 116 | return &common.FieldNotFoundError{ 117 | Field: "command", 118 | Name: id, 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /pkg/devfile/parser/utils.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | 22 | devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 23 | "github.com/devfile/library/v2/pkg/devfile/parser/data" 24 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 25 | ) 26 | 27 | // GetDeployComponents gets the default deploy command associated components 28 | func GetDeployComponents(devfileData data.DevfileData) (map[string]string, error) { 29 | deployCommandFilter := common.DevfileOptions{ 30 | CommandOptions: common.CommandOptions{ 31 | CommandGroupKind: devfilev1.DeployCommandGroupKind, 32 | }, 33 | } 34 | deployCommands, err := devfileData.GetCommands(deployCommandFilter) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | deployAssociatedComponents := make(map[string]string) 40 | var deployAssociatedSubCommands []string 41 | 42 | for _, command := range deployCommands { 43 | if command.Apply != nil { 44 | if len(deployCommands) > 1 && command.Apply.Group.IsDefault != nil && !*command.Apply.Group.IsDefault { 45 | continue 46 | } 47 | deployAssociatedComponents[command.Apply.Component] = command.Apply.Component 48 | } else if command.Composite != nil { 49 | if len(deployCommands) > 1 && command.Composite.Group.IsDefault != nil && !*command.Composite.Group.IsDefault { 50 | continue 51 | } 52 | deployAssociatedSubCommands = append(deployAssociatedSubCommands, command.Composite.Commands...) 53 | } 54 | } 55 | 56 | applyCommandFilter := common.DevfileOptions{ 57 | CommandOptions: common.CommandOptions{ 58 | CommandType: devfilev1.ApplyCommandType, 59 | }, 60 | } 61 | applyCommands, err := devfileData.GetCommands(applyCommandFilter) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | for _, command := range applyCommands { 67 | if command.Apply != nil { 68 | for _, deployCommand := range deployAssociatedSubCommands { 69 | if deployCommand == command.Id { 70 | deployAssociatedComponents[command.Apply.Component] = command.Apply.Component 71 | } 72 | } 73 | 74 | } 75 | } 76 | 77 | return deployAssociatedComponents, nil 78 | } 79 | 80 | // GetImageBuildComponent gets the image build component from the deploy associated components 81 | func GetImageBuildComponent(devfileData data.DevfileData, deployAssociatedComponents map[string]string) (devfilev1.Component, error) { 82 | imageComponentFilter := common.DevfileOptions{ 83 | ComponentOptions: common.ComponentOptions{ 84 | ComponentType: devfilev1.ImageComponentType, 85 | }, 86 | } 87 | 88 | imageComponents, err := devfileData.GetComponents(imageComponentFilter) 89 | if err != nil { 90 | return devfilev1.Component{}, err 91 | } 92 | 93 | var imageBuildComponent devfilev1.Component 94 | for _, component := range imageComponents { 95 | if _, ok := deployAssociatedComponents[component.Name]; ok && component.Image != nil { 96 | if reflect.DeepEqual(imageBuildComponent, devfilev1.Component{}) { 97 | imageBuildComponent = component 98 | } else { 99 | return devfilev1.Component{}, fmt.Errorf("expected to find one devfile image component with a deploy command for build. Currently there is more than one image component") 100 | } 101 | } 102 | } 103 | 104 | // If there is not one image component defined in the deploy command, err out 105 | if reflect.DeepEqual(imageBuildComponent, devfilev1.Component{}) { 106 | return devfilev1.Component{}, fmt.Errorf("expected to find one devfile image component with a deploy command for build. Currently there is no image component") 107 | } 108 | 109 | return imageBuildComponent, nil 110 | } 111 | -------------------------------------------------------------------------------- /scripts/updateApi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright Red Hat 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | BLUE='\033[1;34m' 19 | GREEN='\033[0;32m' 20 | RED='\033[0;31m' 21 | NC='\033[0m' 22 | BOLD='\033[1m' 23 | 24 | set -e 25 | 26 | DIR=$(dirname $0) 27 | CURRENT_DIR=$(pwd) 28 | API_PKG="github.com/devfile/api/v2" 29 | SCHEMA_URL_MASTER="https://raw.githubusercontent.com/devfile/api/main/schemas/latest/devfile.json" 30 | 31 | # 2.0.0 devfile 32 | SCHEMA_URL_200="https://raw.githubusercontent.com/devfile/api/2.0.x/schemas/latest/devfile.json" 33 | PACKAGE_VERSION_200="version200" 34 | JSON_SCHEMA_200="JsonSchema200" 35 | FILE_PATH_200="$DIR/../pkg/devfile/parser/data/v2/2.0.0/devfileJsonSchema200.go" 36 | 37 | # 2.1.0 devfile 38 | SCHEMA_URL_210="https://raw.githubusercontent.com/devfile/api/2.1.x/schemas/latest/devfile.json" 39 | PACKAGE_VERSION_210="version210" 40 | JSON_SCHEMA_210="JsonSchema210" 41 | FILE_PATH_210="$DIR/../pkg/devfile/parser/data/v2/2.1.0/devfileJsonSchema210.go" 42 | 43 | # 2.2.0 devfile 44 | SCHEMA_URL_220="https://raw.githubusercontent.com/devfile/api/2.2.x/schemas/latest/devfile.json" 45 | PACKAGE_VERSION_220="version220" 46 | JSON_SCHEMA_220="JsonSchema220" 47 | FILE_PATH_220="$DIR/../pkg/devfile/parser/data/v2/2.2.0/devfileJsonSchema220.go" 48 | 49 | # 2.2.1 devfile 50 | SCHEMA_URL_221="https://raw.githubusercontent.com/devfile/api/2.2.x/schemas/latest/devfile.json" 51 | PACKAGE_VERSION_221="version221" 52 | JSON_SCHEMA_221="JsonSchema221" 53 | FILE_PATH_221="$DIR/../pkg/devfile/parser/data/v2/2.2.1/devfileJsonSchema221.go" 54 | 55 | # 2.2.2 devfile 56 | SCHEMA_URL_222="https://raw.githubusercontent.com/devfile/api/2.2.x/schemas/latest/devfile.json" 57 | PACKAGE_VERSION_222="version222" 58 | JSON_SCHEMA_222="JsonSchema222" 59 | FILE_PATH_222="$DIR/../pkg/devfile/parser/data/v2/2.2.2/devfileJsonSchema222.go" 60 | 61 | # 2.3.0 devfile 62 | PACKAGE_VERSION_230="version230" 63 | JSON_SCHEMA_230="JsonSchema230" 64 | FILE_PATH_230="$DIR/../pkg/devfile/parser/data/v2/2.3.0/devfileJsonSchema230.go" 65 | 66 | onError() { 67 | cd "${CURRENT_DIR}" 68 | } 69 | trap 'onError' ERR 70 | 71 | 72 | echo -e "${GREEN}Updating devfile/api in go.mod${NC}" 73 | go get "${API_PKG}@main" 74 | 75 | echo -e "${GREEN}Get latest schema${NC}" 76 | 77 | case "${1}" in 78 | "2.0.0") 79 | SCHEMA_URL=${SCHEMA_URL_200} 80 | PACKAGE_VERSION=${PACKAGE_VERSION_200} 81 | JSON_SCHEMA=${JSON_SCHEMA_200} 82 | FILE_PATH=${FILE_PATH_200} 83 | ;; 84 | "2.1.0") 85 | SCHEMA_URL=${SCHEMA_URL_210} 86 | PACKAGE_VERSION=${PACKAGE_VERSION_210} 87 | JSON_SCHEMA=${JSON_SCHEMA_210} 88 | FILE_PATH=${FILE_PATH_210} 89 | ;; 90 | "2.2.0") 91 | SCHEMA_URL=${SCHEMA_URL_220} 92 | PACKAGE_VERSION=${PACKAGE_VERSION_220} 93 | JSON_SCHEMA=${JSON_SCHEMA_220} 94 | FILE_PATH=${FILE_PATH_220} 95 | ;; 96 | "2.2.1") 97 | SCHEMA_URL=${SCHEMA_URL_221} 98 | PACKAGE_VERSION=${PACKAGE_VERSION_221} 99 | JSON_SCHEMA=${JSON_SCHEMA_221} 100 | FILE_PATH=${FILE_PATH_221} 101 | ;; 102 | "2.2.2") 103 | SCHEMA_URL=${SCHEMA_URL_222} 104 | PACKAGE_VERSION=${PACKAGE_VERSION_222} 105 | JSON_SCHEMA=${JSON_SCHEMA_222} 106 | FILE_PATH=${FILE_PATH_222} 107 | ;; 108 | *) 109 | # default 110 | SCHEMA_URL=${SCHEMA_URL_MASTER} 111 | PACKAGE_VERSION=${PACKAGE_VERSION_230} 112 | JSON_SCHEMA=${JSON_SCHEMA_230} 113 | FILE_PATH=${FILE_PATH_230} 114 | ;; 115 | esac 116 | 117 | schema=$(curl -L "${SCHEMA_URL}") 118 | 119 | #replace all ` with ' and write to schema file 120 | echo -e "${GREEN}Write to go file${NC}" 121 | go build $DIR/../*.go 122 | ./main updateSchema "${schema}" "${SCHEMA_URL}" "${PACKAGE_VERSION}" "${JSON_SCHEMA}" "${FILE_PATH}" 123 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/volumes.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | "fmt" 20 | "strings" 21 | 22 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 23 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 24 | ) 25 | 26 | // AddVolumeMounts adds the volume mounts to the specified container component 27 | func (d *DevfileV2) AddVolumeMounts(containerName string, volumeMounts []v1.VolumeMount) error { 28 | var pathErrorContainers []string 29 | found := false 30 | for _, component := range d.Components { 31 | if component.Container != nil && component.Name == containerName { 32 | found = true 33 | for _, devfileVolumeMount := range component.Container.VolumeMounts { 34 | for _, volumeMount := range volumeMounts { 35 | if devfileVolumeMount.Path == volumeMount.Path { 36 | pathErrorContainers = append(pathErrorContainers, fmt.Sprintf("unable to mount volume %s, as another volume %s is mounted to the same path %s in the container %s", volumeMount.Name, devfileVolumeMount.Name, volumeMount.Path, component.Name)) 37 | } 38 | } 39 | } 40 | if len(pathErrorContainers) == 0 { 41 | component.Container.VolumeMounts = append(component.Container.VolumeMounts, volumeMounts...) 42 | } 43 | } 44 | } 45 | 46 | if !found { 47 | return &common.FieldNotFoundError{ 48 | Field: "container component", 49 | Name: containerName, 50 | } 51 | } 52 | 53 | if len(pathErrorContainers) > 0 { 54 | return fmt.Errorf("errors while adding volume mounts:\n%s", strings.Join(pathErrorContainers, "\n")) 55 | } 56 | 57 | return nil 58 | } 59 | 60 | // DeleteVolumeMount deletes the volume mount from container components 61 | func (d *DevfileV2) DeleteVolumeMount(name string) error { 62 | found := false 63 | for i := range d.Components { 64 | if d.Components[i].Container != nil && d.Components[i].Name != name { 65 | // Volume Mounts can have multiple instances of a volume mounted at different paths 66 | // As arrays are rearraged/shifted for deletion, we lose one element every time there is a match 67 | // Looping backward is efficient, otherwise we would have to manually decrement counter 68 | // if we looped forward 69 | for j := len(d.Components[i].Container.VolumeMounts) - 1; j >= 0; j-- { 70 | if d.Components[i].Container.VolumeMounts[j].Name == name { 71 | found = true 72 | d.Components[i].Container.VolumeMounts = append(d.Components[i].Container.VolumeMounts[:j], d.Components[i].Container.VolumeMounts[j+1:]...) 73 | } 74 | } 75 | } 76 | } 77 | 78 | if !found { 79 | return &common.FieldNotFoundError{ 80 | Field: "volume mount", 81 | Name: name, 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | 88 | // GetVolumeMountPaths gets all the mount paths of the specified volume mount from the specified container component. 89 | // A container can mount at different paths for a given volume. 90 | func (d *DevfileV2) GetVolumeMountPaths(mountName, containerName string) ([]string, error) { 91 | componentFound := false 92 | var mountPaths []string 93 | 94 | for _, component := range d.Components { 95 | if component.Container != nil && component.Name == containerName { 96 | componentFound = true 97 | for _, volumeMount := range component.Container.VolumeMounts { 98 | if volumeMount.Name == mountName { 99 | mountPaths = append(mountPaths, volumeMount.Path) 100 | } 101 | } 102 | } 103 | } 104 | 105 | if !componentFound { 106 | return mountPaths, &common.FieldNotFoundError{ 107 | Field: "container component", 108 | Name: containerName, 109 | } 110 | } 111 | 112 | if len(mountPaths) == 0 { 113 | return mountPaths, fmt.Errorf("volume %s not mounted to component %s", mountName, containerName) 114 | } 115 | 116 | return mountPaths, nil 117 | } 118 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | "reflect" 22 | "strings" 23 | 24 | devfilepkg "github.com/devfile/library/v2/pkg/devfile" 25 | "github.com/devfile/library/v2/pkg/devfile/parser" 26 | v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" 27 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 28 | ) 29 | 30 | func main() { 31 | if len(os.Args) > 1 && os.Args[1] == "updateSchema" { 32 | ReplaceSchemaFile() 33 | } else { 34 | parserTest() 35 | } 36 | } 37 | 38 | func parserTest() { 39 | var args parser.ParserArgs 40 | if len(os.Args) > 1 { 41 | if strings.HasPrefix(os.Args[1], "http") { 42 | args = parser.ParserArgs{ 43 | URL: os.Args[1], 44 | } 45 | } else { 46 | args = parser.ParserArgs{ 47 | Path: os.Args[1], 48 | } 49 | } 50 | fmt.Println("parsing devfile from " + os.Args[1]) 51 | 52 | } else { 53 | args = parser.ParserArgs{ 54 | Path: "devfile.yaml", 55 | } 56 | fmt.Println("parsing devfile from ./devfile.yaml") 57 | } 58 | devfile, warning, err := devfilepkg.ParseDevfileAndValidate(args) 59 | if err != nil { 60 | fmt.Println(err) 61 | } else { 62 | if len(warning.Commands) > 0 || len(warning.Components) > 0 || len(warning.Projects) > 0 || len(warning.StarterProjects) > 0 { 63 | fmt.Printf("top-level variables were not substituted successfully %+v\n", warning) 64 | } 65 | devdata := devfile.Data 66 | if (reflect.TypeOf(devdata) == reflect.TypeOf(&v2.DevfileV2{})) { 67 | d := devdata.(*v2.DevfileV2) 68 | fmt.Printf("schema version: %s\n", d.SchemaVersion) 69 | } 70 | 71 | components, e := devfile.Data.GetComponents(common.DevfileOptions{}) 72 | if e != nil { 73 | fmt.Printf("err: %v\n", err) 74 | } 75 | fmt.Printf("All component: \n") 76 | for _, component := range components { 77 | fmt.Printf("%s\n", component.Name) 78 | } 79 | 80 | fmt.Printf("All Exec commands: \n") 81 | commands, e := devfile.Data.GetCommands(common.DevfileOptions{}) 82 | if e != nil { 83 | fmt.Printf("err: %v\n", err) 84 | } 85 | for _, command := range commands { 86 | if command.Exec != nil { 87 | fmt.Printf("command %s is with kind: %s\n", command.Id, command.Exec.Group.Kind) 88 | fmt.Printf("workingDir is: %s\n", command.Exec.WorkingDir) 89 | } 90 | } 91 | 92 | fmt.Println("=========================================================") 93 | 94 | compOptions := common.DevfileOptions{ 95 | Filter: map[string]interface{}{ 96 | "tool": "console-import", 97 | "import": map[string]interface{}{ 98 | "strategy": "Dockerfile", 99 | }, 100 | }, 101 | } 102 | 103 | components, e = devfile.Data.GetComponents(compOptions) 104 | if e != nil { 105 | fmt.Printf("err: %v\n", err) 106 | } 107 | fmt.Printf("Container components applied filter: \n") 108 | for _, component := range components { 109 | if component.Container != nil { 110 | fmt.Printf("%s\n", component.Name) 111 | } 112 | } 113 | 114 | cmdOptions := common.DevfileOptions{ 115 | Filter: map[string]interface{}{ 116 | "tool": "odo", 117 | }, 118 | } 119 | 120 | fmt.Printf("Exec commands applied filter: \n") 121 | commands, e = devfile.Data.GetCommands(cmdOptions) 122 | if e != nil { 123 | fmt.Printf("err: %v\n", err) 124 | } 125 | for _, command := range commands { 126 | if command.Exec != nil { 127 | fmt.Printf("command %s is with kind: %s", command.Id, command.Exec.Group.Kind) 128 | fmt.Printf("workingDir is: %s\n", command.Exec.WorkingDir) 129 | } 130 | } 131 | 132 | var err error 133 | metadataAttr := devfile.Data.GetMetadata().Attributes 134 | dockerfilePath := metadataAttr.GetString("alpha.build-dockerfile", &err) 135 | if err != nil { 136 | fmt.Printf("err: %v\n", err) 137 | } 138 | fmt.Printf("dockerfilePath: %s\n", dockerfilePath) 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /pkg/devfile/parse.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package devfile 17 | 18 | import ( 19 | "github.com/devfile/api/v2/pkg/validation/variables" 20 | "github.com/devfile/library/v2/pkg/devfile/parser" 21 | errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors" 22 | "github.com/devfile/library/v2/pkg/devfile/validate" 23 | ) 24 | 25 | // ParseFromURLAndValidate func parses the devfile data from the url 26 | // and validates the devfile integrity with the schema 27 | // and validates the devfile data. 28 | // Creates devfile context and runtime objects. 29 | // Deprecated, use ParseDevfileAndValidate() instead 30 | func ParseFromURLAndValidate(url string) (d parser.DevfileObj, err error) { 31 | 32 | // read and parse devfile from the given URL 33 | d, err = parser.ParseFromURL(url) 34 | if err != nil { 35 | return d, err 36 | } 37 | 38 | // generic validation on devfile content 39 | err = validate.ValidateDevfileData(d.Data) 40 | if err != nil { 41 | return d, err 42 | } 43 | 44 | return d, err 45 | } 46 | 47 | // ParseFromDataAndValidate func parses the devfile data 48 | // and validates the devfile integrity with the schema 49 | // and validates the devfile data. 50 | // Creates devfile context and runtime objects. 51 | // Deprecated, use ParseDevfileAndValidate() instead 52 | func ParseFromDataAndValidate(data []byte) (d parser.DevfileObj, err error) { 53 | // read and parse devfile from the given bytes 54 | d, err = parser.ParseFromData(data) 55 | if err != nil { 56 | return d, err 57 | } 58 | // generic validation on devfile content 59 | err = validate.ValidateDevfileData(d.Data) 60 | if err != nil { 61 | return d, err 62 | } 63 | 64 | return d, err 65 | } 66 | 67 | // ParseAndValidate func parses the devfile data 68 | // and validates the devfile integrity with the schema 69 | // and validates the devfile data. 70 | // Creates devfile context and runtime objects. 71 | // Deprecated, use ParseDevfileAndValidate() instead 72 | func ParseAndValidate(path string) (d parser.DevfileObj, err error) { 73 | 74 | // read and parse devfile from given path 75 | d, err = parser.Parse(path) 76 | if err != nil { 77 | return d, err 78 | } 79 | 80 | // generic validation on devfile content 81 | err = validate.ValidateDevfileData(d.Data) 82 | if err != nil { 83 | return d, err 84 | } 85 | 86 | return d, err 87 | } 88 | 89 | // ParseDevfileAndValidate func parses the devfile data, validates the devfile integrity with the schema 90 | // replaces the top-level variable keys if present and validates the devfile data. 91 | // It returns devfile context and runtime objects, variable substitution warning if any and an error. 92 | func ParseDevfileAndValidate(args parser.ParserArgs) (d parser.DevfileObj, varWarning variables.VariableWarning, err error) { 93 | d, err = parser.ParseDevfile(args) 94 | if err != nil { 95 | return d, varWarning, err 96 | } 97 | 98 | if d.Data.GetSchemaVersion() != "2.0.0" { 99 | 100 | // add external variables to spec variables 101 | if d.Data.GetDevfileWorkspaceSpec().Variables == nil { 102 | d.Data.GetDevfileWorkspaceSpec().Variables = map[string]string{} 103 | } 104 | allVariables := d.Data.GetDevfileWorkspaceSpec().Variables 105 | for key, val := range args.ExternalVariables { 106 | allVariables[key] = val 107 | } 108 | 109 | // replace the top level variable keys with their values in the devfile 110 | varWarning = variables.ValidateAndReplaceGlobalVariable(d.Data.GetDevfileWorkspaceSpec()) 111 | } 112 | 113 | // Use image names as selectors after variable substitution, 114 | // as users might be using variables for image names. 115 | if args.ImageNamesAsSelector != nil && args.ImageNamesAsSelector.Registry != "" { 116 | err = replaceImageNames(&d, args.ImageNamesAsSelector.Registry, args.ImageNamesAsSelector.Tag) 117 | if err != nil { 118 | return d, varWarning, err 119 | } 120 | } 121 | 122 | // generic validation on devfile content 123 | err = validate.ValidateDevfileData(d.Data) 124 | if err != nil { 125 | return d, varWarning, &errPkg.NonCompliantDevfile{Err: err.Error()} 126 | } 127 | 128 | return d, varWarning, err 129 | } 130 | -------------------------------------------------------------------------------- /pkg/devfile/parser/configurables.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 20 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 21 | ) 22 | 23 | const ( 24 | Name = "Name" 25 | Ports = "Ports" 26 | Memory = "Memory" 27 | PortsDescription = "Ports to be opened in all component containers" 28 | MemoryDescription = "The Maximum memory all the component containers can consume" 29 | NameDescription = "The name of the component" 30 | ) 31 | 32 | // SetMetadataName set metadata name in a devfile 33 | func (d DevfileObj) SetMetadataName(name string) error { 34 | metadata := d.Data.GetMetadata() 35 | metadata.Name = name 36 | d.Data.SetMetadata(metadata) 37 | return d.WriteYamlDevfile() 38 | } 39 | 40 | // AddEnvVars accepts a map of container name mapped to an array of the env vars to be set; 41 | // it adds the envirnoment variables to a given container name, and writes to the devfile 42 | // Example of containerEnvMap : {"runtime": {{Name: "Foo", Value: "Bar"}}} 43 | func (d DevfileObj) AddEnvVars(containerEnvMap map[string][]v1.EnvVar) error { 44 | err := d.Data.AddEnvVars(containerEnvMap) 45 | if err != nil { 46 | return err 47 | } 48 | return d.WriteYamlDevfile() 49 | } 50 | 51 | // RemoveEnvVars accepts a map of container name mapped to an array of environment variables to be removed; 52 | // it removes the env vars from the specified container name and writes it to the devfile 53 | func (d DevfileObj) RemoveEnvVars(containerEnvMap map[string][]string) (err error) { 54 | err = d.Data.RemoveEnvVars(containerEnvMap) 55 | if err != nil { 56 | return err 57 | } 58 | return d.WriteYamlDevfile() 59 | } 60 | 61 | // SetPorts accepts a map of container name mapped to an array of port numbers to be set; 62 | // it converts ports to endpoints, sets the endpoint to a given container name, and writes to the devfile 63 | // Example of containerPortsMap: {"runtime": {"8080", "9000"}, "wildfly": {"12956"}} 64 | func (d DevfileObj) SetPorts(containerPortsMap map[string][]string) error { 65 | err := d.Data.SetPorts(containerPortsMap) 66 | if err != nil { 67 | return err 68 | } 69 | return d.WriteYamlDevfile() 70 | } 71 | 72 | // RemovePorts accepts a map of container name mapped to an array of port numbers to be removed; 73 | // it removes the container endpoints with the specified port numbers of the specified container, and writes to the devfile 74 | // Example of containerPortsMap: {"runtime": {"8080", "9000"}, "wildfly": {"12956"}} 75 | func (d DevfileObj) RemovePorts(containerPortsMap map[string][]string) error { 76 | err := d.Data.RemovePorts(containerPortsMap) 77 | if err != nil { 78 | return err 79 | } 80 | return d.WriteYamlDevfile() 81 | } 82 | 83 | // HasPorts checks if a devfile contains container endpoints 84 | func (d DevfileObj) HasPorts() bool { 85 | components, err := d.Data.GetComponents(common.DevfileOptions{}) 86 | if err != nil { 87 | return false 88 | } 89 | for _, component := range components { 90 | if component.Container != nil { 91 | if len(component.Container.Endpoints) > 0 { 92 | return true 93 | } 94 | } 95 | } 96 | return false 97 | } 98 | 99 | // SetMemory sets memoryLimit in devfile container 100 | func (d DevfileObj) SetMemory(memory string) error { 101 | components, err := d.Data.GetComponents(common.DevfileOptions{}) 102 | if err != nil { 103 | return err 104 | } 105 | for _, component := range components { 106 | if component.Container != nil { 107 | component.Container.MemoryLimit = memory 108 | _ = d.Data.UpdateComponent(component) 109 | } 110 | } 111 | return d.WriteYamlDevfile() 112 | } 113 | 114 | // GetMemory gets memoryLimit from devfile container 115 | func (d DevfileObj) GetMemory() string { 116 | components, err := d.Data.GetComponents(common.DevfileOptions{}) 117 | if err != nil { 118 | return "" 119 | } 120 | for _, component := range components { 121 | if component.Container != nil { 122 | if component.Container.MemoryLimit != "" { 123 | return component.Container.MemoryLimit 124 | } 125 | } 126 | 127 | } 128 | return "" 129 | } 130 | 131 | // GetMetadataName gets metadata name from a devfile 132 | func (d DevfileObj) GetMetadataName() string { 133 | return d.Data.GetMetadata().Name 134 | } 135 | -------------------------------------------------------------------------------- /pkg/devfile/parser/context/schema_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | 23 | v200 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.0.0" 24 | ) 25 | 26 | const ( 27 | validJson200 = `{ 28 | "schemaVersion": "2.0.0", 29 | "metadata": { 30 | "name": "nodejs-stack" 31 | }, 32 | "projects": [ 33 | { 34 | "name": "project", 35 | "git": { 36 | "remotes": { 37 | "origin": "https://github.com/che-samples/web-nodejs-sample.git" 38 | } 39 | } 40 | } 41 | ], 42 | "components": [ 43 | { 44 | "name": "che-theia-plugin", 45 | "plugin": { 46 | "id": "eclipse/che-theia/7.1.0" 47 | } 48 | }, 49 | { 50 | "name": "che-exec-plugin", 51 | "plugin": { 52 | "id": "eclipse/che-machine-exec-plugin/7.1.0" 53 | } 54 | }, 55 | { 56 | "name": "typescript-plugin", 57 | "plugin": { 58 | "id": "che-incubator/typescript/1.30.2", 59 | "components": [ 60 | { 61 | "name": "somecontainer", 62 | "container": { 63 | "memoryLimit": "512Mi" 64 | } 65 | } 66 | ] 67 | } 68 | }, 69 | { 70 | "name": "nodejs", 71 | "container": { 72 | "image": "quay.io/eclipse/che-nodejs10-ubi:nightly", 73 | "memoryLimit": "512Mi", 74 | "endpoints": [ 75 | { 76 | "name": "nodejs", 77 | "protocol": "http", 78 | "targetPort": 3000 79 | } 80 | ], 81 | "mountSources": true 82 | } 83 | } 84 | ], 85 | "commands": [ 86 | { 87 | "id": "download-dependencies", 88 | "exec": { 89 | "component": "nodejs", 90 | "commandLine": "npm install", 91 | "workingDir": "${PROJECTS_ROOT}/project/app", 92 | "group": { 93 | "kind": "build" 94 | } 95 | } 96 | }, 97 | { 98 | "id": "run-the-app", 99 | "exec": { 100 | "component": "nodejs", 101 | "commandLine": "nodemon app.js", 102 | "workingDir": "${PROJECTS_ROOT}/project/app", 103 | "group": { 104 | "kind": "run", 105 | "isDefault": true 106 | } 107 | } 108 | }, 109 | { 110 | "id": "run-the-app-debugging-enabled", 111 | "exec": { 112 | "component": "nodejs", 113 | "commandLine": "nodemon --inspect app.js", 114 | "workingDir": "${PROJECTS_ROOT}/project/app", 115 | "group": { 116 | "kind": "run" 117 | } 118 | } 119 | }, 120 | { 121 | "id": "stop-the-app", 122 | "exec": { 123 | "component": "nodejs", 124 | "commandLine": "node_server_pids=$(pgrep -fx '.*nodemon (--inspect )?app.js' | tr \"\\\\n\" \" \") && echo \"Stopping node server with PIDs: ${node_server_pids}\" && kill -15 ${node_server_pids} &>/dev/null && echo 'Done.'" 125 | } 126 | }, 127 | { 128 | "id": "attach-remote-debugger", 129 | "vscodeLaunch": { 130 | "inlined": "{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"type\": \"node\",\n \"request\": \"attach\",\n \"name\": \"Attach to Remote\",\n \"address\": \"localhost\",\n \"port\": 9229,\n \"localRoot\": \"${workspaceFolder}\",\n \"remoteRoot\": \"${workspaceFolder}\"\n }\n ]\n}\n" 131 | } 132 | } 133 | ] 134 | }` 135 | ) 136 | 137 | func TestValidateDevfileSchema(t *testing.T) { 138 | 139 | t.Run("valid 2.0.0 json schema", func(t *testing.T) { 140 | 141 | var ( 142 | d = DevfileCtx{ 143 | jsonSchema: v200.JsonSchema200, 144 | rawContent: validJsonRawContent200(), 145 | } 146 | ) 147 | 148 | err := d.ValidateDevfileSchema() 149 | if err != nil { 150 | t.Errorf("TestValidateDevfileSchema() unexpected error: '%v'", err) 151 | } 152 | }) 153 | 154 | expectedErr := "invalid devfile schema. errors :\n*.*schemaVersion is required" 155 | t.Run("invalid 2.0.0 json schema", func(t *testing.T) { 156 | 157 | var ( 158 | d = DevfileCtx{ 159 | jsonSchema: v200.JsonSchema200, 160 | rawContent: []byte("{}"), 161 | } 162 | ) 163 | 164 | err := d.ValidateDevfileSchema() 165 | if err == nil { 166 | t.Errorf("TestValidateDevfileSchema() expected error, didn't get one") 167 | } else { 168 | assert.Regexp(t, expectedErr, err.Error(), "TestValidateDevfileSchema(): Error message should match") 169 | } 170 | }) 171 | } 172 | 173 | func validJsonRawContent200() []byte { 174 | return []byte(validJson200) 175 | } 176 | -------------------------------------------------------------------------------- /pkg/devfile/parser/data/v2/components.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v2 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | "strings" 22 | 23 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 24 | "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" 25 | ) 26 | 27 | // GetComponents returns the slice of Component objects parsed from the Devfile 28 | func (d *DevfileV2) GetComponents(options common.DevfileOptions) ([]v1.Component, error) { 29 | 30 | if reflect.DeepEqual(options, common.DevfileOptions{}) { 31 | return d.Components, nil 32 | } 33 | 34 | var components []v1.Component 35 | for _, component := range d.Components { 36 | // Filter Component Attributes 37 | filterIn, err := common.FilterDevfileObject(component.Attributes, options) 38 | if err != nil { 39 | return nil, err 40 | } else if !filterIn { 41 | continue 42 | } 43 | 44 | // Filter Component Type - Container, Volume, etc. 45 | componentType, err := common.GetComponentType(component) 46 | if err != nil { 47 | return nil, err 48 | } 49 | if options.ComponentOptions.ComponentType != "" && componentType != options.ComponentOptions.ComponentType { 50 | continue 51 | } 52 | 53 | if options.FilterByName == "" || component.Name == options.FilterByName { 54 | components = append(components, component) 55 | } 56 | } 57 | 58 | return components, nil 59 | } 60 | 61 | // GetDevfileContainerComponents iterates through the components in the devfile and returns a list of devfile container components. 62 | // Deprecated, use GetComponents() with the DevfileOptions. 63 | func (d *DevfileV2) GetDevfileContainerComponents(options common.DevfileOptions) ([]v1.Component, error) { 64 | var components []v1.Component 65 | devfileComponents, err := d.GetComponents(options) 66 | if err != nil { 67 | return nil, err 68 | } 69 | for _, comp := range devfileComponents { 70 | if comp.Container != nil { 71 | components = append(components, comp) 72 | } 73 | } 74 | return components, nil 75 | } 76 | 77 | // GetDevfileVolumeComponents iterates through the components in the devfile and returns a list of devfile volume components. 78 | // Deprecated, use GetComponents() with the DevfileOptions. 79 | func (d *DevfileV2) GetDevfileVolumeComponents(options common.DevfileOptions) ([]v1.Component, error) { 80 | var components []v1.Component 81 | devfileComponents, err := d.GetComponents(options) 82 | if err != nil { 83 | return nil, err 84 | } 85 | for _, comp := range devfileComponents { 86 | if comp.Volume != nil { 87 | components = append(components, comp) 88 | } 89 | } 90 | return components, nil 91 | } 92 | 93 | // AddComponents adds the slice of Component objects to the devfile's components 94 | // a component is considered as invalid if it is already defined 95 | // component list passed in will be all processed, and returns a total error of all invalid components 96 | func (d *DevfileV2) AddComponents(components []v1.Component) error { 97 | var errorsList []string 98 | for _, component := range components { 99 | var err error 100 | for _, devfileComponent := range d.Components { 101 | if component.Name == devfileComponent.Name { 102 | err = &common.FieldAlreadyExistError{Name: component.Name, Field: "component"} 103 | errorsList = append(errorsList, err.Error()) 104 | break 105 | } 106 | } 107 | if err == nil { 108 | d.Components = append(d.Components, component) 109 | } 110 | } 111 | if len(errorsList) > 0 { 112 | return fmt.Errorf("errors while adding components:\n%s", strings.Join(errorsList, "\n")) 113 | } 114 | return nil 115 | } 116 | 117 | // UpdateComponent updates the component with the given name 118 | // return an error if the component is not found 119 | func (d *DevfileV2) UpdateComponent(component v1.Component) error { 120 | for i := range d.Components { 121 | if d.Components[i].Name == component.Name { 122 | d.Components[i] = component 123 | return nil 124 | } 125 | } 126 | return fmt.Errorf("update component failed: component %s not found", component.Name) 127 | } 128 | 129 | // DeleteComponent removes the specified component 130 | func (d *DevfileV2) DeleteComponent(name string) error { 131 | 132 | for i := range d.Components { 133 | if d.Components[i].Name == name { 134 | d.Components = append(d.Components[:i], d.Components[i+1:]...) 135 | return nil 136 | } 137 | } 138 | 139 | return &common.FieldNotFoundError{ 140 | Field: "component", 141 | Name: name, 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /pkg/testingutil/filesystem/fake_fs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | This package is a FORK of https://github.com/kubernetes/kubernetes/blob/master/pkg/util/filesystem/fakefs.go 19 | See above license 20 | */ 21 | 22 | package filesystem 23 | 24 | import ( 25 | "os" 26 | "path/filepath" 27 | "time" 28 | 29 | "github.com/spf13/afero" 30 | ) 31 | 32 | // fakeFs is implemented in terms of afero 33 | type fakeFs struct { 34 | a afero.Afero 35 | } 36 | 37 | // NewFakeFs returns a fake Filesystem that exists in-memory, useful for unit tests 38 | func NewFakeFs() Filesystem { 39 | return &fakeFs{a: afero.Afero{Fs: afero.NewMemMapFs()}} 40 | } 41 | 42 | // Stat via afero.Fs.Stat 43 | func (fs *fakeFs) Stat(name string) (os.FileInfo, error) { 44 | return fs.a.Fs.Stat(name) 45 | } 46 | 47 | // Create via afero.Fs.Create 48 | func (fs *fakeFs) Create(name string) (File, error) { 49 | file, err := fs.a.Fs.Create(name) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &fakeFile{file}, nil 54 | } 55 | 56 | // Open via afero.Fs.Open 57 | func (fs *fakeFs) Open(name string) (File, error) { 58 | file, err := fs.a.Fs.Open(name) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return &fakeFile{file}, nil 63 | } 64 | 65 | // OpenFile via afero.Fs.OpenFile 66 | func (fs *fakeFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 67 | file, err := fs.a.Fs.OpenFile(name, flag, perm) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return &fakeFile{file}, nil 72 | } 73 | 74 | // Rename via afero.Fs.Rename 75 | func (fs *fakeFs) Rename(oldpath, newpath string) error { 76 | return fs.a.Fs.Rename(oldpath, newpath) 77 | } 78 | 79 | // MkdirAll via afero.Fs.MkdirAll 80 | func (fs *fakeFs) MkdirAll(path string, perm os.FileMode) error { 81 | return fs.a.Fs.MkdirAll(path, perm) 82 | } 83 | 84 | // Chtimes via afero.Fs.Chtimes 85 | func (fs *fakeFs) Chtimes(name string, atime time.Time, mtime time.Time) error { 86 | return fs.a.Fs.Chtimes(name, atime, mtime) 87 | } 88 | 89 | // ReadFile via afero.ReadFile 90 | func (fs *fakeFs) ReadFile(filename string) ([]byte, error) { 91 | return fs.a.ReadFile(filename) 92 | } 93 | 94 | // WriteFile via afero.WriteFile 95 | func (fs *fakeFs) WriteFile(filename string, data []byte, perm os.FileMode) error { 96 | return fs.a.WriteFile(filename, data, perm) 97 | } 98 | 99 | // TempDir via afero.TempDir 100 | func (fs *fakeFs) TempDir(dir, prefix string) (string, error) { 101 | return fs.a.TempDir(dir, prefix) 102 | } 103 | 104 | // TempFile via afero.TempFile 105 | func (fs *fakeFs) TempFile(dir, prefix string) (File, error) { 106 | file, err := fs.a.TempFile(dir, prefix) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return &fakeFile{file}, nil 111 | } 112 | 113 | // ReadDir via afero.ReadDir 114 | func (fs *fakeFs) ReadDir(dirname string) ([]os.FileInfo, error) { 115 | return fs.a.ReadDir(dirname) 116 | } 117 | 118 | // Walk via afero.Walk 119 | func (fs *fakeFs) Walk(root string, walkFn filepath.WalkFunc) error { 120 | return fs.a.Walk(root, walkFn) 121 | } 122 | 123 | // RemoveAll via afero.RemoveAll 124 | func (fs *fakeFs) RemoveAll(path string) error { 125 | return fs.a.RemoveAll(path) 126 | } 127 | 128 | func (fs *fakeFs) Getwd() (dir string, err error) { 129 | return ".", nil 130 | } 131 | 132 | // Remove via afero.RemoveAll 133 | func (fs *fakeFs) Remove(name string) error { 134 | return fs.a.Remove(name) 135 | } 136 | 137 | // Chmod via afero.Chmod 138 | func (fs *fakeFs) Chmod(name string, mode os.FileMode) error { 139 | return fs.a.Chmod(name, mode) 140 | } 141 | 142 | // fakeFile implements File; for use with fakeFs 143 | type fakeFile struct { 144 | file afero.File 145 | } 146 | 147 | // Name via afero.File.Name 148 | func (file *fakeFile) Name() string { 149 | return file.file.Name() 150 | } 151 | 152 | // Write via afero.File.Write 153 | func (file *fakeFile) Write(b []byte) (n int, err error) { 154 | return file.file.Write(b) 155 | } 156 | 157 | // WriteString via afero.File.WriteString 158 | func (file *fakeFile) WriteString(s string) (n int, err error) { 159 | return file.file.WriteString(s) 160 | } 161 | 162 | // Sync via afero.File.Sync 163 | func (file *fakeFile) Sync() error { 164 | return file.file.Sync() 165 | } 166 | 167 | // Close via afero.File.Close 168 | func (file *fakeFile) Close() error { 169 | return file.file.Close() 170 | } 171 | 172 | func (file *fakeFile) Readdir(n int) ([]os.FileInfo, error) { 173 | return file.file.Readdir(n) 174 | } 175 | 176 | func (file *fakeFile) Read(b []byte) (n int, err error) { 177 | return file.file.Read(b) 178 | } 179 | -------------------------------------------------------------------------------- /pkg/devfile/parser/writer_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package parser 17 | 18 | import ( 19 | "fmt" 20 | "strings" 21 | "testing" 22 | 23 | v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 24 | apiAttributes "github.com/devfile/api/v2/pkg/attributes" 25 | devfilepkg "github.com/devfile/api/v2/pkg/devfile" 26 | devfileCtx "github.com/devfile/library/v2/pkg/devfile/parser/context" 27 | v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" 28 | "github.com/devfile/library/v2/pkg/testingutil/filesystem" 29 | ) 30 | 31 | func TestDevfileObj_WriteYamlDevfile(t *testing.T) { 32 | 33 | var ( 34 | schemaVersion = "2.2.0" 35 | uri = "./relative/path/deploy.yaml" 36 | uri2 = "./relative/path/deploy2.yaml" 37 | ) 38 | 39 | tests := []struct { 40 | name string 41 | fileName string 42 | wantErr bool 43 | }{ 44 | { 45 | name: "write devfile with .yaml extension", 46 | fileName: OutputDevfileYamlPath, 47 | }, 48 | { 49 | name: "write .devfile with .yaml extension", 50 | fileName: ".devfile.yaml", 51 | }, 52 | { 53 | name: "write devfile with .yml extension", 54 | fileName: "devfile.yml", 55 | }, 56 | { 57 | name: "write .devfile with .yml extension", 58 | fileName: ".devfile.yml", 59 | }, 60 | { 61 | name: "write any file, regardless of name and extension", 62 | fileName: "some-random-file", 63 | }, 64 | } 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | var ( 68 | // Use fakeFs 69 | fs = filesystem.NewFakeFs() 70 | attributes = apiAttributes.Attributes{}.PutString(K8sLikeComponentOriginalURIKey, uri) 71 | attributes2 = apiAttributes.Attributes{}.PutString(K8sLikeComponentOriginalURIKey, uri2) 72 | ) 73 | 74 | // DevfileObj 75 | devfileObj := DevfileObj{ 76 | Ctx: devfileCtx.FakeContext(fs, tt.fileName), 77 | Data: &v2.DevfileV2{ 78 | Devfile: v1.Devfile{ 79 | DevfileHeader: devfilepkg.DevfileHeader{ 80 | SchemaVersion: schemaVersion, 81 | Metadata: devfilepkg.DevfileMetadata{ 82 | Name: tt.name, 83 | }, 84 | }, 85 | DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{ 86 | DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{ 87 | Components: []v1.Component{ 88 | { 89 | Name: "kubeComp", 90 | Attributes: attributes, 91 | ComponentUnion: v1.ComponentUnion{ 92 | Kubernetes: &v1.KubernetesComponent{ 93 | K8sLikeComponent: v1.K8sLikeComponent{ 94 | K8sLikeComponentLocation: v1.K8sLikeComponentLocation{ 95 | Inlined: "placeholder", 96 | }, 97 | }, 98 | }, 99 | }, 100 | }, 101 | { 102 | Name: "openshiftComp", 103 | Attributes: attributes2, 104 | ComponentUnion: v1.ComponentUnion{ 105 | Openshift: &v1.OpenshiftComponent{ 106 | K8sLikeComponent: v1.K8sLikeComponent{ 107 | K8sLikeComponentLocation: v1.K8sLikeComponentLocation{ 108 | Inlined: "placeholder", 109 | }, 110 | }, 111 | }, 112 | }, 113 | }, 114 | }, 115 | }, 116 | }, 117 | }, 118 | }, 119 | } 120 | devfileObj.Ctx.SetConvertUriToInlined(true) 121 | 122 | // test func() 123 | err := devfileObj.WriteYamlDevfile() 124 | if (err != nil) != tt.wantErr { 125 | t.Errorf("TestWriteYamlDevfile() unexpected error: '%v', wantErr=%v", err, tt.wantErr) 126 | return 127 | } 128 | 129 | if _, err := fs.Stat(tt.fileName); err != nil { 130 | t.Errorf("TestWriteYamlDevfile() unexpected error: '%v'", err) 131 | } 132 | 133 | data, err := fs.ReadFile(tt.fileName) 134 | if err != nil { 135 | t.Errorf("TestWriteYamlDevfile() unexpected error: '%v'", err) 136 | return 137 | } 138 | 139 | content := string(data) 140 | if strings.Contains(content, "inlined") || strings.Contains(content, K8sLikeComponentOriginalURIKey) { 141 | t.Errorf("TestWriteYamlDevfile() failed: kubernetes component should not contain inlined or %s", K8sLikeComponentOriginalURIKey) 142 | } 143 | if !strings.Contains(content, fmt.Sprintf("uri: %s", uri)) { 144 | t.Errorf("TestWriteYamlDevfile() failed: kubernetes component does not contain uri") 145 | } 146 | if !strings.Contains(content, fmt.Sprintf("uri: %s", uri2)) { 147 | t.Errorf("TestWriteYamlDevfile() failed: openshift component does not contain uri") 148 | } 149 | }) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/v2/utils/library/command_test_utils.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package utils 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "os" 22 | 23 | "github.com/google/go-cmp/cmp" 24 | "sigs.k8s.io/yaml" 25 | 26 | schema "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" 27 | commonUtils "github.com/devfile/api/v2/test/v200/utils/common" 28 | ) 29 | 30 | // getSchemaCommand get a specified command from the devfile schema structure 31 | func getSchemaCommand(commands []schema.Command, id string) (*schema.Command, bool) { 32 | found := false 33 | var schemaCommand schema.Command 34 | for _, command := range commands { 35 | if command.Id == id { 36 | schemaCommand = command 37 | found = true 38 | break 39 | } 40 | } 41 | return &schemaCommand, found 42 | } 43 | 44 | // UpdateCommand randomly updates attribute values of a specified command in the devfile schema 45 | func UpdateCommand(devfile *commonUtils.TestDevfile, commandId string) error { 46 | 47 | var err error 48 | testCommand, found := getSchemaCommand(devfile.SchemaDevFile.Commands, commandId) 49 | if found { 50 | commonUtils.LogInfoMessage(fmt.Sprintf("Updating command id: %s", commandId)) 51 | if testCommand.Exec != nil { 52 | devfile.SetExecCommandValues(testCommand) 53 | } else if testCommand.Composite != nil { 54 | devfile.SetCompositeCommandValues(testCommand) 55 | } else if testCommand.Apply != nil { 56 | devfile.SetApplyCommandValues(testCommand) 57 | } 58 | } else { 59 | err = errors.New(commonUtils.LogErrorMessage(fmt.Sprintf("Command not found in test : %s", commandId))) 60 | } 61 | return err 62 | } 63 | 64 | // VerifyCommands verifies commands returned by the parser are the same as those saved in the devfile schema 65 | func VerifyCommands(devfile *commonUtils.TestDevfile, parserCommands []schema.Command) error { 66 | 67 | commonUtils.LogInfoMessage("Enter VerifyCommands") 68 | var errorString []string 69 | 70 | // Compare entire array of commands 71 | if !cmp.Equal(parserCommands, devfile.SchemaDevFile.Commands) { 72 | errorString = append(errorString, commonUtils.LogErrorMessage(fmt.Sprintf("Command array compare failed."))) 73 | // Array compare failed. Narrow down by comparing indivdual commands 74 | for _, command := range parserCommands { 75 | if testCommand, found := getSchemaCommand(devfile.SchemaDevFile.Commands, command.Id); found { 76 | if !cmp.Equal(command, *testCommand) { 77 | parserFilename := commonUtils.AddSuffixToFileName(devfile.FileName, "_"+command.Id+"_Parser") 78 | testFilename := commonUtils.AddSuffixToFileName(devfile.FileName, "_"+command.Id+"_Test") 79 | commonUtils.LogInfoMessage(fmt.Sprintf(".......marshall and write devfile %s", devfile.FileName)) 80 | c, err := yaml.Marshal(command) 81 | if err != nil { 82 | errorString = append(errorString, commonUtils.LogErrorMessage(fmt.Sprintf(".......marshall devfile %s", parserFilename))) 83 | } else { 84 | err = os.WriteFile(parserFilename, c, 0644) 85 | if err != nil { 86 | errorString = append(errorString, commonUtils.LogErrorMessage(fmt.Sprintf(".......write devfile %s", parserFilename))) 87 | } 88 | } 89 | commonUtils.LogInfoMessage(fmt.Sprintf(".......marshall and write devfile %s", testFilename)) 90 | c, err = yaml.Marshal(testCommand) 91 | if err != nil { 92 | errorString = append(errorString, commonUtils.LogErrorMessage(fmt.Sprintf(".......marshall devfile %s", testFilename))) 93 | } else { 94 | err = os.WriteFile(testFilename, c, 0644) 95 | if err != nil { 96 | errorString = append(errorString, commonUtils.LogErrorMessage(fmt.Sprintf(".......write devfile %s", testFilename))) 97 | } 98 | } 99 | errorString = append(errorString, commonUtils.LogInfoMessage(fmt.Sprintf("Command %s did not match, see files : %s and %s", command.Id, parserFilename, testFilename))) 100 | } else { 101 | commonUtils.LogInfoMessage(fmt.Sprintf(" --> Command matched : %s", command.Id)) 102 | } 103 | } else { 104 | errorString = append(errorString, commonUtils.LogErrorMessage(fmt.Sprintf("Command from parser not known to test - id : %s ", command.Id))) 105 | } 106 | 107 | } 108 | for _, command := range devfile.SchemaDevFile.Commands { 109 | if _, found := getSchemaCommand(parserCommands, command.Id); !found { 110 | errorString = append(errorString, commonUtils.LogErrorMessage(fmt.Sprintf("Command from test not returned by parser : %s ", command.Id))) 111 | } 112 | } 113 | } else { 114 | commonUtils.LogInfoMessage(fmt.Sprintf(" --> Command structures matched")) 115 | } 116 | 117 | var err error 118 | if len(errorString) > 0 { 119 | err = errors.New(fmt.Sprint(errorString)) 120 | } 121 | return err 122 | } 123 | -------------------------------------------------------------------------------- /pkg/devfile/parser/util/mock.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Red Hat 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package util 17 | 18 | import ( 19 | "fmt" 20 | "net/http" 21 | "os" 22 | 23 | "github.com/devfile/library/v2/pkg/util" 24 | ) 25 | 26 | // Default filenames for create devfile to be used in mocks 27 | const ( 28 | OutputDevfileYamlPath = "devfile.yaml" 29 | ) 30 | 31 | type MockDevfileUtilsClient struct { 32 | // Specify a valid git URL as an alias if using a localhost HTTP server in order to pass validation. 33 | ParentURLAlias string 34 | 35 | // MockGitUrl struct for mocking git related ops 36 | MockGitURL util.MockGitUrl 37 | 38 | // Mock Git token. Specify the string "valid-token" for the mock CloneGitRepo to pass 39 | GitTestToken string 40 | 41 | // Options to specify what file download needs to be mocked 42 | DownloadOptions util.MockDownloadOptions 43 | } 44 | 45 | func NewMockDevfileUtilsClient() MockDevfileUtilsClient { 46 | return MockDevfileUtilsClient{} 47 | } 48 | 49 | func (gc *MockDevfileUtilsClient) DownloadInMemory(params util.HTTPRequestParams) ([]byte, error) { 50 | var httpClient = &http.Client{Transport: &http.Transport{ 51 | ResponseHeaderTimeout: util.HTTPRequestResponseTimeout, 52 | }, Timeout: util.HTTPRequestResponseTimeout} 53 | 54 | if gc.MockGitURL.Host != "" { 55 | if util.IsGitProviderRepo(gc.MockGitURL.Host) { 56 | gc.MockGitURL.Token = gc.GitTestToken 57 | } 58 | } else if params.URL != "" { 59 | // Not all clients have the ability to pass in mock data 60 | // So we should be adaptable and use the function params 61 | // and mock the output 62 | if util.IsGitProviderRepo(params.URL) { 63 | gc.MockGitURL.Host = params.URL 64 | gc.MockGitURL.Token = params.Token 65 | } 66 | } 67 | 68 | if gc.DownloadOptions.MockParent == nil { 69 | gc.DownloadOptions.MockParent = &util.MockParent{} 70 | } 71 | 72 | file, err := gc.MockGitURL.DownloadInMemoryWithClient(params, httpClient, gc.DownloadOptions) 73 | 74 | if gc.DownloadOptions.MockParent != nil && gc.DownloadOptions.MockParent.IsMainDevfileDownloaded && gc.DownloadOptions.MockParent.IsParentDevfileDownloaded { 75 | // Since gc is a pointer, if both the main and parent devfiles are downloaded, reset the flag. 76 | // So that other tests can use the Mock Parent Devfile download if required. 77 | gc.DownloadOptions.MockParent.IsMainDevfileDownloaded = false 78 | gc.DownloadOptions.MockParent.IsParentDevfileDownloaded = false 79 | } 80 | 81 | if gc.MockGitURL.Host != "" && params.URL != "" { 82 | // Since gc is a pointer, reset the mock data if both the URL and Host are present 83 | gc.MockGitURL.Host = "" 84 | gc.MockGitURL.Token = "" 85 | } 86 | 87 | return file, err 88 | } 89 | 90 | func (gc MockDevfileUtilsClient) DownloadGitRepoResources(url string, destDir string, token string) error { 91 | 92 | // if mock data is unavailable as certain clients cant provide mock data 93 | // then adapt and create mock data from actual params 94 | if gc.ParentURLAlias == "" { 95 | gc.ParentURLAlias = url 96 | gc.MockGitURL.IsFile = true 97 | gc.MockGitURL.Revision = "main" 98 | gc.MockGitURL.Path = OutputDevfileYamlPath 99 | gc.MockGitURL.Host = "github.com" 100 | gc.MockGitURL.Protocol = "https" 101 | gc.MockGitURL.Owner = "devfile" 102 | gc.MockGitURL.Repo = "library" 103 | } 104 | 105 | if gc.GitTestToken == "" { 106 | gc.GitTestToken = token 107 | } 108 | 109 | //the url parameter that gets passed in will be the localhost IP of the test server, so it will fail all the validation checks. We will use the global testURL variable instead 110 | //skip the Git Provider check since it'll fail 111 | if util.IsGitProviderRepo(gc.ParentURLAlias) { 112 | // this converts the test git URL to a mock URL 113 | mockGitUrl := gc.MockGitURL 114 | mockGitUrl.Token = gc.GitTestToken 115 | 116 | if !mockGitUrl.IsFile || mockGitUrl.Revision == "" || !ValidateDevfileExistence((mockGitUrl.Path)) { 117 | return fmt.Errorf("error getting devfile from url: failed to retrieve %s", url+"/"+mockGitUrl.Path) 118 | } 119 | 120 | stackDir, err := os.MkdirTemp("", "git-resources") 121 | if err != nil { 122 | return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err) 123 | } 124 | 125 | defer func(path string) { 126 | err = os.RemoveAll(path) 127 | if err != nil { 128 | err = fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err) 129 | } 130 | }(stackDir) 131 | 132 | err = mockGitUrl.CloneGitRepo(stackDir) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | err = util.CopyAllDirFiles(stackDir, destDir) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | } else { 143 | return fmt.Errorf("failed to download resources from parent devfile. Unsupported Git Provider for %s ", gc.ParentURLAlias) 144 | } 145 | 146 | return nil 147 | } 148 | --------------------------------------------------------------------------------