Skip to content

Commit f0f6a47

Browse files
Merge pull request che-incubator#555 from RomanNikitenko/fix-merging-extensions
fix: Fix merging ConfigMap and workspace file extensions
2 parents 36d87cd + 612eeaa commit f0f6a47

2 files changed

Lines changed: 116 additions & 16 deletions

File tree

launcher/src/editor-configurations.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**********************************************************************
2-
* Copyright (c) 2024 Red Hat, Inc.
2+
* Copyright (c) 2024-2025 Red Hat, Inc.
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -91,16 +91,29 @@ export class EditorConfigurations {
9191
private async configureExtensions(configmap: k8s.V1ConfigMap): Promise<void> {
9292
const configmapContent = configmap.data![EditorConfigs.Extensions];
9393
if (!configmapContent) {
94+
console.log(` > Configmap does not contain ${EditorConfigs.Extensions}. Skip this step.`);
9495
return;
9596
}
9697

9798
console.log(' > Configure workspace extensions...');
9899

99100
try {
100-
const extensionsFromConfigmap = parseJSON(configmapContent, {
101+
const parsedConfigmapContent = parseJSON(configmapContent, {
101102
errorMessage: 'Configmap content is not valid.',
102103
});
103104

105+
const configMapExtensions = parsedConfigmapContent?.recommendations;
106+
if (!Array.isArray(configMapExtensions) || configMapExtensions.length < 1) {
107+
console.log(
108+
` > ${EditorConfigs.Extensions} section of Configmap does not contain recomendations. Skip this step.`
109+
);
110+
return;
111+
} else {
112+
console.log(
113+
` > ${EditorConfigs.Extensions} section of Configmap contains ${configMapExtensions.length} extensions.`
114+
);
115+
}
116+
104117
if (!this.workspaceFilePath) {
105118
console.log(' > Missing workspace file. Skip this step.');
106119
return;
@@ -118,10 +131,12 @@ export class EditorConfigurations {
118131
errorMessage: 'Workspace file is not valid.',
119132
});
120133

121-
workspaceConfigData['extensions'] = {
122-
...(workspaceConfigData['extensions'] || {}),
123-
...extensionsFromConfigmap,
124-
};
134+
const workspaceFileExtensions = workspaceConfigData?.extensions?.recommendations || [];
135+
console.log(` > Workspace file contains ${workspaceFileExtensions.length} extensions.`);
136+
137+
const combinedExtensions = Array.from(new Set([...configMapExtensions, ...workspaceFileExtensions]));
138+
workspaceConfigData.extensions = workspaceConfigData.extensions ?? {};
139+
workspaceConfigData.extensions.recommendations = combinedExtensions;
125140

126141
const json = JSON.stringify(workspaceConfigData, null, '\t');
127142
await fs.writeFile(this.workspaceFilePath, json);

launcher/tests/editor-configurations.spec.ts

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**********************************************************************
2-
* Copyright (c) 2024 Red Hat, Inc.
2+
* Copyright (c) 2024-2025 Red Hat, Inc.
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -42,26 +42,50 @@ const CONFIGMAP_SETTINGS_DATA = {
4242
'settings.json': SETTINGS_CONTENT,
4343
};
4444

45-
const EXTENSIONS_CONTENT =
45+
const WORKSPACE_FILE_EXTENSIONS_CONTENT =
4646
'{\n' +
4747
' "recommendations": [\n' +
4848
' "dbaeumer.vscode-eslint",\n' +
4949
' "github.vscode-pull-request-github"\n' +
5050
' ]\n' +
5151
'}\n';
52-
const EXTENSIONS_JSON = JSON.parse(EXTENSIONS_CONTENT);
53-
const WORKSPACE_CONFIG_JSON = JSON.parse(WORKSPACE_FILE_CONTENT);
54-
WORKSPACE_CONFIG_JSON['extensions'] = EXTENSIONS_JSON;
55-
const WORKSPACE_CONFIG_WITH_EXTENSIONS_TO_FILE = JSON.stringify(WORKSPACE_CONFIG_JSON, null, '\t');
52+
53+
// prettier-ignore
54+
const CONFIGMAP_EXTENSIONS_CONTENT =
55+
'{\n' +
56+
' "recommendations": [\n' +
57+
' "redhat.vscode-yaml",\n' +
58+
' "redhat.java"\n' +
59+
' ]\n' +
60+
'}\n';
61+
62+
const MERGED_EXTENSIONS_CONTENT =
63+
'{\n' +
64+
' "recommendations": [\n' +
65+
' "redhat.vscode-yaml",\n' +
66+
' "redhat.java",\n' +
67+
' "dbaeumer.vscode-eslint",\n' +
68+
' "github.vscode-pull-request-github"\n' +
69+
' ]\n' +
70+
'}\n';
71+
const WORKSPACE_EXTENSIONS_JSON = JSON.parse(WORKSPACE_FILE_EXTENSIONS_CONTENT);
72+
const WORKSPACE_CONFIG_WITHOUT_EXTENSIONS_JSON = JSON.parse(WORKSPACE_FILE_CONTENT);
73+
const CONFIGMAP_EXTENSIONS_JSON = JSON.parse(CONFIGMAP_EXTENSIONS_CONTENT);
74+
const MERGED_EXTENSIONS_JSON = JSON.parse(MERGED_EXTENSIONS_CONTENT);
5675
const CONFIGMAP_EXTENSIOSN_DATA = {
57-
'extensions.json': EXTENSIONS_CONTENT,
76+
'extensions.json': CONFIGMAP_EXTENSIONS_CONTENT,
5877
};
5978

6079
const CONFIGMAP_INCORRECT_DATA = {
6180
'extensions.json': '//some incorrect data',
6281
'settings.json': '//some incorrect data',
6382
};
6483

84+
const EMPTY_RECOMMENDATIONS_CONTENT = '{\n' + ' "recommendations": []\n' + '}\n';
85+
const EMPTY_RECOMMENDATIONS_DATA = {
86+
'extensions.json': EMPTY_RECOMMENDATIONS_CONTENT,
87+
};
88+
6589
jest.mock('@kubernetes/client-node', () => {
6690
const actual = jest.requireActual('@kubernetes/client-node');
6791
return {
@@ -106,7 +130,7 @@ describe('Test applying editor configurations:', () => {
106130
}));
107131
});
108132

109-
it('should skip applying editor congis if there is no DEVWORKSPACE_NAMESPACE', async () => {
133+
it('should skip applying editor configs if there is no DEVWORKSPACE_NAMESPACE', async () => {
110134
await new EditorConfigurations().configure();
111135

112136
expect(mockMakeApiClient).not.toHaveBeenCalled();
@@ -214,6 +238,27 @@ describe('Test applying editor configurations:', () => {
214238
expect(writeFileMock).not.toHaveBeenCalled();
215239
});
216240

241+
it('should skip applying extensions when empty list of extensions in a configmap', async () => {
242+
env.DEVWORKSPACE_NAMESPACE = DEVWORKSPACE_NAMESPACE;
243+
const mockResponse = {
244+
response: {} as IncomingMessage,
245+
body: { data: EMPTY_RECOMMENDATIONS_DATA } as V1ConfigMap,
246+
};
247+
mockCoreV1Api.readNamespacedConfigMap.mockResolvedValue(mockResponse);
248+
fileExistsMock.mockResolvedValue(false);
249+
250+
await new EditorConfigurations(WORKSPACE_FILE_PATH).configure();
251+
252+
expect(mockMakeApiClient).toHaveBeenCalledWith(CoreV1Api);
253+
expect(mockCoreV1Api.readNamespacedConfigMap).toHaveBeenCalledWith(
254+
'vscode-editor-configurations',
255+
DEVWORKSPACE_NAMESPACE
256+
);
257+
expect(fileExistsMock).not.toHaveBeenCalled();
258+
expect(readFileMock).not.toHaveBeenCalled();
259+
expect(writeFileMock).not.toHaveBeenCalled();
260+
});
261+
217262
it('should skip applying extensions when the workspace file is not found', async () => {
218263
env.DEVWORKSPACE_NAMESPACE = DEVWORKSPACE_NAMESPACE;
219264
const mockResponse = {
@@ -233,8 +278,13 @@ describe('Test applying editor configurations:', () => {
233278
expect(writeFileMock).not.toHaveBeenCalled();
234279
});
235280

236-
it('should apply extensions from a configmap', async () => {
281+
it('should apply extensions from a configmap when workspace file does not contain extensions', async () => {
237282
env.DEVWORKSPACE_NAMESPACE = DEVWORKSPACE_NAMESPACE;
283+
const workspaceConfigWithConfigMapExtensionsJson = {
284+
...WORKSPACE_CONFIG_WITHOUT_EXTENSIONS_JSON,
285+
extensions: CONFIGMAP_EXTENSIONS_JSON,
286+
};
287+
const workspaceConfigWithExtensionsToFile = JSON.stringify(workspaceConfigWithConfigMapExtensionsJson, null, '\t');
238288
const mockResponse = {
239289
response: {} as IncomingMessage,
240290
body: { data: CONFIGMAP_EXTENSIOSN_DATA } as V1ConfigMap,
@@ -251,7 +301,42 @@ describe('Test applying editor configurations:', () => {
251301
DEVWORKSPACE_NAMESPACE
252302
);
253303
expect(writeFileMock).toBeCalledTimes(1); // only extensions were applied
254-
expect(writeFileMock).toBeCalledWith(WORKSPACE_FILE_PATH, WORKSPACE_CONFIG_WITH_EXTENSIONS_TO_FILE);
304+
expect(writeFileMock).toBeCalledWith(WORKSPACE_FILE_PATH, workspaceConfigWithExtensionsToFile);
305+
});
306+
307+
it('should merge extensions from the configmap and workspace file extensions', async () => {
308+
env.DEVWORKSPACE_NAMESPACE = DEVWORKSPACE_NAMESPACE;
309+
const workspaceConfigWithExtensionsJson = {
310+
...WORKSPACE_CONFIG_WITHOUT_EXTENSIONS_JSON,
311+
extensions: WORKSPACE_EXTENSIONS_JSON,
312+
};
313+
const workspaceConfigWithMergedExtensionsJson = {
314+
...WORKSPACE_CONFIG_WITHOUT_EXTENSIONS_JSON,
315+
extensions: MERGED_EXTENSIONS_JSON,
316+
};
317+
const workspaceConfigWithExtensionsToFile = JSON.stringify(workspaceConfigWithExtensionsJson, null, '\t');
318+
const workspaceConfigWithMergedExtensionsToFile = JSON.stringify(
319+
workspaceConfigWithMergedExtensionsJson,
320+
null,
321+
'\t'
322+
);
323+
const mockResponse = {
324+
response: {} as IncomingMessage,
325+
body: { data: CONFIGMAP_EXTENSIOSN_DATA } as V1ConfigMap,
326+
};
327+
mockCoreV1Api.readNamespacedConfigMap.mockResolvedValue(mockResponse);
328+
fileExistsMock.mockResolvedValue(true);
329+
readFileMock.mockResolvedValue(workspaceConfigWithExtensionsToFile);
330+
331+
await new EditorConfigurations(WORKSPACE_FILE_PATH).configure();
332+
333+
expect(mockMakeApiClient).toHaveBeenCalledWith(CoreV1Api);
334+
expect(mockCoreV1Api.readNamespacedConfigMap).toHaveBeenCalledWith(
335+
'vscode-editor-configurations',
336+
DEVWORKSPACE_NAMESPACE
337+
);
338+
expect(writeFileMock).toBeCalledTimes(1); // only extensions were applied
339+
expect(writeFileMock).toBeCalledWith(WORKSPACE_FILE_PATH, workspaceConfigWithMergedExtensionsToFile);
255340
});
256341

257342
it('should merge product.json with a provided config map', async () => {

0 commit comments

Comments
 (0)