Skip to content

Commit

Permalink
Fix editor update flow (#1310)
Browse files Browse the repository at this point in the history
* fix: editor update flow

Signed-off-by: Oleksii Orel <[email protected]>
  • Loading branch information
olexii4 committed Feb 11, 2025
1 parent e90b692 commit ab4c398
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import devfileApi from '@/services/devfileApi';
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';

const getVSCodeDevWorkspaceTemplate = (cpuLimit = '1500m'): devfileApi.DevWorkspaceTemplate => {
return {
Expand All @@ -19,7 +20,7 @@ const getVSCodeDevWorkspaceTemplate = (cpuLimit = '1500m'): devfileApi.DevWorksp
metadata: {
annotations: {
'che.eclipse.org/components-update-policy': 'managed',
'che.eclipse.org/plugin-registry-url': 'che-incubator/che-code/latest',
'che.eclipse.org/plugin-registry-url': `${EDITOR_DEVFILE_API_QUERY}che-incubator/che-code/latest`,
},
creationTimestamp: new Date('2024-05-30T12:51:45Z'),
generation: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import { V230Devfile } from '@devfile/api';

const getVSCodeEditorDefinition = (): V230Devfile => {
const getVSCodeEditorDefinition = (cpuLimit = '500m'): V230Devfile => {
return {
attributes: {
version: null,
Expand All @@ -36,7 +36,7 @@ const getVSCodeEditorDefinition = (): V230Devfile => {
{
container: {
command: ['/entrypoint-init-container.sh'],
cpuLimit: '500m',
cpuLimit,
cpuRequest: '30m',
image: 'quay.io/che-incubator/che-code:latest',
memoryLimit: '256Mi',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import mockAxios from 'axios';
import { dump } from 'js-yaml';

import { container } from '@/inversify.config';
import { dashboardBackendPrefix } from '@/services/backend-client/const';
Expand All @@ -23,11 +24,7 @@ import {
DevWorkspaceClient,
REGISTRY_URL,
} from '@/services/workspace-client/devworkspace/devWorkspaceClient';

const mockFetchData = jest.fn();
jest.mock('@/services/registry/fetchData', () => ({
fetchData: (...args: unknown[]) => mockFetchData(...args),
}));
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';

describe('DevWorkspace client editor update', () => {
const namespace = 'admin-che';
Expand Down Expand Up @@ -134,53 +131,49 @@ describe('DevWorkspace client editor update', () => {

describe('DevWorkspaceTemplate with plugin registry URL', () => {
it('should return patch for an editor if it has been updated', async () => {
const template = getVSCodeDevWorkspaceTemplate('1000m');
const editor = getVSCodeEditorDefinition('1000m') as devfileApi.Devfile;
const template = getVSCodeDevWorkspaceTemplate('500m');
template.metadata.annotations = {
'che.eclipse.org/components-update-policy': 'managed',
'che.eclipse.org/plugin-registry-url':
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
'https://192.168.64.24.nip.io/che-incubator/che-code/devfile.yaml',
};

const mockPatch = mockAxios.get as jest.Mock;
mockPatch.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));
const mockGet = mockAxios.get as jest.Mock;
mockGet.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));

// if cpuLimit changed from '1000m' to '500m'
const newTemplate = getVSCodeDevWorkspaceTemplate('500m');
newTemplate.metadata.annotations = {
'che.eclipse.org/components-update-policy': 'managed',
'che.eclipse.org/plugin-registry-url':
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
};
const mockPost = mockAxios.post as jest.Mock;
mockPost.mockResolvedValueOnce(new Promise(resolve => resolve({ data: dump(editor) })));

const editors: devfileApi.Devfile[] = [getVSCodeEditorDefinition() as devfileApi.Devfile];
const editorName = newTemplate.metadata.name;
const editorName = template.metadata.name;

const patch = await client.checkForTemplatesUpdate(
editorName,
namespace,
editors,
[editor],
pluginRegistryUrl,
pluginRegistryInternalUrl,
undefined,
);

expect(mockPatch.mock.calls).toEqual([
expect(mockGet.mock.calls).toEqual([
[`${dashboardBackendPrefix}/namespace/${namespace}/devworkspacetemplates/${editorName}`],
]);

expect(patch).toEqual([
{
op: 'replace',
path: '/metadata/annotations',
value: {
[COMPONENT_UPDATE_POLICY]: 'managed',
[REGISTRY_URL]: 'che-incubator/che-code/latest',
expect(mockPost.mock.calls).toEqual([
[
`${dashboardBackendPrefix}/data/resolver`,
{
url: 'https://192.168.64.24.nip.io/che-incubator/che-code/devfile.yaml',
},
},
],
]);

expect(patch).toEqual([
{
op: 'replace',
path: '/spec',
value: newTemplate.spec,
value: getVSCodeDevWorkspaceTemplate('1000m').spec,
},
]);
});
Expand All @@ -196,16 +189,13 @@ describe('DevWorkspace client editor update', () => {
const mockPatch = mockAxios.get as jest.Mock;
mockPatch.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));

// if cpuLimit changed from '1000m' to '500m'
const newTemplate = getVSCodeDevWorkspaceTemplate('500m');
newTemplate.metadata.annotations = {
'che.eclipse.org/components-update-policy': 'managed',
'che.eclipse.org/plugin-registry-url':
'https://192.168.64.24.nip.io/custom-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
};
const editors: devfileApi.Devfile[] = [
getVSCodeEditorDefinition('1000m') as devfileApi.Devfile,
];
const editorName = template.metadata.name;

const editors: devfileApi.Devfile[] = [getVSCodeEditorDefinition() as devfileApi.Devfile];
const editorName = newTemplate.metadata.name;
const mockPost = mockAxios.post as jest.Mock;
mockPost.mockResolvedValueOnce(new Promise(resolve => resolve({ data: editors[0] })));

const patch = await client.checkForTemplatesUpdate(
editorName,
Expand All @@ -216,8 +206,13 @@ describe('DevWorkspace client editor update', () => {
undefined,
);

expect(mockPatch.mock.calls).toEqual([
[`${dashboardBackendPrefix}/namespace/${namespace}/devworkspacetemplates/${editorName}`],
expect(mockPost.mock.calls).toEqual([
[
`${dashboardBackendPrefix}/data/resolver`,
{
url: 'https://192.168.64.24.nip.io/custom-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
},
],
]);

expect(patch).toEqual([]);
Expand All @@ -230,10 +225,6 @@ describe('DevWorkspace client editor update', () => {
'che.eclipse.org/plugin-registry-url':
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
};

const mockPatch = mockAxios.get as jest.Mock;
mockPatch.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));

// if cpuLimit changed from '1000m' to '500m'
const newTemplate = getVSCodeDevWorkspaceTemplate('500m');
newTemplate.metadata.annotations = {
Expand All @@ -242,6 +233,9 @@ describe('DevWorkspace client editor update', () => {
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
};

const mockGet = mockAxios.get as jest.Mock;
mockGet.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));

const editors: devfileApi.Devfile[] = [getVSCodeEditorDefinition() as devfileApi.Devfile];
const editorName = newTemplate.metadata.name;

Expand All @@ -254,7 +248,7 @@ describe('DevWorkspace client editor update', () => {
undefined,
);

expect(mockPatch.mock.calls).toEqual([
expect(mockGet.mock.calls).toEqual([
[`${dashboardBackendPrefix}/namespace/${namespace}/devworkspacetemplates/${editorName}`],
]);

Expand All @@ -264,7 +258,7 @@ describe('DevWorkspace client editor update', () => {
path: '/metadata/annotations',
value: {
[COMPONENT_UPDATE_POLICY]: 'managed',
[REGISTRY_URL]: 'che-incubator/che-code/latest',
[REGISTRY_URL]: `${EDITOR_DEVFILE_API_QUERY}che-incubator/che-code/latest`,
},
},
]);
Expand Down Expand Up @@ -311,7 +305,7 @@ describe('DevWorkspace client editor update', () => {
path: '/metadata/annotations',
value: {
[COMPONENT_UPDATE_POLICY]: 'managed',
[REGISTRY_URL]: 'che-incubator/custom-editor/latest',
[REGISTRY_URL]: `${EDITOR_DEVFILE_API_QUERY}che-incubator/custom-editor/latest`,
},
},
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@devfile/api';
import { api } from '@eclipse-che/common';
import { inject, injectable } from 'inversify';
import { load } from 'js-yaml';
import cloneDeep from 'lodash/cloneDeep';
import isEqual from 'lodash/isEqual';

Expand All @@ -37,13 +38,15 @@ import {
import { delay } from '@/services/helpers/delay';
import { isWebTerminal } from '@/services/helpers/devworkspace';
import { DevWorkspaceStatus } from '@/services/helpers/types';
import { fetchData } from '@/services/registry/fetchData';
import { WorkspaceAdapter } from '@/services/workspace-adapter';
import {
devWorkspaceApiGroup,
devWorkspaceSingularSubresource,
devWorkspaceVersion,
} from '@/services/workspace-client/devworkspace/converters';
import { DevWorkspaceDefaultPluginsHandler } from '@/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler';
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';
import { WorkspacesDefaultPlugins } from '@/store/Plugins/devWorkspacePlugins';

export const COMPONENT_UPDATE_POLICY = 'che.eclipse.org/components-update-policy';
Expand Down Expand Up @@ -687,68 +690,91 @@ export class DevWorkspaceClient {
title: string;
},
): Promise<api.IPatch[]> {
const managedTemplate = await DwtApi.getTemplateByName(namespace, editorName);

const patch: api.IPatch[] = [];
const managedTemplate = await DwtApi.getTemplateByName(namespace, editorName);
const _editorIdOrPath = managedTemplate.metadata?.annotations?.[REGISTRY_URL];
const updatePolicy = managedTemplate.metadata?.annotations?.[COMPONENT_UPDATE_POLICY];

let editorReference = managedTemplate.metadata?.annotations?.[REGISTRY_URL];

if (
!editorReference ||
managedTemplate.metadata?.annotations?.[COMPONENT_UPDATE_POLICY] !== 'managed'
) {
if (!_editorIdOrPath || updatePolicy !== 'managed') {
console.log('Template is not managed');
return patch;
}

if (/^(https?:\/\/)/.test(editorReference)) {
let url: string | undefined;
if (/^(https?:\/\/)/.test(_editorIdOrPath)) {
url = _editorIdOrPath;
// Define a regular expression pattern to match URLs containing 'plugin-registry/v3/plugins'
// and ending with 'devfile.yaml'. The part between 'v3/plugins/' and '/devfile.yaml' is captured.
const pluginRegistryURLPattern = /plugin-registry\/v3\/plugins\/(.+?)\/devfile\.yaml$/;
const match = editorReference.match(pluginRegistryURLPattern);
const match = url.match(pluginRegistryURLPattern);

if (match) {
editorReference = match[1];
const annotations = {
[COMPONENT_UPDATE_POLICY]: 'managed',
[REGISTRY_URL]: editorReference,
[REGISTRY_URL]: `${EDITOR_DEVFILE_API_QUERY}${match[1]}`,
};
// Create a patch to update the annotations by replacing plugin registry URL with the editor reference
patch.push({
op: 'replace',
path: '/metadata/annotations',
value: annotations,
});
} else {
console.log('Template is not managed');
return patch;
}
} else {
url = `${EDITOR_DEVFILE_API_QUERY}${_editorIdOrPath}`;
const annotations = {
[COMPONENT_UPDATE_POLICY]: 'managed',
[REGISTRY_URL]: url,
};
patch.push({
op: 'replace',
path: '/metadata/annotations',
value: annotations,
});
}

let editor: devfileApi.Devfile | undefined = undefined;
// Found the target editors
if (url.startsWith(EDITOR_DEVFILE_API_QUERY)) {
const editorReference = url.replace(EDITOR_DEVFILE_API_QUERY, '');

const _editor = editors.find(e => {
return (
e.metadata?.attributes?.publisher +
'/' +
e.metadata?.name +
'/' +
e.metadata?.attributes?.version ===
editorReference
);
});
if (_editor !== undefined) {
editor = cloneDeep(_editor);
}
} else {
const editorContent = await fetchData<string | devfileApi.Devfile>(url);
if (typeof editorContent === 'string') {
editor = load(editorContent) as devfileApi.Devfile;
} else if (typeof editorContent === 'object') {
editor = editorContent;
}
}

const originalEditor: devfileApi.Devfile | undefined = editors.find(editor => {
return (
editor.metadata?.attributes?.publisher +
'/' +
editor.metadata?.name +
'/' +
editor.metadata?.attributes?.version ===
editorReference
);
});
if (!originalEditor) {
if (editor === undefined) {
console.warn('Failed to get editor');
return patch;
}

const spec: Partial<V1alpha2DevWorkspaceTemplateSpec> = {};
for (const key in originalEditor) {
for (const key in editor) {
if (key !== 'schemaVersion' && key !== 'metadata') {
if (key === 'components') {
originalEditor.components?.forEach(component => {
editor.components?.forEach(component => {
if (component.container && !component.container.sourceMapping) {
component.container.sourceMapping = '/projects';
}
});
spec.components = originalEditor.components;
spec.components = editor.components;
this.addEnvVarsToContainers(
spec.components,
pluginRegistryUrl,
Expand All @@ -757,7 +783,7 @@ export class DevWorkspaceClient {
clusterConsole,
);
} else {
spec[key] = originalEditor[key];
spec[key] = editor[key];
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { dump } from 'js-yaml';
import devfileApi from '@/services/devfileApi';
import { RootState } from '@/store';
import { actionCreators } from '@/store/DevfileRegistries/actions';
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';
import { getEditor } from '@/store/DevfileRegistries/getEditor';
import { State } from '@/store/DevfileRegistries/reducer';

Expand Down Expand Up @@ -140,7 +141,7 @@ describe('getEditor', () => {

expect(result).toEqual({
content: 'dumped devfile content',
editorYamlUrl: 'che-incubator/che-idea/next',
editorYamlUrl: `${EDITOR_DEVFILE_API_QUERY}che-incubator/che-idea/next`,
});
expect(dispatch).not.toHaveBeenCalled();
});
Expand Down
Loading

0 comments on commit ab4c398

Please sign in to comment.