From 203577fb69877dc4eafd64404bc9550fdd459668 Mon Sep 17 00:00:00 2001 From: Oleksii Orel Date: Thu, 18 Jan 2024 10:42:15 +0200 Subject: [PATCH] Add a new workspace creation URL parameter (#1037) * feat: Add a new workspace creation URL parameter named editor-image Signed-off-by: Oleksii Orel --- .deps/EXCLUDED/prod.md | 1 + .deps/prod.md | 4 +- .../CreatingSteps/Apply/Resources/index.tsx | 2 +- .../src/preload/__tests__/main.spec.ts | 9 + .../helpers/factoryFlow/buildFactoryParams.ts | 8 + .../devWorkspaces/__tests__/actions.spec.ts | 19 +- .../__tests__/editorImage.spec.ts | 175 ++++++++++++++++++ .../test-devfile-without-components.yaml | 3 + ...vworkspace-template-with-custom-image.yaml | 18 ++ ...workspace-template-without-components.yaml | 7 + .../fixtures/test-devworkspace-template.yaml | 18 ++ ...test-editor-devfile-with-custom-image.yaml | 14 ++ .../fixtures/test-editor-devfile.yaml | 14 ++ .../Workspaces/devWorkspaces/editorImage.ts | 104 +++++++++++ .../store/Workspaces/devWorkspaces/index.ts | 29 ++- .../store/__mocks__/devWorkspaceBuilder.ts | 5 + yarn.lock | 13 +- 17 files changed, 424 insertions(+), 19 deletions(-) create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/editorImage.spec.ts create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devfile-without-components.yaml create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-with-custom-image.yaml create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-without-components.yaml create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template.yaml create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile-with-custom-image.yaml create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile.yaml create mode 100644 packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/editorImage.ts diff --git a/.deps/EXCLUDED/prod.md b/.deps/EXCLUDED/prod.md index e20e6a09d..280b6d71d 100644 --- a/.deps/EXCLUDED/prod.md +++ b/.deps/EXCLUDED/prod.md @@ -3,6 +3,7 @@ This file lists dependencies that do not need CQs or auto-detection does not wor | Packages | Resolved CQs | | --- | --- | | `@fastify/cors@8.4.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@fastify/cors/8.4.1) | +| `@fastify/reply-from@^9.6.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@fastify/reply-from/9.6.0) | | `@patternfly/react-core@4.278.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-core/4.278.0) | | `@patternfly/react-table@4.113.6` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-table/4.113.6) | | `@patternfly/react-icons@4.93.7` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-icons/4.93.7) | diff --git a/.deps/prod.md b/.deps/prod.md index c80325d6c..8f8131e80 100644 --- a/.deps/prod.md +++ b/.deps/prod.md @@ -20,7 +20,7 @@ | [`@fastify/fast-json-stringify-compiler@4.3.0`](git+https://github.com/fastify/fast-json-stringify-compiler.git) | MIT | clearlydefined | | [`@fastify/http-proxy@9.3.0`](git+https://github.com/fastify/fastify-http-proxy.git) | MIT | clearlydefined | | [`@fastify/oauth2@7.5.0`](git+https://github.com/fastify/fastify-oauth2.git) | MIT | clearlydefined | -| [`@fastify/reply-from@9.4.0`](git+https://github.com/fastify/fastify-reply-from.git) | MIT | clearlydefined | +| [`@fastify/reply-from@9.6.0`](git+https://github.com/fastify/fastify-reply-from.git) | MIT | clearlydefined | | [`@fastify/send@2.1.0`](git+https://github.com/fastify/send.git) | MIT | clearlydefined | | [`@fastify/static@6.12.0`](https://github.com/fastify/fastify-static.git) | MIT | clearlydefined | | [`@fastify/swagger-ui@1.9.2`](git+https://github.com/fastify/fastify-swagger-ui.git) | MIT | clearlydefined | @@ -170,7 +170,7 @@ | [`file-selector@0.1.19`](https://github.com/react-dropzone/file-selector.git) | MIT | [CQ22350](https://dev.eclipse.org/ipzilla/show_bug.cgi?id=22350) | | [`find-my-way@7.7.0`](git+https://github.com/delvedor/find-my-way.git) | MIT | clearlydefined | | [`focus-trap@6.9.2`](git+https://github.com/focus-trap/focus-trap.git) | MIT | clearlydefined | -| [`follow-redirects@1.15.3`](git@github.com:follow-redirects/follow-redirects.git) | MIT | #10782 | +| [`follow-redirects@1.15.4`](git@github.com:follow-redirects/follow-redirects.git) | MIT | #10782 | | [`forever-agent@0.6.1`](https://github.com/mikeal/forever-agent) | Apache-2.0 | clearlydefined | | [`form-data@4.0.0`](git://github.com/form-data/form-data.git) | MIT | clearlydefined | | [`forwarded@0.2.0`](https://github.com/jshttp/forwarded.git) | MIT | clearlydefined | diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx index 55cee5dda..3e5a00c29 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx @@ -226,7 +226,7 @@ class CreatingStepApplyResources extends ProgressStep { return false; } - await this.props.createWorkspaceFromResources(...resources, cheEditor); + await this.props.createWorkspaceFromResources(...resources, factoryParams, cheEditor); // wait for the workspace creation to complete return false; diff --git a/packages/dashboard-frontend/src/preload/__tests__/main.spec.ts b/packages/dashboard-frontend/src/preload/__tests__/main.spec.ts index 8b617dfd2..bc45a7b40 100644 --- a/packages/dashboard-frontend/src/preload/__tests__/main.spec.ts +++ b/packages/dashboard-frontend/src/preload/__tests__/main.spec.ts @@ -32,6 +32,15 @@ describe('test buildFactoryLoaderPath()', () => { ); }); + test('editor-image parameter', () => { + const result = buildFactoryLoaderPath( + 'git@github.com:eclipse-che/che-dashboard.git?editor-image=quay.io/mloriedo/che-code:copilot-builtin', + ); + expect(result).toEqual( + '/f?editor-image=quay.io%2Fmloriedo%2Fche-code%3Acopilot-builtin&url=git%40github.com%3Aeclipse-che%2Fche-dashboard.git', + ); + }); + test('devfilePath parameter', () => { const result = buildFactoryLoaderPath( 'git@github.com:eclipse-che/che-dashboard.git?devfilePath=devfilev2.yaml', diff --git a/packages/dashboard-frontend/src/services/helpers/factoryFlow/buildFactoryParams.ts b/packages/dashboard-frontend/src/services/helpers/factoryFlow/buildFactoryParams.ts index a334479fe..1d9263f81 100644 --- a/packages/dashboard-frontend/src/services/helpers/factoryFlow/buildFactoryParams.ts +++ b/packages/dashboard-frontend/src/services/helpers/factoryFlow/buildFactoryParams.ts @@ -20,6 +20,7 @@ export const POLICIES_CREATE_ATTR = 'policies.create'; export const STORAGE_TYPE_ATTR = 'storageType'; export const REMOTES_ATTR = 'remotes'; export const IMAGE_ATTR = 'image'; +export const EDITOR_IMAGE_ATTR = 'editor-image'; export const USE_DEFAULT_DEVFILE = 'useDefaultDevfile'; export const DEBUG_WORKSPACE_START = 'debugWorkspaceStart'; export const PROPAGATE_FACTORY_ATTRS = [ @@ -32,6 +33,7 @@ export const PROPAGATE_FACTORY_ATTRS = [ STORAGE_TYPE_ATTR, REMOTES_ATTR, IMAGE_ATTR, + EDITOR_IMAGE_ATTR, ]; export const OVERRIDE_ATTR_PREFIX = 'override.'; export const DEFAULT_POLICIES_CREATE = 'peruser'; @@ -46,6 +48,7 @@ export type FactoryParams = { errorCode: ErrorCode | undefined; storageType: che.WorkspaceStorageType | undefined; cheEditor: string | undefined; + editorImage: string | undefined; remotes: string | undefined; image: string | undefined; useDefaultDevfile: boolean; @@ -59,6 +62,7 @@ export type ErrorCode = 'invalid_request' | 'access_denied'; export function buildFactoryParams(searchParams: URLSearchParams): FactoryParams { return { cheEditor: getEditorId(searchParams), + editorImage: getEditorImage(searchParams), errorCode: getErrorCode(searchParams), factoryId: buildFactoryId(searchParams), factoryUrl: getFactoryUrl(searchParams), @@ -108,6 +112,10 @@ function getEditorId(searchParams: URLSearchParams): string | undefined { return searchParams.get(EDITOR_ATTR) || undefined; } +function getEditorImage(searchParams: URLSearchParams): string | undefined { + return searchParams.get(EDITOR_IMAGE_ATTR) || undefined; +} + function getErrorCode(searchParams: URLSearchParams): ErrorCode | undefined { return (searchParams.get(ERROR_CODE_ATTR) as ErrorCode) || undefined; } diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts index 3def11df6..63a1fdc7a 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/actions.spec.ts @@ -804,7 +804,11 @@ describe('DevWorkspace store, actions', () => { mockOnStart.mockResolvedValueOnce(undefined); await store.dispatch( - testStore.actionCreators.createWorkspaceFromResources(devWorkspace, devWorkspaceTemplate), + testStore.actionCreators.createWorkspaceFromResources( + devWorkspace, + devWorkspaceTemplate, + {}, + ), ); const actions = store.getActions(); @@ -844,7 +848,11 @@ describe('DevWorkspace store, actions', () => { try { await store.dispatch( - testStore.actionCreators.createWorkspaceFromResources(devWorkspace, devWorkspaceTemplate), + testStore.actionCreators.createWorkspaceFromResources( + devWorkspace, + devWorkspaceTemplate, + {}, + ), ); } catch (e) { // no-op @@ -892,7 +900,11 @@ describe('DevWorkspace store, actions', () => { it('should provide default editor id when creating a new workspace from resources', async () => { await store.dispatch( - testStore.actionCreators.createWorkspaceFromResources(devWorkspace, devWorkspaceTemplate), + testStore.actionCreators.createWorkspaceFromResources( + devWorkspace, + devWorkspaceTemplate, + {}, + ), ); expect(mockCreateDevWorkspace).toHaveBeenCalledWith( @@ -907,6 +919,7 @@ describe('DevWorkspace store, actions', () => { testStore.actionCreators.createWorkspaceFromResources( devWorkspace, devWorkspaceTemplate, + {}, 'editorid', ), ); diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/editorImage.spec.ts b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/editorImage.spec.ts new file mode 100644 index 000000000..d5b5610e0 --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/editorImage.spec.ts @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2018-2024 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 common from '@eclipse-che/common'; +import fs from 'fs'; +import { dump, load } from 'js-yaml'; + +import devfileApi from '@/services/devfileApi'; +import { DevWorkspaceBuilder } from '@/store/__mocks__/devWorkspaceBuilder'; +import { + getEditorImage, + updateDevWorkspaceTemplate, + updateEditorDevfile, +} from '@/store/Workspaces/devWorkspaces/editorImage'; + +describe('Update editor image', () => { + describe('devfile source annotation', () => { + it('should return undefined without devfile-source', () => { + const devWorkspace = new DevWorkspaceBuilder() + .withTemplateAttributes({ + 'dw.metadata.annotations': {}, + }) + .build(); + + const editorImageParam = getEditorImage(devWorkspace); + + expect(editorImageParam).toBeUndefined(); + }); + + it('should return undefined without factory params', () => { + const devWorkspace = new DevWorkspaceBuilder() + .withTemplateAttributes({ + 'dw.metadata.annotations': { + 'che.eclipse.org/devfile-source': dump({ + factory: {}, + }), + }, + }) + .build(); + + const editorImageParam = getEditorImage(devWorkspace); + + expect(editorImageParam).toBeUndefined(); + }); + + it('should return the editor image param', () => { + const devWorkspace = new DevWorkspaceBuilder() + .withTemplateAttributes({ + 'dw.metadata.annotations': { + 'che.eclipse.org/devfile-source': dump({ + factory: { + params: + 'editor-image=test-images/che-code:tag&url=https://github.com/eclipse-che/che-dashboard', + }, + }), + }, + }) + .build(); + + const editorImageParam = getEditorImage(devWorkspace); + + expect(editorImageParam).toStrictEqual('test-images/che-code:tag'); + }); + }); + + describe('editor devfile', () => { + it('should throw an error if editorContent is not defined', () => { + const customEditorImage = 'test-images/che-code:tag'; + + const editorContent = ''; + + let errorMessage: string | undefined; + try { + updateEditorDevfile(editorContent, customEditorImage); + } catch (err) { + errorMessage = common.helpers.errors.getMessage(err); + } + + expect(errorMessage).toEqual('Editor content is empty.'); + }); + + it('should throw an error if editor components are not defined', () => { + const customEditorImage = 'test-images/che-code:tag'; + + const editorContent = fs.readFileSync( + __dirname + '/fixtures/test-devfile-without-components.yaml', + 'utf-8', + ); + + let errorMessage: string | undefined; + try { + updateEditorDevfile(editorContent, customEditorImage); + } catch (err) { + errorMessage = common.helpers.errors.getMessage(err); + } + + expect(errorMessage).toEqual( + 'Failed to update editor image. Editor components is not defined.', + ); + }); + + it('should update the target image', () => { + const customEditorImage = 'test-images/che-code:tag'; + + const editorContent = fs.readFileSync( + __dirname + '/fixtures/test-editor-devfile.yaml', + 'utf-8', + ); + + const customEditorContent = updateEditorDevfile(editorContent, customEditorImage); + + const output = fs.readFileSync( + __dirname + '/fixtures/test-editor-devfile-with-custom-image.yaml', + 'utf-8', + ); + + expect(customEditorContent).toStrictEqual(output); + }); + }); + + describe('devWorkspace template', () => { + it('should throw an error if editor components are not defined', () => { + const customEditorImage = 'test-images/che-code:tag'; + + const devWorkspaceTemplate = load( + fs.readFileSync( + __dirname + '/fixtures/test-devworkspace-template-without-components.yaml', + 'utf-8', + ), + ) as devfileApi.DevWorkspaceTemplate; + + let errorMessage: string | undefined; + try { + updateDevWorkspaceTemplate(devWorkspaceTemplate, customEditorImage); + } catch (err) { + errorMessage = common.helpers.errors.getMessage(err); + } + + expect(errorMessage).toEqual( + 'Failed to update editor image. Editor components is not defined.', + ); + }); + + it('should update the target image', () => { + const customEditorImage = 'test-images/che-code:tag'; + + const devWorkspaceTemplate = load( + fs.readFileSync(__dirname + '/fixtures/test-devworkspace-template.yaml', 'utf-8'), + ) as devfileApi.DevWorkspaceTemplate; + + const customEditorContent = updateDevWorkspaceTemplate( + devWorkspaceTemplate, + customEditorImage, + ); + + const output = load( + fs.readFileSync( + __dirname + '/fixtures/test-devworkspace-template-with-custom-image.yaml', + 'utf-8', + ), + ); + + expect(customEditorContent).toStrictEqual(output); + }); + }); +}); diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devfile-without-components.yaml b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devfile-without-components.yaml new file mode 100644 index 000000000..c8381ad6f --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devfile-without-components.yaml @@ -0,0 +1,3 @@ +schemaVersion: 2.2.0 +metadata: + name: che-test diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-with-custom-image.yaml b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-with-custom-image.yaml new file mode 100644 index 000000000..57a406d36 --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-with-custom-image.yaml @@ -0,0 +1,18 @@ +apiVersion: workspace.devfile.io/v1alpha2 +kind: DevWorkspaceTemplate +metadata: + annotations: + che.eclipse.org/components-update-policy: manual + name: che-code +spec: + components: + - attributes: + controller.devfile.io/container-contribution: true + container: + image: test-images/che-code:tag + name: che-code-runtime-description + - name: checode + volume: {} + - container: + image: quay.io/che-incubator/che-code:next + name: che-code-injector diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-without-components.yaml b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-without-components.yaml new file mode 100644 index 000000000..8cd279664 --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template-without-components.yaml @@ -0,0 +1,7 @@ +apiVersion: workspace.devfile.io/v1alpha2 +kind: DevWorkspaceTemplate +metadata: + annotations: + che.eclipse.org/components-update-policy: managed + name: che-code +spec: {} diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template.yaml b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template.yaml new file mode 100644 index 000000000..7fc5d7a1b --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-devworkspace-template.yaml @@ -0,0 +1,18 @@ +apiVersion: workspace.devfile.io/v1alpha2 +kind: DevWorkspaceTemplate +metadata: + annotations: + che.eclipse.org/components-update-policy: managed + name: che-code +spec: + components: + - attributes: + controller.devfile.io/container-contribution: true + container: + image: quay.io/devfile/universal-developer-image:next + name: che-code-runtime-description + - name: checode + volume: {} + - container: + image: quay.io/che-incubator/che-code:next + name: che-code-injector diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile-with-custom-image.yaml b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile-with-custom-image.yaml new file mode 100644 index 000000000..67c9e524e --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile-with-custom-image.yaml @@ -0,0 +1,14 @@ +schemaVersion: 2.2.0 +metadata: + name: che-code +components: + - name: che-code-runtime-description + container: + image: test-images/che-code:tag + attributes: + controller.devfile.io/container-contribution: true + - name: checode + volume: {} + - name: che-code-injector + container: + image: quay.io/che-incubator/che-code:next diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile.yaml b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile.yaml new file mode 100644 index 000000000..ed67918aa --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/__tests__/fixtures/test-editor-devfile.yaml @@ -0,0 +1,14 @@ +schemaVersion: 2.2.0 +metadata: + name: che-code +components: + - name: che-code-runtime-description + container: + image: quay.io/devfile/universal-developer-image:next + attributes: + controller.devfile.io/container-contribution: true + - name: checode + volume: {} + - name: che-code-injector + container: + image: quay.io/che-incubator/che-code:next diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/editorImage.ts b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/editorImage.ts new file mode 100644 index 000000000..492826b23 --- /dev/null +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/editorImage.ts @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018-2024 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 { V222DevfileComponents } from '@devfile/api'; +import common from '@eclipse-che/common'; +import { dump, load } from 'js-yaml'; +import cloneDeep from 'lodash/cloneDeep'; + +import { FactorySource } from '@/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/prepareDevfile'; +import devfileApi from '@/services/devfileApi'; +import { buildFactoryParams } from '@/services/helpers/factoryFlow/buildFactoryParams'; +import { COMPONENT_UPDATE_POLICY } from '@/services/workspace-client/devworkspace/devWorkspaceClient'; +import { + DEVWORKSPACE_DEVFILE_SOURCE, + DEVWORKSPACE_METADATA_ANNOTATION, +} from '@/services/workspace-client/devworkspace/devWorkspaceClient'; + +export function updateEditorDevfile( + editorContent: string, + editorImage: string | undefined, +): string { + if (editorImage === undefined) { + return editorContent; + } + if (!editorContent) { + throw new Error('Editor content is empty.'); + } + try { + const editorObj = load(editorContent) as devfileApi.Devfile; + updateComponents(editorObj.components, editorImage); + return dump(editorObj); + } catch (err) { + throw new Error(`Failed to update editor image. ${common.helpers.errors.getMessage(err)}`); + } +} + +export function updateDevWorkspaceTemplate( + devWorkspaceTemplate: devfileApi.DevWorkspaceTemplate, + editorImage: string | undefined, +): devfileApi.DevWorkspaceTemplate { + if (editorImage === undefined) { + return devWorkspaceTemplate; + } + const _devWorkspaceTemplate = cloneDeep(devWorkspaceTemplate); + try { + const isUpdated = updateComponents(_devWorkspaceTemplate.spec?.components, editorImage); + if ( + isUpdated && + _devWorkspaceTemplate.metadata?.annotations?.[COMPONENT_UPDATE_POLICY] === 'managed' + ) { + _devWorkspaceTemplate.metadata.annotations[COMPONENT_UPDATE_POLICY] = 'manual'; + } + return _devWorkspaceTemplate; + } catch (err) { + throw new Error(`Failed to update editor image. ${common.helpers.errors.getMessage(err)}`); + } +} + +export function getEditorImage(workspace: devfileApi.DevWorkspace): string | undefined { + const devfileSourceYaml = + workspace.spec.template.attributes?.[DEVWORKSPACE_METADATA_ANNOTATION]?.[ + DEVWORKSPACE_DEVFILE_SOURCE + ]; + if (!devfileSourceYaml) { + return undefined; + } + const factorySearchParams = (load(devfileSourceYaml) as FactorySource)?.factory?.params; + if (!factorySearchParams) { + return undefined; + } + const factoryParams = buildFactoryParams(new URLSearchParams(factorySearchParams)); + + return factoryParams.editorImage; +} + +function updateComponents( + components: V222DevfileComponents[] | undefined, + editorImage: string, +): boolean { + if (!components) { + throw new Error('Editor components is not defined.'); + } + let isUpdated = false; + for (let i = 0; i < components.length; i++) { + if ( + components[i].attributes?.['controller.devfile.io/container-contribution'] && + components[i].container?.image + ) { + components[i].container!.image = editorImage; + isUpdated = true; + break; + } + } + return isUpdated; +} diff --git a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts index 875956838..0257d9978 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/devWorkspaces/index.ts @@ -59,6 +59,11 @@ import { selectPluginRegistryUrl, } from '@/store/ServerConfig/selectors'; import { checkRunningWorkspacesLimit } from '@/store/Workspaces/devWorkspaces/checkRunningWorkspacesLimit'; +import { + getEditorImage, + updateDevWorkspaceTemplate, + updateEditorDevfile, +} from '@/store/Workspaces/devWorkspaces/editorImage'; import { selectDevWorkspacesResourceVersion } from '@/store/Workspaces/devWorkspaces/selectors'; export const onStatusChangeCallbacks = new Map void>(); @@ -176,7 +181,7 @@ export type ActionCreators = { ) => AppThunk>; createWorkspaceFromDevfile: ( devfile: devfileApi.Devfile, - attributes: Partial, + params: Partial, optionalFilesContent: { [fileName: string]: string; }, @@ -184,6 +189,7 @@ export type ActionCreators = { createWorkspaceFromResources: ( devWorkspace: devfileApi.DevWorkspace, devWorkspaceTemplate: devfileApi.DevWorkspaceTemplate, + params: Partial, // it could be editorId or editorContent editor?: string, ) => AppThunk>; @@ -531,8 +537,9 @@ export const actionCreators: ActionCreators = { createWorkspaceFromResources: ( - devWorkspaceResource: devfileApi.DevWorkspace, - devWorkspaceTemplateResource: devfileApi.DevWorkspaceTemplate, + devWorkspace: devfileApi.DevWorkspace, + devWorkspaceTemplate: devfileApi.DevWorkspaceTemplate, + params: Partial, editor?: string, ): AppThunk> => async (dispatch, getState): Promise => { @@ -553,7 +560,7 @@ export const actionCreators: ActionCreators = { /* create a new DevWorkspace */ const createResp = await getDevWorkspaceClient().createDevWorkspace( defaultNamespace, - devWorkspaceResource, + devWorkspace, cheEditor, ); @@ -572,11 +579,12 @@ export const actionCreators: ActionCreators = { app => app.id === ApplicationId.CLUSTER_CONSOLE, ); + devWorkspaceTemplate = updateDevWorkspaceTemplate(devWorkspaceTemplate, params.editorImage); /* create a new DevWorkspaceTemplate */ await getDevWorkspaceClient().createDevWorkspaceTemplate( defaultNamespace, createResp.devWorkspace, - devWorkspaceTemplateResource, + devWorkspaceTemplate, pluginRegistryUrl, pluginRegistryInternalUrl, openVSXUrl, @@ -653,6 +661,11 @@ export const actionCreators: ActionCreators = { defaultsDevfile.metadata.name = workspace.metadata.name; delete defaultsDevfile.metadata.generateName; + + const editorImage = getEditorImage(workspace); + if (editorImage) { + editorContent = updateEditorDevfile(editorContent, editorImage); + } const resourcesContent = await fetchResources({ pluginRegistryUrl, devfileContent: dump(defaultsDevfile), @@ -808,7 +821,7 @@ export const actionCreators: ActionCreators = { createWorkspaceFromDevfile: ( devfile: devfileApi.Devfile, - attributes: Partial, + params: Partial, optionalFilesContent: { [fileName: string]: string; }, @@ -821,7 +834,7 @@ export const actionCreators: ActionCreators = { let editorContent: string | undefined; let editorYamlUrl: string | undefined; // do we have an optional editor parameter ? - let editor = attributes.cheEditor; + let editor = params.cheEditor; if (editor) { const response = await getEditor(editor, dispatch, getState, pluginRegistryUrl); if (response.content) { @@ -868,6 +881,7 @@ export const actionCreators: ActionCreators = { const error = selectSanityCheckError(getState()); throw new Error(error); } + editorContent = updateEditorDevfile(editorContent, params.editorImage); const resourcesContent = await fetchResources({ pluginRegistryUrl, devfileContent: dump(devfile), @@ -913,6 +927,7 @@ export const actionCreators: ActionCreators = { actionCreators.createWorkspaceFromResources( devWorkspaceResource, devWorkspaceTemplateResource, + params, editor ? editor : editorContent, ), ); diff --git a/packages/dashboard-frontend/src/store/__mocks__/devWorkspaceBuilder.ts b/packages/dashboard-frontend/src/store/__mocks__/devWorkspaceBuilder.ts index 5627b9f0f..fef836dfb 100644 --- a/packages/dashboard-frontend/src/store/__mocks__/devWorkspaceBuilder.ts +++ b/packages/dashboard-frontend/src/store/__mocks__/devWorkspaceBuilder.ts @@ -14,6 +14,7 @@ import { V1alpha2DevWorkspaceStatus, V1alpha2DevWorkspaceStatusConditions } from import devfileApi from '@/services/devfileApi'; import { DevWorkspacePlugin } from '@/services/devfileApi/devWorkspace'; +import { DevWorkspaceSpecTemplateAttribute } from '@/services/devfileApi/devWorkspace/spec/template'; import getRandomString from '@/services/helpers/random'; import { DevWorkspaceStatus } from '@/services/helpers/types'; @@ -132,6 +133,10 @@ export class DevWorkspaceBuilder { this.workspace.spec.template.projects = projects; return this; } + withTemplateAttributes(attributes: DevWorkspaceSpecTemplateAttribute): DevWorkspaceBuilder { + this.workspace.spec.template.attributes = attributes; + return this; + } build(): devfileApi.DevWorkspace { return this.workspace; diff --git a/yarn.lock b/yarn.lock index 4a8976778..f5d82a3be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -510,12 +510,13 @@ simple-oauth2 "^5.0.0" "@fastify/reply-from@^9.0.0": - version "9.4.0" - resolved "https://registry.yarnpkg.com/@fastify/reply-from/-/reply-from-9.4.0.tgz#7d648cd5221e1aa9a0ec2cd5451f32078c431dd3" - integrity sha512-A0rT2o2Y8Xe9+CpS/VCfpWMLyptwI7peljdtdsW3nlLwwMbDr3kxDry5aiIb43SCfI1nr3B5mMH48kerJAZ8YQ== + version "9.6.0" + resolved "https://registry.yarnpkg.com/@fastify/reply-from/-/reply-from-9.6.0.tgz#63d45f4cd63f95a012549cc56fcb73ea6432d660" + integrity sha512-c1AqCq5+Wsc9iGpeRE5UpkzNJFn1HW1tCBgQw9h/EcaohqhYp2zoeXFM/MVFZvVKPph9eFkbWzLoQqMH4degfg== dependencies: "@fastify/error" "^3.0.0" end-of-stream "^1.4.4" + fast-content-type-parse "^1.1.0" fast-querystring "^1.0.0" fastify-plugin "^4.0.0" pump "^3.0.0" @@ -5184,9 +5185,9 @@ focus-trap@6.9.2: tabbable "^5.3.2" follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== for-each@^0.3.3: version "0.3.3"