diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/__tests__/fixtures/kubeconfig.yaml b/packages/dashboard-backend/src/devworkspaceClient/services/__tests__/fixtures/kubeconfig.yaml new file mode 100644 index 000000000..82c81f531 --- /dev/null +++ b/packages/dashboard-backend/src/devworkspaceClient/services/__tests__/fixtures/kubeconfig.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Config +clusters: + - name: inCluster + cluster: + server: https://0.0.0.0:443 + certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + insecure-skip-tls-verify: false +users: + - name: developer + user: + token: token +contexts: + - name: logged-user + context: + user: developer + cluster: inCluster + name: logged-user +preferences: {} +current-context: logged-user diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/__tests__/kubeConfigApi.spec.ts b/packages/dashboard-backend/src/devworkspaceClient/services/__tests__/kubeConfigApi.spec.ts index 19a950b3e..044fb8a77 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/__tests__/kubeConfigApi.spec.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/__tests__/kubeConfigApi.spec.ts @@ -14,6 +14,8 @@ import * as mockClient from '@kubernetes/client-node'; import { CoreV1Api, V1PodList } from '@kubernetes/client-node'; +import fs from 'fs'; +import { parse, stringify } from 'yaml'; import * as helper from '@/devworkspaceClient/services/helpers/exec'; import { KubeConfigApiService } from '@/devworkspaceClient/services/kubeConfigApi'; @@ -28,7 +30,7 @@ const mockExecPrintenvHome = jest.fn().mockReturnValue({ stdError: '', }); -const mockExecCatKubeConfig = jest.fn().mockReturnValue({ +let mockExecCatKubeConfig = jest.fn().mockReturnValue({ stdOut: '', stdError: '', }); @@ -61,11 +63,14 @@ const spyExec = jest const namespace = 'user-che'; const workspaceName = 'workspace-1'; const containerName = 'container-1'; -const config = JSON.stringify({ +const config = { apiVersion: 'v1', kind: 'Config', 'current-context': 'logged-user', -}); + contexts: [], + clusters: [], + users: [], +}; describe('Kubernetes Config API Service', () => { let kubeConfigService: KubeConfigApiService; @@ -81,7 +86,7 @@ describe('Kubernetes Config API Service', () => { }, } as CoreV1Api; }); - kubeConfig.exportConfig = jest.fn().mockReturnValue(config); + kubeConfig.exportConfig = jest.fn().mockReturnValue(JSON.stringify(config)); kubeConfig.getCurrentCluster = jest.fn().mockReturnValue(''); kubeConfig.applyToRequest = jest.fn(); @@ -142,7 +147,24 @@ describe('Kubernetes Config API Service', () => { workspaceName, namespace, containerName, - ['sh', '-c', `echo '${config}' > ${kubeConfigDir}/config`], + ['sh', '-c', `echo '${stringify(config)}' > ${kubeConfigDir}/config`], + expect.anything(), + ); + }); + test('should merge configs', async () => { + const configContent = fs.readFileSync(__dirname + '/fixtures/kubeconfig.yaml', 'utf-8'); + mockExecCatKubeConfig = jest.fn().mockReturnValue({ + stdOut: configContent, + stdError: '', + }); + await kubeConfigService.injectKubeConfig(namespace, 'wksp-id'); + + expect(spyExec).toHaveBeenNthCalledWith( + 5, + workspaceName, + namespace, + containerName, + ['sh', '-c', `echo '${configContent}' > ${kubeConfigDir}/config`], expect.anything(), ); }); diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/helpers/exec.ts b/packages/dashboard-backend/src/devworkspaceClient/services/helpers/exec.ts index ff49fcef1..c2bf3dbae 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/helpers/exec.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/helpers/exec.ts @@ -104,8 +104,7 @@ export async function exec( return; } - let message = Buffer.from(event.data.substr(1), 'base64').toString('utf-8'); - message = message.replace(/\n/g, ' ').trim(); + const message = Buffer.from(event.data.substr(1), 'base64').toString('utf-8').trim(); if (channel === CHANNELS[CHANNELS.STD_OUT]) { stdOut += message; diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/kubeConfigApi.ts b/packages/dashboard-backend/src/devworkspaceClient/services/kubeConfigApi.ts index a3f772643..e1b52ecc8 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/kubeConfigApi.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/kubeConfigApi.ts @@ -12,6 +12,7 @@ import { helpers } from '@eclipse-che/common'; import * as k8s from '@kubernetes/client-node'; +import { parse, stringify } from 'yaml'; import { exec, ServerConfig } from '@/devworkspaceClient/services/helpers/exec'; import { @@ -215,11 +216,11 @@ export class KubeConfigApiService implements IKubeConfigApi { private setNamespaceInContext(kubeConfig: string, namespace: string): string { try { - const kubeConfigJson = JSON.parse(kubeConfig); - for (const context of kubeConfigJson.contexts) { + const kubeConfigYaml = parse(kubeConfig); + for (const context of kubeConfigYaml.contexts) { context.context.namespace = namespace; } - return JSON.stringify(kubeConfigJson, undefined, ' '); + return stringify(kubeConfigYaml); } catch (e) { logger.error(e, 'Failed to parse kubeconfig'); return kubeConfig; @@ -230,31 +231,27 @@ export class KubeConfigApiService implements IKubeConfigApi { // If the inbounds kubeconfig match the kubeconfig format then merge them private mergeKubeConfig(kubeconfigSource: string, generatedKubeconfig: string): string { try { - const kubeConfigJson = JSON.parse(kubeconfigSource); - const generatedKubeConfigJson = JSON.parse(generatedKubeconfig); - for (const context of generatedKubeConfigJson.contexts) { - if (kubeConfigJson.contexts.find((c: any) => c.name === context.name)) { - kubeConfigJson.contexts = kubeConfigJson.contexts.filter( - (c: any) => c.name !== context.name, - ); + const kubeConfig = parse(kubeconfigSource); + const generatedKubeConfig = parse(generatedKubeconfig); + for (const context of generatedKubeConfig.contexts) { + if (kubeConfig.contexts.find((c: any) => c.name === context.name)) { + kubeConfig.contexts = kubeConfig.contexts.filter((c: any) => c.name !== context.name); } - kubeConfigJson.contexts.push(context); + kubeConfig.contexts.push(context); } - for (const cluster of generatedKubeConfigJson.clusters) { - if (kubeConfigJson.clusters.find((c: any) => c.name === cluster.name)) { - kubeConfigJson.clusters = kubeConfigJson.clusters.filter( - (c: any) => c.name !== cluster.name, - ); + for (const cluster of generatedKubeConfig.clusters) { + if (kubeConfig.clusters.find((c: any) => c.name === cluster.name)) { + kubeConfig.clusters = kubeConfig.clusters.filter((c: any) => c.name !== cluster.name); } - kubeConfigJson.clusters.push(cluster); + kubeConfig.clusters.push(cluster); } - for (const user of generatedKubeConfigJson.users) { - if (kubeConfigJson.users.find((c: any) => c.name === user.name)) { - kubeConfigJson.users = kubeConfigJson.users.filter((c: any) => c.name !== user.name); + for (const user of generatedKubeConfig.users) { + if (kubeConfig.users.find((c: any) => c.name === user.name)) { + kubeConfig.users = kubeConfig.users.filter((c: any) => c.name !== user.name); } - kubeConfigJson.users.push(user); + kubeConfig.users.push(user); } - return JSON.stringify(kubeConfigJson, undefined, ' '); + return stringify(kubeConfig); } catch (e) { logger.error(e, 'Failed to merge kubeconfig, returning source'); return kubeconfigSource;