diff --git a/.deps/EXCLUDED/dev.md b/.deps/EXCLUDED/dev.md index 237c38b161..c6ded57955 100755 --- a/.deps/EXCLUDED/dev.md +++ b/.deps/EXCLUDED/dev.md @@ -5,35 +5,16 @@ This file contains a manual contribution to .deps/dev.md and it's needed because | `@eclipse-che/license-tool@2.0.0` | ecd.che | | `jsbn@0.1.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/jsbn/0.1.1) | | `brace-expansion@1.1.15` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/brace-expansion/1.1.15) | -| `@commitlint/cli@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/cli/21.0.2) | | `@commitlint/config-conventional@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/config-conventional/21.0.2) | | `@commitlint/config-validator@21.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/config-validator/21.0.1) | -| `@commitlint/ensure@21.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/ensure/21.0.1) | -| `@commitlint/execute-rule@21.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/execute-rule/21.0.1) | -| `@commitlint/format@21.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/format/21.0.1) | -| `@commitlint/is-ignored@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/is-ignored/21.0.2) | -| `@commitlint/lint@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/lint/21.0.2) | | `@commitlint/load@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/load/21.0.2) | -| `@commitlint/message@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/message/21.0.2) | -| `@commitlint/parse@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/parse/21.0.2) | -| `@commitlint/read@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/read/21.0.2) | -| `@commitlint/resolve-extends@21.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/resolve-extends/21.0.1) | -| `@commitlint/rules@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/rules/21.0.2) | -| `@commitlint/to-lines@21.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/to-lines/21.0.1) | -| `@commitlint/top-level@21.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/top-level/21.0.2) | -| `@commitlint/types@21.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/types/21.0.1) | | `@conventional-changelog/git-client@2.7.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@conventional-changelog/git-client/2.7.0) | -| `@simple-libs/child-process-utils@1.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@simple-libs/child-process-utils/1.0.2) | | `@simple-libs/stream-utils@1.2.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@simple-libs/stream-utils/1.2.0) | | `conventional-changelog-angular@8.3.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/conventional-changelog-angular/8.3.1) | | `conventional-changelog-conventionalcommits@9.3.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/conventional-changelog-conventionalcommits/9.3.1) | | `conventional-commits-parser@6.4.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/conventional-commits-parser/6.4.0) | -| `cosmiconfig@9.0.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cosmiconfig/9.0.2) | | `cosmiconfig-typescript-loader@6.3.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cosmiconfig-typescript-loader/6.3.0) | | `es-toolkit@1.47.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/es-toolkit/1.47.0) | -| `git-raw-commits@5.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/git-raw-commits/5.0.1) | -| `pidtree@0.6.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/pidtree/0.6.1) | -| `semver@7.8.3` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/semver/7.8.3) | -| `tinyexec@1.2.4` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/tinyexec/1.2.4) | + diff --git a/.deps/EXCLUDED/prod.md b/.deps/EXCLUDED/prod.md index fd619d1bf3..93e3e2b6d8 100755 --- a/.deps/EXCLUDED/prod.md +++ b/.deps/EXCLUDED/prod.md @@ -2,10 +2,25 @@ This file lists dependencies that do not need CQs or auto-detection does not wor | Packages | Resolved CQs | | --- | --- | +| `@codemirror/autocomplete@6.20.3` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/autocomplete/6.20.3) | +| `@codemirror/commands@6.10.3` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/commands/6.10.3) | +| `@codemirror/lang-yaml@6.1.3` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/lang-yaml/6.1.3) | +| `@codemirror/language@6.12.4` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/language/6.12.4) | +| `@codemirror/lint@6.9.5` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/lint/6.9.5) | +| `@codemirror/state@6.7.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/state/6.7.1) | +| `@codemirror/view@6.43.6` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/view/6.43.6) | +| `@fastify/busboy@3.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@fastify/busboy/3.0.1) | +| `@isaacs/brace-expansion@5.0.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@isaacs/brace-expansion/5.0.1) | +| `axios@1.15.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/axios/1.15.2) | +| `@lezer/lr@1.4.8` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lezer/lr/1.4.8) | +| `@lezer/yaml@1.0.4` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lezer/yaml/1.0.4) | +| `@uiw/codemirror-theme-github@4.25.8` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@uiw/codemirror-theme-github/4.25.8) | +| `@uiw/codemirror-themes@4.25.8` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@uiw/codemirror-themes/4.25.8) | | `any-signal@4.2.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/any-signal/4.2.0) | | `cronstrue@3.13.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cronstrue/3.13.0) | | `fast-uri@3.1.3` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/fast-uri/3.1.3) | | `fastify-plugin@5.2.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/fastify-plugin/5.2.1) | +| `fastify@5.8.4` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/fastify/5.8.4) | | `jsep@1.3.9` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/jsep/1.3.9) | | `undici@7.28.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/undici/7.28.0) | | `@hapi/wreck@18.1.2` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@hapi/wreck/18.1.2) | @@ -15,3 +30,4 @@ This file lists dependencies that do not need CQs or auto-detection does not wor | `js-yaml@4.3.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/js-yaml/4.3.0) | + diff --git a/.deps/dev.md b/.deps/dev.md index 3c19b0ef8f..d10553d82a 100644 --- a/.deps/dev.md +++ b/.deps/dev.md @@ -58,23 +58,23 @@ | `@babel/types@7.27.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@babel/types/7.27.0) | | `@babel/types@7.28.5` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@babel/types/7.28.5) | | `@bcoe/v8-coverage@0.2.3` | ISC AND MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@bcoe/v8-coverage/0.2.3) | -| `@commitlint/cli@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/cli/21.0.2) | +| `@commitlint/cli@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/cli/21.0.2) | | `@commitlint/config-conventional@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/config-conventional/21.0.2) | | `@commitlint/config-validator@21.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/config-validator/21.0.1) | -| `@commitlint/ensure@21.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/ensure/21.0.1) | -| `@commitlint/execute-rule@21.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/execute-rule/21.0.1) | -| `@commitlint/format@21.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/format/21.0.1) | -| `@commitlint/is-ignored@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/is-ignored/21.0.2) | -| `@commitlint/lint@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/lint/21.0.2) | +| `@commitlint/ensure@21.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/ensure/21.0.1) | +| `@commitlint/execute-rule@21.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/execute-rule/21.0.1) | +| `@commitlint/format@21.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/format/21.0.1) | +| `@commitlint/is-ignored@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/is-ignored/21.0.2) | +| `@commitlint/lint@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/lint/21.0.2) | | `@commitlint/load@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/load/21.0.2) | -| `@commitlint/message@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/message/21.0.2) | -| `@commitlint/parse@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/parse/21.0.2) | -| `@commitlint/read@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/read/21.0.2) | -| `@commitlint/resolve-extends@21.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/resolve-extends/21.0.1) | -| `@commitlint/rules@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/rules/21.0.2) | -| `@commitlint/to-lines@21.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/to-lines/21.0.1) | -| `@commitlint/top-level@21.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/top-level/21.0.2) | -| `@commitlint/types@21.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/types/21.0.1) | +| `@commitlint/message@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/message/21.0.2) | +| `@commitlint/parse@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/parse/21.0.2) | +| `@commitlint/read@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/read/21.0.2) | +| `@commitlint/resolve-extends@21.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/resolve-extends/21.0.1) | +| `@commitlint/rules@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/rules/21.0.2) | +| `@commitlint/to-lines@21.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/to-lines/21.0.1) | +| `@commitlint/top-level@21.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/top-level/21.0.2) | +| `@commitlint/types@21.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@commitlint/types/21.0.1) | | `@conventional-changelog/git-client@2.7.0` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@conventional-changelog/git-client/2.7.0) | | `@csstools/css-parser-algorithms@3.0.4` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@csstools/css-parser-algorithms/3.0.4) | | `@csstools/css-tokenizer@3.0.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@csstools/css-tokenizer/3.0.3) | @@ -147,7 +147,7 @@ | `@pkgr/utils@2.4.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@pkgr/utils/2.4.2) | | `@polka/url@1.0.0-next.23` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@polka/url/1.0.0-next.23) | | `@react-mock/state@0.1.8` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@react-mock/state/0.1.8) | -| `@simple-libs/child-process-utils@1.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@simple-libs/child-process-utils/1.0.2) | +| `@simple-libs/child-process-utils@1.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@simple-libs/child-process-utils/1.0.2) | | `@simple-libs/stream-utils@1.2.0` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@simple-libs/stream-utils/1.2.0) | | `@sinclair/typebox@0.27.8` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@sinclair/typebox/0.27.8) | | `@sinclair/typebox@0.34.41` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@sinclair/typebox/0.34.41) | @@ -168,7 +168,6 @@ | `@types/babel__template@7.4.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/babel__template/7.4.3) | | `@types/babel__traverse@7.20.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/babel__traverse/7.20.3) | | `@types/caseless@0.12.4` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/caseless/0.12.4) | -| `@types/codemirror@5.60.15` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/codemirror/5.60.15) | | `@types/eslint-scope@3.7.7` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/eslint-scope/3.7.7) | | `@types/eslint@8.44.6` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/eslint/8.44.6) | | `@types/eslint@9.6.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/eslint/9.6.1) | @@ -211,7 +210,6 @@ | `@types/stack-utils@2.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/stack-utils/2.0.2) | | `@types/stack-utils@2.0.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/stack-utils/2.0.3) | | `@types/stream-buffers@3.0.8` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/stream-buffers/3.0.8) | -| `@types/tern@0.23.9` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/tern/0.23.9) | | `@types/tough-cookie@4.0.4` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/tough-cookie/4.0.4) | | `@types/webpack@5.28.5` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/webpack/5.28.5) | | `@types/ws@8.18.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/ws/8.18.1) | @@ -415,7 +413,7 @@ | `core-util-is@1.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/core-util-is/1.0.2) | | `cosmiconfig-typescript-loader@6.3.0` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cosmiconfig-typescript-loader/6.3.0) | | `cosmiconfig@9.0.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cosmiconfig/9.0.0) | -| `cosmiconfig@9.0.2` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cosmiconfig/9.0.2) | +| `cosmiconfig@9.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cosmiconfig/9.0.2) | | `css-declaration-sorter@6.4.1` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/css-declaration-sorter/6.4.1) | | `css-functions-list@3.2.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/css-functions-list/3.2.3) | | `css-loader@6.11.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/css-loader/6.11.0) | @@ -576,7 +574,7 @@ | `get-stream@8.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/get-stream/8.0.1) | | `get-symbol-description@1.0.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/get-symbol-description/1.0.0) | | `getpass@0.1.7` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/getpass/0.1.7) | -| `git-raw-commits@5.0.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/git-raw-commits/5.0.1) | +| `git-raw-commits@5.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/git-raw-commits/5.0.1) | | `glob-parent@6.0.2` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/glob-parent/6.0.2) | | `glob-to-regexp@0.4.1` | BSD-2-Clause | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/glob-to-regexp/0.4.1) | | `global-directory@5.0.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/global-directory/5.0.0) | @@ -881,7 +879,7 @@ | `performance-now@2.1.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/performance-now/2.1.0) | | `picocolors@1.0.0` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/picocolors/1.0.0) | | `picocolors@1.1.1` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/picocolors/1.1.1) | -| `pidtree@0.6.1` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/pidtree/0.6.1) | +| `pidtree@0.6.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/pidtree/0.6.1) | | `pify@3.0.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/pify/3.0.0) | | `pify@4.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/pify/4.0.1) | | `pirates@4.0.7` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/pirates/4.0.7) | @@ -993,7 +991,7 @@ | `semver@7.5.4` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/semver/7.5.4) | | `semver@7.7.3` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/semver/7.7.3) | | `semver@7.7.4` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/semver/7.7.4) | -| `semver@7.8.3` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/semver/7.8.3) | +| `semver@7.8.3` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/semver/7.8.3) | | `serialize-javascript@6.0.2` | BSD-3-Clause | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/serialize-javascript/6.0.2) | | `set-function-length@1.1.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/set-function-length/1.1.1) | | `set-function-name@2.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/set-function-name/2.0.1) | @@ -1073,7 +1071,7 @@ | `test-exclude@6.0.0` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/test-exclude/6.0.0) | | `text-decoder@1.2.3` | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/text-decoder/1.2.3) | | `text-table@0.2.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/text-table/0.2.0) | -| `tinyexec@1.2.4` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/tinyexec/1.2.4) | +| `tinyexec@1.2.4` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/tinyexec/1.2.4) | | `titleize@3.0.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/titleize/3.0.0) | | `tmpl@1.0.5` | BSD-3-Clause | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/tmpl/1.0.5) | | `to-regex-range@5.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/to-regex-range/5.0.1) | diff --git a/.deps/prod.md b/.deps/prod.md index 5b91b18bf7..6b2eaf986e 100644 --- a/.deps/prod.md +++ b/.deps/prod.md @@ -3,14 +3,16 @@ | Packages | License | Resolved CQs | | --- | --- | --- | | `@codemirror/autocomplete@6.18.6` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/autocomplete/6.18.6) | +| `@codemirror/autocomplete@6.20.3` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/autocomplete/6.20.3) | +| `@codemirror/commands@6.10.4` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/commands/6.10.4) | | `@codemirror/commands@6.8.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/commands/6.8.1) | -| `@codemirror/lang-yaml@6.1.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/lang-yaml/6.1.2) | +| `@codemirror/lang-yaml@6.1.3` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/lang-yaml/6.1.3) | | `@codemirror/language@6.11.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/language/6.11.3) | +| `@codemirror/language@6.12.4` | | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/language/6.12.4) | | `@codemirror/lint@6.8.5` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/lint/6.8.5) | +| `@codemirror/lint@6.9.7` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/lint/6.9.7) | | `@codemirror/search@6.5.11` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/search/6.5.11) | -| `@codemirror/state@6.5.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/state/6.5.2) | -| `@codemirror/theme-one-dark@6.1.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/theme-one-dark/6.1.2) | -| `@codemirror/view@6.37.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/view/6.37.1) | +| `@codemirror/theme-one-dark@6.1.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@codemirror/theme-one-dark/6.1.3) | | `@devfile/api@2.3.0-1757407014` | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@devfile/api/2.3.0-1757407014) | | `@eclipse-che/che-devworkspace-generator@7.113.0-next-7b6a101` | Apache-2.0 AND EPL-2.0 | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@eclipse-che/che-devworkspace-generator/7.113.0-next-7b6a101) | | `@fastify/accept-negotiator@2.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@fastify/accept-negotiator/2.0.1) | @@ -42,11 +44,11 @@ | `@inversifyjs/reflect-metadata-utils@1.1.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@inversifyjs/reflect-metadata-utils/1.1.0) | | `@kubernetes/client-node@1.4.0` | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@kubernetes/client-node/1.4.0) | | `@lezer/common@1.2.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lezer/common/1.2.3) | +| `@lezer/common@1.5.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lezer/common/1.5.2) | | `@lezer/highlight@1.2.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lezer/highlight/1.2.1) | | `@lezer/lr@1.4.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lezer/lr/1.4.2) | | `@lezer/yaml@1.0.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lezer/yaml/1.0.3) | | `@lukeed/ms@2.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@lukeed/ms/2.0.2) | -| `@marijn/find-cluster-break@1.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@marijn/find-cluster-break/1.0.2) | | `@patternfly/react-core@6.4.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-core/6.4.2) | | `@patternfly/react-icons@6.4.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-icons/6.4.0) | | `@patternfly/react-styles@6.4.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-styles/6.4.0) | @@ -59,13 +61,13 @@ | `@sideway/formula@3.0.1` | BSD-3-Clause | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@sideway/formula/3.0.1) | | `@sideway/pinpoint@2.0.0` | BSD-3-Clause | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@sideway/pinpoint/2.0.0) | | `@types/use-sync-external-store@0.0.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@types/use-sync-external-store/0.0.3) | -| `@uiw/codemirror-extensions-basic-setup@4.23.5` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@uiw/codemirror-extensions-basic-setup/4.23.5) | -| `@uiw/codemirror-theme-github@4.25.9` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@uiw/codemirror-theme-github/4.25.9) | -| `@uiw/codemirror-themes@4.25.9` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@uiw/codemirror-themes/4.25.9) | -| `@uiw/react-codemirror@4.23.5` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@uiw/react-codemirror/4.23.5) | +| `@xterm/addon-fit@0.10.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@xterm/addon-fit/0.10.0) | +| `@xterm/addon-web-links@0.11.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@xterm/addon-web-links/0.11.0) | +| `@xterm/xterm@5.5.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@xterm/xterm/5.5.0) | | `abort-controller@3.0.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/abort-controller/3.0.0) | | `abstract-logging@2.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/abstract-logging/2.0.1) | | `ajv-formats@3.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/ajv-formats/3.0.1) | +| `ajv@8.20.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/ajv/8.20.0) | | `any-signal@4.2.0` | Apache-2.0 AND LicenseRef-scancode-unknown-license-reference AND MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/any-signal/4.2.0) | | `args@5.0.3` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/args/5.0.3) | | `asn1.js@5.4.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/asn1.js/5.4.1) | @@ -85,7 +87,7 @@ | `call-bind-apply-helpers@1.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/call-bind-apply-helpers/1.0.2) | | `call-bound@1.0.4` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/call-bound/1.0.4) | | `camelcase@5.0.0` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/camelcase/5.0.0) | -| `codemirror@6.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/codemirror/6.0.1) | +| `codemirror@6.0.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/codemirror/6.0.2) | | `content-disposition@1.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/content-disposition/1.0.1) | | `cookie@1.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cookie/1.0.1) | | `cookie@1.1.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cookie/1.1.1) | @@ -242,4 +244,4 @@ | `universalify@2.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/universalify/2.0.1) | | `use-sync-external-store@1.2.2` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/use-sync-external-store/1.2.2) | | `value-equal@1.0.1` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/value-equal/1.0.1) | -| `w3c-keyname@2.2.8` | MIT | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/w3c-keyname/2.2.8) | +| `yaml@2.8.3` | ISC | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/yaml/2.8.3) | diff --git a/package.json b/package.json index 16a47f3128..a0778e46fd 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,8 @@ "@fastify/static": "^9.1.1", "brace-expansion@^1.0.0": "^1.1.13", "brace-expansion@^5.0.5": "5.0.7", - "fast-uri": "3.1.3" + "fast-uri": "3.1.3", + "@codemirror/state": "^6.7.1", + "@codemirror/view": "^6.43.6" } } diff --git a/packages/common/src/constants/__tests__/terminal-themes.spec.ts b/packages/common/src/constants/__tests__/terminal-themes.spec.ts new file mode 100644 index 0000000000..d61e7b8242 --- /dev/null +++ b/packages/common/src/constants/__tests__/terminal-themes.spec.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { isTerminalThemeName, TERMINAL_THEMES } from '../terminal-themes'; + +describe('terminal-themes', () => { + test('TERMINAL_THEMES has all expected themes', () => { + expect(TERMINAL_THEMES).toHaveProperty('dracula'); + expect(TERMINAL_THEMES).toHaveProperty('dark'); + expect(TERMINAL_THEMES).toHaveProperty('light'); + }); + + describe('isTerminalThemeName', () => { + it.each(['dracula', 'dark', 'light'])( + 'should return true for "%s"', + name => { + expect(isTerminalThemeName(name)).toBe(true); + }, + ); + + it.each(['', 'invalid', 'Dracula', 'DARK', 'solarized'])( + 'should return false for "%s"', + name => { + expect(isTerminalThemeName(name)).toBe(false); + }, + ); + }); +}); diff --git a/packages/common/src/constants/index.ts b/packages/common/src/constants/index.ts index 44d670c240..7dc81f19d7 100644 --- a/packages/common/src/constants/index.ts +++ b/packages/common/src/constants/index.ts @@ -11,3 +11,4 @@ */ export * from './backup'; +export * from './terminal-themes'; diff --git a/packages/common/src/constants/terminal-themes.ts b/packages/common/src/constants/terminal-themes.ts new file mode 100644 index 0000000000..912d2eae54 --- /dev/null +++ b/packages/common/src/constants/terminal-themes.ts @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +export interface TerminalTheme { + foreground: string; + background: string; + cursor: string; + cursorAccent: string; + selectionBackground: string; + selectionForeground: string; + black: string; + red: string; + green: string; + yellow: string; + blue: string; + magenta: string; + cyan: string; + white: string; + brightBlack: string; + brightRed: string; + brightGreen: string; + brightYellow: string; + brightBlue: string; + brightMagenta: string; + brightCyan: string; + brightWhite: string; +} + +export type TerminalThemeName = 'dracula' | 'dark' | 'light'; + +export const TERMINAL_THEMES: Record = { + dracula: { + foreground: '#f8f8f2', + background: '#282a36', + cursor: '#f8f8f2', + cursorAccent: '#282a36', + selectionBackground: '#44475a', + selectionForeground: '#f8f8f2', + black: '#21222c', + red: '#ff5555', + green: '#50fa7b', + yellow: '#f1fa8c', + blue: '#bd93f9', + magenta: '#ff79c6', + cyan: '#8be9fd', + white: '#f8f8f2', + brightBlack: '#6272a4', + brightRed: '#ff6e6e', + brightGreen: '#69ff94', + brightYellow: '#ffffa5', + brightBlue: '#d6acff', + brightMagenta: '#ff92df', + brightCyan: '#a4ffff', + brightWhite: '#ffffff', + }, + dark: { + foreground: '#d4d4d4', + background: '#1e1e1e', + cursor: '#d4d4d4', + cursorAccent: '#1e1e1e', + selectionBackground: '#264f78', + selectionForeground: '#ffffff', + black: '#000000', + red: '#cd3131', + green: '#0dbc79', + yellow: '#e5e510', + blue: '#2472c8', + magenta: '#bc3fbc', + cyan: '#11a8cd', + white: '#e5e5e5', + brightBlack: '#666666', + brightRed: '#f14c4c', + brightGreen: '#23d18b', + brightYellow: '#f5f543', + brightBlue: '#3b8eea', + brightMagenta: '#d670d6', + brightCyan: '#29b8db', + brightWhite: '#e5e5e5', + }, + light: { + foreground: '#383a42', + background: '#fafafa', + cursor: '#6083ad', + cursorAccent: '#fafafa', + selectionBackground: '#d7d4f0', + selectionForeground: '#383a42', + black: '#000000', + red: '#e45649', + green: '#50a14f', + yellow: '#BDB76B', + blue: '#4078f2', + magenta: '#a626a4', + cyan: '#0184bc', + white: '#a0a1a7', + brightBlack: '#5c6370', + brightRed: '#e06c75', + brightGreen: '#98c379', + brightYellow: '#e5c07b', + brightBlue: '#61afef', + brightMagenta: '#c678dd', + brightCyan: '#56b6c2', + brightWhite: '#909090', + }, +}; + +export function isTerminalThemeName(value: string): value is TerminalThemeName { + return value === 'dracula' || value === 'dark' || value === 'light'; +} diff --git a/packages/common/src/dto/api/__tests__/webSocket.spec.ts b/packages/common/src/dto/api/__tests__/webSocket.spec.ts index c29e4e081f..e780665234 100644 --- a/packages/common/src/dto/api/__tests__/webSocket.spec.ts +++ b/packages/common/src/dto/api/__tests__/webSocket.spec.ts @@ -13,6 +13,7 @@ import { webSocket } from '..'; import { Channel, + isConfigMapMessage, isDevWorkspaceMessage, isEventMessage, isLogsMessage, @@ -265,6 +266,40 @@ describe('api.webSocket typeguards', () => { expect(isWebSocketEventData('foo')).toBeFalsy(); }); + test('isConfigMapMessage', () => { + expect( + isConfigMapMessage({ + eventPhase: webSocket.EventPhase.ADDED, + configMap: {}, + }), + ).toBeTruthy(); + expect( + isConfigMapMessage({ + eventPhase: webSocket.EventPhase.DELETED, + configMap: {}, + }), + ).toBeTruthy(); + expect( + isConfigMapMessage({ + eventPhase: webSocket.EventPhase.MODIFIED, + configMap: {}, + }), + ).toBeTruthy(); + + expect(isConfigMapMessage(undefined)).toBeFalsy(); + expect(isConfigMapMessage({})).toBeFalsy(); + expect( + isConfigMapMessage({ + eventPhase: webSocket.EventPhase.ADDED, + }), + ).toBeFalsy(); + expect( + isConfigMapMessage({ + configMap: {}, + }), + ).toBeFalsy(); + }); + test('isLogsMessage', () => { expect( isLogsMessage({ diff --git a/packages/common/src/dto/api/index.ts b/packages/common/src/dto/api/index.ts index 33ca0821b1..fabdc2b5be 100644 --- a/packages/common/src/dto/api/index.ts +++ b/packages/common/src/dto/api/index.ts @@ -77,6 +77,29 @@ export interface IPatch { value?: any; } +// Generated by Claude Opus 4.6 + +export interface AiAgentDefinition { + id: string; + name: string; + publisher: string; + description: string; + icon: string; + docsUrl: string; + image: string; + tag: string; + memoryLimit: string; + cpuLimit: string; + terminalPort: number; + env: Array<{ name: string; value: string }>; + initCommand: string; +} + +export interface IAiAgentRegistry { + agents: AiAgentDefinition[]; + defaultAgentId: string; +} + export interface IGitConfig { resourceVersion?: string; gitconfig: { diff --git a/packages/common/src/dto/api/webSocket.ts b/packages/common/src/dto/api/webSocket.ts index 3f336d75c4..1f47506868 100644 --- a/packages/common/src/dto/api/webSocket.ts +++ b/packages/common/src/dto/api/webSocket.ts @@ -11,22 +11,29 @@ */ import { V1alpha2DevWorkspace } from '@devfile/api'; -import { CoreV1Event, V1Pod, V1Status } from '@kubernetes/client-node'; +import { + CoreV1Event, + V1ConfigMap, + V1Pod, + V1Status, +} from '@kubernetes/client-node'; export enum Channel { DEV_WORKSPACE = 'devWorkspace', EVENT = 'event', POD = 'pod', LOGS = 'logs', + CONFIGMAP = 'configmap', } export function isWebSocketChannel(channel: unknown): channel is Channel { return ( - (channel !== undefined && - ((channel as Channel) === Channel.DEV_WORKSPACE || - (channel as Channel) === Channel.EVENT)) || - (channel as Channel) === Channel.POD || - (channel as Channel) === Channel.LOGS + channel !== undefined && + ((channel as Channel) === Channel.DEV_WORKSPACE || + (channel as Channel) === Channel.EVENT || + (channel as Channel) === Channel.POD || + (channel as Channel) === Channel.LOGS || + (channel as Channel) === Channel.CONFIGMAP) ); } @@ -44,7 +51,11 @@ export type SubscribeMessage = { method: 'SUBSCRIBE'; } & ( | { - channel: Channel.DEV_WORKSPACE | Channel.EVENT | Channel.POD; + channel: + | Channel.DEV_WORKSPACE + | Channel.EVENT + | Channel.POD + | Channel.CONFIGMAP; params: SubscribeParams; } | { @@ -138,6 +149,10 @@ export type PodMessage = { eventPhase: EventPhase.ADDED | EventPhase.MODIFIED | EventPhase.DELETED; pod: V1Pod; }; +export type ConfigMapMessage = { + eventPhase: EventPhase.ADDED | EventPhase.MODIFIED | EventPhase.DELETED; + configMap: V1ConfigMap; +}; export type LogsMessage = { eventPhase: EventPhase.ADDED; podName: string; @@ -153,6 +168,7 @@ export type NotificationMessage = | EventMessage | DevWorkspaceMessage | PodMessage + | ConfigMapMessage | LogsMessage | StatusMessage; export type EventData = { @@ -220,6 +236,18 @@ export function isStatusMessage(message: unknown): message is StatusMessage { ); } +export function isConfigMapMessage( + message: unknown, +): message is ConfigMapMessage { + return ( + message !== undefined && + ((message as ConfigMapMessage).eventPhase === EventPhase.ADDED || + (message as ConfigMapMessage).eventPhase === EventPhase.MODIFIED || + (message as ConfigMapMessage).eventPhase === EventPhase.DELETED) && + (message as ConfigMapMessage).configMap !== undefined + ); +} + export function isLogsMessage(message: unknown): message is LogsMessage { return ( message !== undefined && diff --git a/packages/dashboard-backend/src/app.ts b/packages/dashboard-backend/src/app.ts index 21bc82afce..dacb408f28 100644 --- a/packages/dashboard-backend/src/app.ts +++ b/packages/dashboard-backend/src/app.ts @@ -22,6 +22,8 @@ import { registerCors } from '@/plugins/cors'; import { registerStaticServer } from '@/plugins/staticServer'; import { registerSwagger } from '@/plugins/swagger'; import { registerWebSocket } from '@/plugins/webSocket'; +import { registerAgentsRoute } from '@/routes/api/agents'; +import { registerAiAgentRegistryRoute } from '@/routes/api/aiAgentRegistry'; import { registerAiConfigRoutes } from '@/routes/api/aiConfig'; import { registerAiRegistryRoute } from '@/routes/api/aiRegistry'; import { registerAirGapSampleRoute } from '@/routes/api/airGapSample'; @@ -29,9 +31,12 @@ import { registerBackupRoutes } from '@/routes/api/backup'; import { registerClusterConfigRoute } from '@/routes/api/clusterConfig'; import { registerClusterInfoRoute } from '@/routes/api/clusterInfo'; import { registerDataResolverRoute } from '@/routes/api/dataResolver'; +import { registerDevfilesRoute } from '@/routes/api/devfiles'; +import { registerDevfileSchemaRoute } from '@/routes/api/devfileSchema'; import { registerDevWorkspaceClusterRoutes } from '@/routes/api/devworkspaceCluster'; import { registerDevworkspaceResourcesRoute } from '@/routes/api/devworkspaceResources'; import { registerDevworkspacesRoutes } from '@/routes/api/devworkspaces'; +import { registerDevWorkspaceSchemaRoute } from '@/routes/api/devworkspaceSchema'; import { registerDevWorkspaceTemplates } from '@/routes/api/devworkspaceTemplates'; import { registerDockerConfigRoutes } from '@/routes/api/dockerConfig'; import { registerEditorsRoutes } from '@/routes/api/editors'; @@ -144,6 +149,16 @@ export default async function buildApp(server: FastifyInstance): Promise = { + dracula: TERMINAL_THEMES.dracula, + dark: TERMINAL_THEMES.dark, + light: { + ...TERMINAL_THEMES.light, + black: '#f7f7f7', + }, +}; diff --git a/packages/dashboard-backend/src/devfileSchemas/2.0.0/devfile.json b/packages/dashboard-backend/src/devfileSchemas/2.0.0/devfile.json new file mode 100644 index 0000000000..3f62b95697 --- /dev/null +++ b/packages/dashboard-backend/src/devfileSchemas/2.0.0/devfile.json @@ -0,0 +1,2852 @@ +{ + "description": "Devfile describes the structure of a cloud-native workspace and development environment.", + "type": "object", + "title": "Devfile schema - Version 2.0.0", + "required": ["schemaVersion"], + "properties": { + "commands": { + "description": "Predefined, ready-to-use, workspace-related commands", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["vscodeTask"] + }, + { + "required": ["vscodeLaunch"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a workspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the workspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at workspace start by default.", + "type": "object", + "required": ["component"], + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "required": ["commandLine", "component"], + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "vscodeLaunch": { + "description": "Command providing the definition of a VsCode launch action", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + }, + "vscodeTask": { + "description": "Command providing the definition of a VsCode Task", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "List of the workspace components, such as editor and plugins, user-provided containers, or other types of components", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["plugin"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring workspace-related containers", + "type": "object", + "required": ["image"], + "properties": { + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string", + "default": "/projects" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the workspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the workspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "plugin": { + "description": "Allows importing a plugin.\n\nPlugins are mainly imported devfiles that contribute components, commands and events as a consistent single unit. They are defined in either YAML files following the devfile syntax, or as `DevWorkspaceTemplate` Kubernetes Custom Resources", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["vscodeTask"] + }, + { + "required": ["vscodeLaunch"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a workspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the workspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at workspace start by default.", + "type": "object", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "vscodeLaunch": { + "description": "Command providing the definition of a VsCode launch action", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + }, + "vscodeTask": { + "description": "Command providing the definition of a VsCode Task", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring workspace-related containers", + "type": "object", + "properties": { + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the workspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the workspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "registryUrl": { + "type": "string" + }, + "uri": { + "description": "Uri of a Devfile yaml file", + "type": "string" + } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "events": { + "description": "Bindings of commands to events. Each command is referred-to by its name.", + "type": "object", + "properties": { + "postStart": { + "description": "IDs of commands that should be executed after the workspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser.", + "type": "array", + "items": { + "type": "string" + } + }, + "postStop": { + "description": "IDs of commands that should be executed after stopping the workspace.", + "type": "array", + "items": { + "type": "string" + } + }, + "preStart": { + "description": "IDs of commands that should be executed before the workspace start. Kubernetes-wise, these commands would typically be executed in init containers of the workspace POD.", + "type": "array", + "items": { + "type": "string" + } + }, + "preStop": { + "description": "IDs of commands that should be executed before stopping the workspace.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "metadata": { + "description": "Optional metadata", + "type": "object", + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "description": { + "description": "Optional devfile description", + "type": "string" + }, + "displayName": { + "description": "Optional devfile display name", + "type": "string" + }, + "globalMemoryLimit": { + "description": "Optional devfile global memory limit", + "type": "string" + }, + "icon": { + "description": "Optional devfile icon", + "type": "string" + }, + "name": { + "description": "Optional devfile name", + "type": "string" + }, + "tags": { + "description": "Optional devfile tags", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "description": "Optional semver-compatible version", + "type": "string", + "pattern": "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + } + }, + "additionalProperties": true + }, + "parent": { + "description": "Parent workspace template", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["vscodeTask"] + }, + { + "required": ["vscodeLaunch"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a workspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the workspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at workspace start by default.", + "type": "object", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "vscodeLaunch": { + "description": "Command providing the definition of a VsCode launch action", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + }, + "vscodeTask": { + "description": "Command providing the definition of a VsCode Task", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["plugin"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring workspace-related containers", + "type": "object", + "properties": { + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the workspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the workspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "plugin": { + "description": "Allows importing a plugin.\n\nPlugins are mainly imported devfiles that contribute components, commands and events as a consistent single unit. They are defined in either YAML files following the devfile syntax, or as `DevWorkspaceTemplate` Kubernetes Custom Resources", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["vscodeTask"] + }, + { + "required": ["vscodeLaunch"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a workspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the workspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at workspace start by default.", + "type": "object", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "vscodeLaunch": { + "description": "Command providing the definition of a VsCode launch action", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + }, + "vscodeTask": { + "description": "Command providing the definition of a VsCode Task", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "inlined": { + "description": "Inlined content of the VsCode configuration", + "type": "string" + }, + "uri": { + "description": "Location as an absolute of relative URI the VsCode configuration will be fetched from", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring workspace-related containers", + "type": "object", + "properties": { + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the workspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the workspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main workspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main workspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "registryUrl": { + "type": "string" + }, + "uri": { + "description": "Uri of a Devfile yaml file", + "type": "string" + } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "projects": { + "description": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["github"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "github": { + "description": "Project's GitHub source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "sparseCheckoutDirs": { + "description": "Populate the project sparsely with selected directories.", + "type": "array", + "items": { + "type": "string" + } + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "registryUrl": { + "type": "string" + }, + "starterProjects": { + "description": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["github"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "description": { + "description": "Description of a starter project", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "github": { + "description": "Project's GitHub source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "uri": { + "description": "Uri of a Devfile yaml file", + "type": "string" + } + }, + "additionalProperties": false + }, + "projects": { + "description": "Projects worked on in the workspace, containing names and sources locations", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["github"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "github": { + "description": "Project's GitHub source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "sparseCheckoutDirs": { + "description": "Populate the project sparsely with selected directories.", + "type": "array", + "items": { + "type": "string" + } + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "schemaVersion": { + "description": "Devfile schema version", + "type": "string", + "pattern": "^2\\.0\\.0$" + }, + "starterProjects": { + "description": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["github"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "description": { + "description": "Description of a starter project", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "github": { + "description": "Project's GitHub source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} diff --git a/packages/dashboard-backend/src/devfileSchemas/2.1.0/devfile.json b/packages/dashboard-backend/src/devfileSchemas/2.1.0/devfile.json new file mode 100644 index 0000000000..bc55619d30 --- /dev/null +++ b/packages/dashboard-backend/src/devfileSchemas/2.1.0/devfile.json @@ -0,0 +1,1430 @@ +{ + "description": "Devfile describes the structure of a cloud-native devworkspace and development environment.", + "type": "object", + "title": "Devfile schema - Version 2.1.0", + "required": ["schemaVersion"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "commands": { + "description": "Predefined, ready-to-use, devworkspace-related commands", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default.", + "type": "object", + "required": ["component"], + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "required": ["commandLine", "component"], + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "List of the devworkspace components, such as editor and plugins, user-provided containers, or other types of components", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "required": ["image"], + "properties": { + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string", + "default": "/projects" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "events": { + "description": "Bindings of commands to events. Each command is referred-to by its name.", + "type": "object", + "properties": { + "postStart": { + "description": "IDs of commands that should be executed after the devworkspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser.", + "type": "array", + "items": { + "type": "string" + } + }, + "postStop": { + "description": "IDs of commands that should be executed after stopping the devworkspace.", + "type": "array", + "items": { + "type": "string" + } + }, + "preStart": { + "description": "IDs of commands that should be executed before the devworkspace start. Kubernetes-wise, these commands would typically be executed in init containers of the devworkspace POD.", + "type": "array", + "items": { + "type": "string" + } + }, + "preStop": { + "description": "IDs of commands that should be executed before stopping the devworkspace.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "metadata": { + "description": "Optional metadata", + "type": "object", + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead.", + "type": "object", + "additionalProperties": true + }, + "description": { + "description": "Optional devfile description", + "type": "string" + }, + "displayName": { + "description": "Optional devfile display name", + "type": "string" + }, + "globalMemoryLimit": { + "description": "Optional devfile global memory limit", + "type": "string" + }, + "icon": { + "description": "Optional devfile icon, can be a URI or a relative path in the project", + "type": "string" + }, + "language": { + "description": "Optional devfile language", + "type": "string" + }, + "name": { + "description": "Optional devfile name", + "type": "string" + }, + "projectType": { + "description": "Optional devfile project type", + "type": "string" + }, + "tags": { + "description": "Optional devfile tags", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "description": "Optional semver-compatible version", + "type": "string", + "pattern": "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + }, + "website": { + "description": "Optional devfile website", + "type": "string" + } + }, + "additionalProperties": true + }, + "parent": { + "description": "Parent devworkspace template", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default.", + "type": "object", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "properties": { + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + } + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "projects": { + "description": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `regsitryURL` when `Id` is used.", + "type": "string" + }, + "starterProjects": { + "description": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "description": { + "description": "Description of a starter project", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "projects": { + "description": "Projects worked on in the devworkspace, containing names and sources locations", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "schemaVersion": { + "description": "Devfile schema version", + "type": "string", + "pattern": "^2\\.1\\.0$" + }, + "starterProjects": { + "description": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "description": { + "description": "Description of a starter project", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Must have at least one remote configured", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "variables": { + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false +} diff --git a/packages/dashboard-backend/src/devfileSchemas/2.2.0/devfile.json b/packages/dashboard-backend/src/devfileSchemas/2.2.0/devfile.json new file mode 100644 index 0000000000..ed4afd332f --- /dev/null +++ b/packages/dashboard-backend/src/devfileSchemas/2.2.0/devfile.json @@ -0,0 +1,2037 @@ +{ + "description": "Devfile describes the structure of a cloud-native devworkspace and development environment.\n\nIDE-targeted variants of the schemas provide the following difference compared to the main schemas:\n- They contain additional non-standard `markdownDescription` attributes that are used by IDEs such a VSCode\nto provide markdown-rendered documentation hovers. \n- They don't contain `default` attributes, since this triggers unwanted addition of defaulted fields during completion in IDEs.", + "type": "object", + "title": "Devfile schema - Version 2.2.0 - IDE-targeted variant", + "required": ["schemaVersion"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "commands": { + "description": "Predefined, ready-to-use, devworkspace-related commands", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "type": "object", + "required": ["component"], + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string", + "markdownDescription": "Describes component that will be applied" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + } + }, + "additionalProperties": false, + "markdownDescription": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false." + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The commands that comprise this composite command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean", + "markdownDescription": "Indicates if the sub-commands should be executed concurrently" + } + }, + "additionalProperties": false, + "markdownDescription": "Composite command that allows executing several sub-commands either sequentially or concurrently" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "required": ["commandLine", "component"], + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string", + "markdownDescription": "Describes component to which given action relates" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Optional list of environment variables that have to be set before running the command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + } + }, + "additionalProperties": false, + "markdownDescription": "CLI Command executed in an existing component container" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events." + } + }, + "additionalProperties": false + }, + "markdownDescription": "Predefined, ready-to-use, devworkspace-related commands" + }, + "components": { + "description": "List of the devworkspace components, such as editor and plugins, user-provided containers, or other types of components", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "required": ["image"], + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "type": "object", + "properties": { + "deployment": { + "description": "Annotations to be added to deployment", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to deployment" + }, + "service": { + "description": "Annotations to be added to service", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to service" + } + }, + "additionalProperties": false, + "markdownDescription": "Annotations that should be added to specific resources for this container" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean", + "markdownDescription": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true." + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string", + "markdownDescription": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used." + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files." + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string", + "markdownDescription": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`." + } + }, + "additionalProperties": false, + "markdownDescription": "Volume that should be mounted to a component container" + }, + "markdownDescription": "List of volumes mounts that should be mounted is this container." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows adding and configuring devworkspace-related containers" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "type": "object", + "required": ["imageName"], + "oneOf": [ + { + "required": ["dockerfile"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the image should be built during startup.\n\nDefault value is `false`" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the dockerfile build." + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string", + "markdownDescription": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string", + "markdownDescription": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image." + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string", + "markdownDescription": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Devfile Registry source" + }, + "git": { + "description": "Dockerfile's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string", + "markdownDescription": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile." + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Git source" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a privileged builder pod is required.\n\nDefault value is `false`" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string", + "markdownDescription": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying dockerfile type build" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string", + "markdownDescription": "Name of the image for the resulting outerloop build" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of an image for outer loop builds" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production." + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin." + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production." + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean", + "markdownDescription": "Ephemeral volumes are not stored persistently across restarts. Defaults to false" + }, + "size": { + "description": "Size of the volume", + "type": "string", + "markdownDescription": "Size of the volume" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of a volume shared by several other components" + } + }, + "additionalProperties": false + }, + "markdownDescription": "List of the devworkspace components, such as editor and plugins, user-provided containers, or other types of components" + }, + "events": { + "description": "Bindings of commands to events. Each command is referred-to by its name.", + "type": "object", + "properties": { + "postStart": { + "description": "IDs of commands that should be executed after the devworkspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed after the devworkspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser." + }, + "postStop": { + "description": "IDs of commands that should be executed after stopping the devworkspace.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed after stopping the devworkspace." + }, + "preStart": { + "description": "IDs of commands that should be executed before the devworkspace start. Kubernetes-wise, these commands would typically be executed in init containers of the devworkspace POD.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed before the devworkspace start. Kubernetes-wise, these commands would typically be executed in init containers of the devworkspace POD." + }, + "preStop": { + "description": "IDs of commands that should be executed before stopping the devworkspace.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed before stopping the devworkspace." + } + }, + "additionalProperties": false, + "markdownDescription": "Bindings of commands to events. Each command is referred-to by its name." + }, + "metadata": { + "description": "Optional metadata", + "type": "object", + "properties": { + "architectures": { + "description": "Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture", + "type": "array", + "uniqueItems": true, + "items": { + "description": "Architecture describes the architecture type", + "type": "string", + "enum": ["amd64", "arm64", "ppc64le", "s390x"], + "markdownDescription": "Architecture describes the architecture type" + }, + "markdownDescription": "Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture" + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead." + }, + "description": { + "description": "Optional devfile description", + "type": "string", + "markdownDescription": "Optional devfile description" + }, + "displayName": { + "description": "Optional devfile display name", + "type": "string", + "markdownDescription": "Optional devfile display name" + }, + "globalMemoryLimit": { + "description": "Optional devfile global memory limit", + "type": "string", + "markdownDescription": "Optional devfile global memory limit" + }, + "icon": { + "description": "Optional devfile icon, can be a URI or a relative path in the project", + "type": "string", + "markdownDescription": "Optional devfile icon, can be a URI or a relative path in the project" + }, + "language": { + "description": "Optional devfile language", + "type": "string", + "markdownDescription": "Optional devfile language" + }, + "name": { + "description": "Optional devfile name", + "type": "string", + "markdownDescription": "Optional devfile name" + }, + "projectType": { + "description": "Optional devfile project type", + "type": "string", + "markdownDescription": "Optional devfile project type" + }, + "provider": { + "description": "Optional devfile provider information", + "type": "string", + "markdownDescription": "Optional devfile provider information" + }, + "supportUrl": { + "description": "Optional link to a page that provides support information", + "type": "string", + "markdownDescription": "Optional link to a page that provides support information" + }, + "tags": { + "description": "Optional devfile tags", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "Optional devfile tags" + }, + "version": { + "description": "Optional semver-compatible version", + "type": "string", + "pattern": "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$", + "markdownDescription": "Optional semver-compatible version" + }, + "website": { + "description": "Optional devfile website", + "type": "string", + "markdownDescription": "Optional devfile website" + } + }, + "additionalProperties": true, + "markdownDescription": "Optional metadata" + }, + "parent": { + "description": "Parent devworkspace template", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "type": "object", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string", + "markdownDescription": "Describes component that will be applied" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + } + }, + "additionalProperties": false, + "markdownDescription": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false." + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The commands that comprise this composite command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean", + "markdownDescription": "Indicates if the sub-commands should be executed concurrently" + } + }, + "additionalProperties": false, + "markdownDescription": "Composite command that allows executing several sub-commands either sequentially or concurrently" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string", + "markdownDescription": "Describes component to which given action relates" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Optional list of environment variables that have to be set before running the command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + } + }, + "additionalProperties": false, + "markdownDescription": "CLI Command executed in an existing component container" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events." + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "type": "object", + "properties": { + "deployment": { + "description": "Annotations to be added to deployment", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to deployment" + }, + "service": { + "description": "Annotations to be added to service", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to service" + } + }, + "additionalProperties": false, + "markdownDescription": "Annotations that should be added to specific resources for this container" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean", + "markdownDescription": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true." + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string", + "markdownDescription": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used." + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files." + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string", + "markdownDescription": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`." + } + }, + "additionalProperties": false, + "markdownDescription": "Volume that should be mounted to a component container" + }, + "markdownDescription": "List of volumes mounts that should be mounted is this container." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows adding and configuring devworkspace-related containers" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "type": "object", + "oneOf": [ + { + "required": ["dockerfile"] + }, + { + "required": ["autoBuild"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the image should be built during startup.\n\nDefault value is `false`" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the dockerfile build." + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string", + "markdownDescription": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "type": "object", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string", + "markdownDescription": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image." + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string", + "markdownDescription": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Devfile Registry source" + }, + "git": { + "description": "Dockerfile's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string", + "markdownDescription": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile." + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Git source" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a privileged builder pod is required.\n\nDefault value is `false`" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string", + "markdownDescription": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying dockerfile type build" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string", + "markdownDescription": "Name of the image for the resulting outerloop build" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of an image for outer loop builds" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production." + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin." + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production." + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean", + "markdownDescription": "Ephemeral volumes are not stored persistently across restarts. Defaults to false" + }, + "size": { + "description": "Size of the volume", + "type": "string", + "markdownDescription": "Size of the volume" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of a volume shared by several other components" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string", + "markdownDescription": "Id in a registry that contains a Devfile yaml file" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "additionalProperties": false, + "markdownDescription": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate" + }, + "projects": { + "description": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string", + "markdownDescription": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name." + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used.", + "type": "string", + "markdownDescription": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used." + }, + "starterProjects": { + "description": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "description": { + "description": "Description of a starter project", + "type": "string", + "markdownDescription": "Description of a starter project" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string", + "markdownDescription": "Sub-directory from a starter project to be used as root for starter project." + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string", + "markdownDescription": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI." + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "version": { + "description": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used.", + "type": "string", + "pattern": "^(latest)|(([1-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?)$", + "markdownDescription": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used." + } + }, + "additionalProperties": false, + "markdownDescription": "Parent devworkspace template" + }, + "projects": { + "description": "Projects worked on in the devworkspace, containing names and sources locations", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string", + "markdownDescription": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name." + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Projects worked on in the devworkspace, containing names and sources locations" + }, + "schemaVersion": { + "description": "Devfile schema version", + "type": "string", + "pattern": "^([2-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$", + "markdownDescription": "Devfile schema version" + }, + "starterProjects": { + "description": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "description": { + "description": "Description of a starter project", + "type": "string", + "markdownDescription": "Description of a starter project" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string", + "markdownDescription": "Sub-directory from a starter project to be used as root for starter project." + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" + }, + "variables": { + "description": "Map of key-value variables used for string replacement in the devfile. Values can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source\n\n - element identifiers, e.g. command id, component name, endpoint name, project name\n\n - references to identifiers, e.g. in events, a command's component, container's volume mount name\n\n - string enums, e.g. command group kind, endpoint exposure", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Map of key-value variables used for string replacement in the devfile. Values can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source\n\n - element identifiers, e.g. command id, component name, endpoint name, project name\n\n - references to identifiers, e.g. in events, a command's component, container's volume mount name\n\n - string enums, e.g. command group kind, endpoint exposure" + } + }, + "additionalProperties": false, + "markdownDescription": "Devfile describes the structure of a cloud-native devworkspace and development environment.\n\nIDE-targeted variants of the schemas provide the following difference compared to the main schemas:\n- They contain additional non-standard `markdownDescription` attributes that are used by IDEs such a VSCode\nto provide markdown-rendered documentation hovers. \n- They don't contain `default` attributes, since this triggers unwanted addition of defaulted fields during completion in IDEs." +} diff --git a/packages/dashboard-backend/src/devfileSchemas/2.2.1-alpha/devfile.json b/packages/dashboard-backend/src/devfileSchemas/2.2.1-alpha/devfile.json new file mode 100644 index 0000000000..79c983ab45 --- /dev/null +++ b/packages/dashboard-backend/src/devfileSchemas/2.2.1-alpha/devfile.json @@ -0,0 +1,2037 @@ +{ + "description": "Devfile describes the structure of a cloud-native devworkspace and development environment.\n\nIDE-targeted variants of the schemas provide the following difference compared to the main schemas:\n- They contain additional non-standard `markdownDescription` attributes that are used by IDEs such a VSCode\nto provide markdown-rendered documentation hovers.\n- They don't contain `default` attributes, since this triggers unwanted addition of defaulted fields during completion in IDEs.", + "type": "object", + "title": "Devfile schema - Version 2.2.1-alpha - IDE-targeted variant", + "required": ["schemaVersion"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "commands": { + "description": "Predefined, ready-to-use, devworkspace-related commands", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "type": "object", + "required": ["component"], + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string", + "markdownDescription": "Describes component that will be applied" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + } + }, + "additionalProperties": false, + "markdownDescription": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false." + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The commands that comprise this composite command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean", + "markdownDescription": "Indicates if the sub-commands should be executed concurrently" + } + }, + "additionalProperties": false, + "markdownDescription": "Composite command that allows executing several sub-commands either sequentially or concurrently" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "required": ["commandLine", "component"], + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string", + "markdownDescription": "Describes component to which given action relates" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Optional list of environment variables that have to be set before running the command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + } + }, + "additionalProperties": false, + "markdownDescription": "CLI Command executed in an existing component container" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events." + } + }, + "additionalProperties": false + }, + "markdownDescription": "Predefined, ready-to-use, devworkspace-related commands" + }, + "components": { + "description": "List of the devworkspace components, such as editor and plugins, user-provided containers, or other types of components", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "required": ["image"], + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "type": "object", + "properties": { + "deployment": { + "description": "Annotations to be added to deployment", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to deployment" + }, + "service": { + "description": "Annotations to be added to service", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to service" + } + }, + "additionalProperties": false, + "markdownDescription": "Annotations that should be added to specific resources for this container" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean", + "markdownDescription": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true." + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string", + "markdownDescription": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used." + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files." + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string", + "markdownDescription": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`." + } + }, + "additionalProperties": false, + "markdownDescription": "Volume that should be mounted to a component container" + }, + "markdownDescription": "List of volumes mounts that should be mounted is this container." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows adding and configuring devworkspace-related containers" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "type": "object", + "required": ["imageName"], + "oneOf": [ + { + "required": ["dockerfile"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the image should be built during startup.\n\nDefault value is `false`" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the dockerfile build." + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string", + "markdownDescription": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string", + "markdownDescription": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image." + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string", + "markdownDescription": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Devfile Registry source" + }, + "git": { + "description": "Dockerfile's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string", + "markdownDescription": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile." + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Git source" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a privileged builder pod is required.\n\nDefault value is `false`" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string", + "markdownDescription": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying dockerfile type build" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string", + "markdownDescription": "Name of the image for the resulting outerloop build" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of an image for outer loop builds" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production." + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin." + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production." + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean", + "markdownDescription": "Ephemeral volumes are not stored persistently across restarts. Defaults to false" + }, + "size": { + "description": "Size of the volume", + "type": "string", + "markdownDescription": "Size of the volume" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of a volume shared by several other components" + } + }, + "additionalProperties": false + }, + "markdownDescription": "List of the devworkspace components, such as editor and plugins, user-provided containers, or other types of components" + }, + "events": { + "description": "Bindings of commands to events. Each command is referred-to by its name.", + "type": "object", + "properties": { + "postStart": { + "description": "IDs of commands that should be executed after the devworkspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed after the devworkspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser." + }, + "postStop": { + "description": "IDs of commands that should be executed after stopping the devworkspace.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed after stopping the devworkspace." + }, + "preStart": { + "description": "IDs of commands that should be executed before the devworkspace start. Kubernetes-wise, these commands would typically be executed in init containers of the devworkspace POD.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed before the devworkspace start. Kubernetes-wise, these commands would typically be executed in init containers of the devworkspace POD." + }, + "preStop": { + "description": "IDs of commands that should be executed before stopping the devworkspace.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "IDs of commands that should be executed before stopping the devworkspace." + } + }, + "additionalProperties": false, + "markdownDescription": "Bindings of commands to events. Each command is referred-to by its name." + }, + "metadata": { + "description": "Optional metadata", + "type": "object", + "properties": { + "architectures": { + "description": "Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture", + "type": "array", + "uniqueItems": true, + "items": { + "description": "Architecture describes the architecture type", + "type": "string", + "enum": ["amd64", "arm64", "ppc64le", "s390x"], + "markdownDescription": "Architecture describes the architecture type" + }, + "markdownDescription": "Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture" + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead." + }, + "description": { + "description": "Optional devfile description", + "type": "string", + "markdownDescription": "Optional devfile description" + }, + "displayName": { + "description": "Optional devfile display name", + "type": "string", + "markdownDescription": "Optional devfile display name" + }, + "globalMemoryLimit": { + "description": "Optional devfile global memory limit", + "type": "string", + "markdownDescription": "Optional devfile global memory limit" + }, + "icon": { + "description": "Optional devfile icon, can be a URI or a relative path in the project", + "type": "string", + "markdownDescription": "Optional devfile icon, can be a URI or a relative path in the project" + }, + "language": { + "description": "Optional devfile language", + "type": "string", + "markdownDescription": "Optional devfile language" + }, + "name": { + "description": "Optional devfile name", + "type": "string", + "markdownDescription": "Optional devfile name" + }, + "projectType": { + "description": "Optional devfile project type", + "type": "string", + "markdownDescription": "Optional devfile project type" + }, + "provider": { + "description": "Optional devfile provider information", + "type": "string", + "markdownDescription": "Optional devfile provider information" + }, + "supportUrl": { + "description": "Optional link to a page that provides support information", + "type": "string", + "markdownDescription": "Optional link to a page that provides support information" + }, + "tags": { + "description": "Optional devfile tags", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "Optional devfile tags" + }, + "version": { + "description": "Optional semver-compatible version", + "type": "string", + "pattern": "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$", + "markdownDescription": "Optional semver-compatible version" + }, + "website": { + "description": "Optional devfile website", + "type": "string", + "markdownDescription": "Optional devfile website" + } + }, + "additionalProperties": true, + "markdownDescription": "Optional metadata" + }, + "parent": { + "description": "Parent devworkspace template", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "type": "object", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string", + "markdownDescription": "Describes component that will be applied" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + } + }, + "additionalProperties": false, + "markdownDescription": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false." + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The commands that comprise this composite command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean", + "markdownDescription": "Indicates if the sub-commands should be executed concurrently" + } + }, + "additionalProperties": false, + "markdownDescription": "Composite command that allows executing several sub-commands either sequentially or concurrently" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string", + "markdownDescription": "Describes component to which given action relates" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Optional list of environment variables that have to be set before running the command" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean", + "markdownDescription": "Identifies the default command for a given group kind" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"], + "markdownDescription": "Kind of group the command is part of" + } + }, + "additionalProperties": false, + "markdownDescription": "Defines the group this command is part of" + }, + "hotReloadCapable": { + "description": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Whether the command is capable to reload itself when source code changes. If set to `true` the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is `false`" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string", + "markdownDescription": "Optional label that provides a label for this command to be used in Editor UI menus for example" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one.", + "type": "string", + "markdownDescription": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/\u003cproject-name\u003e). If there are multiple projects, this will point to the directory of the first one." + } + }, + "additionalProperties": false, + "markdownDescription": "CLI Command executed in an existing component container" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events." + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "type": "object", + "properties": { + "deployment": { + "description": "Annotations to be added to deployment", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to deployment" + }, + "service": { + "description": "Annotations to be added to service", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to service" + } + }, + "additionalProperties": false, + "markdownDescription": "Annotations that should be added to specific resources for this container" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image." + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean", + "markdownDescription": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true." + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string", + "markdownDescription": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used." + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files." + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`.", + "type": "string", + "markdownDescription": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/\u003cname\u003e`." + } + }, + "additionalProperties": false, + "markdownDescription": "Volume that should be mounted to a component container" + }, + "markdownDescription": "List of volumes mounts that should be mounted is this container." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows adding and configuring devworkspace-related containers" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "type": "object", + "oneOf": [ + { + "required": ["dockerfile"] + }, + { + "required": ["autoBuild"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the image should be built during startup.\n\nDefault value is `false`" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The arguments to supply to the dockerfile build." + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string", + "markdownDescription": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "type": "object", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string", + "markdownDescription": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image." + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string", + "markdownDescription": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Devfile Registry source" + }, + "git": { + "description": "Dockerfile's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string", + "markdownDescription": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile." + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Dockerfile's Git source" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Specify if a privileged builder pod is required.\n\nDefault value is `false`" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string", + "markdownDescription": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying dockerfile type build" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string", + "markdownDescription": "Name of the image for the resulting outerloop build" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of an image for outer loop builds" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production." + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin." + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean", + "markdownDescription": "Defines if the component should be deployed during startup.\n\nDefault value is `false`" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Annotations to be added to Kubernetes Ingress or Openshift Route" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\"," + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"], + "markdownDescription": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`" + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string", + "markdownDescription": "Path of the endpoint URL" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "markdownDescription": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean", + "markdownDescription": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`." + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer", + "markdownDescription": "Port number to be used within the container component. The same port cannot be used by two different container components." + } + }, + "additionalProperties": false + } + }, + "inlined": { + "description": "Inlined manifest", + "type": "string", + "markdownDescription": "Inlined manifest" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string", + "markdownDescription": "Location in a file fetched from a uri." + } + }, + "additionalProperties": false, + "markdownDescription": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production." + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean", + "markdownDescription": "Ephemeral volumes are not stored persistently across restarts. Defaults to false" + }, + "size": { + "description": "Size of the volume", + "type": "string", + "markdownDescription": "Size of the volume" + } + }, + "additionalProperties": false, + "markdownDescription": "Allows specifying the definition of a volume shared by several other components" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string", + "markdownDescription": "Id in a registry that contains a Devfile yaml file" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "additionalProperties": false, + "markdownDescription": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate" + }, + "projects": { + "description": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string", + "markdownDescription": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name." + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used.", + "type": "string", + "markdownDescription": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used." + }, + "starterProjects": { + "description": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "description": { + "description": "Description of a starter project", + "type": "string", + "markdownDescription": "Description of a starter project" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string", + "markdownDescription": "Sub-directory from a starter project to be used as root for starter project." + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string", + "markdownDescription": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI." + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "version": { + "description": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used.", + "type": "string", + "pattern": "^(latest)|(([1-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?)$", + "markdownDescription": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used." + } + }, + "additionalProperties": false, + "markdownDescription": "Parent devworkspace template" + }, + "projects": { + "description": "Projects worked on in the devworkspace, containing names and sources locations", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string", + "markdownDescription": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name." + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "Projects worked on in the devworkspace, containing names and sources locations" + }, + "schemaVersion": { + "description": "Devfile schema version", + "type": "string", + "pattern": "^([2-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$", + "markdownDescription": "Devfile schema version" + }, + "starterProjects": { + "description": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + }, + "description": { + "description": "Description of a starter project", + "type": "string", + "markdownDescription": "Description of a starter project" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string", + "markdownDescription": "The remote name should be used as init. Required if there are more than one remote configured" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string", + "markdownDescription": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found." + } + }, + "additionalProperties": false, + "markdownDescription": "Defines from what the project should be checked out. Required if there are more than one remote configured" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects \u0026 Image Component's Git source can only have at most one remote configured." + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Git source" + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "markdownDescription": "Project name" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string", + "markdownDescription": "Sub-directory from a starter project to be used as root for starter project." + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string", + "markdownDescription": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH" + } + }, + "additionalProperties": false, + "markdownDescription": "Project's Zip source" + } + }, + "additionalProperties": false + }, + "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" + }, + "variables": { + "description": "Map of key-value variables used for string replacement in the devfile. Values can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source\n\n - element identifiers, e.g. command id, component name, endpoint name, project name\n\n - references to identifiers, e.g. in events, a command's component, container's volume mount name\n\n - string enums, e.g. command group kind, endpoint exposure", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Map of key-value variables used for string replacement in the devfile. Values can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source\n\n - element identifiers, e.g. command id, component name, endpoint name, project name\n\n - references to identifiers, e.g. in events, a command's component, container's volume mount name\n\n - string enums, e.g. command group kind, endpoint exposure" + } + }, + "additionalProperties": false, + "markdownDescription": "Devfile describes the structure of a cloud-native devworkspace and development environment.\n\nIDE-targeted variants of the schemas provide the following difference compared to the main schemas:\n- They contain additional non-standard `markdownDescription` attributes that are used by IDEs such a VSCode\nto provide markdown-rendered documentation hovers.\n- They don't contain `default` attributes, since this triggers unwanted addition of defaulted fields during completion in IDEs." +} diff --git a/packages/dashboard-backend/src/devfileSchemas/2.3.0/devfile.json b/packages/dashboard-backend/src/devfileSchemas/2.3.0/devfile.json new file mode 100644 index 0000000000..b35ffe31ca --- /dev/null +++ b/packages/dashboard-backend/src/devfileSchemas/2.3.0/devfile.json @@ -0,0 +1,1608 @@ +{ + "description": "Devfile describes the structure of a cloud-native devworkspace and development environment.", + "type": "object", + "title": "Devfile schema - Version 2.3.0", + "required": ["schemaVersion"], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "commands": { + "description": "Predefined, ready-to-use, devworkspace-related commands", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { "required": ["exec"] }, + { "required": ["apply"] }, + { "required": ["composite"] } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "type": "object", + "required": ["component"], + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { "type": "string" } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "required": ["commandLine", "component"], + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { "name": { "type": "string" }, "value": { "type": "string" } }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "required": ["kind"], + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Specify whether the command is restarted or not when the source code changes. If set to `true` the command won't be restarted. A *hotReloadCapable* `run` or `debug` command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* `build` command is expected to be executed only once and won't be executed again. This field is taken into account only for commands `build`, `run` and `debug` with `isDefault` set to `true`.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "List of the devworkspace components, such as editor and plugins, user-provided containers, or other types of components", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { "required": ["container"] }, + { "required": ["kubernetes"] }, + { "required": ["openshift"] }, + { "required": ["volume"] }, + { "required": ["image"] } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "required": ["image"], + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "type": "object", + "properties": { + "deployment": { + "description": "Annotations to be added to deployment", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "service": { + "description": "Annotations to be added to service", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { "type": "string" } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { "type": "string" } + }, + "cpuLimit": { "type": "string" }, + "cpuRequest": { "type": "string" }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { "description": "Path of the endpoint URL", "type": "string" }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { "name": { "type": "string" }, "value": { "type": "string" } }, + "additionalProperties": false + } + }, + "image": { "type": "string" }, + "memoryLimit": { "type": "string" }, + "memoryRequest": { "type": "string" }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string", + "default": "/projects" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "type": "object", + "required": ["imageName"], + "oneOf": [{ "required": ["dockerfile"] }], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup.\n\nDefault value is `false`", + "type": "boolean" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "type": "object", + "oneOf": [ + { "required": ["uri"] }, + { "required": ["devfileRegistry"] }, + { "required": ["git"] } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "type": "array", + "items": { "type": "string" } + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string" + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string" + } + }, + "additionalProperties": false + }, + "git": { + "description": "Dockerfile's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required.\n\nDefault value is `false`", + "type": "boolean" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string" + } + }, + "additionalProperties": false + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string" + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [{ "required": ["uri"] }, { "required": ["inlined"] }], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { "description": "Path of the endpoint URL", "type": "string" }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { "description": "Inlined manifest", "type": "string" }, + "uri": { "description": "Location in a file fetched from a uri.", "type": "string" } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [{ "required": ["uri"] }, { "required": ["inlined"] }], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "targetPort"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "default": "public", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { "description": "Path of the endpoint URL", "type": "string" }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "default": "http", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { "description": "Inlined manifest", "type": "string" }, + "uri": { "description": "Location in a file fetched from a uri.", "type": "string" } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { "description": "Size of the volume", "type": "string" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "dependentProjects": { + "description": "Additional projects related to the main project in the devfile, contianing names and sources locations", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [{ "required": ["git"] }, { "required": ["zip"] }], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "events": { + "description": "Bindings of commands to events. Each command is referred-to by its name.", + "type": "object", + "properties": { + "postStart": { + "description": "IDs of commands that should be executed after the devworkspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser.", + "type": "array", + "items": { "type": "string" } + }, + "postStop": { + "description": "IDs of commands that should be executed after stopping the devworkspace.", + "type": "array", + "items": { "type": "string" } + }, + "preStart": { + "description": "IDs of commands that should be executed before the devworkspace start. Kubernetes-wise, these commands would typically be executed in init containers of the devworkspace POD.", + "type": "array", + "items": { "type": "string" } + }, + "preStop": { + "description": "IDs of commands that should be executed before stopping the devworkspace.", + "type": "array", + "items": { "type": "string" } + } + }, + "additionalProperties": false + }, + "metadata": { + "description": "Optional metadata", + "type": "object", + "properties": { + "architectures": { + "description": "Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture", + "type": "array", + "uniqueItems": true, + "items": { + "description": "Architecture describes the architecture type", + "type": "string", + "enum": ["amd64", "arm64", "ppc64le", "s390x"] + } + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead.", + "type": "object", + "additionalProperties": true + }, + "description": { "description": "Optional devfile description", "type": "string" }, + "displayName": { "description": "Optional devfile display name", "type": "string" }, + "globalMemoryLimit": { + "description": "Optional devfile global memory limit", + "type": "string" + }, + "icon": { + "description": "Optional devfile icon, can be a URI or a relative path in the project", + "type": "string" + }, + "language": { "description": "Optional devfile language", "type": "string" }, + "name": { "description": "Optional devfile name", "type": "string" }, + "projectType": { "description": "Optional devfile project type", "type": "string" }, + "provider": { "description": "Optional devfile provider information", "type": "string" }, + "supportUrl": { + "description": "Optional link to a page that provides support information", + "type": "string" + }, + "tags": { + "description": "Optional devfile tags", + "type": "array", + "items": { "type": "string" } + }, + "version": { + "description": "Optional semver-compatible version", + "type": "string", + "pattern": "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + }, + "website": { "description": "Optional devfile website", "type": "string" } + }, + "additionalProperties": true + }, + "parent": { + "description": "Parent devworkspace template", + "type": "object", + "oneOf": [{ "required": ["uri"] }, { "required": ["id"] }, { "required": ["kubernetes"] }], + "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["id"], + "oneOf": [ + { "required": ["exec"] }, + { "required": ["apply"] }, + { "required": ["composite"] } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event.\n\nFor example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`.\n\nWhen no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "type": "object", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "additionalProperties": false + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "type": "object", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "type": "array", + "items": { "type": "string" } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"] + } + }, + "additionalProperties": false + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "type": "object", + "properties": { + "commandLine": { + "description": "The actual command-line string\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { "name": { "type": "string" }, "value": { "type": "string" } }, + "additionalProperties": false + } + }, + "group": { + "description": "Defines the group this command is part of", + "type": "object", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "type": "string", + "enum": ["build", "run", "test", "debug", "deploy"] + } + }, + "additionalProperties": false + }, + "hotReloadCapable": { + "description": "Specify whether the command is restarted or not when the source code changes. If set to `true` the command won't be restarted. A *hotReloadCapable* `run` or `debug` command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* `build` command is expected to be executed only once and won't be executed again. This field is taken into account only for commands `build`, `run` and `debug` with `isDefault` set to `true`.\n\nDefault value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed\n\nSpecial variables that can be used:\n\n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping.\n\n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "additionalProperties": false + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + } + }, + "additionalProperties": false + } + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [ + { "required": ["container"] }, + { "required": ["kubernetes"] }, + { "required": ["openshift"] }, + { "required": ["volume"] }, + { "required": ["image"] } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "type": "object", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "type": "object", + "properties": { + "deployment": { + "description": "Annotations to be added to deployment", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "service": { + "description": "Annotations to be added to service", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { "type": "string" } + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image.\n\nDefaults to an empty array, meaning use whatever is defined in the image.", + "type": "array", + "items": { "type": "string" } + }, + "cpuLimit": { "type": "string" }, + "cpuRequest": { "type": "string" }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { "description": "Path of the endpoint URL", "type": "string" }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "env": { + "description": "Environment variables used in this container.\n\nThe following variables are reserved and cannot be overridden via env:\n\n - `$PROJECTS_ROOT`\n\n - `$PROJECT_SOURCE`", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { "name": { "type": "string" }, "value": { "type": "string" } }, + "additionalProperties": false + } + }, + "image": { "type": "string" }, + "memoryLimit": { "type": "string" }, + "memoryRequest": { "type": "string" }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component.\n\nDefaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "type": "array", + "items": { + "description": "Volume that should be mounted to a component container", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/`.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "type": "object", + "oneOf": [{ "required": ["dockerfile"] }, { "required": ["autoBuild"] }], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup.\n\nDefault value is `false`", + "type": "boolean" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "type": "object", + "oneOf": [ + { "required": ["uri"] }, + { "required": ["devfileRegistry"] }, + { "required": ["git"] } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "type": "array", + "items": { "type": "string" } + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "type": "object", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string" + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string" + } + }, + "additionalProperties": false + }, + "git": { + "description": "Dockerfile's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string" + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required.\n\nDefault value is `false`", + "type": "boolean" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string" + } + }, + "additionalProperties": false + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string" + } + }, + "additionalProperties": false + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [{ "required": ["uri"] }, { "required": ["inlined"] }], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { "description": "Path of the endpoint URL", "type": "string" }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { "description": "Inlined manifest", "type": "string" }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "type": "object", + "oneOf": [{ "required": ["uri"] }, { "required": ["inlined"] }], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup.\n\nDefault value is `false`", + "type": "boolean" + }, + "endpoints": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "annotation": { + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes.\n\nExamples of Che-specific attributes:\n- cookiesAuthEnabled: \"true\" / \"false\",\n- type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "additionalProperties": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network.\n- `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route.\n- `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network.\n- `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address.\n\nDefault value is `public`", + "type": "string", + "enum": ["public", "internal", "none"] + }, + "name": { + "type": "string", + "maxLength": 15, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "path": { "description": "Path of the endpoint URL", "type": "string" }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint.\n- `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`.\n- `https`: Endpoint will have `https` traffic, typically on a TCP connection.\n- `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`.\n- `wss`: Endpoint will have `wss` traffic, typically on a TCP connection.\n- `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol.\n- `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol.\n\nDefault value is `http`", + "type": "string", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"] + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "inlined": { "description": "Inlined manifest", "type": "string" }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "additionalProperties": false + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "type": "object", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { "description": "Size of the volume", "type": "string" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "dependentProjects": { + "description": "Overrides of dependentProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [{ "required": ["git"] }, { "required": ["zip"] }], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "type": "object", + "required": ["name"], + "properties": { "name": { "type": "string" }, "namespace": { "type": "string" } }, + "additionalProperties": false + }, + "projects": { + "description": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [{ "required": ["git"] }, { "required": ["zip"] }], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used.", + "type": "string" + }, + "starterProjects": { + "description": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [{ "required": ["git"] }, { "required": ["zip"] }], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "description": { + "description": "Description of a starter project", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { "type": "string" } + }, + "version": { + "description": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used.", + "type": "string", + "pattern": "^(latest)|(([1-9])\\.([0-9]+)\\.([0-9]+)(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?)$" + } + }, + "additionalProperties": false + }, + "projects": { + "description": "Projects worked on in the devworkspace, containing names and sources locations", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [{ "required": ["git"] }, { "required": ["zip"] }], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "schemaVersion": { + "description": "Devfile schema version", + "type": "string", + "pattern": "^([2-9])\\.([0-9]+)\\.([0-9]+)(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + }, + "starterProjects": { + "description": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects", + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "oneOf": [{ "required": ["git"] }, { "required": ["zip"] }], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "additionalProperties": true + }, + "description": { "description": "Description of a starter project", "type": "string" }, + "git": { + "description": "Project's Git source", + "type": "object", + "required": ["remotes"], + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "type": "object", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "additionalProperties": false + }, + "remotes": { + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "name": { + "description": "Project name", + "type": "string", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "type": "object", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "variables": { + "description": "Map of key-value variables used for string replacement in the devfile. Values can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source\n\n - element identifiers, e.g. command id, component name, endpoint name, project name\n\n - references to identifiers, e.g. in events, a command's component, container's volume mount name\n\n - string enums, e.g. command group kind, endpoint exposure", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false +} diff --git a/packages/dashboard-backend/src/devworkspaceClient/__mocks__/index.ts b/packages/dashboard-backend/src/devworkspaceClient/__mocks__/index.ts index 49ded318f5..43f8274914 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/__mocks__/index.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/__mocks__/index.ts @@ -10,6 +10,8 @@ * Red Hat, Inc. - initial API and implementation */ +import { api } from '@eclipse-che/common'; + import { IAiProviderKeyApi, IAiRegistryApi, @@ -30,12 +32,16 @@ import { ISccPermissionApi, IServerConfigApi, IShhKeysApi, + IWatcherService, IWorkspacePreferencesApi, } from '@/devworkspaceClient/types'; import { IDevWorkspaceClient } from '..'; export class DevWorkspaceClient implements IDevWorkspaceClient { + get configMapWatchApi(): IWatcherService { + throw new Error('Method not implemented.'); + } get eventApi(): IEventApi { throw new Error('Method not implemented.'); } diff --git a/packages/dashboard-backend/src/devworkspaceClient/index.ts b/packages/dashboard-backend/src/devworkspaceClient/index.ts index 4854485f51..3212154b19 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/index.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/index.ts @@ -15,6 +15,7 @@ import * as k8s from '@kubernetes/client-node'; import { AiProviderKeyApiService } from '@/devworkspaceClient/services/aiProviderKeyApi'; import { AiRegistryApiService } from '@/devworkspaceClient/services/aiRegistryApi'; import { AirGapSampleApiService } from '@/devworkspaceClient/services/airGapSampleApi'; +import { ConfigMapWatchApiService } from '@/devworkspaceClient/services/configMapWatchApi'; import { DevWorkspaceApiService } from '@/devworkspaceClient/services/devWorkspaceApi'; import { DevWorkspaceClusterApiService } from '@/devworkspaceClient/services/devWorkspaceClusterApiService'; import { DevWorkspaceTemplateApiService } from '@/devworkspaceClient/services/devWorkspaceTemplateApi'; @@ -65,6 +66,10 @@ export class DevWorkspaceClient implements IDevWorkspaceClient { this.kubeConfig = kc; } + get configMapWatchApi(): ConfigMapWatchApiService { + return new ConfigMapWatchApiService(this.kubeConfig); + } + get eventApi(): IEventApi { return new EventApiService(this.kubeConfig); } diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/configMapWatchApi.ts b/packages/dashboard-backend/src/devworkspaceClient/services/configMapWatchApi.ts new file mode 100644 index 0000000000..718be26323 --- /dev/null +++ b/packages/dashboard-backend/src/devworkspaceClient/services/configMapWatchApi.ts @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { api } from '@eclipse-che/common'; +import * as k8s from '@kubernetes/client-node'; +import { V1ConfigMap, V1Status } from '@kubernetes/client-node'; + +import { prepareCustomObjectWatch } from '@/devworkspaceClient/services/helpers/prepareCustomObjectWatch'; +import { IWatcherService } from '@/devworkspaceClient/types'; +import { MessageListener } from '@/services/types/Observer'; +import { logger } from '@/utils/logger'; + +const CONFIGMAP_NAME = 'devfile-creator-storage'; + +/** + * Watches the devfile-creator-storage ConfigMap for changes. + * Uses a fieldSelector to only receive events for this specific ConfigMap. + */ +export class ConfigMapWatchApiService implements IWatcherService { + private readonly customObjectWatch: k8s.Watch; + private stopWatch?: () => void; + + constructor(kubeConfig: k8s.KubeConfig) { + this.customObjectWatch = prepareCustomObjectWatch(kubeConfig); + } + + public async watchInNamespace( + listener: MessageListener, + params: api.webSocket.SubscribeParams, + ): Promise { + const path = `/api/v1/namespaces/${params.namespace}/configmaps`; + const queryParams = { + watch: true, + resourceVersion: params.resourceVersion, + fieldSelector: `metadata.name=${CONFIGMAP_NAME}`, + }; + + this.stopWatching(); + + let abortController: AbortController | undefined; + try { + abortController = await this.customObjectWatch.watch( + path, + queryParams, + (eventPhase: string, apiObj: V1ConfigMap | V1Status) => + this.handleWatchMessage(eventPhase, apiObj, listener, params), + (error: unknown) => { + if (error instanceof Error && error.name === 'AbortError') { + return; + } + try { + logger.warn(error, `Stopped watching ${path}.`); + this.notifyWatchError(error, listener, params); + abortController?.abort(); + } catch (callbackError) { + logger.error(callbackError, `Error in ConfigMap watch done callback for ${path}.`); + } + }, + ); + } catch (error) { + logger.warn(error, `Failed to start watching ${path}.`); + this.notifyWatchError(error, listener, params); + return; + } + + this.stopWatch = () => abortController?.abort(); + } + + private handleWatchMessage( + eventPhase: string, + apiObj: V1ConfigMap | V1Status, + listener: MessageListener, + params: api.webSocket.SubscribeParams, + ): void { + switch (eventPhase) { + case api.webSocket.EventPhase.ADDED: + case api.webSocket.EventPhase.MODIFIED: + case api.webSocket.EventPhase.DELETED: { + const configMap = apiObj as V1ConfigMap; + listener({ eventPhase, configMap }); + break; + } + case api.webSocket.EventPhase.ERROR: { + const status = apiObj as V1Status; + listener({ eventPhase, status, params }); + break; + } + } + } + + private notifyWatchError( + error: unknown, + listener: MessageListener, + params: api.webSocket.SubscribeParams, + ): void { + const status: V1Status = { + kind: 'Status', + apiVersion: 'v1', + status: 'Failure', + message: error instanceof Error ? error.message : String(error), + code: (error as { statusCode?: number }).statusCode, + }; + listener({ eventPhase: api.webSocket.EventPhase.ERROR, status, params }); + } + + public stopWatching(): void { + this.stopWatch?.(); + this.stopWatch = undefined; + } +} diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/helpers/patchOptions.ts b/packages/dashboard-backend/src/devworkspaceClient/services/helpers/patchOptions.ts index 26c28c1b2d..6620a5a124 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/helpers/patchOptions.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/helpers/patchOptions.ts @@ -49,3 +49,12 @@ export const STRATEGIC_MERGE_PATCH_OPTIONS: ConfigurationOptions = wrapOptions({ export const JSON_PATCH_OPTIONS: ConfigurationOptions = wrapOptions({ middleware: [createPatchMiddleware(PatchStrategy.JsonPatch)], })!; + +/** + * Configuration options for patch operations that need to delete keys from objects. + * Uses json-merge-patch (RFC 7396): setting a key to null removes it. + * Required for deleting keys from ConfigMap data (strategic merge patch can't delete keys). + */ +export const JSON_MERGE_PATCH_OPTIONS: ConfigurationOptions = wrapOptions({ + middleware: [createPatchMiddleware(PatchStrategy.MergePatch)], +})!; diff --git a/packages/dashboard-backend/src/devworkspaceClient/types/index.ts b/packages/dashboard-backend/src/devworkspaceClient/types/index.ts index 6e8dc993df..d2619007b7 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/types/index.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/types/index.ts @@ -508,6 +508,7 @@ export interface IPersonalAccessTokenApi { export interface IDevWorkspaceClient { devWorkspaceTemplateApi: IDevWorkspaceTemplateApi; devworkspaceApi: IDevWorkspaceApi; + configMapWatchApi: IWatcherService; dockerConfigApi: IDockerConfigApi; eventApi: IEventApi; kubeConfigApi: IKubeConfigApi; diff --git a/packages/dashboard-backend/src/devworkspaceSchemas/devworkspace.json b/packages/dashboard-backend/src/devworkspaceSchemas/devworkspace.json new file mode 100644 index 0000000000..70a3635919 --- /dev/null +++ b/packages/dashboard-backend/src/devworkspaceSchemas/devworkspace.json @@ -0,0 +1,4179 @@ +{ + "description": "DevWorkspace is the Schema for the devworkspaces API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "DevWorkspaceSpec defines the desired state of DevWorkspace", + "properties": { + "contributions": { + "items": { + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event. \n For example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`. \n When no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commandType": { + "description": "Type of devworkspace command", + "enum": ["Exec", "Apply", "Composite"], + "type": "string" + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "items": { + "type": "string" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "type": "object" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "properties": { + "commandLine": { + "description": "The actual command-line string \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "hotReloadCapable": { + "description": "Specify whether the command is restarted or not when the source code changes. If set to `true` the command won't be restarted. A *hotReloadCapable* `run` or `debug` command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* `build` command is expected to be executed only once and won't be executed again. This field is taken into account only for commands `build`, `run` and `debug` with `isDefault` set to `true`. \n Default value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + } + }, + "required": ["id"], + "type": "object" + }, + "type": "array" + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "componentType": { + "description": "Type of component", + "enum": ["Container", "Kubernetes", "Openshift", "Volume", "Image"], + "type": "string" + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "properties": { + "deployment": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to deployment", + "type": "object" + }, + "service": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to service", + "type": "object" + } + }, + "type": "object" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "env": { + "description": "Environment variables used in this container. \n The following variables are reserved and cannot be overridden via env: \n - `$PROJECTS_ROOT` \n - `$PROJECT_SOURCE`", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component. \n Defaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "items": { + "description": "Volume that should be mounted to a component container", + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/`.", + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "oneOf": [ + { + "required": ["dockerfile"] + }, + { + "required": ["autoBuild"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup. \n Default value is `false`", + "type": "boolean" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "items": { + "type": "string" + }, + "type": "array" + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string" + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string" + } + }, + "type": "object" + }, + "git": { + "description": "Dockerfile's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "type": "object" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required. \n Default value is `false`", + "type": "boolean" + }, + "srcType": { + "description": "Type of Dockerfile src", + "enum": ["Uri", "DevfileRegistry", "Git"], + "type": "string" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string" + } + }, + "type": "object" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string" + }, + "imageType": { + "description": "Type of image", + "enum": ["Dockerfile", "AutoBuild"], + "type": "string" + } + }, + "type": "object" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "importReferenceType": { + "description": "type of location from where the referenced template structure should be retrieved", + "enum": ["Uri", "Id", "Kubernetes"], + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used.", + "type": "string" + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string" + }, + "version": { + "description": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used.", + "pattern": "^(latest)|(([1-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?)$", + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "routingClass": { + "type": "string" + }, + "started": { + "type": "boolean" + }, + "template": { + "description": "Structure of the devworkspace. This is also the specification of a devworkspace template.", + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commands": { + "description": "Predefined, ready-to-use, devworkspace-related commands", + "items": { + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + }, + { + "required": ["custom"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event. \n For example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`. \n When no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "required": ["kind"], + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "required": ["component"], + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commandType": { + "description": "Type of devworkspace command", + "enum": ["Exec", "Apply", "Composite", "Custom"], + "type": "string" + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "items": { + "type": "string" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "required": ["kind"], + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "type": "object" + }, + "custom": { + "description": "Custom command whose logic is implementation-dependant and should be provided by the user possibly through some dedicated plugin", + "properties": { + "commandClass": { + "description": "Class of command that the associated implementation component should use to process this command with the appropriate logic", + "type": "string" + }, + "embeddedResource": { + "description": "Additional free-form configuration for this custom command that the implementation component will know how to use", + "type": "object", + "x-kubernetes-embedded-resource": true, + "x-kubernetes-preserve-unknown-fields": true + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "required": ["kind"], + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "required": ["commandClass", "embeddedResource"], + "type": "object" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "properties": { + "commandLine": { + "description": "The actual command-line string \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name", "value"], + "type": "object" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "required": ["kind"], + "type": "object" + }, + "hotReloadCapable": { + "description": "Specify whether the command is restarted or not when the source code changes. If set to `true` the command won't be restarted. A *hotReloadCapable* `run` or `debug` command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* `build` command is expected to be executed only once and won't be executed again. This field is taken into account only for commands `build`, `run` and `debug` with `isDefault` set to `true`. \n Default value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "required": ["commandLine", "component"], + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + } + }, + "required": ["id"], + "type": "object" + }, + "type": "array" + }, + "components": { + "description": "List of the devworkspace components, such as editor and plugins, user-provided containers, or other types of components", + "items": { + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + }, + { + "required": ["plugin"] + }, + { + "required": ["custom"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "componentType": { + "description": "Type of component", + "enum": [ + "Container", + "Kubernetes", + "Openshift", + "Volume", + "Image", + "Plugin", + "Custom" + ], + "type": "string" + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "properties": { + "deployment": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to deployment", + "type": "object" + }, + "service": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to service", + "type": "object" + } + }, + "type": "object" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "default": "public", + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "default": "http", + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name", "targetPort"], + "type": "object" + }, + "type": "array" + }, + "env": { + "description": "Environment variables used in this container. \n The following variables are reserved and cannot be overridden via env: \n - `$PROJECTS_ROOT` \n - `$PROJECT_SOURCE`", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name", "value"], + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component. \n Defaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "default": "/projects", + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "items": { + "description": "Volume that should be mounted to a component container", + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/`.", + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + } + }, + "required": ["image"], + "type": "object" + }, + "custom": { + "description": "Custom component whose logic is implementation-dependant and should be provided by the user possibly through some dedicated controller", + "properties": { + "componentClass": { + "description": "Class of component that the associated implementation controller should use to process this command with the appropriate logic", + "type": "string" + }, + "embeddedResource": { + "description": "Additional free-form configuration for this custom component that the implementation controller will know how to use", + "type": "object", + "x-kubernetes-embedded-resource": true, + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": ["componentClass", "embeddedResource"], + "type": "object" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "oneOf": [ + { + "required": ["dockerfile"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup. \n Default value is `false`", + "type": "boolean" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "items": { + "type": "string" + }, + "type": "array" + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string" + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string" + } + }, + "required": ["id"], + "type": "object" + }, + "git": { + "description": "Dockerfile's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "required": ["remotes"], + "type": "object" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required. \n Default value is `false`", + "type": "boolean" + }, + "srcType": { + "description": "Type of Dockerfile src", + "enum": ["Uri", "DevfileRegistry", "Git"], + "type": "string" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string" + } + }, + "type": "object" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string" + }, + "imageType": { + "description": "Type of image", + "enum": ["Dockerfile"], + "type": "string" + } + }, + "required": ["imageName"], + "type": "object" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "default": "public", + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "default": "http", + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name", "targetPort"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "default": "public", + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "default": "http", + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name", "targetPort"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "plugin": { + "description": "Allows importing a plugin. \n Plugins are mainly imported devfiles that contribute components, commands and events as a consistent single unit. They are defined in either YAML files following the devfile syntax, or as `DevWorkspaceTemplate` Kubernetes Custom Resources", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event. \n For example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`. \n When no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commandType": { + "description": "Type of devworkspace command", + "enum": ["Exec", "Apply", "Composite"], + "type": "string" + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "items": { + "type": "string" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "type": "object" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "properties": { + "commandLine": { + "description": "The actual command-line string \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "hotReloadCapable": { + "description": "Specify whether the command is restarted or not when the source code changes. If set to `true` the command won't be restarted. A *hotReloadCapable* `run` or `debug` command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* `build` command is expected to be executed only once and won't be executed again. This field is taken into account only for commands `build`, `run` and `debug` with `isDefault` set to `true`. \n Default value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + } + }, + "required": ["id"], + "type": "object" + }, + "type": "array" + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "componentType": { + "description": "Type of component", + "enum": ["Container", "Kubernetes", "Openshift", "Volume", "Image"], + "type": "string" + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "properties": { + "deployment": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to deployment", + "type": "object" + }, + "service": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to service", + "type": "object" + } + }, + "type": "object" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "env": { + "description": "Environment variables used in this container. \n The following variables are reserved and cannot be overridden via env: \n - `$PROJECTS_ROOT` \n - `$PROJECT_SOURCE`", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component. \n Defaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "items": { + "description": "Volume that should be mounted to a component container", + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/`.", + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "oneOf": [ + { + "required": ["dockerfile"] + }, + { + "required": ["autoBuild"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup. \n Default value is `false`", + "type": "boolean" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "items": { + "type": "string" + }, + "type": "array" + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string" + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string" + } + }, + "type": "object" + }, + "git": { + "description": "Dockerfile's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "type": "object" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required. \n Default value is `false`", + "type": "boolean" + }, + "srcType": { + "description": "Type of Dockerfile src", + "enum": ["Uri", "DevfileRegistry", "Git"], + "type": "string" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string" + } + }, + "type": "object" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string" + }, + "imageType": { + "description": "Type of image", + "enum": ["Dockerfile", "AutoBuild"], + "type": "string" + } + }, + "type": "object" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "importReferenceType": { + "description": "type of location from where the referenced template structure should be retrieved", + "enum": ["Uri", "Id", "Kubernetes"], + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used.", + "type": "string" + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string" + }, + "version": { + "description": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used.", + "pattern": "^(latest)|(([1-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?)$", + "type": "string" + } + }, + "type": "object" + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "dependentProjects": { + "description": "Additional projects related to the main project in the devfile, contianing names and sources locations", + "items": { + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + }, + { + "required": ["custom"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "custom": { + "description": "Project's Custom source", + "properties": { + "embeddedResource": { + "type": "object", + "x-kubernetes-embedded-resource": true, + "x-kubernetes-preserve-unknown-fields": true + }, + "projectSourceClass": { + "type": "string" + } + }, + "required": ["embeddedResource", "projectSourceClass"], + "type": "object" + }, + "git": { + "description": "Project's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "required": ["remotes"], + "type": "object" + }, + "name": { + "description": "Project name", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "sourceType": { + "description": "Type of project source", + "enum": ["Git", "Zip", "Custom"], + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "events": { + "description": "Bindings of commands to events. Each command is referred-to by its name.", + "properties": { + "postStart": { + "description": "IDs of commands that should be executed after the devworkspace is completely started. In the case of Che-Theia, these commands should be executed after all plugins and extensions have started, including project cloning. This means that those commands are not triggered until the user opens the IDE in his browser.", + "items": { + "type": "string" + }, + "type": "array" + }, + "postStop": { + "description": "IDs of commands that should be executed after stopping the devworkspace.", + "items": { + "type": "string" + }, + "type": "array" + }, + "preStart": { + "description": "IDs of commands that should be executed before the devworkspace start. Kubernetes-wise, these commands would typically be executed in init containers of the devworkspace POD.", + "items": { + "type": "string" + }, + "type": "array" + }, + "preStop": { + "description": "IDs of commands that should be executed before stopping the devworkspace.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "parent": { + "description": "Parent devworkspace template", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event. \n For example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`. \n When no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commandType": { + "description": "Type of devworkspace command", + "enum": ["Exec", "Apply", "Composite"], + "type": "string" + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "items": { + "type": "string" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "type": "object" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "properties": { + "commandLine": { + "description": "The actual command-line string \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "hotReloadCapable": { + "description": "Specify whether the command is restarted or not when the source code changes. If set to `true` the command won't be restarted. A *hotReloadCapable* `run` or `debug` command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* `build` command is expected to be executed only once and won't be executed again. This field is taken into account only for commands `build`, `run` and `debug` with `isDefault` set to `true`. \n Default value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + } + }, + "required": ["id"], + "type": "object" + }, + "type": "array" + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + }, + { + "required": ["plugin"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "componentType": { + "description": "Type of component", + "enum": [ + "Container", + "Kubernetes", + "Openshift", + "Volume", + "Image", + "Plugin" + ], + "type": "string" + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "properties": { + "deployment": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to deployment", + "type": "object" + }, + "service": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to service", + "type": "object" + } + }, + "type": "object" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "env": { + "description": "Environment variables used in this container. \n The following variables are reserved and cannot be overridden via env: \n - `$PROJECTS_ROOT` \n - `$PROJECT_SOURCE`", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component. \n Defaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "items": { + "description": "Volume that should be mounted to a component container", + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/`.", + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "oneOf": [ + { + "required": ["dockerfile"] + }, + { + "required": ["autoBuild"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup. \n Default value is `false`", + "type": "boolean" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "items": { + "type": "string" + }, + "type": "array" + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string" + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string" + } + }, + "type": "object" + }, + "git": { + "description": "Dockerfile's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "type": "object" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required. \n Default value is `false`", + "type": "boolean" + }, + "srcType": { + "description": "Type of Dockerfile src", + "enum": ["Uri", "DevfileRegistry", "Git"], + "type": "string" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string" + } + }, + "type": "object" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string" + }, + "imageType": { + "description": "Type of image", + "enum": ["Dockerfile", "AutoBuild"], + "type": "string" + } + }, + "type": "object" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "plugin": { + "description": "Allows importing a plugin. \n Plugins are mainly imported devfiles that contribute components, commands and events as a consistent single unit. They are defined in either YAML files following the devfile syntax, or as `DevWorkspaceTemplate` Kubernetes Custom Resources", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["id"] + }, + { + "required": ["kubernetes"] + } + ], + "properties": { + "commands": { + "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["exec"] + }, + { + "required": ["apply"] + }, + { + "required": ["composite"] + } + ], + "properties": { + "apply": { + "description": "Command that consists in applying a given component definition, typically bound to a devworkspace event. \n For example, when an `apply` command is bound to a `preStart` event, and references a `container` component, it will start the container as a K8S initContainer in the devworkspace POD, unless the component has its `dedicatedPod` field set to `true`. \n When no `apply` command exist for a given component, it is assumed the component will be applied at devworkspace start by default, unless `deployByDefault` for that component is set to false.", + "properties": { + "component": { + "description": "Describes component that will be applied", + "type": "string" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + } + }, + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "commandType": { + "description": "Type of devworkspace command", + "enum": ["Exec", "Apply", "Composite"], + "type": "string" + }, + "composite": { + "description": "Composite command that allows executing several sub-commands either sequentially or concurrently", + "properties": { + "commands": { + "description": "The commands that comprise this composite command", + "items": { + "type": "string" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "parallel": { + "description": "Indicates if the sub-commands should be executed concurrently", + "type": "boolean" + } + }, + "type": "object" + }, + "exec": { + "description": "CLI Command executed in an existing component container", + "properties": { + "commandLine": { + "description": "The actual command-line string \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + }, + "component": { + "description": "Describes component to which given action relates", + "type": "string" + }, + "env": { + "description": "Optional list of environment variables that have to be set before running the command", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "group": { + "description": "Defines the group this command is part of", + "properties": { + "isDefault": { + "description": "Identifies the default command for a given group kind", + "type": "boolean" + }, + "kind": { + "description": "Kind of group the command is part of", + "enum": ["build", "run", "test", "debug", "deploy"], + "type": "string" + } + }, + "type": "object" + }, + "hotReloadCapable": { + "description": "Specify whether the command is restarted or not when the source code changes. If set to `true` the command won't be restarted. A *hotReloadCapable* `run` or `debug` command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* `build` command is expected to be executed only once and won't be executed again. This field is taken into account only for commands `build`, `run` and `debug` with `isDefault` set to `true`. \n Default value is `false`", + "type": "boolean" + }, + "label": { + "description": "Optional label that provides a label for this command to be used in Editor UI menus for example", + "type": "string" + }, + "workingDir": { + "description": "Working directory where the command should be executed \n Special variables that can be used: \n - `$PROJECTS_ROOT`: A path where projects sources are mounted as defined by container component's sourceMapping. \n - `$PROJECT_SOURCE`: A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one.", + "type": "string" + } + }, + "type": "object" + }, + "id": { + "description": "Mandatory identifier that allows referencing this command in composite commands, from a parent, or in events.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + } + }, + "required": ["id"], + "type": "object" + }, + "type": "array" + }, + "components": { + "description": "Overrides of components encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["container"] + }, + { + "required": ["kubernetes"] + }, + { + "required": ["openshift"] + }, + { + "required": ["volume"] + }, + { + "required": ["image"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "componentType": { + "description": "Type of component", + "enum": [ + "Container", + "Kubernetes", + "Openshift", + "Volume", + "Image" + ], + "type": "string" + }, + "container": { + "description": "Allows adding and configuring devworkspace-related containers", + "properties": { + "annotation": { + "description": "Annotations that should be added to specific resources for this container", + "properties": { + "deployment": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to deployment", + "type": "object" + }, + "service": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to service", + "type": "object" + } + }, + "type": "object" + }, + "args": { + "description": "The arguments to supply to the command running the dockerimage component. The arguments are supplied either to the default command provided in the image or to the overridden command. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "description": "The command to run in the dockerimage component instead of the default one provided in the image. \n Defaults to an empty array, meaning use whatever is defined in the image.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "dedicatedPod": { + "description": "Specify if a container should run in its own separated pod, instead of running as part of the main development environment pod. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "env": { + "description": "Environment variables used in this container. \n The following variables are reserved and cannot be overridden via env: \n - `$PROJECTS_ROOT` \n - `$PROJECT_SOURCE`", + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "memoryLimit": { + "type": "string" + }, + "memoryRequest": { + "type": "string" + }, + "mountSources": { + "description": "Toggles whether or not the project source code should be mounted in the component. \n Defaults to true for all component types except plugins and components that set `dedicatedPod` to true.", + "type": "boolean" + }, + "sourceMapping": { + "description": "Optional specification of the path in the container where project sources should be transferred/mounted when `mountSources` is `true`. When omitted, the default value of /projects is used.", + "type": "string" + }, + "volumeMounts": { + "description": "List of volumes mounts that should be mounted is this container.", + "items": { + "description": "Volume that should be mounted to a component container", + "properties": { + "name": { + "description": "The volume mount name is the name of an existing `Volume` component. If several containers mount the same volume name then they will reuse the same volume and will be able to access to the same files.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "The path in the component container where the volume should be mounted. If not path is mentioned, default path is the is `/`.", + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "image": { + "description": "Allows specifying the definition of an image for outer loop builds", + "oneOf": [ + { + "required": ["dockerfile"] + }, + { + "required": ["autoBuild"] + } + ], + "properties": { + "autoBuild": { + "description": "Defines if the image should be built during startup. \n Default value is `false`", + "type": "boolean" + }, + "dockerfile": { + "description": "Allows specifying dockerfile type build", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["devfileRegistry"] + }, + { + "required": ["git"] + } + ], + "properties": { + "args": { + "description": "The arguments to supply to the dockerfile build.", + "items": { + "type": "string" + }, + "type": "array" + }, + "buildContext": { + "description": "Path of source directory to establish build context. Defaults to ${PROJECT_SOURCE} in the container", + "type": "string" + }, + "devfileRegistry": { + "description": "Dockerfile's Devfile Registry source", + "properties": { + "id": { + "description": "Id in a devfile registry that contains a Dockerfile. The src in the OCI registry required for the Dockerfile build will be downloaded for building the image.", + "type": "string" + }, + "registryUrl": { + "description": "Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src. To ensure the Dockerfile gets resolved consistently in different environments, it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.", + "type": "string" + } + }, + "type": "object" + }, + "git": { + "description": "Dockerfile's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "fileLocation": { + "description": "Location of the Dockerfile in the Git repository when using git as Dockerfile src. Defaults to Dockerfile.", + "type": "string" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "type": "object" + }, + "rootRequired": { + "description": "Specify if a privileged builder pod is required. \n Default value is `false`", + "type": "boolean" + }, + "srcType": { + "description": "Type of Dockerfile src", + "enum": ["Uri", "DevfileRegistry", "Git"], + "type": "string" + }, + "uri": { + "description": "URI Reference of a Dockerfile. It can be a full URL or a relative URI from the current devfile as the base URI.", + "type": "string" + } + }, + "type": "object" + }, + "imageName": { + "description": "Name of the image for the resulting outerloop build", + "type": "string" + }, + "imageType": { + "description": "Type of image", + "enum": ["Dockerfile", "AutoBuild"], + "type": "string" + } + }, + "type": "object" + }, + "kubernetes": { + "description": "Allows importing into the devworkspace the Kubernetes resources defined in a given manifest. For example this allows reusing the Kubernetes definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "name": { + "description": "Mandatory name that allows referencing the component from other elements (such as commands) or from an external devfile that may reference this component through a parent or a plugin.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "openshift": { + "description": "Allows importing into the devworkspace the OpenShift resources defined in a given manifest. For example this allows reusing the OpenShift definitions used to deploy some runtime components in production.", + "oneOf": [ + { + "required": ["uri"] + }, + { + "required": ["inlined"] + } + ], + "properties": { + "deployByDefault": { + "description": "Defines if the component should be deployed during startup. \n Default value is `false`", + "type": "boolean" + }, + "endpoints": { + "items": { + "properties": { + "annotation": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to Kubernetes Ingress or Openshift Route", + "type": "object" + }, + "attributes": { + "description": "Map of implementation-dependant string-based free-form attributes. \n Examples of Che-specific attributes: \n - cookiesAuthEnabled: \"true\" / \"false\", \n - type: \"terminal\" / \"ide\" / \"ide-dev\",", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "exposure": { + "description": "Describes how the endpoint should be exposed on the network. \n - `public` means that the endpoint will be exposed on the public network, typically through a K8S ingress or an OpenShift route. \n - `internal` means that the endpoint will be exposed internally outside of the main devworkspace POD, typically by K8S services, to be consumed by other elements running on the same cloud internal network. \n - `none` means that the endpoint will not be exposed and will only be accessible inside the main devworkspace POD, on a local address. \n Default value is `public`", + "enum": ["public", "internal", "none"], + "type": "string" + }, + "name": { + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "path": { + "description": "Path of the endpoint URL", + "type": "string" + }, + "protocol": { + "description": "Describes the application and transport protocols of the traffic that will go through this endpoint. \n - `http`: Endpoint will have `http` traffic, typically on a TCP connection. It will be automaticaly promoted to `https` when the `secure` field is set to `true`. \n - `https`: Endpoint will have `https` traffic, typically on a TCP connection. \n - `ws`: Endpoint will have `ws` traffic, typically on a TCP connection. It will be automaticaly promoted to `wss` when the `secure` field is set to `true`. \n - `wss`: Endpoint will have `wss` traffic, typically on a TCP connection. \n - `tcp`: Endpoint will have traffic on a TCP connection, without specifying an application protocol. \n - `udp`: Endpoint will have traffic on an UDP connection, without specifying an application protocol. \n Default value is `http`", + "enum": ["http", "https", "ws", "wss", "tcp", "udp"], + "type": "string" + }, + "secure": { + "description": "Describes whether the endpoint should be secured and protected by some authentication process. This requires a protocol of `https` or `wss`.", + "type": "boolean" + }, + "targetPort": { + "description": "Port number to be used within the container component. The same port cannot be used by two different container components.", + "type": "integer" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "inlined": { + "description": "Inlined manifest", + "type": "string" + }, + "locationType": { + "description": "Type of Kubernetes-like location", + "enum": ["Uri", "Inlined"], + "type": "string" + }, + "uri": { + "description": "Location in a file fetched from a uri.", + "type": "string" + } + }, + "type": "object" + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "importReferenceType": { + "description": "type of location from where the referenced template structure should be retrieved", + "enum": ["Uri", "Id", "Kubernetes"], + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "type": "object" + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used.", + "type": "string" + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string" + }, + "version": { + "description": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used.", + "pattern": "^(latest)|(([1-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?)$", + "type": "string" + } + }, + "type": "object" + }, + "volume": { + "description": "Allows specifying the definition of a volume shared by several other components", + "properties": { + "ephemeral": { + "description": "Ephemeral volumes are not stored persistently across restarts. Defaults to false", + "type": "boolean" + }, + "size": { + "description": "Size of the volume", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "dependentProjects": { + "description": "Overrides of dependentProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "type": "object" + }, + "name": { + "description": "Project name", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "sourceType": { + "description": "Type of project source", + "enum": ["Git", "Zip"], + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "id": { + "description": "Id in a registry that contains a Devfile yaml file", + "type": "string" + }, + "importReferenceType": { + "description": "type of location from where the referenced template structure should be retrieved", + "enum": ["Uri", "Id", "Kubernetes"], + "type": "string" + }, + "kubernetes": { + "description": "Reference to a Kubernetes CRD of type DevWorkspaceTemplate", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": ["name"], + "type": "object" + }, + "projects": { + "description": "Overrides of projects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "type": "object" + }, + "name": { + "description": "Project name", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "sourceType": { + "description": "Type of project source", + "enum": ["Git", "Zip"], + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "registryUrl": { + "description": "Registry URL to pull the parent devfile from when using id in the parent reference. To ensure the parent devfile gets resolved consistently in different environments, it is recommended to always specify the `registryUrl` when `id` is used.", + "type": "string" + }, + "starterProjects": { + "description": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "items": { + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description of a starter project", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "type": "object" + }, + "name": { + "description": "Project name", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "sourceType": { + "description": "Type of project source", + "enum": ["Git", "Zip"], + "type": "string" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "uri": { + "description": "URI Reference of a parent devfile YAML file. It can be a full URL or a relative URI with the current devfile as the base URI.", + "type": "string" + }, + "variables": { + "additionalProperties": { + "type": "string" + }, + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object" + }, + "version": { + "description": "Specific stack/sample version to pull the parent devfile from, when using id in the parent reference. To specify `version`, `id` must be defined and used as the import reference source. `version` can be either a specific stack version, or `latest`. If no `version` specified, default version will be used.", + "pattern": "^(latest)|(([1-9])\\.([0-9]+)\\.([0-9]+)(\\-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?)$", + "type": "string" + } + }, + "type": "object" + }, + "projects": { + "description": "Projects worked on in the devworkspace, containing names and sources locations", + "items": { + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + }, + { + "required": ["custom"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "clonePath": { + "description": "Path relative to the root of the projects to which this project should be cloned into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it is absolute or tries to escape the project root through the usage of '..'. If not specified, defaults to the project name.", + "type": "string" + }, + "custom": { + "description": "Project's Custom source", + "properties": { + "embeddedResource": { + "type": "object", + "x-kubernetes-embedded-resource": true, + "x-kubernetes-preserve-unknown-fields": true + }, + "projectSourceClass": { + "type": "string" + } + }, + "required": ["embeddedResource", "projectSourceClass"], + "type": "object" + }, + "git": { + "description": "Project's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "required": ["remotes"], + "type": "object" + }, + "name": { + "description": "Project name", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "sourceType": { + "description": "Type of project source", + "enum": ["Git", "Zip", "Custom"], + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "starterProjects": { + "description": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects", + "items": { + "oneOf": [ + { + "required": ["git"] + }, + { + "required": ["zip"] + }, + { + "required": ["custom"] + } + ], + "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "custom": { + "description": "Project's Custom source", + "properties": { + "embeddedResource": { + "type": "object", + "x-kubernetes-embedded-resource": true, + "x-kubernetes-preserve-unknown-fields": true + }, + "projectSourceClass": { + "type": "string" + } + }, + "required": ["embeddedResource", "projectSourceClass"], + "type": "object" + }, + "description": { + "description": "Description of a starter project", + "type": "string" + }, + "git": { + "description": "Project's Git source", + "properties": { + "checkoutFrom": { + "description": "Defines from what the project should be checked out. Required if there are more than one remote configured", + "properties": { + "remote": { + "description": "The remote name should be used as init. Required if there are more than one remote configured", + "type": "string" + }, + "revision": { + "description": "The revision to checkout from. Should be branch name, tag or commit id. Default branch is used if missing or specified revision is not found.", + "type": "string" + } + }, + "type": "object" + }, + "remotes": { + "additionalProperties": { + "type": "string" + }, + "description": "The remotes map which should be initialized in the git project. Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.", + "type": "object" + } + }, + "required": ["remotes"], + "type": "object" + }, + "name": { + "description": "Project name", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "sourceType": { + "description": "Type of project source", + "enum": ["Git", "Zip", "Custom"], + "type": "string" + }, + "subDir": { + "description": "Sub-directory from a starter project to be used as root for starter project.", + "type": "string" + }, + "zip": { + "description": "Project's Zip source", + "properties": { + "location": { + "description": "Zip project's source location address. Should be file path of the archive, e.g. file://$FILE_PATH", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "type": "array" + }, + "variables": { + "additionalProperties": { + "type": "string" + }, + "description": "Map of key-value variables used for string replacement in the devfile. Values can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for \n - schemaVersion, metadata, parent source \n - element identifiers, e.g. command id, component name, endpoint name, project name \n - references to identifiers, e.g. in events, a command's component, container's volume mount name \n - string enums, e.g. command group kind, endpoint exposure", + "type": "object" + } + }, + "type": "object" + } + }, + "required": ["started"], + "type": "object" + }, + "status": { + "description": "DevWorkspaceStatus defines the observed state of DevWorkspace", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of an object's state", + "items": { + "description": "DevWorkspaceCondition contains details for the current condition of this devworkspace.", + "properties": { + "lastTransitionTime": { + "description": "Last time the condition transitioned from one status to another.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "Human-readable message indicating details about last transition.", + "type": "string" + }, + "reason": { + "description": "Unique, one-word, CamelCase reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Phase is the status of the condition. Can be True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type is the type of the condition.", + "type": "string" + } + }, + "required": ["status", "type"], + "type": "object" + }, + "type": "array" + }, + "devworkspaceId": { + "description": "Id of the DevWorkspace", + "type": "string" + }, + "mainUrl": { + "description": "Main URL for this DevWorkspace", + "type": "string" + }, + "message": { + "description": "Message is a short user-readable message giving additional information about an object's state", + "type": "string" + }, + "phase": { + "type": "string" + } + }, + "required": ["devworkspaceId"], + "type": "object" + } + }, + "type": "object" +} diff --git a/packages/dashboard-backend/src/models/restParams.ts b/packages/dashboard-backend/src/models/restParams.ts index 2e855d110e..e282fdebb5 100644 --- a/packages/dashboard-backend/src/models/restParams.ts +++ b/packages/dashboard-backend/src/models/restParams.ts @@ -25,6 +25,10 @@ export interface IDevWorkspaceSpecParams { devworkspace: V1alpha2DevWorkspace; } +export interface IDevfileVersionParams { + version: string; +} + export interface IYamlResolverParams { url: string; } diff --git a/packages/dashboard-backend/src/routes/api/__tests__/aiAgentRegistry.spec.ts b/packages/dashboard-backend/src/routes/api/__tests__/aiAgentRegistry.spec.ts new file mode 100644 index 0000000000..3c246853f8 --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/__tests__/aiAgentRegistry.spec.ts @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { FastifyInstance } from 'fastify'; + +import { baseApiPath } from '@/constants/config'; +import { setup, teardown } from '@/utils/appBuilder'; + +jest.mock('../helpers/getServiceAccountToken.ts'); +jest.mock('../helpers/getDevWorkspaceClient.ts'); + +const mockListNamespacedConfigMap = jest.fn(); + +jest.mock('@/services/kubeclient/kubeConfigProvider', () => ({ + KubeConfigProvider: jest.fn().mockImplementation(() => ({ + getKubeConfig: jest.fn(() => ({ + makeApiClient: jest.fn(() => ({ + listNamespacedConfigMap: mockListNamespacedConfigMap, + })), + })), + })), +})); + +describe('AI Agent Registry Route', () => { + let app: FastifyInstance; + const originalEnv = process.env; + let dateNowSpy: jest.SpyInstance; + let currentTime: number; + + beforeAll(async () => { + app = await setup({ env: { CHECLUSTER_CR_NAMESPACE: 'eclipse-che' } }); + }); + + afterAll(() => { + teardown(app); + process.env = originalEnv; + }); + + beforeEach(() => { + jest.clearAllMocks(); + currentTime = (currentTime || Date.now()) + 120_000; + dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(currentTime); + }); + + afterEach(() => { + dateNowSpy.mockRestore(); + }); + + test('should return empty registry when CHECLUSTER_CR_NAMESPACE is not set', async () => { + delete process.env.CHECLUSTER_CR_NAMESPACE; + + const res = await app.inject({ + url: `${baseApiPath}/ai-agent-registry`, + headers: { authorization: 'Bearer test-token' }, + }); + + expect(res.statusCode).toEqual(200); + expect(res.json()).toEqual({ agents: [], defaultAgentId: '' }); + + process.env.CHECLUSTER_CR_NAMESPACE = 'eclipse-che'; + }); + + test('should return empty registry when no ConfigMaps found', async () => { + mockListNamespacedConfigMap.mockResolvedValue({ items: [] }); + + const res = await app.inject({ + url: `${baseApiPath}/ai-agent-registry`, + headers: { authorization: 'Bearer test-token' }, + }); + + expect(res.statusCode).toEqual(200); + expect(res.json()).toEqual({ agents: [], defaultAgentId: '' }); + }); + + test('should return agents from registry ConfigMap', async () => { + const registryData = { + agents: [ + { + id: 'test-agent', + name: 'Test Agent', + publisher: 'Test', + description: 'A test agent', + image: 'quay.io/test/agent', + tag: 'v1', + memoryLimit: '2Gi', + cpuLimit: '1', + terminalPort: 8080, + env: [], + }, + ], + defaultAgentId: 'test-agent', + }; + + mockListNamespacedConfigMap.mockResolvedValue({ + items: [ + { + data: { + 'registry.json': JSON.stringify(registryData), + }, + }, + ], + }); + + const res = await app.inject({ + url: `${baseApiPath}/ai-agent-registry`, + headers: { authorization: 'Bearer test-token' }, + }); + + expect(res.statusCode).toEqual(200); + const body = res.json(); + expect(body.agents).toHaveLength(1); + expect(body.agents[0].id).toBe('test-agent'); + expect(body.defaultAgentId).toBe('test-agent'); + }); + + test('should filter out invalid agents', async () => { + const registryData = { + agents: [ + { + id: 'valid-agent', + name: 'Valid', + image: 'img', + tag: 'v1', + terminalPort: 8080, + }, + { + id: 'invalid-agent', + }, + 'not-an-object', + ], + defaultAgentId: 'valid-agent', + }; + + mockListNamespacedConfigMap.mockResolvedValue({ + items: [{ data: { 'registry.json': JSON.stringify(registryData) } }], + }); + + const res = await app.inject({ + url: `${baseApiPath}/ai-agent-registry`, + headers: { authorization: 'Bearer test-token' }, + }); + + expect(res.statusCode).toEqual(200); + const body = res.json(); + expect(body.agents).toHaveLength(1); + expect(body.agents[0].id).toBe('valid-agent'); + }); + + test('should return empty registry when ConfigMap has no data', async () => { + mockListNamespacedConfigMap.mockResolvedValue({ + items: [{ data: null }], + }); + + const res = await app.inject({ + url: `${baseApiPath}/ai-agent-registry`, + headers: { authorization: 'Bearer test-token' }, + }); + + expect(res.statusCode).toEqual(200); + expect(res.json()).toEqual({ agents: [], defaultAgentId: '' }); + }); + + test('should return empty registry when registry.json key is missing', async () => { + mockListNamespacedConfigMap.mockResolvedValue({ + items: [{ data: { 'other-key': 'value' } }], + }); + + const res = await app.inject({ + url: `${baseApiPath}/ai-agent-registry`, + headers: { authorization: 'Bearer test-token' }, + }); + + expect(res.statusCode).toEqual(200); + expect(res.json()).toEqual({ agents: [], defaultAgentId: '' }); + }); + + test('should handle API errors gracefully', async () => { + mockListNamespacedConfigMap.mockRejectedValue(new Error('API unavailable')); + + const res = await app.inject({ + url: `${baseApiPath}/ai-agent-registry`, + headers: { authorization: 'Bearer test-token' }, + }); + + expect(res.statusCode).toEqual(200); + expect(res.json()).toEqual({ agents: [], defaultAgentId: '' }); + }); +}); diff --git a/packages/dashboard-backend/src/routes/api/__tests__/websocket.spec.ts b/packages/dashboard-backend/src/routes/api/__tests__/websocket.spec.ts index 19b2141e43..1c5a859d6e 100644 --- a/packages/dashboard-backend/src/routes/api/__tests__/websocket.spec.ts +++ b/packages/dashboard-backend/src/routes/api/__tests__/websocket.spec.ts @@ -14,6 +14,7 @@ import { api } from '@eclipse-che/common'; import EventEmitter from 'events'; import { getDevWorkspaceClient } from '@/routes/api/helpers/getDevWorkspaceClient'; +import { getToken } from '@/routes/api/helpers/getToken'; import { webSocketHandler } from '@/routes/api/websocket'; import { logger } from '@/utils/logger'; @@ -23,13 +24,14 @@ jest.mock('../helpers/getToken.ts', () => ({ jest.mock('../helpers/getDevWorkspaceClient.ts'); describe('WebSocket handler', () => { - let mockWs: EventEmitter & { send: jest.Mock }; + let mockWs: EventEmitter & { send: jest.Mock; close: jest.Mock }; let mockRequest: unknown; beforeEach(() => { jest.clearAllMocks(); mockWs = Object.assign(new EventEmitter(), { send: jest.fn(), + close: jest.fn(), readyState: 1, // WebSocket.OPEN }); mockRequest = { @@ -37,6 +39,17 @@ describe('WebSocket handler', () => { }; }); + it('should close connection when authentication fails', () => { + (getToken as jest.Mock).mockImplementationOnce(() => { + throw new Error('Bearer Token Authorization is required'); + }); + + webSocketHandler(mockWs as never, mockRequest as never); + + expect(mockWs.close).toHaveBeenCalledWith(1008, 'Authentication required'); + expect(getDevWorkspaceClient).not.toHaveBeenCalled(); + }); + it('should send StatusMessage to subscriber when watcher.start() rejects', async () => { const watchError = Object.assign(new Error('Unauthorized'), { statusCode: 401 }); (getDevWorkspaceClient as jest.Mock).mockReturnValue({ @@ -56,6 +69,10 @@ describe('WebSocket handler', () => { watchInNamespace: jest.fn().mockResolvedValue(undefined), stopWatching: jest.fn(), }, + configMapWatchApi: { + watchInNamespace: jest.fn().mockResolvedValue(undefined), + stopWatching: jest.fn(), + }, }); webSocketHandler(mockWs as never, mockRequest as never); @@ -106,6 +123,10 @@ describe('WebSocket handler', () => { watchInNamespace: jest.fn().mockRejectedValue(watchError), stopWatching: jest.fn(), }, + configMapWatchApi: { + watchInNamespace: jest.fn().mockResolvedValue(undefined), + stopWatching: jest.fn(), + }, }); webSocketHandler(mockWs as never, mockRequest as never); @@ -150,6 +171,10 @@ describe('WebSocket handler', () => { watchInNamespace: jest.fn().mockResolvedValue(undefined), stopWatching: jest.fn(), }, + configMapWatchApi: { + watchInNamespace: jest.fn().mockResolvedValue(undefined), + stopWatching: jest.fn(), + }, }); webSocketHandler(mockWs as never, mockRequest as never); @@ -186,6 +211,10 @@ describe('WebSocket handler', () => { watchInNamespace: jest.fn().mockResolvedValue(undefined), stopWatching: jest.fn(), }, + configMapWatchApi: { + watchInNamespace: jest.fn().mockResolvedValue(undefined), + stopWatching: jest.fn(), + }, }); webSocketHandler(mockWs as never, mockRequest as never); @@ -220,6 +249,10 @@ describe('WebSocket handler', () => { watchInNamespace: jest.fn().mockResolvedValue(undefined), stopWatching: jest.fn(), }, + configMapWatchApi: { + watchInNamespace: jest.fn().mockResolvedValue(undefined), + stopWatching: jest.fn(), + }, }); webSocketHandler(mockWs as never, mockRequest as never); @@ -253,6 +286,10 @@ describe('WebSocket handler', () => { watchInNamespace: jest.fn().mockResolvedValue(undefined), stopWatching: jest.fn(), }, + configMapWatchApi: { + watchInNamespace: jest.fn().mockResolvedValue(undefined), + stopWatching: jest.fn(), + }, }); webSocketHandler(mockWs as never, mockRequest as never); @@ -282,6 +319,10 @@ describe('WebSocket handler', () => { watchInNamespace: jest.fn().mockResolvedValue(undefined), stopWatching: jest.fn(), }, + configMapWatchApi: { + watchInNamespace: jest.fn().mockResolvedValue(undefined), + stopWatching: jest.fn(), + }, }); webSocketHandler(mockWs as never, mockRequest as never); diff --git a/packages/dashboard-backend/src/routes/api/agents.ts b/packages/dashboard-backend/src/routes/api/agents.ts new file mode 100644 index 0000000000..b52b972dfb --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/agents.ts @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import http from 'node:http'; +import https from 'node:https'; + +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import WebSocket from 'ws'; + +import { baseApiPath } from '@/constants/config'; +import { namespacedSchema } from '@/constants/schemas'; +import { isLocalRun } from '@/localRun'; +import { restParams } from '@/models'; +import { + AgentServiceAccess, + cleanupExpiredAgentPods, + createAgentPod, + deleteAgentPod, + getAgentPodStatus, + getAgentServiceAccess, + heartbeatAgentPod, + startPeriodicAgentCleanup, +} from '@/routes/api/helpers/agentPod'; +import { getServiceAccountToken } from '@/routes/api/helpers/getServiceAccountToken'; +import { getToken } from '@/routes/api/helpers/getToken'; +import { proxyToTerminal } from '@/routes/api/helpers/terminal'; +import { getSchema } from '@/services/helpers'; +import { logger } from '@/utils/logger'; + +const tags = ['Agent Pods']; + +const DEFAULT_AGENT_ID = 'anthropic/claude-code'; +const MIN_TERMINAL_PORT = 1024; +const MAX_TERMINAL_PORT = 65535; + +function validateTerminalPort(port: number): number { + if (!Number.isInteger(port) || port < MIN_TERMINAL_PORT || port > MAX_TERMINAL_PORT) { + throw new Error( + `terminalPort must be an integer between ${MIN_TERMINAL_PORT} and ${MAX_TERMINAL_PORT}`, + ); + } + return port; +} + +const terminalAccessCache = new Map(); +const TERMINAL_URL_CACHE_TTL = 30 * 1000; +const MAX_TERMINAL_CACHE_SIZE = 100; + +function terminalCacheKey(namespace: string, agentId: string): string { + return `${namespace}/${agentId}`; +} + +function getCachedTerminalAccess( + namespace: string, + agentId: string, +): AgentServiceAccess | undefined { + const key = terminalCacheKey(namespace, agentId); + const entry = terminalAccessCache.get(key); + if (!entry) return undefined; + if (Date.now() - entry.timestamp > TERMINAL_URL_CACHE_TTL) { + terminalAccessCache.delete(key); + return undefined; + } + return entry.access; +} + +function setCachedTerminalAccess( + namespace: string, + agentId: string, + access: AgentServiceAccess, +): void { + const key = terminalCacheKey(namespace, agentId); + terminalAccessCache.set(key, { access, timestamp: Date.now() }); + + if (terminalAccessCache.size > MAX_TERMINAL_CACHE_SIZE) { + const firstKey = terminalAccessCache.keys().next().value; + if (firstKey !== undefined) { + terminalAccessCache.delete(firstKey); + } + } +} + +function clearCachedTerminalAccess(namespace: string, agentId: string): void { + terminalAccessCache.delete(terminalCacheKey(namespace, agentId)); +} + +const namespacedAgentIdSchema = { + type: 'object', + required: ['namespace', 'agentId'], + properties: { + namespace: { type: 'string', description: 'Kubernetes namespace of the user' }, + agentId: { type: 'string', description: 'Unique identifier of the agent pod instance' }, + }, +}; + +const agentCreateBodySchema = { + type: 'object', + required: ['agentId', 'image', 'tag', 'memoryLimit', 'cpuLimit', 'terminalPort'], + properties: { + agentId: { + type: 'string', + minLength: 1, + maxLength: 128, + description: 'Unique identifier for the agent pod (e.g. "anthropic/claude-code-474")', + }, + image: { + type: 'string', + minLength: 1, + maxLength: 512, + description: 'Container image for the agent (e.g. "quay.io/oorel/dashboard-agent")', + }, + tag: { + type: 'string', + minLength: 1, + maxLength: 128, + description: 'Image tag (e.g. "v20")', + }, + memoryLimit: { + type: 'string', + pattern: '^[0-9]+(Mi|Gi)$', + maxLength: 10, + default: '4Gi', + description: 'Memory limit for the agent container (e.g. "4Gi", "512Mi")', + }, + cpuLimit: { + type: 'string', + pattern: '^[0-9]+(m)?$', + maxLength: 10, + default: '1', + description: 'CPU limit for the agent container (e.g. "1", "1000m")', + }, + terminalPort: { + type: 'integer', + minimum: MIN_TERMINAL_PORT, + maximum: MAX_TERMINAL_PORT, + description: 'Port on which the ttyd terminal server listens inside the agent container', + }, + env: { + type: 'array', + description: 'Additional environment variables to inject into the agent container', + items: { + type: 'object', + required: ['name', 'value'], + properties: { + name: { type: 'string', minLength: 1 }, + value: { type: 'string' }, + }, + additionalProperties: false, + }, + }, + }, + additionalProperties: false, +}; + +export function registerAgentsRoute(instance: FastifyInstance) { + startPeriodicAgentCleanup(getServiceAccountToken); + + instance.register(async server => { + // Create agent pod + service + server.post( + `${baseApiPath}/namespace/:namespace/agent`, + getSchema({ + tags, + summary: 'Create a new AI agent pod', + description: + 'Creates a new Kubernetes pod running an AI agent container with a ttyd terminal server. ' + + 'Also creates a ClusterIP service for terminal access. ' + + 'Maximum 3 active agent pods per user; returns 409 if the limit is exceeded.', + params: namespacedSchema, + body: agentCreateBodySchema, + }), + async function (request: FastifyRequest) { + const { namespace } = request.params as restParams.INamespacedParams; + const token = getToken(request); + const body = request.body as { + agentId: string; + image: string; + tag: string; + memoryLimit: string; + cpuLimit: string; + terminalPort: number; + env: Array<{ name: string; value: string }>; + }; + + const status = await createAgentPod(token, namespace, body.agentId, { + image: body.image, + tag: body.tag, + memoryLimit: body.memoryLimit, + cpuLimit: body.cpuLimit, + terminalPort: validateTerminalPort(body.terminalPort), + env: body.env || [], + }); + + if (status.serviceUrl) { + setCachedTerminalAccess( + namespace, + body.agentId, + getAgentServiceAccess(namespace, body.agentId, validateTerminalPort(body.terminalPort)), + ); + } + + return status; + }, + ); + + // Get agent pod status + server.get( + `${baseApiPath}/namespace/:namespace/agent/:agentId`, + getSchema({ + tags, + summary: 'Get agent pod status', + description: + 'Returns the current status of an agent pod including its phase, readiness, ' + + 'and in-cluster service URL for terminal access.', + params: namespacedAgentIdSchema, + }), + async function (request: FastifyRequest, reply: FastifyReply) { + const { namespace } = request.params as restParams.INamespacedParams; + const token = getToken(request); + const { agentId } = request.params as { agentId: string }; + const query = request.query as Record; + const terminalPort = query.terminalPort + ? validateTerminalPort(parseInt(query.terminalPort, 10)) + : 8080; + + const status = await getAgentPodStatus(token, namespace, agentId, terminalPort); + if (!status) { + reply.code(404).send({ message: 'Agent pod not found' }); + return; + } + + if (status.serviceUrl) { + setCachedTerminalAccess( + namespace, + agentId, + getAgentServiceAccess(namespace, agentId, terminalPort), + ); + } + + return status; + }, + ); + + // Delete agent pod + server.delete( + `${baseApiPath}/namespace/:namespace/agent/:agentId`, + getSchema({ + tags, + summary: 'Stop and delete an agent pod', + description: + 'Deletes the agent pod, its ClusterIP service (via ownerReference), and ' + + 'the associated token secret. The pod is given a 5-second graceful termination period.', + params: namespacedAgentIdSchema, + }), + async function (request: FastifyRequest, reply: FastifyReply) { + const { namespace } = request.params as restParams.INamespacedParams; + const token = getToken(request); + const { agentId } = request.params as { agentId: string }; + + await deleteAgentPod(token, namespace, agentId); + clearCachedTerminalAccess(namespace, agentId); + reply.code(204).send(); + }, + ); + + // Agent heartbeat + server.post( + `${baseApiPath}/namespace/:namespace/agent/:agentId/heartbeat`, + getSchema({ + tags, + summary: 'Send agent pod heartbeat', + description: + 'Updates the last-heartbeat annotation on the agent pod to prevent TTL-based ' + + 'auto-cleanup. Also triggers a background cleanup of any expired agent pods ' + + 'in the namespace (pods with no heartbeat for more than 10 minutes).', + params: namespacedAgentIdSchema, + }), + async function (request: FastifyRequest) { + const { namespace } = request.params as restParams.INamespacedParams; + const token = getToken(request); + const { agentId } = request.params as { agentId: string }; + + await heartbeatAgentPod(token, namespace, agentId); + cleanupExpiredAgentPods(token, namespace).catch(e => + logger.warn(e, 'Heartbeat-triggered cleanup failed'), + ); + return { ok: true }; + }, + ); + + // Check if agent terminal is reachable + server.get( + `${baseApiPath}/namespace/:namespace/agent-terminal-url`, + getSchema({ + tags, + summary: 'Check agent terminal reachability', + description: + 'Performs a health check against the agent ttyd terminal server via its in-cluster ' + + 'service URL. Returns the service URL if the terminal responds with a non-5xx status ' + + 'within 2 seconds.', + params: namespacedSchema, + }), + async function (request: FastifyRequest) { + const { namespace } = request.params as restParams.INamespacedParams; + const token = getToken(request); + if (!token) { + throw new Error('Authentication required'); + } + const query = request.query as Record; + const agentId = query.agentId || DEFAULT_AGENT_ID; + const terminalPort = query.terminalPort + ? validateTerminalPort(parseInt(query.terminalPort, 10)) + : 8080; + + const access = getAgentServiceAccess(namespace, agentId, terminalPort); + setCachedTerminalAccess(namespace, agentId, access); + + await new Promise((resolve, reject) => { + const base = access.baseUrl.endsWith('/') ? access.baseUrl : access.baseUrl + '/'; + const url = new URL(base); + const isHttps = url.protocol === 'https:'; + const transport = isHttps ? https : http; + const options: http.RequestOptions = { + hostname: url.hostname, + port: url.port || (isHttps ? 443 : 80), + path: url.pathname + url.search, + timeout: 2000, + headers: {}, + }; + if (isHttps && access.httpsOptions) { + (options as https.RequestOptions).rejectUnauthorized = + access.httpsOptions.rejectUnauthorized; + if (access.httpsOptions.ca) { + (options as https.RequestOptions).ca = access.httpsOptions.ca; + } + } + if (isHttps && token) { + options.headers = { Authorization: `Bearer ${token}` }; + } + const req = transport.get(options, res => { + res.resume(); + if (res.statusCode && res.statusCode < 500) { + resolve(); + } else { + reject(new Error('Terminal server not ready')); + } + }); + req.on('error', reject); + req.on('timeout', () => { + req.destroy(); + reject(new Error('Terminal server timeout')); + }); + }); + + return { url: access.baseUrl }; + }, + ); + + // WebSocket proxy for ttyd terminal + server.get( + `${baseApiPath}/namespace/:namespace/agent/t/ws`, + { + websocket: true, + schema: { + tags, + summary: 'WebSocket proxy for agent terminal', + description: + 'Relays binary WebSocket frames between the browser and the in-cluster ttyd server. ' + + 'Buffers client messages until the upstream connection is open.', + }, + }, + (socket: WebSocket, request: FastifyRequest) => { + const token = getToken(request); + if (!token) { + socket.close(1008, 'Authentication required'); + return; + } + const { namespace } = request.params as restParams.INamespacedParams; + const requestUrl = new URL(request.url, 'http://localhost'); + const agentId = requestUrl.searchParams.get('agentId') || DEFAULT_AGENT_ID; + const cachedAccess = getCachedTerminalAccess(namespace, agentId); + if (!cachedAccess) { + socket.close(1011, 'Terminal service URL not found'); + return; + } + + const CLUSTER_SVC_PATTERN = /^https?:\/\/[a-z0-9-]+\.[a-z0-9-]+\.svc:\d+$/; + const LOCAL_PROXY_PATTERN = + /^https?:\/\/[^/]+\/api\/v1\/namespaces\/[a-z0-9-]+\/services\/[a-z0-9-]+:\d+\/proxy$/; + const urlIsValid = isLocalRun() + ? LOCAL_PROXY_PATTERN.test(cachedAccess.baseUrl) + : CLUSTER_SVC_PATTERN.test(cachedAccess.baseUrl); + if (!urlIsValid) { + logger.warn( + 'Rejected terminal URL not matching expected pattern: %s', + cachedAccess.baseUrl, + ); + socket.close(1011, 'Invalid terminal service URL'); + return; + } + + const allowedWsParams = new URLSearchParams(); + const incomingParams = new URL(request.url, 'http://localhost').searchParams; + for (const key of ['agentId', 'arg', 'terminalPort'] as const) { + const val = incomingParams.get(key); + if (val !== null) { + allowedWsParams.set(key, val); + } + } + const sanitizedQuery = allowedWsParams.toString(); + const wsUrl = `${cachedAccess.baseUrl.replace(/^http/, 'ws')}/ws${sanitizedQuery ? `?${sanitizedQuery}` : ''}`; + const wsOptions: WebSocket.ClientOptions = {}; + if (cachedAccess.httpsOptions) { + wsOptions.rejectUnauthorized = cachedAccess.httpsOptions.rejectUnauthorized; + if (cachedAccess.httpsOptions.ca) { + wsOptions.ca = cachedAccess.httpsOptions.ca; + } + wsOptions.headers = { Authorization: `Bearer ${token}` }; + } + const upstream = new WebSocket(wsUrl, ['tty'], wsOptions); + + const pendingMessages: Array<{ data: WebSocket.RawData; isBinary: boolean }> = []; + + upstream.on('open', () => { + logger.info('Terminal WebSocket upstream connected: %s', wsUrl); + for (const msg of pendingMessages) { + upstream.send(msg.data, { binary: msg.isBinary }); + } + pendingMessages.length = 0; + }); + + socket.on('message', (data: WebSocket.RawData, isBinary: boolean) => { + if (upstream.readyState === WebSocket.OPEN) { + upstream.send(data, { binary: isBinary }); + } else if (upstream.readyState === WebSocket.CONNECTING) { + pendingMessages.push({ data, isBinary }); + } + }); + + upstream.on('message', (data: WebSocket.RawData, isBinary: boolean) => { + if (socket.readyState === WebSocket.OPEN) { + socket.send(data, { binary: isBinary }); + } + }); + + socket.on('close', () => { + if (upstream.readyState <= WebSocket.OPEN) upstream.close(); + }); + upstream.on('close', () => { + if (socket.readyState <= WebSocket.OPEN) socket.close(); + }); + + socket.on('error', err => { + logger.error(err, 'Terminal WebSocket client error'); + upstream.close(); + }); + upstream.on('error', err => { + logger.error(err, 'Terminal WebSocket upstream error'); + socket.close(); + }); + }, + ); + + // HTTP reverse proxy for ttyd terminal assets + const terminalProxyHandler = async function (request: FastifyRequest, reply: FastifyReply) { + const token = getToken(request); + if (!token) { + reply.code(401).send({ message: 'Authentication required' }); + return; + } + const { namespace } = request.params as restParams.INamespacedParams; + const requestUrl = new URL(request.url, 'http://localhost'); + const agentId = requestUrl.searchParams.get('agentId') || DEFAULT_AGENT_ID; + const terminalPort = requestUrl.searchParams.get('terminalPort') + ? validateTerminalPort(parseInt(requestUrl.searchParams.get('terminalPort') as string, 10)) + : 8080; + + let access: AgentServiceAccess; + const cachedAccess = getCachedTerminalAccess(namespace, agentId); + if (cachedAccess) { + access = cachedAccess; + } else { + access = getAgentServiceAccess(namespace, agentId, terminalPort); + setCachedTerminalAccess(namespace, agentId, access); + } + + const prefix = `${baseApiPath}/namespace/${namespace}/agent/t`; + const subPath = request.url.slice(prefix.length) || '/'; + + logger.info( + 'Terminal proxy: %s %s -> %s%s', + request.method, + request.url, + access.baseUrl, + subPath, + ); + return proxyToTerminal(access, subPath, prefix, token, request, reply); + }; + + server.get( + `${baseApiPath}/namespace/:namespace/agent/t/*`, + getSchema({ + tags, + summary: 'Proxy agent terminal assets', + description: + 'HTTP reverse proxy for the ttyd terminal server. Serves HTML, JS, CSS, and other ' + + 'static assets through the dashboard origin so the terminal iframe can load them. ' + + 'Injects a bridge script for theme synchronization and init command execution.', + params: namespacedSchema, + }), + terminalProxyHandler, + ); + }); +} diff --git a/packages/dashboard-backend/src/routes/api/aiAgentRegistry.ts b/packages/dashboard-backend/src/routes/api/aiAgentRegistry.ts new file mode 100644 index 0000000000..49503729a0 --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/aiAgentRegistry.ts @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { api } from '@eclipse-che/common'; +import * as k8s from '@kubernetes/client-node'; +import { FastifyInstance, FastifyRequest } from 'fastify'; + +import { baseApiPath } from '@/constants/config'; +import { getServiceAccountToken } from '@/routes/api/helpers/getServiceAccountToken'; +import { getToken } from '@/routes/api/helpers/getToken'; +import { getSchema } from '@/services/helpers'; +import { KubeConfigProvider } from '@/services/kubeclient/kubeConfigProvider'; +import { logger } from '@/utils/logger'; + +const tags = ['AI Agent Registry']; + +const rateLimitConfig = { + config: { + rateLimit: { + max: 30, + timeWindow: '1 minute', + }, + }, +}; + +const AI_AGENT_REGISTRY_LABEL_SELECTOR = + 'app.kubernetes.io/component=ai-agent-registry,app.kubernetes.io/part-of=che.eclipse.org'; + +const EMPTY_REGISTRY: api.IAiAgentRegistry = { agents: [], defaultAgentId: '' }; + +const REGISTRY_CACHE_TTL_MS = 60 * 1000; +let registryCache: { data: api.IAiAgentRegistry; timestamp: number } | undefined; + +function isValidAgent(obj: unknown): obj is api.AiAgentDefinition { + if (typeof obj !== 'object' || obj === null) return false; + const agent = obj as Record; + return ( + typeof agent.id === 'string' && + typeof agent.name === 'string' && + typeof agent.image === 'string' && + typeof agent.tag === 'string' && + typeof agent.terminalPort === 'number' + ); +} + +function parseRegistryJson(raw: string): api.IAiAgentRegistry { + const parsed = JSON.parse(raw) as Record; + const agents = Array.isArray(parsed.agents) ? parsed.agents.filter(isValidAgent) : []; + const defaultAgentId = typeof parsed.defaultAgentId === 'string' ? parsed.defaultAgentId : ''; + return { agents, defaultAgentId }; +} + +export function registerAiAgentRegistryRoute(instance: FastifyInstance) { + instance.register(async server => { + server.get( + `${baseApiPath}/ai-agent-registry`, + Object.assign({}, rateLimitConfig, getSchema({ tags })), + async function (request: FastifyRequest): Promise { + getToken(request); + if (registryCache && Date.now() - registryCache.timestamp < REGISTRY_CACHE_TTL_MS) { + return registryCache.data; + } + + const cheNamespace = process.env.CHECLUSTER_CR_NAMESPACE; + if (!cheNamespace) { + logger.warn('CHECLUSTER_CR_NAMESPACE not set, returning empty AI agent registry'); + return EMPTY_REGISTRY; + } + + try { + const token = getServiceAccountToken(); + const provider = new KubeConfigProvider(); + const kc = provider.getKubeConfig(token); + const coreV1Api = kc.makeApiClient(k8s.CoreV1Api); + + const response = await coreV1Api.listNamespacedConfigMap({ + namespace: cheNamespace, + labelSelector: AI_AGENT_REGISTRY_LABEL_SELECTOR, + }); + + const configMaps = response.items; + if (configMaps.length === 0) { + registryCache = { data: EMPTY_REGISTRY, timestamp: Date.now() }; + return EMPTY_REGISTRY; + } + + const data = configMaps[0].data; + if (!data) { + registryCache = { data: EMPTY_REGISTRY, timestamp: Date.now() }; + return EMPTY_REGISTRY; + } + + const registryJson = data['registry.json']; + if (!registryJson) { + registryCache = { data: EMPTY_REGISTRY, timestamp: Date.now() }; + return EMPTY_REGISTRY; + } + + const result = parseRegistryJson(registryJson); + registryCache = { data: result, timestamp: Date.now() }; + return result; + } catch (error) { + logger.error(error, 'Failed to read AI agent registry ConfigMap'); + return EMPTY_REGISTRY; + } + }, + ); + }); +} diff --git a/packages/dashboard-backend/src/routes/api/devfileSchema.ts b/packages/dashboard-backend/src/routes/api/devfileSchema.ts new file mode 100644 index 0000000000..6613477259 --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/devfileSchema.ts @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { JSONSchema7 } from 'json-schema'; + +import { baseApiPath } from '@/constants/config'; +import { devfileVersionSchema } from '@/constants/schemas'; +import * as devfileSchemaV200 from '@/devfileSchemas/2.0.0/devfile.json'; +import * as devfileSchemaV210 from '@/devfileSchemas/2.1.0/devfile.json'; +import * as devfileSchemaV220 from '@/devfileSchemas/2.2.0/devfile.json'; +import * as devfileSchemaV230 from '@/devfileSchemas/2.3.0/devfile.json'; +import { restParams } from '@/models'; +import { getSchema } from '@/services/helpers'; + +const tags = ['Devfile']; + +export function registerDevfileSchemaRoute(server: FastifyInstance) { + server.get( + `${baseApiPath}/devfile`, + getSchema({ tags, query: devfileVersionSchema }), + async function ( + request: FastifyRequest, + reply: FastifyReply, + ): Promise { + const { version } = request.query as restParams.IDevfileVersionParams; + switch (version) { + case '2.0.0': + return devfileSchemaV200 as unknown as JSONSchema7; + case '2.1.0': + return devfileSchemaV210 as unknown as JSONSchema7; + case '2.2.0': + return devfileSchemaV220 as unknown as JSONSchema7; + case 'latest': + case '2.3.0': + return devfileSchemaV230 as unknown as JSONSchema7; + } + reply.code(404); + }, + ); +} diff --git a/packages/dashboard-backend/src/routes/api/devfiles.ts b/packages/dashboard-backend/src/routes/api/devfiles.ts new file mode 100644 index 0000000000..98cad9ca72 --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/devfiles.ts @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; + +import { baseApiPath } from '@/constants/config'; +import { namespacedSchema } from '@/constants/schemas'; +import { + JSON_MERGE_PATCH_OPTIONS, + STRATEGIC_MERGE_PATCH_OPTIONS, +} from '@/devworkspaceClient/services/helpers/patchOptions'; +import { restParams } from '@/models'; +import { + CONFIGMAP_NAME, + DevfileIdParams, + generateDevfileId, + getCoreV1Api, + getOrCreateConfigMap, + MODIFIED_ANNOTATION_PREFIX, + parseEntries, +} from '@/routes/api/helpers/devfile'; +import { getToken } from '@/routes/api/helpers/getToken'; +import { getSchema } from '@/services/helpers'; + +const tags = ['Devfiles']; + +const namespacedIdSchema = { + type: 'object', + required: ['namespace', 'id'], + properties: { + namespace: { type: 'string', description: 'Kubernetes namespace of the user' }, + id: { type: 'string', description: 'UUID of the devfile entry in the ConfigMap' }, + }, +}; + +const MAX_DEVFILE_CONTENT_SIZE = 524288; // 512 KiB + +const devfileContentBodySchema = { + type: 'object', + required: ['content'], + properties: { + content: { + type: 'string', + minLength: 1, + maxLength: MAX_DEVFILE_CONTENT_SIZE, + description: 'YAML content of the devfile (max 512 KiB)', + }, + }, + additionalProperties: false, +}; + +export function registerDevfilesRoute(instance: FastifyInstance) { + instance.register(async server => { + // List all devfiles + server.get( + `${baseApiPath}/devfiles/namespace/:namespace`, + getSchema({ + tags, + summary: 'List all devfiles', + description: + 'Returns all devfile entries stored in the devfile-creator-storage ConfigMap ' + + 'in the given namespace. Each entry includes parsed metadata (name, description, ' + + 'project count) and the raw YAML content.', + params: namespacedSchema, + }), + async function (request: FastifyRequest) { + const { namespace } = request.params as restParams.INamespacedParams; + const token = getToken(request); + const coreV1Api = getCoreV1Api(token); + + const configMap = await getOrCreateConfigMap(coreV1Api, namespace); + return { devfiles: parseEntries(configMap.data, configMap.metadata?.annotations) }; + }, + ); + + // Create new devfile + server.post( + `${baseApiPath}/devfiles/namespace/:namespace`, + getSchema({ + tags, + summary: 'Create a new devfile', + description: + 'Stores a new devfile YAML entry in the devfile-creator-storage ConfigMap. ' + + 'Assigns a random UUID as the entry key and records a last-modified annotation.', + params: namespacedSchema, + body: devfileContentBodySchema, + }), + async function (request: FastifyRequest) { + const { namespace } = request.params as restParams.INamespacedParams; + const body = request.body as { content: string }; + const token = getToken(request); + const coreV1Api = getCoreV1Api(token); + + const id = generateDevfileId(body.content); + const configMap = await getOrCreateConfigMap(coreV1Api, namespace); + const data = { ...configMap.data, [id]: body.content }; + const now = new Date().toISOString(); + + await coreV1Api.patchNamespacedConfigMap( + { + name: CONFIGMAP_NAME, + namespace, + body: { + metadata: { + annotations: { [`${MODIFIED_ANNOTATION_PREFIX}${id}`]: now }, + }, + data, + }, + }, + STRATEGIC_MERGE_PATCH_OPTIONS, + ); + + return { id }; + }, + ); + + // Update devfile + server.put( + `${baseApiPath}/devfiles/namespace/:namespace/:id`, + getSchema({ + tags, + summary: 'Update an existing devfile', + description: + 'Replaces the YAML content of an existing devfile entry identified by its UUID. ' + + 'Returns 404 if the entry does not exist.', + params: namespacedIdSchema, + body: devfileContentBodySchema, + }), + async function (request: FastifyRequest) { + const { namespace, id } = request.params as DevfileIdParams; + const body = request.body as { content: string }; + const token = getToken(request); + const coreV1Api = getCoreV1Api(token); + + const configMap = await getOrCreateConfigMap(coreV1Api, namespace); + if (!configMap.data?.[id]) { + throw new Error(`Devfile with id "${id}" not found`); + } + + const data: Record = { ...configMap.data, [id]: body.content }; + const annotations: Record = {}; + const now = new Date().toISOString(); + annotations[`${MODIFIED_ANNOTATION_PREFIX}${id}`] = now; + + await coreV1Api.patchNamespacedConfigMap( + { + name: CONFIGMAP_NAME, + namespace, + body: { metadata: { annotations }, data }, + }, + JSON_MERGE_PATCH_OPTIONS, + ); + + return { id }; + }, + ); + + // Delete devfile + server.delete( + `${baseApiPath}/devfiles/namespace/:namespace/:id`, + getSchema({ + tags, + summary: 'Delete a devfile', + description: + 'Removes a devfile entry from the devfile-creator-storage ConfigMap using ' + + 'a JSON merge patch (sets the key to null). Returns 204 on success.', + params: namespacedIdSchema, + }), + async function (request: FastifyRequest, reply: FastifyReply) { + const { namespace, id } = request.params as DevfileIdParams; + const token = getToken(request); + const coreV1Api = getCoreV1Api(token); + + const configMap = await getOrCreateConfigMap(coreV1Api, namespace); + if (configMap.data?.[id]) { + await coreV1Api.patchNamespacedConfigMap( + { + name: CONFIGMAP_NAME, + namespace, + body: { data: { [id]: null } }, + }, + JSON_MERGE_PATCH_OPTIONS, + ); + } + + reply.code(204).send(); + }, + ); + + // Raw devfile content + server.get( + `${baseApiPath}/devfiles/namespace/:namespace/:id/raw`, + getSchema({ + tags, + summary: 'Get raw devfile YAML', + description: + 'Returns the raw YAML content of a devfile entry as text/yaml. ' + + 'Used by the factory flow to create workspaces from stored devfiles.', + params: namespacedIdSchema, + }), + async function (request: FastifyRequest, reply: FastifyReply) { + const { namespace, id } = request.params as DevfileIdParams; + const token = getToken(request); + const coreV1Api = getCoreV1Api(token); + + const configMap = await getOrCreateConfigMap(coreV1Api, namespace); + const content = configMap.data?.[id]; + if (!content) { + return reply.status(404).send('Devfile not found'); + } + + reply.header('Content-Type', 'text/yaml; charset=utf-8'); + return reply.send(content); + }, + ); + }); +} diff --git a/packages/dashboard-backend/src/routes/api/devworkspaceSchema.ts b/packages/dashboard-backend/src/routes/api/devworkspaceSchema.ts new file mode 100644 index 0000000000..ff47a18616 --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/devworkspaceSchema.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { FastifyInstance } from 'fastify'; +import { JSONSchema7 } from 'json-schema'; + +import { baseApiPath } from '@/constants/config'; +import * as devworkspaceSchema from '@/devworkspaceSchemas/devworkspace.json'; +import { getSchema } from '@/services/helpers'; + +const tags = ['DevWorkspace']; + +export function registerDevWorkspaceSchemaRoute(server: FastifyInstance) { + server.get( + `${baseApiPath}/devworkspace-schema`, + getSchema({ tags }), + async function (): Promise { + return devworkspaceSchema as unknown as JSONSchema7; + }, + ); +} diff --git a/packages/dashboard-backend/src/routes/api/devworkspaces.ts b/packages/dashboard-backend/src/routes/api/devworkspaces.ts index 3cbd6b4b67..97a3a2ade4 100644 --- a/packages/dashboard-backend/src/routes/api/devworkspaces.ts +++ b/packages/dashboard-backend/src/routes/api/devworkspaces.ts @@ -28,6 +28,12 @@ import { PostStartInjector } from '@/services/PostStartInjector'; const tags = ['Devworkspace']; +const RESOURCE_TYPE_LABEL = 'che.eclipse.org/type'; + +function isAgentWorkspace(labels: Record | undefined): boolean { + return labels?.[RESOURCE_TYPE_LABEL] === 'agent'; +} + export function registerDevworkspacesRoutes(instance: FastifyInstance) { instance.register(async server => { server.get( @@ -66,12 +72,13 @@ export function registerDevworkspacesRoutes(instance: FastifyInstance) { // Trigger server-side injection when workspace is created in started mode // (e.g. factory URL flow). Without this the watch would only be set up on // PATCH /spec/started=true (restart), missing first-time creation. + // Skip for agent workspaces — they don't need kubeconfig/podman injection. const workspaceName = devWorkspace.metadata?.name; - if (devworkspace.spec?.started === true && workspaceName) { - // Pass a fresh devworkspaceApi instance (DevWorkspaceClient.devworkspaceApi is a - // getter — each access returns a new DevWorkspaceApiService). PostStartInjector - // calls stopWatching() on this instance when done; passing the same instance that - // the route already used for .create() would cancel the route's own watch. + if ( + devworkspace.spec?.started === true && + workspaceName && + !isAgentWorkspace(devworkspace.metadata?.labels) + ) { PostStartInjector.watchAndInject( namespace, workspaceName, @@ -113,8 +120,7 @@ export function registerDevworkspacesRoutes(instance: FastifyInstance) { ); const isStarting = patch.some(p => p.path === '/spec/started' && p.value === true); - if (isStarting) { - // See comment above — pass a fresh devworkspaceApi instance. + if (isStarting && !isAgentWorkspace(devWorkspace.metadata?.labels)) { PostStartInjector.watchAndInject( namespace, workspaceName, diff --git a/packages/dashboard-backend/src/routes/api/helpers/__tests__/agentPod.spec.ts b/packages/dashboard-backend/src/routes/api/helpers/__tests__/agentPod.spec.ts new file mode 100644 index 0000000000..ed359c2cd4 --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/helpers/__tests__/agentPod.spec.ts @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import * as k8s from '@kubernetes/client-node'; + +import { + cleanupExpiredAgentPods, + createAgentPod, + deleteAgentPod, + getAgentPodStatus, + getAgentServiceAccess, + getAgentServiceUrl, + heartbeatAgentPod, + MAX_AGENT_PODS_PER_USER, + startPeriodicAgentCleanup, + stopPeriodicAgentCleanup, +} from '@/routes/api/helpers/agentPod'; + +jest.mock('@/localRun', () => ({ + isLocalRun: jest.fn(() => false), +})); + +const mockListNamespacedPod = jest.fn(); +const mockCreateNamespacedPod = jest.fn(); +const mockDeleteNamespacedPod = jest.fn(); +const mockPatchNamespacedPod = jest.fn(); +const mockCreateNamespacedService = jest.fn(); +const mockListNamespacedSecret = jest.fn(); +const mockListNamespacedConfigMap = jest.fn(); +const mockCreateNamespacedSecret = jest.fn(); +const mockReplaceNamespacedSecret = jest.fn(); +const mockDeleteNamespacedSecret = jest.fn(); +const mockPatchNamespacedSecret = jest.fn(); +const mockListPodForAllNamespaces = jest.fn(); + +jest.mock('@/routes/api/helpers/getDevWorkspaceClient', () => ({ + getKubeConfig: jest.fn(() => ({ + makeApiClient: jest.fn(() => ({ + listNamespacedPod: mockListNamespacedPod, + createNamespacedPod: mockCreateNamespacedPod, + deleteNamespacedPod: mockDeleteNamespacedPod, + patchNamespacedPod: mockPatchNamespacedPod, + createNamespacedService: mockCreateNamespacedService, + listNamespacedSecret: mockListNamespacedSecret, + listNamespacedConfigMap: mockListNamespacedConfigMap, + createNamespacedSecret: mockCreateNamespacedSecret, + replaceNamespacedSecret: mockReplaceNamespacedSecret, + deleteNamespacedSecret: mockDeleteNamespacedSecret, + patchNamespacedSecret: mockPatchNamespacedSecret, + listPodForAllNamespaces: mockListPodForAllNamespaces, + })), + getCurrentCluster: jest.fn(() => ({ server: 'https://api.cluster.example.com' })), + })), +})); + +const NAMESPACE = 'test-namespace'; +const TOKEN = 'test-token'; +const AGENT_ID = 'anthropic/claude-code'; + +function makePod( + overrides: Partial<{ + name: string; + phase: string; + ready: boolean; + agentId: string; + heartbeat: string; + deletionTimestamp: string; + uid: string; + }> = {}, +): k8s.V1Pod { + return { + metadata: { + name: overrides.name || 'agent-anthropic-claude-code', + namespace: NAMESPACE, + uid: overrides.uid || 'pod-uid-123', + labels: { + 'app.kubernetes.io/component': 'ai-agent', + 'app.kubernetes.io/part-of': 'che.eclipse.org', + 'che.eclipse.org/agent-id': 'anthropic-claude-code', + }, + annotations: { + 'che.eclipse.org/ai-agent-id': overrides.agentId || AGENT_ID, + 'che.eclipse.org/last-heartbeat': overrides.heartbeat || new Date().toISOString(), + }, + deletionTimestamp: overrides.deletionTimestamp + ? new Date(overrides.deletionTimestamp) + : undefined, + }, + status: { + phase: overrides.phase || 'Running', + containerStatuses: [ + { + name: 'agent', + ready: overrides.ready !== undefined ? overrides.ready : true, + restartCount: 0, + image: 'test-image', + imageID: 'test-image-id', + started: true, + state: {}, + }, + ], + }, + }; +} + +describe('agentPod helpers', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + stopPeriodicAgentCleanup(); + }); + + describe('getAgentServiceUrl', () => { + it('should construct a cluster-internal service URL', () => { + const url = getAgentServiceUrl('my-ns', 'my-org/my-agent', 9090); + expect(url).toBe('http://agent-my-org-my-agent-svc.my-ns.svc:9090'); + }); + + it('should default to port 8080', () => { + const url = getAgentServiceUrl('ns', 'agent-id'); + expect(url).toBe('http://agent-agent-id-svc.ns.svc:8080'); + }); + }); + + describe('getAgentServiceAccess', () => { + it('should return in-cluster URL when not local run', () => { + const access = getAgentServiceAccess('ns', 'test/agent', 8080); + expect(access.baseUrl).toBe('http://agent-test-agent-svc.ns.svc:8080'); + expect(access.httpsOptions).toBeUndefined(); + }); + }); + + describe('getAgentPodStatus', () => { + it('should return undefined when no pod exists', async () => { + mockListNamespacedPod.mockResolvedValue({ items: [] }); + + const result = await getAgentPodStatus(TOKEN, NAMESPACE, AGENT_ID); + expect(result).toBeUndefined(); + }); + + it('should return pod status when pod exists and is running', async () => { + mockListNamespacedPod.mockResolvedValue({ + items: [makePod({ phase: 'Running', ready: true })], + }); + + const result = await getAgentPodStatus(TOKEN, NAMESPACE, AGENT_ID); + expect(result).toBeDefined(); + expect(result!.phase).toBe('Running'); + expect(result!.ready).toBe(true); + expect(result!.agentId).toBe(AGENT_ID); + expect(result!.serviceUrl).toContain('svc:8080'); + }); + + it('should return not-ready status for pending pod', async () => { + mockListNamespacedPod.mockResolvedValue({ + items: [makePod({ phase: 'Pending', ready: false })], + }); + + const result = await getAgentPodStatus(TOKEN, NAMESPACE, AGENT_ID); + expect(result).toBeDefined(); + expect(result!.phase).toBe('Pending'); + expect(result!.ready).toBe(false); + expect(result!.serviceUrl).toBeUndefined(); + }); + + it('should skip pods with deletionTimestamp', async () => { + mockListNamespacedPod.mockResolvedValue({ + items: [makePod({ deletionTimestamp: '2025-01-01T00:00:00Z' })], + }); + + const result = await getAgentPodStatus(TOKEN, NAMESPACE, AGENT_ID); + expect(result).toBeUndefined(); + }); + }); + + describe('createAgentPod', () => { + const config = { + image: 'quay.io/test/agent', + tag: 'v1', + memoryLimit: '2Gi', + cpuLimit: '1', + terminalPort: 8080, + env: [{ name: 'MY_VAR', value: 'my-value' }], + }; + + it('should create a new pod and service', async () => { + // No existing pod + mockListNamespacedPod.mockResolvedValue({ items: [] }); + // No DW-mountable secrets/configmaps + mockListNamespacedSecret.mockResolvedValue({ items: [] }); + mockListNamespacedConfigMap.mockResolvedValue({ items: [] }); + // Secret creation succeeds + mockCreateNamespacedSecret.mockResolvedValue({}); + // Pod creation succeeds + mockCreateNamespacedPod.mockResolvedValue( + makePod({ phase: 'Pending', ready: false, uid: 'new-pod-uid' }), + ); + // Secret owner ref patch + mockPatchNamespacedSecret.mockResolvedValue({}); + // Service creation + mockCreateNamespacedService.mockResolvedValue({}); + + const result = await createAgentPod(TOKEN, NAMESPACE, AGENT_ID, config); + + expect(result.agentId).toBe(AGENT_ID); + expect(mockCreateNamespacedPod).toHaveBeenCalled(); + expect(mockCreateNamespacedService).toHaveBeenCalled(); + expect(mockCreateNamespacedSecret).toHaveBeenCalled(); + }); + + it('should return existing running pod without creating new one', async () => { + mockListNamespacedPod + .mockResolvedValueOnce({ items: [] }) // cleanup + .mockResolvedValueOnce({ + items: [makePod({ phase: 'Running', ready: true })], + }); + + const result = await createAgentPod(TOKEN, NAMESPACE, AGENT_ID, config); + + expect(result.phase).toBe('Running'); + expect(mockCreateNamespacedPod).not.toHaveBeenCalled(); + }); + + it('should delete failed pod and create new one', async () => { + mockListNamespacedPod + .mockResolvedValueOnce({ items: [] }) // cleanup + .mockResolvedValueOnce({ + items: [makePod({ phase: 'Failed', ready: false })], + }) // find existing (failed) + .mockResolvedValueOnce({ items: [] }); // count active pods + + mockDeleteNamespacedPod.mockResolvedValue({}); + mockListNamespacedSecret.mockResolvedValue({ items: [] }); + mockListNamespacedConfigMap.mockResolvedValue({ items: [] }); + mockCreateNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedPod.mockResolvedValue( + makePod({ phase: 'Pending', ready: false, uid: 'new-uid' }), + ); + mockPatchNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedService.mockResolvedValue({}); + + const result = await createAgentPod(TOKEN, NAMESPACE, AGENT_ID, config); + expect(mockDeleteNamespacedPod).toHaveBeenCalled(); + expect(mockCreateNamespacedPod).toHaveBeenCalled(); + expect(result.agentId).toBe(AGENT_ID); + }); + + it('should throw 409 when max pod limit reached', async () => { + const activePods = Array.from({ length: MAX_AGENT_PODS_PER_USER }, (_, i) => + makePod({ name: `agent-${i}`, agentId: `agent-${i}`, phase: 'Running' }), + ); + + mockListNamespacedPod + .mockResolvedValueOnce({ items: [] }) // cleanup + .mockResolvedValueOnce({ items: [] }) // find existing (none) + .mockResolvedValueOnce({ items: activePods }); // count active + + await expect(createAgentPod(TOKEN, NAMESPACE, 'new-agent', config)).rejects.toThrow( + /Maximum number of agent pods/, + ); + }); + + it('should filter blocked env var names', async () => { + const configWithBlockedEnv = { + ...config, + env: [ + { name: 'MY_VAR', value: 'allowed' }, + { name: 'PATH', value: '/malicious' }, + { name: 'HOME', value: '/evil' }, + { name: 'LD_PRELOAD', value: '/lib/hook.so' }, + { name: 'KUBERNETES_API_URL', value: 'https://evil' }, + { name: 'SAFE_VAR', value: 'ok' }, + ], + }; + + mockListNamespacedPod.mockResolvedValue({ items: [] }); + mockListNamespacedSecret.mockResolvedValue({ items: [] }); + mockListNamespacedConfigMap.mockResolvedValue({ items: [] }); + mockCreateNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedPod.mockResolvedValue( + makePod({ phase: 'Pending', ready: false, uid: 'uid-1' }), + ); + mockPatchNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedService.mockResolvedValue({}); + + await createAgentPod(TOKEN, NAMESPACE, 'test-agent', configWithBlockedEnv); + + const podBody = mockCreateNamespacedPod.mock.calls[0][0].body; + const envNames = podBody.spec.containers[0].env.map((e: { name: string }) => e.name); + expect(envNames).toContain('MY_VAR'); + expect(envNames).toContain('SAFE_VAR'); + expect(envNames).not.toContain('PATH'); + expect(envNames).not.toContain('HOME'); + expect(envNames).not.toContain('LD_PRELOAD'); + // KUBERNETES_API_URL is blocked from user input but added by the system + expect(envNames.filter((n: string) => n === 'KUBERNETES_API_URL').length).toBe(1); + }); + + it('should include seccompProfile in securityContext', async () => { + mockListNamespacedPod.mockResolvedValue({ items: [] }); + mockListNamespacedSecret.mockResolvedValue({ items: [] }); + mockListNamespacedConfigMap.mockResolvedValue({ items: [] }); + mockCreateNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedPod.mockResolvedValue( + makePod({ phase: 'Pending', ready: false, uid: 'uid-2' }), + ); + mockPatchNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedService.mockResolvedValue({}); + + await createAgentPod(TOKEN, NAMESPACE, 'sec-agent', config); + + const podBody = mockCreateNamespacedPod.mock.calls[0][0].body; + const secCtx = podBody.spec.containers[0].securityContext; + expect(secCtx.seccompProfile).toEqual({ type: 'RuntimeDefault' }); + expect(secCtx.allowPrivilegeEscalation).toBe(false); + expect(secCtx.runAsNonRoot).toBe(true); + expect(secCtx.capabilities).toEqual({ drop: ['ALL'] }); + }); + + it('should discover and mount DW secrets and configmaps', async () => { + mockListNamespacedPod.mockResolvedValue({ items: [] }); + mockListNamespacedSecret.mockResolvedValue({ + items: [ + { + metadata: { + name: 'my-secret', + annotations: { + 'controller.devfile.io/mount-as': 'file', + 'controller.devfile.io/mount-path': '/secrets/my-secret', + }, + }, + }, + { + metadata: { + name: 'env-secret', + annotations: { + 'controller.devfile.io/mount-as': 'env', + }, + }, + }, + ], + }); + mockListNamespacedConfigMap.mockResolvedValue({ + items: [ + { + metadata: { + name: 'env-cm', + annotations: { + 'controller.devfile.io/mount-as': 'env', + }, + }, + }, + ], + }); + mockCreateNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedPod.mockResolvedValue( + makePod({ phase: 'Pending', ready: false, uid: 'uid-3' }), + ); + mockPatchNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedService.mockResolvedValue({}); + + await createAgentPod(TOKEN, NAMESPACE, 'mount-agent', config); + + const podBody = mockCreateNamespacedPod.mock.calls[0][0].body; + const volumes = podBody.spec.volumes; + const volumeMounts = podBody.spec.containers[0].volumeMounts; + const envFrom = podBody.spec.containers[0].envFrom; + + expect(volumes.some((v: k8s.V1Volume) => v.name === 'secret-my-secret')).toBe(true); + expect( + volumeMounts.some((vm: k8s.V1VolumeMount) => vm.mountPath === '/secrets/my-secret'), + ).toBe(true); + expect(envFrom).toBeDefined(); + expect(envFrom.length).toBe(2); // env-secret + env-cm + }); + + it('should handle secret already exists (409) by replacing', async () => { + mockListNamespacedPod.mockResolvedValue({ items: [] }); + mockListNamespacedSecret.mockResolvedValue({ items: [] }); + mockListNamespacedConfigMap.mockResolvedValue({ items: [] }); + mockCreateNamespacedSecret.mockRejectedValue( + Object.assign(new Error('conflict'), { + statusCode: 409, + code: 409, + headers: {}, + body: { message: 'conflict' }, + }), + ); + mockReplaceNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedPod.mockResolvedValue( + makePod({ phase: 'Pending', ready: false, uid: 'uid-4' }), + ); + mockPatchNamespacedSecret.mockResolvedValue({}); + mockCreateNamespacedService.mockResolvedValue({}); + + await createAgentPod(TOKEN, NAMESPACE, 'replace-agent', config); + expect(mockReplaceNamespacedSecret).toHaveBeenCalled(); + }); + }); + + describe('deleteAgentPod', () => { + it('should delete existing pod and secret', async () => { + mockListNamespacedPod.mockResolvedValue({ + items: [makePod()], + }); + mockDeleteNamespacedPod.mockResolvedValue({}); + mockDeleteNamespacedSecret.mockResolvedValue({}); + + await deleteAgentPod(TOKEN, NAMESPACE, AGENT_ID); + + expect(mockDeleteNamespacedPod).toHaveBeenCalled(); + expect(mockDeleteNamespacedSecret).toHaveBeenCalled(); + }); + + it('should handle non-existent pod gracefully', async () => { + mockListNamespacedPod.mockResolvedValue({ items: [] }); + mockDeleteNamespacedSecret.mockRejectedValue( + Object.assign(new Error('not found'), { + statusCode: 404, + code: 404, + headers: {}, + body: { message: 'not found' }, + }), + ); + + await expect(deleteAgentPod(TOKEN, NAMESPACE, AGENT_ID)).resolves.not.toThrow(); + }); + }); + + describe('heartbeatAgentPod', () => { + it('should update heartbeat annotation', async () => { + mockListNamespacedPod.mockResolvedValue({ + items: [makePod()], + }); + mockPatchNamespacedPod.mockResolvedValue({}); + + await heartbeatAgentPod(TOKEN, NAMESPACE, AGENT_ID); + + expect(mockPatchNamespacedPod).toHaveBeenCalled(); + const patchBody = mockPatchNamespacedPod.mock.calls[0][0].body; + expect(patchBody.metadata.annotations['che.eclipse.org/last-heartbeat']).toBeDefined(); + }); + + it('should do nothing when pod not found', async () => { + mockListNamespacedPod.mockResolvedValue({ items: [] }); + + await heartbeatAgentPod(TOKEN, NAMESPACE, AGENT_ID); + + expect(mockPatchNamespacedPod).not.toHaveBeenCalled(); + }); + }); + + describe('cleanupExpiredAgentPods', () => { + it('should delete pods with expired heartbeats', async () => { + const expired = new Date(Date.now() - 25 * 60 * 1000).toISOString(); + mockListNamespacedPod.mockResolvedValue({ + items: [makePod({ heartbeat: expired, name: 'expired-pod' })], + }); + mockDeleteNamespacedPod.mockResolvedValue({}); + + const deleted = await cleanupExpiredAgentPods(TOKEN, NAMESPACE); + + expect(deleted).toEqual(['expired-pod']); + expect(mockDeleteNamespacedPod).toHaveBeenCalledWith( + expect.objectContaining({ name: 'expired-pod' }), + ); + }); + + it('should not delete pods with recent heartbeats', async () => { + mockListNamespacedPod.mockResolvedValue({ + items: [makePod({ heartbeat: new Date().toISOString() })], + }); + + const deleted = await cleanupExpiredAgentPods(TOKEN, NAMESPACE); + + expect(deleted).toEqual([]); + expect(mockDeleteNamespacedPod).not.toHaveBeenCalled(); + }); + + it('should skip pods without heartbeat annotation', async () => { + const pod = makePod(); + delete pod.metadata!.annotations!['che.eclipse.org/last-heartbeat']; + mockListNamespacedPod.mockResolvedValue({ items: [pod] }); + + const deleted = await cleanupExpiredAgentPods(TOKEN, NAMESPACE); + + expect(deleted).toEqual([]); + }); + }); + + describe('startPeriodicAgentCleanup', () => { + it('should start periodic cleanup without error', () => { + const getToken = jest.fn(() => TOKEN); + + expect(() => startPeriodicAgentCleanup(getToken)).not.toThrow(); + + stopPeriodicAgentCleanup(); + }); + + it('should not start multiple timers', () => { + const getToken = jest.fn(() => TOKEN); + + startPeriodicAgentCleanup(getToken); + startPeriodicAgentCleanup(getToken); + + stopPeriodicAgentCleanup(); + }); + }); + + describe('normalizeAgentId via getAgentServiceUrl', () => { + it('should handle mixed case agent IDs', () => { + const url = getAgentServiceUrl('ns', 'MyOrg/MyAgent'); + expect(url).toBe('http://agent-myorg-myagent-svc.ns.svc:8080'); + }); + + it('should strip special characters', () => { + const url = getAgentServiceUrl('ns', 'org/agent_v2.1'); + expect(url).toBe('http://agent-org-agentv21-svc.ns.svc:8080'); + }); + }); +}); diff --git a/packages/dashboard-backend/src/routes/api/helpers/__tests__/devfileHelpers.spec.ts b/packages/dashboard-backend/src/routes/api/helpers/__tests__/devfileHelpers.spec.ts new file mode 100644 index 0000000000..dd409c29ea --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/helpers/__tests__/devfileHelpers.spec.ts @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { + countProjects, + extractDevfileDescription, + extractDevfileName, + generateDevfileId, + MODIFIED_ANNOTATION_PREFIX, + parseEntries, +} from '@/routes/api/helpers/devfile'; + +describe('devfile helpers', () => { + describe('extractDevfileName', () => { + it('should extract metadata.name', () => { + const content = 'schemaVersion: 2.3.0\nmetadata:\n name: my-workspace\n'; + expect(extractDevfileName(content)).toBe('my-workspace'); + }); + + it('should fall back to metadata.generateName', () => { + const content = 'schemaVersion: 2.3.0\nmetadata:\n generateName: gen-ws-\n'; + expect(extractDevfileName(content)).toBe('gen-ws-'); + }); + + it('should return "untitled" when no name or generateName', () => { + const content = 'schemaVersion: 2.3.0\nmetadata:\n description: test\n'; + expect(extractDevfileName(content)).toBe('untitled'); + }); + + it('should return "untitled" for invalid YAML', () => { + expect(extractDevfileName('not: valid: yaml: :::')).toBe('untitled'); + }); + + it('should return "untitled" for non-object YAML', () => { + expect(extractDevfileName('just a string')).toBe('untitled'); + }); + }); + + describe('extractDevfileDescription', () => { + it('should extract description', () => { + const content = + 'schemaVersion: 2.3.0\nmetadata:\n name: test\n description: My cool workspace\n'; + expect(extractDevfileDescription(content)).toBe('My cool workspace'); + }); + + it('should return empty string when no description', () => { + const content = 'schemaVersion: 2.3.0\nmetadata:\n name: test\n'; + expect(extractDevfileDescription(content)).toBe(''); + }); + }); + + describe('generateDevfileId', () => { + it('should generate ID with sanitized name prefix', () => { + const content = 'schemaVersion: 2.3.0\nmetadata:\n name: My Workspace!\n'; + const id = generateDevfileId(content); + expect(id).toMatch(/^my-workspace-[a-f0-9]{8}$/); + }); + + it('should generate unique IDs', () => { + const content = 'schemaVersion: 2.3.0\nmetadata:\n name: test\n'; + const ids = new Set(Array.from({ length: 10 }, () => generateDevfileId(content))); + expect(ids.size).toBe(10); + }); + + it('should use "untitled" prefix when no metadata.name', () => { + const content = 'schemaVersion: 2.3.0\n'; + const id = generateDevfileId(content); + expect(id).toMatch(/^untitled-[a-f0-9]{8}$/); + }); + + it('should truncate long names', () => { + const content = + 'schemaVersion: 2.3.0\nmetadata:\n name: this-is-a-very-long-workspace-name-that-exceeds-limit\n'; + const id = generateDevfileId(content); + const prefix = id.split('-').slice(0, -1).join('-'); + expect(prefix.length).toBeLessThanOrEqual(20); + }); + }); + + describe('countProjects', () => { + it('should count projects', () => { + const content = + 'schemaVersion: 2.3.0\nprojects:\n - name: proj1\n git:\n remotes:\n origin: https://github.com/test/proj1\n - name: proj2\n git:\n remotes:\n origin: https://github.com/test/proj2\n'; + expect(countProjects(content)).toBe(2); + }); + + it('should return 0 when no projects', () => { + const content = 'schemaVersion: 2.3.0\nmetadata:\n name: test\n'; + expect(countProjects(content)).toBe(0); + }); + + it('should skip projects without names', () => { + const content = + 'schemaVersion: 2.3.0\nprojects:\n - name: valid\n git:\n remotes:\n origin: https://test\n - git:\n remotes:\n origin: https://test2\n'; + expect(countProjects(content)).toBe(1); + }); + }); + + describe('parseEntries', () => { + it('should parse ConfigMap data to devfile entries', () => { + const data = { + 'id-1': 'schemaVersion: 2.3.0\nmetadata:\n name: ws1\n description: Desc 1\n', + 'id-2': + 'schemaVersion: 2.3.0\nmetadata:\n name: ws2\nprojects:\n - name: p1\n git:\n remotes:\n origin: https://test\n', + }; + const annotations = { + [`${MODIFIED_ANNOTATION_PREFIX}id-1`]: '2025-01-01T00:00:00Z', + }; + + const entries = parseEntries(data, annotations); + expect(entries).toHaveLength(2); + + const entry1 = entries.find(e => e.id === 'id-1')!; + expect(entry1.name).toBe('ws1'); + expect(entry1.description).toBe('Desc 1'); + expect(entry1.lastModified).toBe('2025-01-01T00:00:00Z'); + + const entry2 = entries.find(e => e.id === 'id-2')!; + expect(entry2.name).toBe('ws2'); + expect(entry2.projectCount).toBe(1); + }); + + it('should return empty array for undefined data', () => { + expect(parseEntries(undefined)).toEqual([]); + }); + + it('should handle entries with generateName', () => { + const data = { + 'id-1': 'schemaVersion: 2.3.0\nmetadata:\n generateName: gen-\n', + }; + const entries = parseEntries(data); + expect(entries[0].name).toBe('gen-'); + }); + }); +}); diff --git a/packages/dashboard-backend/src/routes/api/helpers/agentPod.ts b/packages/dashboard-backend/src/routes/api/helpers/agentPod.ts new file mode 100644 index 0000000000..040db18e5a --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/helpers/agentPod.ts @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { helpers } from '@eclipse-che/common'; +import * as k8s from '@kubernetes/client-node'; + +import { STRATEGIC_MERGE_PATCH_OPTIONS } from '@/devworkspaceClient/services/helpers/patchOptions'; +import { isLocalRun } from '@/localRun'; +import { getKubeConfig } from '@/routes/api/helpers/getDevWorkspaceClient'; +import { KubeConfigProvider } from '@/services/kubeclient/kubeConfigProvider'; +import { logger } from '@/utils/logger'; + +const AGENT_LABEL_COMPONENT = 'app.kubernetes.io/component'; +const AGENT_LABEL_PART_OF = 'app.kubernetes.io/part-of'; +const AGENT_LABEL_AGENT_ID = 'che.eclipse.org/agent-id'; +const HEARTBEAT_ANNOTATION = 'che.eclipse.org/last-heartbeat'; +const AGENT_TTL_MINUTES = 20; +export const MAX_AGENT_PODS_PER_USER = 3; +const TOKEN_SECRET_VOLUME = 'che-user-token'; +const TOKEN_MOUNT_PATH = '/var/run/secrets/che/token'; + +const BLOCKED_ENV_NAMES = new Set([ + 'PATH', + 'HOME', + 'LD_PRELOAD', + 'LD_LIBRARY_PATH', + 'KUBERNETES_API_URL', + 'CHE_USER_TOKEN_FILE', + 'AGENT_NAMESPACE', +]); + +export interface AgentPodStatus { + agentId: string; + name: string; + phase: string; + ready: boolean; + serviceUrl: string | undefined; +} + +interface AgentConfig { + image: string; + tag: string; + memoryLimit: string; + cpuLimit: string; + terminalPort: number; + env: Array<{ name: string; value: string }>; +} + +function normalizeAgentId(agentId: string): string { + return agentId + .toLowerCase() + .replace(/\//g, '-') + .replace(/[^a-z0-9-]/g, ''); +} + +function agentPodName(agentId: string): string { + return `agent-${normalizeAgentId(agentId)}`; +} + +function agentServiceName(agentId: string): string { + return `agent-${normalizeAgentId(agentId)}-svc`; +} + +function agentTokenSecretName(agentId: string): string { + return `agent-${normalizeAgentId(agentId)}-token`; +} + +function agentLabels(agentId: string): Record { + return { + [AGENT_LABEL_COMPONENT]: 'ai-agent', + [AGENT_LABEL_PART_OF]: 'che.eclipse.org', + [AGENT_LABEL_AGENT_ID]: normalizeAgentId(agentId), + }; +} + +const DW_MOUNT_LABEL = 'controller.devfile.io/mount-to-devworkspace'; +const DW_MOUNT_AS = 'controller.devfile.io/mount-as'; +const DW_MOUNT_PATH = 'controller.devfile.io/mount-path'; + +interface DevWorkspaceMounts { + volumes: k8s.V1Volume[]; + volumeMounts: k8s.V1VolumeMount[]; + envFrom: k8s.V1EnvFromSource[]; +} + +function getCoreV1Api(token: string): k8s.CoreV1Api { + const kubeConfig = getKubeConfig(token); + return kubeConfig.makeApiClient(k8s.CoreV1Api); +} + +async function discoverDevWorkspaceMounts( + coreV1Api: k8s.CoreV1Api, + namespace: string, +): Promise { + const volumes: k8s.V1Volume[] = []; + const volumeMounts: k8s.V1VolumeMount[] = []; + const envFrom: k8s.V1EnvFromSource[] = []; + + try { + const secrets = await coreV1Api.listNamespacedSecret({ + namespace, + labelSelector: `${DW_MOUNT_LABEL}=true`, + }); + + for (const secret of secrets.items) { + const name = secret.metadata?.name; + if (!name) continue; + + const mountAs = secret.metadata?.annotations?.[DW_MOUNT_AS]; + const mountPath = secret.metadata?.annotations?.[DW_MOUNT_PATH]; + + if (mountAs === 'file' && mountPath) { + const volumeName = `secret-${name}`; + volumes.push({ name: volumeName, secret: { secretName: name } }); + volumeMounts.push({ name: volumeName, mountPath, readOnly: true }); + } else if (mountAs === 'env') { + envFrom.push({ secretRef: { name } }); + } + } + } catch (e) { + logger.warn(e, 'Failed to discover DevWorkspace-mountable secrets'); + } + + try { + const configMaps = await coreV1Api.listNamespacedConfigMap({ + namespace, + labelSelector: `${DW_MOUNT_LABEL}=true`, + }); + + for (const cm of configMaps.items) { + const name = cm.metadata?.name; + if (!name) continue; + + const mountAs = cm.metadata?.annotations?.[DW_MOUNT_AS]; + const mountPath = cm.metadata?.annotations?.[DW_MOUNT_PATH]; + + if (mountAs === 'env') { + envFrom.push({ configMapRef: { name } }); + } else if (mountAs === 'file' && mountPath) { + const volumeName = `cm-${name}`; + volumes.push({ name: volumeName, configMap: { name } }); + volumeMounts.push({ name: volumeName, mountPath, readOnly: true }); + } + } + } catch (e) { + logger.warn(e, 'Failed to discover DevWorkspace-mountable configmaps'); + } + + return { volumes, volumeMounts, envFrom }; +} + +export async function createAgentPod( + token: string, + namespace: string, + agentId: string, + config: AgentConfig, +): Promise { + const coreV1Api = getCoreV1Api(token); + const podName = agentPodName(agentId); + const svcName = agentServiceName(agentId); + const secretName = agentTokenSecretName(agentId); + const labels = agentLabels(agentId); + + // Clean up expired pods before checking limits + await cleanupExpiredAgentPods(token, namespace); + + const existing = await findAgentPod(coreV1Api, namespace, agentId); + if (existing) { + const phase = existing.status?.phase || 'Unknown'; + if (phase === 'Failed' || phase === 'Succeeded') { + await coreV1Api.deleteNamespacedPod({ name: existing.metadata!.name!, namespace }); + } else { + return podToStatus(existing, agentId, namespace, config.terminalPort); + } + } + + // Check active pod count against limit + const activePods = await coreV1Api.listNamespacedPod({ + namespace, + labelSelector: `${AGENT_LABEL_COMPONENT}=ai-agent`, + }); + const runningCount = activePods.items.filter( + p => + !p.metadata?.deletionTimestamp && + p.status?.phase !== 'Failed' && + p.status?.phase !== 'Succeeded', + ).length; + if (runningCount >= MAX_AGENT_PODS_PER_USER) { + throw Object.assign( + new Error( + `Maximum number of agent pods (${MAX_AGENT_PODS_PER_USER}) reached. Stop an existing agent before starting a new one.`, + ), + { statusCode: 409 }, + ); + } + + const dwMounts = await discoverDevWorkspaceMounts(coreV1Api, namespace); + const now = new Date().toISOString(); + + // Resolve the Kubernetes API server URL for the agent env var + const kubeConfig = getKubeConfig(token); + const kubeApiUrl = kubeConfig.getCurrentCluster()?.server || ''; + + // Create the token Secret before the Pod so the volume mount can reference it + await createOrReplaceTokenSecret(coreV1Api, namespace, secretName, token, labels); + + const allVolumes: k8s.V1Volume[] = [ + ...dwMounts.volumes, + { name: TOKEN_SECRET_VOLUME, secret: { secretName } }, + { name: 'tmp-dir', emptyDir: {} }, + ]; + const allVolumeMounts: k8s.V1VolumeMount[] = [ + ...dwMounts.volumeMounts, + { name: TOKEN_SECRET_VOLUME, mountPath: TOKEN_MOUNT_PATH, readOnly: true }, + { name: 'tmp-dir', mountPath: '/tmp' }, + ]; + + const pod = await coreV1Api.createNamespacedPod({ + namespace, + body: { + metadata: { + name: podName, + namespace, + labels, + annotations: { + 'che.eclipse.org/ai-agent-id': agentId, + [HEARTBEAT_ANNOTATION]: now, + }, + }, + spec: { + automountServiceAccountToken: false, + volumes: allVolumes, + containers: [ + { + name: 'agent', + image: `${config.image}:${config.tag}`, + imagePullPolicy: 'IfNotPresent', + ports: [ + { + containerPort: config.terminalPort, + name: 'terminal', + protocol: 'TCP', + }, + ], + resources: { + limits: { + memory: config.memoryLimit, + cpu: config.cpuLimit, + }, + requests: { + memory: '128Mi', + cpu: '100m', + }, + }, + env: [ + ...config.env.filter(e => !BLOCKED_ENV_NAMES.has(e.name)), + { name: 'AGENT_NAMESPACE', value: namespace }, + { name: 'KUBERNETES_API_URL', value: kubeApiUrl }, + { name: 'CHE_USER_TOKEN_FILE', value: `${TOKEN_MOUNT_PATH}/token` }, + ], + envFrom: dwMounts.envFrom.length > 0 ? dwMounts.envFrom : undefined, + volumeMounts: allVolumeMounts, + securityContext: { + allowPrivilegeEscalation: false, + runAsUser: 1001, + runAsNonRoot: true, + readOnlyRootFilesystem: true, + capabilities: { drop: ['ALL'] }, + seccompProfile: { type: 'RuntimeDefault' }, + }, + }, + ], + restartPolicy: 'Never', + terminationGracePeriodSeconds: 5, + }, + }, + }); + + const podUid = pod.metadata!.uid!; + + // Set ownerReference on the Secret so it is garbage-collected with the Pod + await setSecretOwnerReference(coreV1Api, namespace, secretName, podName, podUid); + + await coreV1Api.createNamespacedService({ + namespace, + body: { + metadata: { + name: svcName, + namespace, + labels, + ownerReferences: [ + { + apiVersion: 'v1', + kind: 'Pod', + name: podName, + uid: podUid, + }, + ], + }, + spec: { + type: 'ClusterIP', + selector: { [AGENT_LABEL_AGENT_ID]: normalizeAgentId(agentId) }, + ports: [ + { + name: 'terminal', + port: config.terminalPort, + targetPort: config.terminalPort, + protocol: 'TCP', + }, + ], + }, + }, + }); + + return podToStatus(pod, agentId, namespace, config.terminalPort); +} + +export async function getAgentPodStatus( + token: string, + namespace: string, + agentId: string, + terminalPort = 8080, +): Promise { + const coreV1Api = getCoreV1Api(token); + const pod = await findAgentPod(coreV1Api, namespace, agentId); + if (!pod) return undefined; + return podToStatus(pod, agentId, namespace, terminalPort); +} + +export async function deleteAgentPod( + token: string, + namespace: string, + agentId: string, +): Promise { + const coreV1Api = getCoreV1Api(token); + const pod = await findAgentPod(coreV1Api, namespace, agentId); + if (pod?.metadata?.name) { + await coreV1Api.deleteNamespacedPod({ + name: pod.metadata.name, + namespace, + body: { gracePeriodSeconds: 5 }, + }); + } + + // Best-effort cleanup of the token Secret (may already be gone via ownerReference) + const secretName = agentTokenSecretName(agentId); + try { + await coreV1Api.deleteNamespacedSecret({ name: secretName, namespace }); + } catch (e: unknown) { + if (helpers.errors.isKubeClientError(e) && e.code === 404) { + // Already deleted — nothing to do + } else { + logger.warn(e, 'Failed to delete agent token secret: %s', secretName); + } + } +} + +export async function heartbeatAgentPod( + token: string, + namespace: string, + agentId: string, +): Promise { + const coreV1Api = getCoreV1Api(token); + const pod = await findAgentPod(coreV1Api, namespace, agentId); + if (!pod?.metadata?.name) return; + + const now = new Date().toISOString(); + await coreV1Api.patchNamespacedPod( + { + name: pod.metadata.name, + namespace, + body: { metadata: { annotations: { [HEARTBEAT_ANNOTATION]: now } } }, + }, + STRATEGIC_MERGE_PATCH_OPTIONS, + ); +} + +export async function cleanupExpiredAgentPods(token: string, namespace: string): Promise { + const coreV1Api = getCoreV1Api(token); + const pods = await coreV1Api.listNamespacedPod({ + namespace, + labelSelector: `${AGENT_LABEL_COMPONENT}=ai-agent`, + }); + + const now = Date.now(); + const ttlMs = AGENT_TTL_MINUTES * 60 * 1000; + const deleted: string[] = []; + + for (const pod of pods.items) { + const heartbeat = pod.metadata?.annotations?.[HEARTBEAT_ANNOTATION]; + if (!heartbeat) continue; + + const lastBeat = new Date(heartbeat).getTime(); + if (now - lastBeat > ttlMs) { + const name = pod.metadata!.name!; + logger.info('Cleaning up expired agent pod: %s (last heartbeat: %s)', name, heartbeat); + try { + await coreV1Api.deleteNamespacedPod({ + name, + namespace, + body: { gracePeriodSeconds: 5 }, + }); + deleted.push(name); + } catch (e) { + logger.error(e, 'Failed to delete expired agent pod: %s', name); + } + } + } + return deleted; +} + +const PERIODIC_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; +let periodicCleanupTimer: ReturnType | undefined; + +export function startPeriodicAgentCleanup(getToken: () => string): void { + if (periodicCleanupTimer) return; + + // Requires cluster-wide pod list RBAC for the service account (ClusterRole with pods list verb) + periodicCleanupTimer = setInterval(async () => { + try { + const token = getToken(); + const coreV1Api = getCoreV1Api(token); + const allPods = await coreV1Api.listPodForAllNamespaces({ + labelSelector: `${AGENT_LABEL_COMPONENT}=ai-agent`, + }); + + const now = Date.now(); + const ttlMs = AGENT_TTL_MINUTES * 60 * 1000; + + for (const pod of allPods.items) { + const heartbeat = pod.metadata?.annotations?.[HEARTBEAT_ANNOTATION]; + if (!heartbeat) continue; + + const lastBeat = new Date(heartbeat).getTime(); + if (now - lastBeat > ttlMs) { + const name = pod.metadata!.name!; + const namespace = pod.metadata!.namespace!; + logger.info( + 'Periodic cleanup: deleting expired agent pod %s/%s (last heartbeat: %s)', + namespace, + name, + heartbeat, + ); + try { + await coreV1Api.deleteNamespacedPod({ + name, + namespace, + body: { gracePeriodSeconds: 5 }, + }); + } catch (e) { + logger.error(e, 'Periodic cleanup: failed to delete pod %s/%s', namespace, name); + } + } + } + } catch (e) { + logger.warn(e, 'Periodic agent cleanup failed'); + } + }, PERIODIC_CLEANUP_INTERVAL_MS); + + logger.info( + 'Started periodic agent cleanup (interval: %d min)', + PERIODIC_CLEANUP_INTERVAL_MS / 60_000, + ); +} + +export function stopPeriodicAgentCleanup(): void { + if (periodicCleanupTimer) { + clearInterval(periodicCleanupTimer); + periodicCleanupTimer = undefined; + } +} + +export function getAgentServiceUrl( + namespace: string, + agentId: string, + terminalPort = 8080, +): string { + const svcName = agentServiceName(agentId); + return `http://${svcName}.${namespace}.svc:${terminalPort}`; +} + +export interface AgentServiceAccess { + baseUrl: string; + httpsOptions?: { + rejectUnauthorized: boolean; + ca?: string; + }; +} + +export function getAgentServiceAccess( + namespace: string, + agentId: string, + terminalPort: number, +): AgentServiceAccess { + if (isLocalRun()) { + const kc = new KubeConfigProvider().getSAKubeConfig(); + const cluster = kc.getCurrentCluster(); + if (!cluster) { + throw new Error('No cluster found in kubeconfig'); + } + const svcName = agentServiceName(agentId); + const baseUrl = `${cluster.server}/api/v1/namespaces/${namespace}/services/${svcName}:${terminalPort}/proxy`; + return { + baseUrl, + httpsOptions: { + rejectUnauthorized: !cluster.skipTLSVerify, + ca: cluster.caData ? Buffer.from(cluster.caData, 'base64').toString() : undefined, + }, + }; + } + return { + baseUrl: getAgentServiceUrl(namespace, agentId, terminalPort), + }; +} + +async function findAgentPod( + coreV1Api: k8s.CoreV1Api, + namespace: string, + agentId: string, +): Promise { + const normalized = normalizeAgentId(agentId); + const pods = await coreV1Api.listNamespacedPod({ + namespace, + labelSelector: `${AGENT_LABEL_AGENT_ID}=${normalized}`, + }); + return pods.items.find(pod => !pod.metadata?.deletionTimestamp); +} + +function podToStatus( + pod: k8s.V1Pod, + agentId: string, + namespace: string, + terminalPort: number, +): AgentPodStatus { + const phase = pod.status?.phase || 'Unknown'; + const containerReady = pod.status?.containerStatuses?.some(c => c.ready === true) || false; + const isRunning = phase === 'Running' && containerReady; + + return { + agentId, + name: pod.metadata?.name || '', + phase, + ready: isRunning, + serviceUrl: isRunning ? getAgentServiceUrl(namespace, agentId, terminalPort) : undefined, + }; +} + +async function createOrReplaceTokenSecret( + coreV1Api: k8s.CoreV1Api, + namespace: string, + secretName: string, + userToken: string, + labels: Record, +): Promise { + const secretBody: k8s.V1Secret = { + metadata: { + name: secretName, + namespace, + labels, + }, + stringData: { token: userToken }, + }; + + try { + await coreV1Api.createNamespacedSecret({ namespace, body: secretBody }); + } catch (e: unknown) { + if (helpers.errors.isKubeClientError(e) && e.code === 409) { + // Secret already exists — replace its data + await coreV1Api.replaceNamespacedSecret({ + name: secretName, + namespace, + body: secretBody, + }); + } else { + throw e; + } + } +} + +async function setSecretOwnerReference( + coreV1Api: k8s.CoreV1Api, + namespace: string, + secretName: string, + podName: string, + podUid: string, +): Promise { + await coreV1Api.patchNamespacedSecret( + { + name: secretName, + namespace, + body: { + metadata: { + ownerReferences: [ + { + apiVersion: 'v1', + kind: 'Pod', + name: podName, + uid: podUid, + }, + ], + }, + }, + }, + STRATEGIC_MERGE_PATCH_OPTIONS, + ); +} diff --git a/packages/dashboard-backend/src/routes/api/helpers/devfile/index.ts b/packages/dashboard-backend/src/routes/api/helpers/devfile/index.ts new file mode 100644 index 0000000000..670764b841 --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/helpers/devfile/index.ts @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { randomBytes } from 'node:crypto'; + +import { helpers } from '@eclipse-che/common'; +import * as k8s from '@kubernetes/client-node'; +import { load } from 'js-yaml'; + +import { getKubeConfig } from '@/routes/api/helpers/getDevWorkspaceClient'; + +export const CONFIGMAP_NAME = 'devfile-creator-storage'; +const CONFIGMAP_LABELS = { + 'app.kubernetes.io/component': 'devfile-creator', + 'app.kubernetes.io/part-of': 'che.eclipse.org', +}; + +interface ParsedDevfile { + metadata?: { name?: string; generateName?: string; description?: string }; + projects?: Array<{ name?: string }>; +} + +export interface DevfileEntry { + id: string; + name: string; + description: string; + content: string; + projectCount: number; + lastModified: string; +} + +export interface DevfileIdParams { + namespace: string; + id: string; +} + +function parseDevfileYaml(content: string): ParsedDevfile { + try { + const parsed = load(content); + if (typeof parsed === 'object' && parsed !== null) { + return parsed as ParsedDevfile; + } + } catch { + // invalid YAML + } + return {}; +} + +export function extractDevfileName(content: string): string { + const parsed = parseDevfileYaml(content); + const name = parsed.metadata?.name; + if (typeof name === 'string' && name.length > 0) { + return name; + } + const generateName = parsed.metadata?.generateName; + if (typeof generateName === 'string' && generateName.length > 0) { + return generateName; + } + return 'untitled'; +} + +export function extractDevfileDescription(content: string): string { + const parsed = parseDevfileYaml(content); + const description = parsed.metadata?.description; + return typeof description === 'string' ? description : ''; +} + +export function generateDevfileId(content: string): string { + const name = extractDevfileName(content); + const sanitized = name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, '') + .slice(0, 20); + const suffix = randomBytes(4).toString('hex'); + return `${sanitized || 'devfile'}-${suffix}`; +} + +export function countProjects(content: string): number { + const parsed = parseDevfileYaml(content); + if (!Array.isArray(parsed.projects)) return 0; + return parsed.projects.filter(p => typeof p?.name === 'string').length; +} + +export const MODIFIED_ANNOTATION_PREFIX = 'che.eclipse.org/modified-'; + +export function parseEntries( + data: Record | undefined, + annotations?: Record, +): DevfileEntry[] { + if (!data) return []; + return Object.entries(data).map(([id, content]) => { + const parsed = parseDevfileYaml(content); + const name = parsed.metadata?.name; + const generateName = parsed.metadata?.generateName; + const description = parsed.metadata?.description; + const projects = Array.isArray(parsed.projects) ? parsed.projects : []; + const storedTimestamp = annotations?.[`${MODIFIED_ANNOTATION_PREFIX}${id}`]; + const displayName = + typeof name === 'string' && name.length > 0 + ? name + : typeof generateName === 'string' && generateName.length > 0 + ? generateName + : 'untitled'; + return { + id, + name: displayName, + description: typeof description === 'string' ? description : '', + content, + projectCount: projects.filter(p => typeof p?.name === 'string').length, + lastModified: storedTimestamp || new Date().toISOString(), + }; + }); +} + +export async function getOrCreateConfigMap( + coreV1Api: k8s.CoreV1Api, + namespace: string, +): Promise { + try { + const response = await coreV1Api.readNamespacedConfigMap({ + name: CONFIGMAP_NAME, + namespace, + }); + return response; + } catch (error: unknown) { + if (helpers.errors.isKubeClientError(error) && error.code === 404) { + const configMap: k8s.V1ConfigMap = { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: CONFIGMAP_NAME, + namespace, + labels: CONFIGMAP_LABELS, + }, + data: {}, + }; + return coreV1Api.createNamespacedConfigMap({ + namespace, + body: configMap, + }); + } + throw error; + } +} + +export function getCoreV1Api(token: string): k8s.CoreV1Api { + const kubeConfig = getKubeConfig(token); + return kubeConfig.makeApiClient(k8s.CoreV1Api); +} diff --git a/packages/dashboard-backend/src/routes/api/helpers/terminal/index.ts b/packages/dashboard-backend/src/routes/api/helpers/terminal/index.ts new file mode 100644 index 0000000000..31e8b812cd --- /dev/null +++ b/packages/dashboard-backend/src/routes/api/helpers/terminal/index.ts @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import http from 'node:http'; +import https from 'node:https'; + +import { FastifyReply, FastifyRequest } from 'fastify'; + +import { AGENT_TERMINAL_THEMES } from '@/constants/terminal-themes'; +import { AgentServiceAccess } from '@/routes/api/helpers/agentPod'; + +function buildBridgeScript(darkThemeJson: string, lightThemeJson: string): string { + return ``; +} + +export function proxyToTerminal( + access: AgentServiceAccess, + path: string, + proxyPrefix: string, + token: string, + request: FastifyRequest, + reply: FastifyReply, +): Promise { + return new Promise((resolve, reject) => { + const base = access.baseUrl.endsWith('/') ? access.baseUrl : access.baseUrl + '/'; + const relativePath = path.startsWith('/') ? path.slice(1) : path; + const url = new URL(relativePath, base); + const isHttps = url.protocol === 'https:'; + const transport = isHttps ? https : http; + const options: http.RequestOptions = { + hostname: url.hostname, + port: url.port || (isHttps ? 443 : 80), + path: url.pathname + url.search, + method: request.method, + headers: { + host: `${url.hostname}:${url.port || (isHttps ? 443 : 80)}`, + }, + }; + if (isHttps && access.httpsOptions) { + (options as https.RequestOptions).rejectUnauthorized = access.httpsOptions.rejectUnauthorized; + if (access.httpsOptions.ca) { + (options as https.RequestOptions).ca = access.httpsOptions.ca; + } + options.headers = { ...options.headers, Authorization: `Bearer ${token}` }; + } + + const darkThemeJson = JSON.stringify(AGENT_TERMINAL_THEMES.dark); + const lightThemeJson = JSON.stringify(AGENT_TERMINAL_THEMES.light); + + const proxyReq = transport.request(options, proxyRes => { + reply.status(proxyRes.statusCode || 200); + const ct = proxyRes.headers['content-type']; + if (ct) { + reply.header('content-type', ct); + } + + if (ct && ct.includes('text/html')) { + const chunks: Buffer[] = []; + proxyRes.on('data', (chunk: Buffer) => chunks.push(chunk)); + proxyRes.on('end', () => { + let html = Buffer.concat(chunks).toString('utf-8'); + html = html.replace(/href=(["'])\//g, `href=$1${proxyPrefix}/`); + html = html.replace(/src=(["'])\//g, `src=$1${proxyPrefix}/`); + html = html.replace( + '', + `${buildBridgeScript(darkThemeJson, lightThemeJson)}\n`, + ); + reply.header('content-length', Buffer.byteLength(html)); + reply.send(html); + resolve(); + }); + } else { + reply.send(proxyRes); + resolve(); + } + }); + + proxyReq.on('error', err => { + reject(err); + }); + + if (request.raw.readable && !request.body) { + request.raw.pipe(proxyReq); + } else { + proxyReq.end(); + } + }); +} diff --git a/packages/dashboard-backend/src/routes/api/websocket.ts b/packages/dashboard-backend/src/routes/api/websocket.ts index b9cdb57a0b..65ce2196c4 100644 --- a/packages/dashboard-backend/src/routes/api/websocket.ts +++ b/packages/dashboard-backend/src/routes/api/websocket.ts @@ -29,10 +29,18 @@ export function registerWebsocket(instance: FastifyInstance) { } export function webSocketHandler(ws: WebSocket, request: FastifyRequest): void { + let token: string; + try { + token = getToken(request); + } catch { + ws.close(1008, 'Authentication required'); + return; + } + const subscriptionManager = new SubscriptionManager(ws); - const token = getToken(request); - const { eventApi, devworkspaceApi, logsApi, podApi } = getDevWorkspaceClient(token); + const { eventApi, devworkspaceApi, logsApi, podApi, configMapWatchApi } = + getDevWorkspaceClient(token); const channel = api.webSocket.Channel; const watchers = { @@ -40,6 +48,7 @@ export function webSocketHandler(ws: WebSocket, request: FastifyRequest): void { [channel.EVENT]: new ObjectsWatcher(eventApi, channel.EVENT), [channel.LOGS]: new ObjectsWatcher(logsApi, channel.LOGS), [channel.POD]: new ObjectsWatcher(podApi, channel.POD), + [channel.CONFIGMAP]: new ObjectsWatcher(configMapWatchApi, channel.CONFIGMAP), }; function notifyWatchStartError( @@ -67,7 +76,8 @@ export function webSocketHandler(ws: WebSocket, request: FastifyRequest): void { switch (message.channel) { case channel.DEV_WORKSPACE: case channel.EVENT: - case channel.POD: { + case channel.POD: + case channel.CONFIGMAP: { const watcher = watchers[message.channel]; watcher.attach(subscriptionManager); try { @@ -97,8 +107,8 @@ export function webSocketHandler(ws: WebSocket, request: FastifyRequest): void { watcher.stop(); } function handleUnsubscribeAll() { - [channel.DEV_WORKSPACE, channel.EVENT, channel.LOGS, channel.POD].forEach(channel => - handleChannelUnsubscribe(channel), + [channel.DEV_WORKSPACE, channel.EVENT, channel.LOGS, channel.POD, channel.CONFIGMAP].forEach( + channel => handleChannelUnsubscribe(channel), ); } diff --git a/packages/dashboard-backend/tsconfig.json b/packages/dashboard-backend/tsconfig.json index 40be11ec45..695267d21f 100644 --- a/packages/dashboard-backend/tsconfig.json +++ b/packages/dashboard-backend/tsconfig.json @@ -23,6 +23,7 @@ ], "include": [ "src", - "src/devfileSchemas/**/*.json" + "src/devfileSchemas/**/*.json", + "src/devworkspaceSchemas/**/*.json" ], } \ No newline at end of file diff --git a/packages/dashboard-frontend/package.json b/packages/dashboard-frontend/package.json index 3a96e3f54f..7c6eff22a1 100644 --- a/packages/dashboard-frontend/package.json +++ b/packages/dashboard-frontend/package.json @@ -32,18 +32,24 @@ "test:watch": "yarn test --watch" }, "dependencies": { - "@codemirror/lang-yaml": "^6.1.2", - "@codemirror/language": "^6.10.8", - "@codemirror/view": "^6.36.2", - "@lezer/highlight": "^1.2.1", + "@codemirror/autocomplete": "^6.20.1", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-yaml": "^6.1.3", + "@codemirror/language": "^6.12.3", + "@codemirror/lint": "^6.9.5", + "@codemirror/state": "^6.6.0", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.41.0", "@patternfly/react-core": "^6.4.0", "@patternfly/react-icons": "^6.4.0", "@patternfly/react-table": "^6.4.0", "@reduxjs/toolkit": "^2.2.7", - "@uiw/codemirror-theme-github": "^4.25.4", - "@uiw/codemirror-themes": "^4.25.4", - "@uiw/react-codemirror": "4.23.5", + "@xterm/addon-fit": "0.10.0", + "@xterm/addon-web-links": "0.11.0", + "@xterm/xterm": "5.5.0", + "ajv": "^8.18.0", "buffer": "^6.0.3", + "codemirror": "^6.0.2", "cron-parser": "^5.5.0", "cronstrue": "^3.13.0", "crypto-browserify": "^3.12.0", @@ -68,7 +74,8 @@ "reconnecting-websocket": "^4.4.0", "redux": "^5.0.1", "reflect-metadata": "^0.1.13", - "sanitize-html": "^2.17.4" + "sanitize-html": "^2.17.4", + "yaml": "^2.8.3" }, "devDependencies": { "@devfile/api": "2.3.0-1747843475", @@ -78,7 +85,6 @@ "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", - "@types/codemirror": "^5.60.15", "@types/history": "^4.7.6", "@types/jest": "^29.5.3", "@types/js-yaml": "^4.0.5", diff --git a/packages/dashboard-frontend/src/Layout/Navigation/AgentList.tsx b/packages/dashboard-frontend/src/Layout/Navigation/AgentList.tsx new file mode 100644 index 0000000000..73fc704217 --- /dev/null +++ b/packages/dashboard-frontend/src/Layout/Navigation/AgentList.tsx @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { api } from '@eclipse-che/common'; +import { + Dropdown, + DropdownItem, + DropdownList, + MenuToggle, + MenuToggleElement, + NavGroup, + NavItem, + NavList, + Tooltip, +} from '@patternfly/react-core'; +import { EllipsisVIcon } from '@patternfly/react-icons'; +import React from 'react'; +import { connect, ConnectedProps } from 'react-redux'; + +import { CheTooltip } from '@/components/CheTooltip'; +import { useStatusIcon } from '@/components/Workspace/Status/getStatusIcon'; +import statusStyles from '@/components/Workspace/Status/index.module.css'; +import styles from '@/Layout/Navigation/index.module.css'; +import { DevWorkspaceStatus } from '@/services/helpers/types'; +import { RootState } from '@/store'; +import { selectAiAgents } from '@/store/AiAgentRegistry'; +import { actionCreators, AgentPodPhase, AgentPodStatus } from '@/store/LocalDevfiles'; +import { selectAgentPodStatuses } from '@/store/LocalDevfiles/selectors'; + +function mapPhaseToStatus(phase: AgentPodPhase, ready: boolean): DevWorkspaceStatus { + switch (phase) { + case AgentPodPhase.RUNNING: + return ready ? DevWorkspaceStatus.RUNNING : DevWorkspaceStatus.STARTING; + case AgentPodPhase.PENDING: + return DevWorkspaceStatus.STARTING; + case AgentPodPhase.FAILED: + case AgentPodPhase.UNKNOWN: + return DevWorkspaceStatus.FAILED; + case AgentPodPhase.SUCCEEDED: + return DevWorkspaceStatus.STOPPED; + default: + return DevWorkspaceStatus.STARTING; + } +} + +function AgentStatusIndicator(props: { phase: AgentPodPhase; ready: boolean }): React.ReactElement { + const status = mapPhaseToStatus(props.phase, props.ready); + const icon = useStatusIcon(status); + return ( + + + {icon} + + + ); +} + +function findAgentDefinition( + agentId: string, + agents: api.AiAgentDefinition[], +): api.AiAgentDefinition | undefined { + return agents.find(a => agentId.startsWith(a.id)); +} + +function buildAgentTooltipContent( + agentId: string, + agents: api.AiAgentDefinition[], +): React.ReactElement { + const agentDef = findAgentDefinition(agentId, agents); + return ( +
+
+ {agentDef?.name || 'AI Agent'} +
+ {agentDef?.description &&
{agentDef.description}
} +
ID: {agentId}
+
+ ); +} + +interface AgentPodItemProps { + status: AgentPodStatus; + agents: api.AiAgentDefinition[]; + onStop: (agentId: string) => void; +} + +interface AgentPodItemState { + isDropdownOpen: boolean; + isHovered: boolean; + isFocused: boolean; +} + +class AgentPodItem extends React.PureComponent { + constructor(props: AgentPodItemProps) { + super(props); + this.state = { + isDropdownOpen: false, + isHovered: false, + isFocused: false, + }; + } + + private handleStop = () => { + this.setState({ isDropdownOpen: false }); + this.props.onStop(this.props.status.agentId); + }; + + render(): React.ReactElement { + const { status, agents } = this.props; + const { isDropdownOpen, isHovered, isFocused } = this.state; + const displayName = status.name.replace(/^agent-/, '') || status.agentId; + const isActionsVisible = isHovered || isFocused || isDropdownOpen; + + return ( + this.setState({ isHovered: true })} + onMouseLeave={() => this.setState({ isHovered: false })} + onFocus={() => this.setState({ isFocused: true })} + onBlur={e => { + if (!e.currentTarget.contains(e.relatedTarget as Node)) { + this.setState({ isFocused: false }); + } + }} + > + + + + {displayName} + + +
e.stopPropagation()} + > + this.setState({ isDropdownOpen: open })} + toggle={(toggleRef: React.Ref) => ( + this.setState({ isDropdownOpen: !isDropdownOpen })} + isExpanded={isDropdownOpen} + aria-label="Agent actions" + > + + + )} + popperProps={{ position: 'right' }} + > + + + Stop + + + +
+
+ ); + } +} + +type Props = MappedProps & { + activePath: string; +}; + +export class NavigationAgentList extends React.PureComponent { + private handleStop = (agentId: string) => { + this.props.stopAgent(agentId); + }; + + render(): React.ReactElement { + const { agentPodStatuses, agents } = this.props; + + if (agentPodStatuses.length === 0) { + return ; + } + + return ( + + + {agentPodStatuses.map(status => ( + + ))} + + + ); + } +} + +const mapStateToProps = (state: RootState) => ({ + agentPodStatuses: selectAgentPodStatuses(state), + agents: selectAiAgents(state), +}); + +const mapDispatchToProps = { + stopAgent: actionCreators.stopAgent, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); +type MappedProps = ConnectedProps; + +export default connector(NavigationAgentList); diff --git a/packages/dashboard-frontend/src/Layout/Navigation/MainList.tsx b/packages/dashboard-frontend/src/Layout/Navigation/MainList.tsx index a7f7ae2888..e178adcff5 100644 --- a/packages/dashboard-frontend/src/Layout/Navigation/MainList.tsx +++ b/packages/dashboard-frontend/src/Layout/Navigation/MainList.tsx @@ -17,6 +17,8 @@ import { connect, ConnectedProps } from 'react-redux'; import NavigationMainItem from '@/Layout/Navigation/MainItem'; import { ROUTE } from '@/Routes'; import { RootState } from '@/store'; +import { selectAiAgentRegistryEnabled } from '@/store/AiAgentRegistry'; +import { selectLocalDevfiles } from '@/store/LocalDevfiles/selectors'; import { selectAllWorkspaces } from '@/store/Workspaces/selectors'; import { NavigationItemObject } from '.'; @@ -27,14 +29,18 @@ type Props = MappedProps & { export class NavigationMainList extends React.PureComponent { private get items(): NavigationItemObject[] { - const { allWorkspaces } = this.props; + const { workspaces, devfiles, agentRegistryEnabled } = this.props; - const allWorkspacesNumber = allWorkspaces.length; + const allWorkspacesNumber = workspaces.length; + const devfilesNumber = devfiles.length; - return [ - { to: ROUTE.GET_STARTED, label: 'Create Workspace' }, - { to: ROUTE.WORKSPACES, label: `Workspaces (${allWorkspacesNumber})` }, - ]; + const items: NavigationItemObject[] = [{ to: ROUTE.GET_STARTED, label: 'Create Workspace' }]; + if (agentRegistryEnabled) { + items.push({ to: ROUTE.DEVFILE_CREATOR, label: `Devfiles (${devfilesNumber})` }); + } + items.push({ to: ROUTE.WORKSPACES, label: `Workspaces (${allWorkspacesNumber})` }); + + return items; } public render(): React.ReactElement { @@ -49,7 +55,9 @@ export class NavigationMainList extends React.PureComponent { } const mapStateToProps = (state: RootState) => ({ - allWorkspaces: selectAllWorkspaces(state), + workspaces: selectAllWorkspaces(state), + devfiles: selectLocalDevfiles(state), + agentRegistryEnabled: selectAiAgentRegistryEnabled(state), }); const connector = connect(mapStateToProps); diff --git a/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/index.tsx b/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/index.tsx index 6258f15f2e..a6bf86189d 100644 --- a/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/index.tsx +++ b/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/index.tsx @@ -10,7 +10,7 @@ * Red Hat, Inc. - initial API and implementation */ -import { NavItem } from '@patternfly/react-core'; +import { NavItem, Tooltip } from '@patternfly/react-core'; import React from 'react'; import { WorkspaceStatusIndicator } from '@/components/Workspace/Status/Indicator'; @@ -98,7 +98,13 @@ export class NavigationRecentItem extends React.PureComponent { containerScc={WorkspaceAdapter.getContainerScc(item.workspace.ref)} workspaceName={item.workspace.name} /> - {item.label} + {item.label.length > 25 ? ( + + {item.label} + + ) : ( + {item.label} + )} ({ + useTheme: () => ({ + themePreference: 'LIGHT', + isDarkTheme: false, + setThemePreference: jest.fn(), + }), +})); + +function buildAgentDef(overrides?: Partial): api.AiAgentDefinition { + return { + id: 'test-agent', + name: 'Test Agent', + publisher: 'test-publisher', + description: 'A test agent', + icon: '', + docsUrl: '', + image: 'test-image', + tag: 'latest', + memoryLimit: '512Mi', + cpuLimit: '500m', + terminalPort: 8080, + env: [], + initCommand: '', + ...overrides, + }; +} + +function buildAgentPodStatus(overrides?: Partial): AgentPodStatus { + return { + agentId: 'test-agent', + name: 'agent-test-agent', + phase: AgentPodPhase.RUNNING, + ready: true, + serviceUrl: undefined, + ...overrides, + }; +} + +const mockStopAgent = jest.fn(); + +function renderComponent( + agentPodStatuses: AgentPodStatus[] = [], + agents: api.AiAgentDefinition[] = [], +) { + return render( + , + ); +} + +describe('NavigationAgentList', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('empty state', () => { + test('should not render AGENT PODS title when no agent pod statuses', () => { + renderComponent([], []); + expect(screen.queryByText('AGENT PODS')).not.toBeInTheDocument(); + }); + }); + + describe('with agent pods', () => { + const agents: api.AiAgentDefinition[] = [ + buildAgentDef({ id: 'anthropic/claude', name: 'Claude Code', description: 'AI assistant' }), + buildAgentDef({ id: 'openai/gpt', name: 'GPT Agent', description: '' }), + ]; + + test('should render AGENT PODS nav group title', () => { + const statuses = [buildAgentPodStatus({ agentId: 'anthropic/claude', name: 'agent-claude' })]; + renderComponent(statuses, agents); + expect(screen.getByText('AGENT PODS')).toBeInTheDocument(); + }); + + test('should render agent pod items', () => { + const statuses = [ + buildAgentPodStatus({ agentId: 'anthropic/claude', name: 'agent-claude' }), + buildAgentPodStatus({ + agentId: 'openai/gpt', + name: 'agent-gpt', + phase: AgentPodPhase.PENDING, + ready: false, + }), + ]; + renderComponent(statuses, agents); + const items = screen.getAllByTestId('agent-pod-item'); + expect(items).toHaveLength(2); + }); + + test('should display agent name without agent- prefix', () => { + const statuses = [buildAgentPodStatus({ agentId: 'anthropic/claude', name: 'agent-claude' })]; + renderComponent(statuses, agents); + expect(screen.getByText('claude')).toBeInTheDocument(); + }); + + test('should display full name when name has no agent- prefix', () => { + const statuses = [buildAgentPodStatus({ agentId: 'anthropic/claude', name: 'claude-pod' })]; + renderComponent(statuses, agents); + expect(screen.getByText('claude-pod')).toBeInTheDocument(); + }); + + test('should render status indicator for each agent', () => { + const statuses = [buildAgentPodStatus({ agentId: 'anthropic/claude', name: 'agent-claude' })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-status-indicator')).toBeInTheDocument(); + }); + + test('should render multiple status indicators for multiple agents', () => { + const statuses = [ + buildAgentPodStatus({ agentId: 'anthropic/claude', name: 'agent-claude' }), + buildAgentPodStatus({ agentId: 'openai/gpt', name: 'agent-gpt' }), + ]; + renderComponent(statuses, agents); + expect(screen.getAllByTestId('agent-status-indicator')).toHaveLength(2); + }); + }); + + describe('agent pod phases', () => { + const agents = [buildAgentDef({ id: 'test-agent', name: 'Test Agent' })]; + + test('should render RUNNING phase with ready=true', () => { + const statuses = [buildAgentPodStatus({ phase: AgentPodPhase.RUNNING, ready: true })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-status-indicator')).toBeInTheDocument(); + }); + + test('should render RUNNING phase with ready=false as STARTING', () => { + const statuses = [buildAgentPodStatus({ phase: AgentPodPhase.RUNNING, ready: false })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-status-indicator')).toBeInTheDocument(); + }); + + test('should render PENDING phase', () => { + const statuses = [buildAgentPodStatus({ phase: AgentPodPhase.PENDING, ready: false })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-status-indicator')).toBeInTheDocument(); + }); + + test('should render FAILED phase', () => { + const statuses = [buildAgentPodStatus({ phase: AgentPodPhase.FAILED, ready: false })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-status-indicator')).toBeInTheDocument(); + }); + + test('should render UNKNOWN phase', () => { + const statuses = [buildAgentPodStatus({ phase: AgentPodPhase.UNKNOWN, ready: false })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-status-indicator')).toBeInTheDocument(); + }); + + test('should render SUCCEEDED phase', () => { + const statuses = [buildAgentPodStatus({ phase: AgentPodPhase.SUCCEEDED, ready: false })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-status-indicator')).toBeInTheDocument(); + }); + }); + + describe('stop action', () => { + const agents = [buildAgentDef({ id: 'test-agent', name: 'Test Agent' })]; + + test('should show dropdown with Stop option when kebab menu is clicked', async () => { + const statuses = [buildAgentPodStatus()]; + renderComponent(statuses, agents); + + // Hover over the nav item to make actions visible + const navItem = screen.getByTestId('agent-pod-item').closest('[class*="navItem"]')!; + await userEvent.hover(navItem); + + const kebabButton = screen.getByRole('button', { name: 'Agent actions' }); + await userEvent.click(kebabButton); + + expect(screen.getByText('Stop')).toBeInTheDocument(); + }); + + test('should call stopAgent when Stop is clicked', async () => { + const statuses = [buildAgentPodStatus({ agentId: 'my-agent-id' })]; + renderComponent(statuses, agents); + + const navItem = screen.getByTestId('agent-pod-item').closest('[class*="navItem"]')!; + await userEvent.hover(navItem); + + const kebabButton = screen.getByRole('button', { name: 'Agent actions' }); + await userEvent.click(kebabButton); + + const stopButton = screen.getByText('Stop'); + await userEvent.click(stopButton); + + expect(mockStopAgent).toHaveBeenCalledWith('my-agent-id'); + }); + + test('should close dropdown after Stop is clicked', async () => { + const statuses = [buildAgentPodStatus()]; + renderComponent(statuses, agents); + + const navItem = screen.getByTestId('agent-pod-item').closest('[class*="navItem"]')!; + await userEvent.hover(navItem); + + const kebabButton = screen.getByRole('button', { name: 'Agent actions' }); + await userEvent.click(kebabButton); + expect(kebabButton).toHaveAttribute('aria-expanded', 'true'); + + const stopButton = screen.getByText('Stop'); + await userEvent.click(stopButton); + + // Dropdown toggle should be collapsed after Stop is clicked + expect(kebabButton).toHaveAttribute('aria-expanded', 'false'); + }); + }); + + describe('agent tooltip content', () => { + test('should render agent pod item when agent definition matches by prefix', () => { + const agents = [ + buildAgentDef({ id: 'anthropic/claude', name: 'Claude Code', description: 'AI assistant' }), + ]; + const statuses = [ + buildAgentPodStatus({ agentId: 'anthropic/claude-instance-1', name: 'agent-claude' }), + ]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-pod-item')).toBeInTheDocument(); + }); + + test('should render agent pod item when no matching definition exists', () => { + const statuses = [buildAgentPodStatus({ agentId: 'unknown-agent', name: 'agent-unknown' })]; + renderComponent(statuses, []); + expect(screen.getByTestId('agent-pod-item')).toBeInTheDocument(); + }); + + test('should render agent pod item with agent that has no description', () => { + const agents = [buildAgentDef({ id: 'openai/gpt', name: 'GPT Agent', description: '' })]; + const statuses = [buildAgentPodStatus({ agentId: 'openai/gpt', name: 'agent-gpt' })]; + renderComponent(statuses, agents); + expect(screen.getByTestId('agent-pod-item')).toBeInTheDocument(); + }); + }); + + describe('hover and focus states', () => { + const agents = [buildAgentDef({ id: 'test-agent', name: 'Test Agent' })]; + + test('should handle mouse enter and leave events', async () => { + const statuses = [buildAgentPodStatus()]; + renderComponent(statuses, agents); + const item = screen.getByTestId('agent-pod-item').closest('[class*="navItem"]'); + expect(item).toBeDefined(); + + await userEvent.hover(item!); + await userEvent.unhover(item!); + }); + + test('should handle focus and blur events', () => { + const statuses = [buildAgentPodStatus()]; + renderComponent(statuses, agents); + const item = screen.getByTestId('agent-pod-item').closest('[class*="navItem"]'); + expect(item).toBeDefined(); + + item!.dispatchEvent(new FocusEvent('focus', { bubbles: true })); + item!.dispatchEvent(new FocusEvent('blur', { bubbles: true })); + }); + }); +}); diff --git a/packages/dashboard-frontend/src/Layout/Navigation/index.tsx b/packages/dashboard-frontend/src/Layout/Navigation/index.tsx index a5652d22b4..b1ce44e6f1 100644 --- a/packages/dashboard-frontend/src/Layout/Navigation/index.tsx +++ b/packages/dashboard-frontend/src/Layout/Navigation/index.tsx @@ -15,6 +15,7 @@ import { History, Location, UnregisterCallback } from 'history'; import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import NavigationAgentList from '@/Layout/Navigation/AgentList'; import NavigationMainList from '@/Layout/Navigation/MainList'; import NavigationRecentList from '@/Layout/Navigation/RecentList'; import { CREATE_NEW_IF_EXIST_SWITCH_ID } from '@/pages/GetStarted/SamplesList/Toolbar/CreateNewIfExistSwitch'; @@ -23,6 +24,7 @@ import { ROUTE } from '@/Routes'; import { buildGettingStartedLocation, buildWorkspacesLocation } from '@/services/helpers/location'; import { Workspace } from '@/services/workspace-adapter'; import { RootState } from '@/store'; +import { selectAiAgentRegistryEnabled } from '@/store/AiAgentRegistry'; import { selectAllWorkspaces, selectRecentWorkspaces } from '@/store/Workspaces/selectors'; export interface NavigationItemObject { @@ -151,16 +153,27 @@ export class Navigation extends React.PureComponent { } public render(): React.ReactElement { - const { recentWorkspaces } = this.props; + const { recentWorkspaces, agentRegistryEnabled } = this.props; const { activeLocation } = this.state; return ( ); } @@ -169,6 +182,7 @@ export class Navigation extends React.PureComponent { const mapStateToProps = (state: RootState) => ({ recentWorkspaces: selectRecentWorkspaces(state), allWorkspaces: selectAllWorkspaces(state), + agentRegistryEnabled: selectAiAgentRegistryEnabled(state), }); const connector = connect(mapStateToProps); diff --git a/packages/dashboard-frontend/src/Routes/index.tsx b/packages/dashboard-frontend/src/Routes/index.tsx index 1f2a129fce..76defe91e9 100644 --- a/packages/dashboard-frontend/src/Routes/index.tsx +++ b/packages/dashboard-frontend/src/Routes/index.tsx @@ -20,6 +20,9 @@ import WorkspaceDetailsContainer from '@/containers/WorkspaceDetails'; import WorkspacesListContainer from '@/containers/WorkspacesList'; import { Redirects } from '@/Routes/Redirects'; +const DevfilesListContainer = React.lazy(() => import('@/containers/DevfilesList')); +const DevfileDetailsContainer = React.lazy(() => import('@/containers/DevfileDetails')); + export enum ROUTE { HOME = '/', GET_STARTED = '/create-workspace', @@ -33,6 +36,8 @@ export enum ROUTE { FACTORY_LOADER_URL = '/load-factory?url=:url', USER_PREFERENCES = '/user-preferences', USER_PREFERENCES_TAB = '/user-preferences?tab=:tabId', + DEVFILE_CREATOR = '/devfiles', + DEVFILE_DETAILS = '/devfile/:namespace/:devfileId', } export type WorkspaceRouteParams = Params<'namespace' | 'workspaceName'>; @@ -61,6 +66,16 @@ export function AppRoutes(): React.ReactElement { path={ROUTE.USER_PREFERENCES} element={} /> + } + /> + } + /> } /> ); diff --git a/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx b/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx index 1e76283461..7ad1e412cc 100644 --- a/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx +++ b/packages/dashboard-frontend/src/__tests__/workspaceCreationTimeCheck.check.tsx @@ -124,6 +124,8 @@ describe('Workspace creation time', () => { return responseWithDelay({ permitted: true }, REQUEST_TIME_100); case 'http://localhost/dashboard/devfile-registry/devfiles/empty.yaml': return responseWithDelay('', REQUEST_TIME_200); + case '/dashboard/api/ai-agent-registry': + return responseWithDelay({ agents: [], defaultAgentId: '' }, REQUEST_TIME_100); default: console.warn('GET > unknown url:', url); return responseWithDelay({}, REQUEST_TIME_400); diff --git a/packages/dashboard-frontend/src/components/AgentPodEvents/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/AgentPodEvents/__tests__/index.spec.tsx new file mode 100644 index 0000000000..fc0222be73 --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentPodEvents/__tests__/index.spec.tsx @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { CoreV1Event } from '@kubernetes/client-node'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Store } from 'redux'; + +import AgentPodEvents from '@/components/AgentPodEvents'; +import getComponentRenderer from '@/services/__mocks__/getComponentRenderer'; +import { MockStoreBuilder } from '@/store/__mocks__/mockStore'; + +jest.mock('@/components/WorkspaceEvents/Item'); + +const { renderComponent } = getComponentRenderer(getComponent); + +describe('AgentPodEvents', () => { + const podName = 'agent-pod-1'; + + let event1: CoreV1Event; + let event2: CoreV1Event; + let eventNoMessage: CoreV1Event; + let eventOtherPod: CoreV1Event; + let eventNonPod: CoreV1Event; + + beforeEach(() => { + event1 = { + lastTimestamp: '2021-03-31T14:00:00Z' as unknown as Date, + message: 'message 1', + involvedObject: { + kind: 'Pod', + name: podName, + }, + metadata: { + uid: 'uid-1', + resourceVersion: '1', + }, + }; + event2 = { + lastTimestamp: '2021-03-31T14:01:00Z' as unknown as Date, + message: 'message 2', + involvedObject: { + kind: 'Pod', + name: podName, + }, + metadata: { + uid: 'uid-2', + resourceVersion: '2', + }, + }; + eventNoMessage = { + lastTimestamp: '2021-03-31T14:02:00Z' as unknown as Date, + message: '', + involvedObject: { + kind: 'Pod', + name: podName, + }, + metadata: { + uid: 'uid-3', + resourceVersion: '3', + }, + }; + eventOtherPod = { + lastTimestamp: '2021-03-31T14:03:00Z' as unknown as Date, + message: 'other pod message', + involvedObject: { + kind: 'Pod', + name: 'other-pod', + }, + metadata: { + uid: 'uid-4', + resourceVersion: '4', + }, + }; + eventNonPod = { + lastTimestamp: '2021-03-31T14:04:00Z' as unknown as Date, + message: 'deployment message', + involvedObject: { + kind: 'Deployment', + name: podName, + }, + metadata: { + uid: 'uid-5', + resourceVersion: '5', + }, + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should render empty state when podName is undefined', () => { + const store = new MockStoreBuilder().build(); + renderComponent(store, undefined); + + expect(screen.getByText('No events to show.')).toBeTruthy(); + expect(screen.getByText('Events will be streamed for a starting agent pod.')).toBeTruthy(); + }); + + test('should render empty state when no events match the pod', () => { + const store = new MockStoreBuilder().withEvents({ events: [eventOtherPod] }).build(); + renderComponent(store, podName); + + expect(screen.getByText('No events to show.')).toBeTruthy(); + expect(screen.getByText('Events will be streamed for the agent pod.')).toBeTruthy(); + }); + + test('should render empty state when all pod events have no message', () => { + const store = new MockStoreBuilder().withEvents({ events: [eventNoMessage] }).build(); + renderComponent(store, podName); + + expect(screen.getByText('No events to show.')).toBeTruthy(); + }); + + test('should filter events by podName and kind Pod', () => { + const store = new MockStoreBuilder() + .withEvents({ events: [event1, eventOtherPod, eventNonPod] }) + .build(); + renderComponent(store, podName); + + const eventItems = screen.getAllByTestId('event-item'); + expect(eventItems.length).toBe(1); + }); + + test('should show correct number of events', () => { + const store = new MockStoreBuilder().withEvents({ events: [event1, event2] }).build(); + renderComponent(store, podName); + + const eventItems = screen.getAllByTestId('event-item'); + expect(eventItems.length).toBe(2); + }); + + test('should filter out events without message', () => { + const store = new MockStoreBuilder().withEvents({ events: [event1, eventNoMessage] }).build(); + renderComponent(store, podName); + + const eventItems = screen.getAllByTestId('event-item'); + expect(eventItems.length).toBe(1); + }); + + test('should display streaming status text', () => { + const store = new MockStoreBuilder().withEvents({ events: [event1] }).build(); + renderComponent(store, podName); + + expect(screen.getByText('Streaming events...')).toBeTruthy(); + expect(screen.getByText('Older events are not stored.')).toBeTruthy(); + }); + + test('should display event count using Pluralize', () => { + const store = new MockStoreBuilder().withEvents({ events: [event1, event2] }).build(); + renderComponent(store, podName); + + expect(screen.getByText('2 events')).toBeTruthy(); + }); + + test('should display singular event count', () => { + const store = new MockStoreBuilder().withEvents({ events: [event1] }).build(); + renderComponent(store, podName); + + expect(screen.getByText('1 event')).toBeTruthy(); + }); + + test('should apply className prop', () => { + const store = new MockStoreBuilder().withEvents({ events: [event1] }).build(); + const { reRenderComponent } = renderComponent(store, podName, 'my-custom-class'); + expect(document.querySelector('.my-custom-class')).toBeTruthy(); + + reRenderComponent(store, podName, 'another-class'); + expect(document.querySelector('.another-class')).toBeTruthy(); + }); + + test('should sort events by time (newest first)', () => { + const store = new MockStoreBuilder().withEvents({ events: [event1, event2] }).build(); + renderComponent(store, podName); + + const eventItems = screen.getAllByTestId('event-item'); + // event2 is newer, should appear first after sorting + expect(eventItems.length).toBe(2); + }); + + test('should update when store events change', () => { + const store1 = new MockStoreBuilder().withEvents({ events: [event1] }).build(); + const { reRenderComponent } = renderComponent(store1, podName); + + expect(screen.getAllByTestId('event-item').length).toBe(1); + + const store2 = new MockStoreBuilder().withEvents({ events: [event1, event2] }).build(); + reRenderComponent(store2, podName); + + expect(screen.getAllByTestId('event-item').length).toBe(2); + }); +}); + +function getComponent( + store: Store, + podName: string | undefined, + className?: string, +): React.ReactElement { + return ( + + + + ); +} diff --git a/packages/dashboard-frontend/src/components/AgentPodEvents/index.tsx b/packages/dashboard-frontend/src/components/AgentPodEvents/index.tsx new file mode 100644 index 0000000000..9de8247604 --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentPodEvents/index.tsx @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { CoreV1Event } from '@kubernetes/client-node'; +import { + Content, + ContentVariants, + EmptyState, + EmptyStateBody, + Flex, + FlexItem, + Stack, + StackItem, +} from '@patternfly/react-core'; +import { FileIcon } from '@patternfly/react-icons'; +import React from 'react'; +import Pluralize from 'react-pluralize'; +import { connect, ConnectedProps } from 'react-redux'; + +import compareEventTime from '@/components/WorkspaceEvents/compareEventTime'; +import styles from '@/components/WorkspaceEvents/index.module.css'; +import { WorkspaceEventsItem } from '@/components/WorkspaceEvents/Item'; +import { RootState } from '@/store'; +import { selectAllEvents } from '@/store/Events/selectors'; + +export type Props = { + podName: string | undefined; + className?: string; +} & MappedProps; + +class AgentPodEvents extends React.PureComponent { + private getEventItems(events: CoreV1Event[]): React.ReactNode[] { + return events + .filter(event => event.message) + .sort(compareEventTime) + .map((event, index) => { + return ( + +
+ +
+
+ ); + }); + } + + render() { + const { podName, allEvents } = this.props; + + if (!podName) { + return ( + + Events will be streamed for a starting agent pod. + + ); + } + + const podEvents = allEvents.filter( + event => event.involvedObject.name === podName && event.involvedObject.kind === 'Pod', + ); + + const eventItems = this.getEventItems(podEvents); + + if (eventItems.length === 0) { + return ( + + Events will be streamed for the agent pod. + + ); + } + + return ( +
+ + + + + Streaming events... + + + + Showing + + + + + {eventItems} + + Older events are not stored. + + +
+ ); + } +} + +const mapStateToProps = (state: RootState) => ({ + allEvents: selectAllEvents(state), +}); + +const connector = connect(mapStateToProps); + +type MappedProps = ConnectedProps; +export default connector(AgentPodEvents); diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/StatusConnecting/index.module.css b/packages/dashboard-frontend/src/components/AgentTerminal/StatusConnecting/index.module.css new file mode 100644 index 0000000000..37acaaece5 --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/StatusConnecting/index.module.css @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +.statusMessage { + padding: 8px 12px; + font-size: 12px; + color: var(--pf-t--global--color--status--warning--default); + background-color: var(--pf-t--global--background--color--primary--default); +} diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/StatusConnecting/index.tsx b/packages/dashboard-frontend/src/components/AgentTerminal/StatusConnecting/index.tsx new file mode 100644 index 0000000000..972a5f40da --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/StatusConnecting/index.tsx @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import React from 'react'; + +import styles from '@/components/AgentTerminal/StatusConnecting/index.module.css'; + +const StatusConnecting: React.FC = () => ( +
Connecting to agent...
+); + +export default StatusConnecting; diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/StatusError/index.module.css b/packages/dashboard-frontend/src/components/AgentTerminal/StatusError/index.module.css new file mode 100644 index 0000000000..edeb08cce7 --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/StatusError/index.module.css @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +.statusMessage { + padding: 8px 12px; + font-size: 12px; + color: var(--pf-t--global--color--status--danger--default); + background-color: var(--pf-t--global--background--color--primary--default); +} diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/StatusError/index.tsx b/packages/dashboard-frontend/src/components/AgentTerminal/StatusError/index.tsx new file mode 100644 index 0000000000..d53ba34fc5 --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/StatusError/index.tsx @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import React from 'react'; + +import styles from '@/components/AgentTerminal/StatusError/index.module.css'; + +const StatusError: React.FC = () => ( +
Failed to load terminal
+); + +export default StatusError; diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/StatusConnecting.spec.tsx b/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/StatusConnecting.spec.tsx new file mode 100644 index 0000000000..7e0110219c --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/StatusConnecting.spec.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import StatusConnecting from '@/components/AgentTerminal/StatusConnecting'; + +describe('StatusConnecting', () => { + test('should render without errors', () => { + render(); + expect(screen.getByText('Connecting to agent...')).toBeDefined(); + }); + + test('should contain the expected message text', () => { + const { container } = render(); + expect(container.textContent).toBe('Connecting to agent...'); + }); +}); diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/StatusError.spec.tsx b/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/StatusError.spec.tsx new file mode 100644 index 0000000000..3b61136292 --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/StatusError.spec.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import StatusError from '@/components/AgentTerminal/StatusError'; + +describe('StatusError', () => { + test('should render without errors', () => { + render(); + expect(screen.getByText('Failed to load terminal')).toBeDefined(); + }); + + test('should contain the expected error message text', () => { + const { container } = render(); + expect(container.textContent).toBe('Failed to load terminal'); + }); +}); diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/index.spec.tsx new file mode 100644 index 0000000000..060698cc9f --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/__tests__/index.spec.tsx @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import { act, render, screen } from '@testing-library/react'; +import React from 'react'; + +import AgentTerminal, { Props } from '@/components/AgentTerminal'; + +describe('AgentTerminal', () => { + const defaultProps: Props = { + terminalUrl: 'https://terminal.example.com/ttyd', + namespace: 'user-namespace', + devfileName: 'my-devfile', + isDarkTheme: false, + }; + + let addEventListenerSpy: jest.SpyInstance; + let removeEventListenerSpy: jest.SpyInstance; + + beforeEach(() => { + jest.useFakeTimers(); + addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.clearAllTimers(); + jest.useRealTimers(); + }); + + test('should render iframe with correct src', () => { + render(); + + const iframe = screen.getByTitle('Agent Terminal'); + expect(iframe).toBeTruthy(); + expect(iframe.getAttribute('src')).toBe(defaultProps.terminalUrl); + }); + + test('should show connecting status initially', () => { + render(); + + expect(screen.getByText('Connecting to agent...')).toBeTruthy(); + }); + + test('should register message event listener on mount', () => { + render(); + + expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function)); + }); + + test('should remove message event listener on unmount', () => { + const { unmount } = render(); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function)); + }); + + test('should transition to connected state on ttyd-ready message', () => { + render(); + + act(() => { + const messageEvent = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent); + }); + + expect(screen.queryByText('Connecting to agent...')).toBeNull(); + expect(screen.queryByText('Failed to load terminal')).toBeNull(); + }); + + test('should transition to error state on timeout', () => { + render(); + + act(() => { + jest.advanceTimersByTime(30_000); + }); + + expect(screen.getByText('Failed to load terminal')).toBeTruthy(); + expect(screen.queryByText('Connecting to agent...')).toBeNull(); + }); + + test('should not transition to error state if already connected', () => { + render(); + + // First become connected + act(() => { + const messageEvent = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent); + }); + + // Then wait for timeout + act(() => { + jest.advanceTimersByTime(30_000); + }); + + expect(screen.queryByText('Failed to load terminal')).toBeNull(); + expect(screen.queryByText('Connecting to agent...')).toBeNull(); + }); + + test('should transition to error state via handleError callback', () => { + // React's onError for iframes is not reliably fired in JSDOM. + // We test the handleError path by accessing it through a component ref. + const ref = React.createRef(); + render(); + + expect(screen.getByText('Connecting to agent...')).toBeTruthy(); + + act(() => { + ref.current?.setState({ connectionStatus: 'error' }); + }); + + expect(screen.getByText('Failed to load terminal')).toBeTruthy(); + expect(screen.queryByText('Connecting to agent...')).toBeNull(); + }); + + test('should ignore non-ttyd-ready messages', () => { + render(); + + act(() => { + const messageEvent = new MessageEvent('message', { + data: { type: 'some-other-message' }, + }); + window.dispatchEvent(messageEvent); + }); + + expect(screen.getByText('Connecting to agent...')).toBeTruthy(); + }); + + test('should ignore messages without data', () => { + render(); + + act(() => { + const messageEvent = new MessageEvent('message', {}); + window.dispatchEvent(messageEvent); + }); + + expect(screen.getByText('Connecting to agent...')).toBeTruthy(); + }); + + test('should post init command to iframe on ready when initCommand is provided', () => { + const mockPostMessage = jest.fn(); + const props: Props = { + ...defaultProps, + initCommand: 'che-ai-agent', + }; + + render(); + + const iframe = screen.getByTitle('Agent Terminal') as HTMLIFrameElement; + Object.defineProperty(iframe, 'contentWindow', { + value: { postMessage: mockPostMessage }, + writable: true, + }); + + act(() => { + const messageEvent = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent); + }); + + expect(mockPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'terminal-input', + data: expect.stringContaining('che-ai-agent'), + }), + window.location.origin, + ); + }); + + test('should include devfileId in init command context when provided', () => { + const mockPostMessage = jest.fn(); + const props: Props = { + ...defaultProps, + initCommand: 'che-ai-agent', + devfileId: 'devfile-123', + }; + + render(); + + const iframe = screen.getByTitle('Agent Terminal') as HTMLIFrameElement; + Object.defineProperty(iframe, 'contentWindow', { + value: { postMessage: mockPostMessage }, + writable: true, + }); + + act(() => { + const messageEvent = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent); + }); + + expect(mockPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'terminal-input', + data: expect.stringContaining('devfile-123'), + }), + window.location.origin, + ); + }); + + test('should send empty init command when initCommand is not provided', () => { + const mockPostMessage = jest.fn(); + + render(); + + const iframe = screen.getByTitle('Agent Terminal') as HTMLIFrameElement; + Object.defineProperty(iframe, 'contentWindow', { + value: { postMessage: mockPostMessage }, + writable: true, + }); + + act(() => { + const messageEvent = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent); + }); + + expect(mockPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'terminal-input', + data: '', + }), + window.location.origin, + ); + }); + + test('should send theme update on isDarkTheme prop change', () => { + const mockPostMessage = jest.fn(); + + const { rerender } = render(); + + const iframe = screen.getByTitle('Agent Terminal') as HTMLIFrameElement; + Object.defineProperty(iframe, 'contentWindow', { + value: { postMessage: mockPostMessage }, + writable: true, + }); + + rerender(); + + expect(mockPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'terminal-theme', + theme: expect.any(Object), + }), + window.location.origin, + ); + }); + + test('should not send theme update when isDarkTheme does not change', () => { + const mockPostMessage = jest.fn(); + + const { rerender } = render(); + + const iframe = screen.getByTitle('Agent Terminal') as HTMLIFrameElement; + Object.defineProperty(iframe, 'contentWindow', { + value: { postMessage: mockPostMessage }, + writable: true, + }); + + rerender(); + + expect(mockPostMessage).not.toHaveBeenCalled(); + }); + + test('should not call postMessage when iframe contentWindow is null', () => { + const { rerender } = render(); + + // contentWindow is null by default in JSDOM -- no error should be thrown + expect(() => { + rerender(); + }).not.toThrow(); + }); + + test('should handle duplicate ttyd-ready messages gracefully', () => { + render(); + + act(() => { + const messageEvent1 = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent1); + }); + + // Should not error on second ready message + act(() => { + const messageEvent2 = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent2); + }); + + expect(screen.queryByText('Connecting to agent...')).toBeNull(); + expect(screen.queryByText('Failed to load terminal')).toBeNull(); + }); + + test('should clear ready timer on unmount before timeout', () => { + const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); + + const { unmount } = render(); + + unmount(); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + + clearTimeoutSpy.mockRestore(); + }); + + test('should include namespace and devfileName in init command context', () => { + const mockPostMessage = jest.fn(); + const props: Props = { + ...defaultProps, + namespace: 'test-ns', + devfileName: 'test-devfile', + initCommand: 'run-agent', + }; + + render(); + + const iframe = screen.getByTitle('Agent Terminal') as HTMLIFrameElement; + Object.defineProperty(iframe, 'contentWindow', { + value: { postMessage: mockPostMessage }, + writable: true, + }); + + act(() => { + const messageEvent = new MessageEvent('message', { + data: { type: 'ttyd-ready' }, + }); + window.dispatchEvent(messageEvent); + }); + + const sentData = mockPostMessage.mock.calls.find( + (call: unknown[]) => (call[0] as Record).type === 'terminal-input', + ); + expect(sentData).toBeTruthy(); + const inputData = (sentData[0] as Record).data; + expect(inputData).toContain('test-ns'); + expect(inputData).toContain('test-devfile'); + expect(inputData).toContain('devfile-creator-storage'); + expect(inputData).toContain('run-agent'); + expect(inputData).toMatch(/\r$/); + }); +}); diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/index.module.css b/packages/dashboard-frontend/src/components/AgentTerminal/index.module.css new file mode 100644 index 0000000000..ba485620b4 --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/index.module.css @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +.terminalContainer :global(.xterm) { + margin: 0 5px; +} + +.wrapper { + display: flex; + flex-direction: column; + height: calc(100vh - 350px); + border: 1px solid var(--pf-t--global--border--color--default); +} + +.iframe { + flex: 1; + + width: 100%; + min-height: 0; + + background-color: var(--pf-t--global--background--color--primary--default); + border: none; +} diff --git a/packages/dashboard-frontend/src/components/AgentTerminal/index.tsx b/packages/dashboard-frontend/src/components/AgentTerminal/index.tsx new file mode 100644 index 0000000000..8a2c8e192c --- /dev/null +++ b/packages/dashboard-frontend/src/components/AgentTerminal/index.tsx @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2018-2025 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +// Generated by Claude Opus 4.6 + +import React from 'react'; + +import styles from '@/components/AgentTerminal/index.module.css'; +import StatusConnecting from '@/components/AgentTerminal/StatusConnecting'; +import StatusError from '@/components/AgentTerminal/StatusError'; +import { AGENT_TERMINAL_THEMES } from '@/components/AgentTerminal/themes'; + +const READY_TIMEOUT_MS = 30_000; + +export interface Props { + terminalUrl: string; + namespace: string; + devfileName: string; + devfileId?: string; + isDarkTheme: boolean; + initCommand?: string; +} + +interface State { + connectionStatus: 'connecting' | 'connected' | 'error'; +} + +export default class AgentTerminal extends React.PureComponent { + private iframeRef = React.createRef(); + private readyTimer: ReturnType | undefined; + private messageHandler: ((event: MessageEvent) => void) | undefined; + private readonly initialSrc: string; + + constructor(props: Props) { + super(props); + this.state = { + connectionStatus: 'connecting', + }; + this.initialSrc = props.terminalUrl; + } + + componentDidMount() { + this.messageHandler = (event: MessageEvent) => { + if (event.data?.type === 'ttyd-ready') { + this.onReady(); + } + }; + window.addEventListener('message', this.messageHandler); + + this.readyTimer = setTimeout(() => { + if (this.state.connectionStatus === 'connecting') { + this.setState({ connectionStatus: 'error' }); + } + }, READY_TIMEOUT_MS); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.isDarkTheme !== this.props.isDarkTheme) { + this.sendThemeUpdate(); + } + } + + componentWillUnmount() { + if (this.readyTimer) { + clearTimeout(this.readyTimer); + } + if (this.messageHandler) { + window.removeEventListener('message', this.messageHandler); + } + } + + private onReady() { + if (this.state.connectionStatus === 'connected') return; + if (this.readyTimer) { + clearTimeout(this.readyTimer); + } + this.setState({ connectionStatus: 'connected' }); + this.sendInitCommand(); + } + + private postToIframe(message: Record) { + const iframe = this.iframeRef.current; + if (!iframe?.contentWindow) return; + iframe.contentWindow.postMessage(message, window.location.origin); + } + + private sendThemeUpdate() { + const theme = this.props.isDarkTheme ? AGENT_TERMINAL_THEMES.dark : AGENT_TERMINAL_THEMES.light; + this.postToIframe({ type: 'terminal-theme', theme }); + } + + private buildInitCommand(): string { + const { namespace, devfileName, devfileId, initCommand } = this.props; + if (!initCommand) { + return ''; + } + let context = + `'You are working in namespace "${namespace}" on a devfile named "${devfileName}". ` + + `It is stored in a Kubernetes ConfigMap named "devfile-creator-storage" in namespace "${namespace}".`; + if (devfileId) { + context += ` The entry key is "${devfileId}".`; + } + context += `'`; + return `${initCommand} ${context}\r`; + } + + private sendInitCommand() { + const command = this.buildInitCommand(); + this.postToIframe({ type: 'terminal-input', data: command }); + } + + private handleError = () => { + this.setState({ connectionStatus: 'error' }); + }; + + render(): React.ReactElement { + const { connectionStatus } = this.state; + + return ( +
+ {connectionStatus === 'connecting' && } + {connectionStatus === 'error' && } +