Skip to content

Commit

Permalink
Change the git-userdata configmap to secret
Browse files Browse the repository at this point in the history
  • Loading branch information
vinokurig committed Feb 10, 2025
1 parent f2e8000 commit 32d1dca
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import { api } from '@eclipse-che/common';
import * as mockClient from '@kubernetes/client-node';
import { CoreV1Api, V1ConfigMap } from '@kubernetes/client-node';
import { CoreV1Api, V1Secret } from '@kubernetes/client-node';
import { IncomingMessage } from 'http';

import { GitConfigApiService } from '..';
Expand All @@ -22,22 +22,22 @@ jest.mock('@/devworkspaceClient/services/helpers/retryableExec.ts');
const namespace = 'user-che';
const responseBody = {
data: {
gitconfig: `[user]\n\tname = User One\n\temail = user-1@che`,
gitconfig: btoa(`[user]\n\tname = User One\n\temail = user-1@che`),
},
};

describe('Gitconfig API', () => {
let gitConfigApiService: GitConfigApiService;

describe('configmap not found', () => {
describe('secret not found', () => {
const stubCoreV1Api = {
createNamespacedConfigMap: () => {
createNamespacedSecret: () => {
return Promise.resolve({
body: responseBody as V1ConfigMap,
body: responseBody as V1Secret,
response: {} as IncomingMessage,
});
},
readNamespacedConfigMap: () => {
readNamespacedSecret: () => {
return Promise.reject({
body: {},
response: {} as IncomingMessage,
Expand All @@ -47,7 +47,7 @@ describe('Gitconfig API', () => {
});
},
} as unknown as CoreV1Api;
const spyCreateNamespacedConfigMap = jest.spyOn(stubCoreV1Api, 'createNamespacedConfigMap');
const spyCreateNamespacedSecret = jest.spyOn(stubCoreV1Api, 'createNamespacedSecret');

beforeEach(() => {
const { KubeConfig } = mockClient;
Expand All @@ -61,7 +61,7 @@ describe('Gitconfig API', () => {
jest.clearAllMocks();
});

it('should create the configmap and return gitconfig', async () => {
it('should create the secret and return gitconfig', async () => {
const resp = await gitConfigApiService.read(namespace);

expect(resp.gitconfig).toStrictEqual({
Expand All @@ -71,13 +71,13 @@ describe('Gitconfig API', () => {
},
});

expect(spyCreateNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyCreateNamespacedConfigMap).toHaveBeenCalledWith(namespace, {
expect(spyCreateNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyCreateNamespacedSecret).toHaveBeenCalledWith(namespace, {
data: {
gitconfig: `[user]
gitconfig: btoa(`[user]
name=""
email=""
`,
`),
},
metadata: {
annotations: {
Expand All @@ -86,32 +86,32 @@ email=""
},
labels: {
'controller.devfile.io/mount-to-devworkspace': 'true',
'controller.devfile.io/watch-configmap': 'true',
'controller.devfile.io/watch-secret': 'true',
},
name: 'workspace-userdata-gitconfig-configmap',
name: 'devworkspace-gitconfig-automaunt-secret',
namespace,
},
});
});
});

describe('configmap found', () => {
describe('secret found', () => {
const stubCoreV1Api = {
readNamespacedConfigMap: () => {
readNamespacedSecret: () => {
return Promise.resolve({
body: responseBody as V1ConfigMap,
body: responseBody as V1Secret,
response: {} as IncomingMessage,
});
},
patchNamespacedConfigMap: () => {
patchNamespacedSecret: () => {
return Promise.resolve({
body: responseBody as V1ConfigMap,
body: responseBody as V1Secret,
response: {} as IncomingMessage,
});
},
} as unknown as CoreV1Api;
const spyReadNamespacedConfigMap = jest.spyOn(stubCoreV1Api, 'readNamespacedConfigMap');
const spyPatchNamespacedConfigMap = jest.spyOn(stubCoreV1Api, 'patchNamespacedConfigMap');
const spyReadNamespacedSecret = jest.spyOn(stubCoreV1Api, 'readNamespacedSecret');
const spyPatchNamespacedSecret = jest.spyOn(stubCoreV1Api, 'patchNamespacedSecret');

beforeEach(() => {
const { KubeConfig } = mockClient;
Expand All @@ -138,12 +138,12 @@ email=""
}),
);

expect(spyReadNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedConfigMap).not.toHaveBeenCalled();
expect(spyReadNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedSecret).not.toHaveBeenCalled();
});

it('should throw', async () => {
spyReadNamespacedConfigMap.mockRejectedValueOnce('404 not found');
spyReadNamespacedSecret.mockRejectedValueOnce('404 not found');

try {
await gitConfigApiService.read(namespace);
Expand All @@ -156,8 +156,8 @@ email=""
);
}

expect(spyReadNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedConfigMap).not.toHaveBeenCalled();
expect(spyReadNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedSecret).not.toHaveBeenCalled();
});
});

Expand All @@ -173,17 +173,17 @@ email=""
} as api.IGitConfig;
await gitConfigApiService.patch(namespace, newGitConfig);

expect(spyReadNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedConfigMap).toHaveBeenCalledWith(
'workspace-userdata-gitconfig-configmap',
expect(spyReadNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedSecret).toHaveBeenCalledWith(
'devworkspace-gitconfig-automaunt-secret',
'user-che',
{
data: {
gitconfig: `[user]
gitconfig: btoa(`[user]
email="user-2@che"
name="User Two"
`,
`),
},
},
undefined,
Expand All @@ -195,8 +195,8 @@ name="User Two"
);
});

it('should throw when can`t read the ConfigMap', async () => {
spyReadNamespacedConfigMap.mockRejectedValueOnce('404 not found');
it('should throw when can`t read the secret', async () => {
spyReadNamespacedSecret.mockRejectedValueOnce('404 not found');

const newGitConfig = {
gitconfig: {
Expand All @@ -218,12 +218,12 @@ name="User Two"
);
}

expect(spyReadNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedConfigMap).toHaveBeenCalledTimes(0);
expect(spyReadNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedSecret).toHaveBeenCalledTimes(0);
});

it('should throw when failed to patch the ConfigMap', async () => {
spyPatchNamespacedConfigMap.mockRejectedValueOnce('some error');
it('should throw when failed to patch the secret', async () => {
spyPatchNamespacedSecret.mockRejectedValueOnce('some error');

const newGitConfig = {
gitconfig: {
Expand All @@ -245,23 +245,23 @@ name="User Two"
);
}

expect(spyReadNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyReadNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedSecret).toHaveBeenCalledTimes(1);
});

it('should throw when conflict detected', async () => {
spyReadNamespacedConfigMap.mockResolvedValueOnce({
spyReadNamespacedSecret.mockResolvedValueOnce({
body: {
metadata: {
resourceVersion: '2',
},
data: {
gitconfig: `[user]
gitconfig: btoa(`[user]
name="User Two"
email="user-2@che"
`,
`),
},
} as V1ConfigMap,
} as V1Secret,
response: {} as IncomingMessage,
});

Expand Down Expand Up @@ -290,8 +290,8 @@ email="user-2@che"
'Conflict detected. The gitconfig was modified in the namespace "user-che".',
);

expect(spyReadNamespacedConfigMap).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedConfigMap).toHaveBeenCalledTimes(0);
expect(spyReadNamespacedSecret).toHaveBeenCalledTimes(1);
expect(spyPatchNamespacedSecret).toHaveBeenCalledTimes(0);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '@/devworkspaceClient/services/helpers/prepareCoreV1API';
import { IGitConfigApi } from '@/devworkspaceClient/types';

const GITCONFIG_CONFIGMAP = 'workspace-userdata-gitconfig-configmap';
const GITCONFIG_SECRET = 'devworkspace-gitconfig-automaunt-secret';
const GITCONFIG_API_ERROR_LABEL = 'CORE_V1_API_ERROR';

export class GitConfigApiService implements IGitConfigApi {
Expand All @@ -37,13 +37,13 @@ export class GitConfigApiService implements IGitConfigApi {
*/
public async read(namespace: string): Promise<api.IGitConfig> {
try {
const response = await this.coreV1API.readNamespacedConfigMap(GITCONFIG_CONFIGMAP, namespace);
const response = await this.coreV1API.readNamespacedSecret(GITCONFIG_SECRET, namespace);

return this.toGitConfig(response.body);
} catch (error) {
if (helpers.errors.isKubeClientError(error) && error.statusCode === 404) {
// Create gitconfig configmap if it does not exist
return this.createGitConfigMap(namespace);
// Create gitconfig secret if it does not exist
return this.createGitConfigSecret(namespace);
}

const message = `Unable to read gitconfig in the namespace "${namespace}"`;
Expand All @@ -53,35 +53,37 @@ export class GitConfigApiService implements IGitConfigApi {

/**
* @throws
* Creates `gitconfig` ConfigMap in the given `namespace`.
* Creates `gitconfig` Secret in the given `namespace`.
*/
private async createGitConfigMap(namespace: string): Promise<api.IGitConfig> {
const configMap = new k8s.V1ConfigMap();
configMap.metadata = {
name: GITCONFIG_CONFIGMAP,
private async createGitConfigSecret(namespace: string): Promise<api.IGitConfig> {
const secret = new k8s.V1Secret();
secret.metadata = {
name: GITCONFIG_SECRET,
namespace,
labels: {
'controller.devfile.io/mount-to-devworkspace': 'true',
'controller.devfile.io/watch-configmap': 'true',
'controller.devfile.io/watch-secret': 'true',
},
annotations: {
'controller.devfile.io/mount-as': 'subpath',
'controller.devfile.io/mount-path': '/etc/',
},
};
configMap.data = {
gitconfig: this.fromGitConfig({
gitconfig: {
user: {
name: '',
email: '',
secret.data = {
gitconfig: btoa(
this.fromGitConfig({
gitconfig: {
user: {
name: '',
email: '',
},
},
},
}),
}),
),
};

try {
const response = await this.coreV1API.createNamespacedConfigMap(namespace, configMap);
const response = await this.coreV1API.createNamespacedSecret(namespace, secret);
return this.toGitConfig(response.body);
} catch (error) {
const message = `Unable to create gitconfig in the namespace "${namespace}"`;
Expand Down Expand Up @@ -114,12 +116,12 @@ export class GitConfigApiService implements IGitConfigApi {

try {
const gitconfigStr = this.fromGitConfig(gitConfig);
const response = await this.coreV1API.patchNamespacedConfigMap(
GITCONFIG_CONFIGMAP,
const response = await this.coreV1API.patchNamespacedSecret(
GITCONFIG_SECRET,
namespace,
{
data: {
gitconfig: gitconfigStr,
gitconfig: btoa(gitconfigStr),
},
},
undefined,
Expand Down Expand Up @@ -153,19 +155,19 @@ export class GitConfigApiService implements IGitConfigApi {

/**
* @throws
* Extracts `resourceVersion` and `data.gitconfig` from given `ConfigMap`.
* Extracts `resourceVersion` and `data.gitconfig` from given `Secret`.
*/
private toGitConfig(configMapBody: k8s.V1ConfigMap): api.IGitConfig {
const resourceVersion = configMapBody.metadata?.resourceVersion;
const gitconfigStr = configMapBody.data?.gitconfig;
private toGitConfig(secretBody: k8s.V1ConfigMap): api.IGitConfig {
const resourceVersion = secretBody.metadata?.resourceVersion;
const gitconfigStr = secretBody.data?.gitconfig;

const parser = new ini.Parser();

if (typeof gitconfigStr !== 'string') {
throw new Error('Unexpected data type');
}

const gitconfigLines = gitconfigStr.split(/\r?\n/);
const gitconfigLines = atob(gitconfigStr).split(/\r?\n/);

const gitconfig = parser.parse(gitconfigLines);
if (!isGitConfig(gitconfig)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type CoreV1API = Pick<
| 'listNamespacedEvent'
| 'listNamespacedPod'
| 'listNamespacedSecret'
| 'patchNamespacedSecret'
| 'patchNamespacedConfigMap'
| 'readNamespacedConfigMap'
| 'readNamespacedPod'
Expand All @@ -48,6 +49,8 @@ export function prepareCoreV1API(kc: k8s.KubeConfig): CoreV1API {
retryableExec(() => coreV1API.listNamespacedSecret(...args)),
patchNamespacedConfigMap: (...args: Parameters<typeof coreV1API.patchNamespacedConfigMap>) =>
retryableExec(() => coreV1API.patchNamespacedConfigMap(...args)),
patchNamespacedSecret: (...args: Parameters<typeof coreV1API.patchNamespacedSecret>) =>
retryableExec(() => coreV1API.patchNamespacedSecret(...args)),
readNamespacedConfigMap: (...args: Parameters<typeof coreV1API.readNamespacedConfigMap>) =>
retryableExec(() => coreV1API.readNamespacedConfigMap(...args)),
readNamespacedPod: (...args: Parameters<typeof coreV1API.readNamespacedPod>) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class WorkspacePreferencesApiService implements IWorkspacePreferencesApi

async getWorkspacePreferences(namespace: string): Promise<api.IWorkspacePreferences> {
try {
const response = await this.coreV1API.readNamespacedConfigMap(
const response = await this.coreV1API.readNamespacedSecret(
DEV_WORKSPACE_PREFERENCES_CONFIGMAP,
namespace,
);
Expand Down

0 comments on commit 32d1dca

Please sign in to comment.