diff --git a/src/extension.spec.ts b/src/extension.spec.ts index 4e95e76..7cbdfd3 100644 --- a/src/extension.spec.ts +++ b/src/extension.spec.ts @@ -85,6 +85,45 @@ test('kubernetes provider connection factory is set during activation', async () expect(providerMock.setKubernetesProviderConnectionFactory).toBeCalled(); }); +describe('connectionAuditor', () => { + test('returns error when context name is not provided', async () => { + const result = await extension.connectionAuditor({}); + expect(result.records).toHaveLength(1); + expect(result.records[0].type).toBe('error'); + expect(result.records[0].record).toBe('Context name is required.'); + }); + + test('returns error when context name already exists in kubeconfig', async () => { + const config = new KubeConfig(); + config.loadFromOptions({ + contexts: [{ cluster: 'cluster', name: 'existingContext', user: 'user' }], + clusters: [{ name: 'cluster', server: 'https://server' }], + users: [{ name: 'user', token: 'token' }], + }); + vi.spyOn(kubeconfig, 'createOrLoadFromFile').mockReturnValue(config); + const result = await extension.connectionAuditor({ + [extension.ContextNameParam]: 'existingContext', + }); + expect(result.records).toHaveLength(1); + expect(result.records[0].type).toBe('error'); + expect(result.records[0].record).toBe('Context existingContext already exists, please choose a different name.'); + }); + + test('returns no errors when context name is valid and unique', async () => { + const config = new KubeConfig(); + config.loadFromOptions({ + contexts: [], + clusters: [], + users: [], + }); + vi.spyOn(kubeconfig, 'createOrLoadFromFile').mockReturnValue(config); + const result = await extension.connectionAuditor({ + [extension.ContextNameParam]: 'newContext', + }); + expect(result.records).toHaveLength(0); + }); +}); + suite('kubernetes provider connection factory', () => { async function callCreate( params: { [key: string]: any } = {}, @@ -135,13 +174,6 @@ suite('kubernetes provider connection factory', () => { }; } - test('verifies context name is entered', async () => { - const { error: verificationError } = await callCreate(); - - expect(verificationError).toBeDefined(); - expect(verificationError.message).is.equal('Context name is required.'); - }); - test('creates new context for sandbox with specified url/token and sets it as default context', async () => { const config = new KubeConfig(); await callCreate( diff --git a/src/extension.ts b/src/extension.ts index 2c6b90c..2cd495d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ ***********************************************************************/ import { KubeConfig } from '@kubernetes/client-node'; +import type { AuditRequestItems, AuditResult } from '@podman-desktop/api'; import * as extensionApi from '@podman-desktop/api'; import got from 'got'; import * as kubeconfig from './kubeconfig.js'; @@ -212,6 +213,28 @@ export async function getDevSandboxSignUpStatus(idToken: string): Promise { + const records: extensionApi.AuditRecord[] = []; + + const contextName = items[ContextNameParam]; + if (!contextName) { + records.push({ + type: 'error', + record: 'Context name is required.', + }); + } else { + const config = kubeconfig.createOrLoadFromFile(extensionApi.kubernetes.getKubeconfig().fsPath); + if (config['contexts'].find(context => context['name'] === contextName)) { + records.push({ + type: 'error', + record: `Context ${contextName} already exists, please choose a different name.`, + }); + } + } + + return { records }; +} + export async function activate(extensionContext: extensionApi.ExtensionContext): Promise { console.log('starting extension redhat-developer-sandbox'); @@ -253,70 +276,63 @@ export async function activate(extensionContext: extensionApi.ExtensionContext): const kubeconfigFile = kubeconfigUri.fsPath; console.log('Config file location', kubeconfigFile); - const disposable = provider.setKubernetesProviderConnectionFactory({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - create: async ( - params: { [key: string]: any }, - _logger?: extensionApi.Logger, - _token?: extensionApi.CancellationToken, - ) => { - // check if context name is provided - if (!params[ContextNameParam]) { - throw new Error('Context name is required.'); - } - - // first verify context name does not exists yet in kubeconfig - const config = kubeconfig.createOrLoadFromFile(extensionApi.kubernetes.getKubeconfig().fsPath); - - if (config['contexts'].find(context => context['name'] === params[ContextNameParam])) { - throw new Error(`Context ${params[ContextNameParam]} already exists, please choose a different name.`); - } - - // use existing SSO session or request to login - const ssoSession = await extensionApi.authentication.getSession('redhat.authentication-provider', ['openid'], { - createIfNone: true, - }); - - // check Developer Sandbox status and sign up for it if possible - let status: SBSignupResponse = await getDevSandboxSignUpStatus((ssoSession as any).idToken); + const disposable = provider.setKubernetesProviderConnectionFactory( + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + create: async ( + params: { [key: string]: any }, + _logger?: extensionApi.Logger, + _token?: extensionApi.CancellationToken, + ) => { + const config = kubeconfig.createOrLoadFromFile(extensionApi.kubernetes.getKubeconfig().fsPath); + + const ssoSession = await extensionApi.authentication.getSession('redhat.authentication-provider', ['openid'], { + createIfNone: true, + }); - // get pipeline service account token or create new one - const token = await getPipelineServiceAccountToken( - status.proxyURL, - status.compliantUsername, - (ssoSession as any).idToken, - ); + let status: SBSignupResponse = await getDevSandboxSignUpStatus((ssoSession as any).idToken); - const suffix = Math.random().toString(36).substring(7); + const token = await getPipelineServiceAccountToken( + status.proxyURL, + status.compliantUsername, + (ssoSession as any).idToken, + ); - const clusterName = `sandbox-cluster-${suffix}`; // has unique name - const userName = `sandbox-user-${suffix}`; // generate a unique name for the user + const suffix = Math.random().toString(36).substring(7); + const clusterName = `sandbox-cluster-${suffix}`; + const userName = `sandbox-user-${suffix}`; - config.addCluster({ - server: status.apiEndpoint, - name: clusterName, - skipTLSVerify: false, - }); - config.addUser({ - name: userName, - token, - }); - config.addContext({ - cluster: clusterName, - user: userName, - name: params[ContextNameParam], - namespace: `${status.compliantUsername}-dev`, - }); - if (params[DefaultContextParam]) { - config.setCurrentContext(params[ContextNameParam]); - } + config.addCluster({ + server: status.apiEndpoint, + name: clusterName, + skipTLSVerify: false, + }); + config.addUser({ + name: userName, + token, + }); + config.addContext({ + cluster: clusterName, + user: userName, + name: params[ContextNameParam], + namespace: `${status.compliantUsername}-dev`, + }); + if (params[DefaultContextParam]) { + config.setCurrentContext(params[ContextNameParam]); + } - kubeconfig.exportToFile(config, kubeconfigFile); + kubeconfig.exportToFile(config, kubeconfigFile); - await registerConnection(params[ContextNameParam], status.apiEndpoint, token); + await registerConnection(params[ContextNameParam], status.apiEndpoint, token); + }, + creationDisplayName: ProvideDisplayName, }, - creationDisplayName: ProvideDisplayName, - }); + { + auditItems: async (items: AuditRequestItems) => { + return await connectionAuditor(items); + }, + }, + ); extensionContext.subscriptions.push( extensionApi.commands.registerCommand('sandbox.image.push.to.cluster', image => {