Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "chore: Mount kubeconfig into users containers (#950)" #952

Merged
merged 1 commit into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export { helpers, api };

export const FACTORY_LINK_ATTR = 'factoryLink';
export const ERROR_CODE_ATTR = 'error_code';
export const KUBECONFIG_MOUNT_PATH = '/tmp/.kube';

const common = {
helpers,
Expand Down
5 changes: 4 additions & 1 deletion packages/dashboard-backend/src/constants/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ export const namespacedKubeConfigSchema: JSONSchema7 = {
namespace: {
type: 'string',
},
devworkspaceId: {
type: 'string',
},
},
required: ['namespace'],
required: ['namespace', 'devworkspaceId'],
};

export const namespacedWorkspaceSchema: JSONSchema7 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,67 +13,137 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

import * as mockClient from '@kubernetes/client-node';
import { CoreV1Api, HttpError } from '@kubernetes/client-node';
import { IncomingMessage } from 'http';
import { CoreV1Api, V1PodList } from '@kubernetes/client-node';

import * as helper from '@/devworkspaceClient/services/helpers/exec';
import { KubeConfigApiService } from '@/devworkspaceClient/services/kubeConfigApi';

const homeUserDir = '/home/user';
const kubeConfigDir = `${homeUserDir}/.kube`;
const mockExecPrintenvHome = jest.fn().mockReturnValue({
stdOut: homeUserDir,
stdError: '',
});
const spyExec = jest
.spyOn(helper, 'exec')
.mockImplementation((...args: Parameters<typeof helper.exec>) => {
const [, , , command] = args;
if (command.some(c => c === 'printenv HOME')) {
// directory where to create the kubeconfig
return mockExecPrintenvHome();
} else if (command.some(c => c.startsWith('mkdir -p'))) {
// crete the directory
return Promise.resolve();
} else if (command.some(c => c.startsWith(`[ -f ${homeUserDir}`))) {
// sync config
return Promise.resolve();
}
return Promise.reject({
stdOut: '',
stdError: 'command executing error',
});
});

const namespace = 'user-che';
const workspaceName = 'workspace-1';
const containerName = 'container-1';
const config = JSON.stringify({
apiVersion: 'v1',
kind: 'Config',
'current-context': 'logged-user',
});

describe('Kubernetes Config API Service', () => {
let kubeConfigService: KubeConfigApiService;
let spyCreateNamespacedSecret: jest.SpyInstance;
let spyReadNamespacedSecret: jest.SpyInstance;
let spyReplaceNamespacedSecret: jest.SpyInstance;

console.error = jest.fn();
console.warn = jest.fn();

function initMocks(readNamespacedSecretReturnPromise: Promise<any>): void {
const stubCoreV1Api = {
createNamespacedSecret: (_namespace: string, secret: any) => {
return Promise.resolve({ body: secret });
},
readNamespacedSecret: () => {
return readNamespacedSecretReturnPromise;
},
replaceNamespacedSecret: (_name: string, _namespace: string, secret: any) => {
return Promise.resolve({ body: secret });
},
} as unknown as CoreV1Api;

spyCreateNamespacedSecret = jest.spyOn(stubCoreV1Api, 'createNamespacedSecret');
spyReadNamespacedSecret = jest.spyOn(stubCoreV1Api, 'readNamespacedSecret');
spyReplaceNamespacedSecret = jest.spyOn(stubCoreV1Api, 'replaceNamespacedSecret');

beforeEach(() => {
const { KubeConfig } = mockClient;
const kubeConfig = new KubeConfig();
kubeConfig.makeApiClient = jest.fn().mockImplementation(_api => stubCoreV1Api);

kubeConfig.makeApiClient = jest.fn().mockImplementation(_api => {
return {
listNamespacedPod: namespace => {
return Promise.resolve(buildListNamespacedPod());
},
} as CoreV1Api;
});
kubeConfig.exportConfig = jest.fn().mockReturnValue(config);
kubeConfig.getCurrentCluster = jest.fn().mockReturnValue('');
kubeConfig.applyToRequest = jest.fn();

kubeConfigService = new KubeConfigApiService(kubeConfig);
}
});

afterEach(() => {
jest.clearAllMocks();
});

test('create kubeconfig Secret', async () => {
initMocks(Promise.reject(new HttpError({} as IncomingMessage, undefined, 404)));

await kubeConfigService.applyKubeConfigSecret(namespace);

expect(spyReadNamespacedSecret).toBeCalled();
expect(spyCreateNamespacedSecret).toBeCalled();
expect(spyReplaceNamespacedSecret).toBeCalledTimes(0);
});

test('replace kubeconfig Secret', async () => {
initMocks(Promise.resolve({} as any));

await kubeConfigService.applyKubeConfigSecret(namespace);

expect(spyReadNamespacedSecret).toBeCalled();
expect(spyCreateNamespacedSecret).toBeCalledTimes(0);
expect(spyReplaceNamespacedSecret).toBeCalled();
test('injecting kubeconfig', async () => {
// mute output
console.error = jest.fn();
console.warn = jest.fn();

await kubeConfigService.injectKubeConfig(namespace, 'wksp-id');
expect(spyExec).toHaveBeenCalledTimes(4);

// should attempt to resolve the KUBECONFIG env variable
expect(spyExec).toHaveBeenNthCalledWith(
1,
workspaceName,
namespace,
containerName,
['sh', '-c', 'printenv KUBECONFIG'],
expect.anything(),
);

// should attempt to resolve the HOME env variable
expect(spyExec).toHaveBeenNthCalledWith(
2,
workspaceName,
namespace,
containerName,
['sh', '-c', 'printenv HOME'],
expect.anything(),
);

// should create the directory
expect(spyExec).toHaveBeenNthCalledWith(
3,
workspaceName,
namespace,
containerName,
['sh', '-c', `mkdir -p ${kubeConfigDir}`],
expect.anything(),
);

// should sync the kubeconfig to the container
expect(spyExec).toHaveBeenNthCalledWith(
4,
workspaceName,
namespace,
containerName,
['sh', '-c', `[ -f ${kubeConfigDir}/config ] || echo '${config}' > ${kubeConfigDir}/config`],
expect.anything(),
);
});
});

function buildListNamespacedPod(): { body: V1PodList } {
return {
body: {
apiVersion: 'v1',
items: [
{
metadata: {
name: workspaceName,
namespace,
},
spec: {
containers: [{ name: containerName }],
},
},
],
kind: 'PodList',
},
};
}
Loading
Loading