From 458ddffbbeeae23959e749ccc39a06f4eca5ed55 Mon Sep 17 00:00:00 2001 From: instamenta Date: Mon, 13 Jan 2025 11:30:36 +0200 Subject: [PATCH 1/5] added types and the tasks for selecting context to DeploymentCommand Signed-off-by: instamenta --- src/commands/cluster/configs.ts | 11 ++ src/commands/cluster/handlers.ts | 4 +- src/commands/cluster/tasks.ts | 214 ++++++++++++++++------------- src/commands/deployment.ts | 22 ++- test/unit/commands/cluster.test.ts | 7 + 5 files changed, 155 insertions(+), 103 deletions(-) diff --git a/src/commands/cluster/configs.ts b/src/commands/cluster/configs.ts index 2aea57685..6569b5d6b 100644 --- a/src/commands/cluster/configs.ts +++ b/src/commands/cluster/configs.ts @@ -20,6 +20,7 @@ import {Flags as flags} from '../flags.js'; import * as constants from '../../core/constants.js'; import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; import {SoloError} from '../../core/errors.js'; +import {Namespace} from '../../core/config/remote/types.js'; export const CONNECT_CONFIGS_NAME = 'connectConfig'; @@ -123,3 +124,13 @@ export interface ClusterResetConfigClass { clusterName: string; clusterSetupNamespace: string; } + +export interface SelectClusterContextContext { + config: { + quiet: boolean; + namespace: Namespace; + clusterName: string; + context: string; + clusters: string[]; + }; +} diff --git a/src/commands/cluster/handlers.ts b/src/commands/cluster/handlers.ts index b1dd0b6c6..a36c475a2 100644 --- a/src/commands/cluster/handlers.ts +++ b/src/commands/cluster/handlers.ts @@ -45,9 +45,9 @@ export class ClusterCommandHandlers implements CommandHandlers { this.tasks.initialize(argv, connectConfigBuilder.bind(this)), this.tasks.setupHomeDirectory(), this.parent.getLocalConfig().promptLocalConfigTask(this.parent.getK8()), - this.tasks.selectContext(argv), + this.tasks.selectContext(), RemoteConfigTasks.loadRemoteConfig.bind(this)(argv), - this.tasks.updateLocalConfig(argv), + this.tasks.updateLocalConfig(), ], { concurrent: false, diff --git a/src/commands/cluster/tasks.ts b/src/commands/cluster/tasks.ts index fca930eff..728dc9025 100644 --- a/src/commands/cluster/tasks.ts +++ b/src/commands/cluster/tasks.ts @@ -26,65 +26,79 @@ import chalk from 'chalk'; import {ListrLease} from '../../core/lease/listr_lease.js'; import {type K8} from '../../core/k8.js'; import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; +import type {SoloListrTask, SoloListrTaskWrapper} from '../../types/index.js'; +import type {SelectClusterContextContext} from './configs.js'; +import type {ClusterCommand} from './index.js'; +import type {DeploymentCommand} from '../deployment.js'; +import type {Namespace} from '../../core/config/remote/types.js'; +import type {LocalConfig} from '../../core/config/local_config.js'; export class ClusterCommandTasks { private readonly parent: BaseCommand; constructor( - parent, + parent: ClusterCommand | DeploymentCommand, private readonly k8: K8, ) { this.parent = parent; } - updateLocalConfig(argv) { - return new Task('Update local configuration', async (ctx: any, task: ListrTaskWrapper) => { - this.parent.logger.info('Compare local and remote configuration...'); - const configManager = this.parent.getConfigManager(); - const isQuiet = configManager.getFlag(flags.quiet); - - await this.parent.getRemoteConfigManager().modify(async remoteConfig => { - // Update current deployment with cluster list from remoteConfig - const localConfig = this.parent.getLocalConfig(); - const localDeployments = localConfig.deployments; - const remoteClusterList = []; - for (const cluster of Object.keys(remoteConfig.clusters)) { - if (localConfig.currentDeploymentName === remoteConfig.clusters[cluster]) { - remoteClusterList.push(cluster); - } - } - ctx.config.clusters = remoteClusterList; - localDeployments[localConfig.currentDeploymentName].clusters = ctx.config.clusters; - localConfig.setDeployments(localDeployments); - - const contexts = splitFlagInput(configManager.getFlag(flags.context)); - - for (let i = 0; i < ctx.config.clusters.length; i++) { - const cluster = ctx.config.clusters[i]; - const context = contexts[i]; - - // If a context is provided use it to update the mapping - if (context) { - localConfig.clusterContextMapping[cluster] = context; - } else if (!localConfig.clusterContextMapping[cluster]) { - // In quiet mode use the currently selected context to update the mapping - if (isQuiet) { - localConfig.clusterContextMapping[cluster] = this.parent.getK8().getKubeConfig().getCurrentContext(); + updateLocalConfig(): SoloListrTask { + return { + title: 'Update local configuration', + task: async (ctx, task) => { + this.parent.logger.info('Compare local and remote configuration...'); + const configManager = this.parent.getConfigManager(); + const isQuiet = configManager.getFlag(flags.quiet); + + await this.parent.getRemoteConfigManager().modify(async remoteConfig => { + // Update current deployment with a cluster list from remoteConfig + const localConfig = this.parent.getLocalConfig(); + const localDeployments = localConfig.deployments; + const remoteClusterList: string[] = []; + for (const cluster of Object.keys(remoteConfig.clusters)) { + if (localConfig.currentDeploymentName === remoteConfig.clusters[cluster]) { + remoteClusterList.push(cluster); } + } + ctx.config.clusters = remoteClusterList; + localDeployments[localConfig.currentDeploymentName].clusters = ctx.config.clusters; + localConfig.setDeployments(localDeployments); + + const contexts = splitFlagInput(configManager.getFlag(flags.context)); + + for (let i = 0; i < ctx.config.clusters.length; i++) { + const cluster = ctx.config.clusters[i]; + const context = contexts[i]; + + // If a context is provided, use it to update the mapping + if (context) { + localConfig.clusterContextMapping[cluster] = context; + } else if (!localConfig.clusterContextMapping[cluster]) { + // In quiet mode, use the currently selected context to update the mapping + if (isQuiet) { + localConfig.clusterContextMapping[cluster] = this.parent.getK8().getKubeConfig().getCurrentContext(); + } - // Prompt the user to select a context if mapping value is missing - else { - localConfig.clusterContextMapping[cluster] = await this.promptForContext(task, cluster); + // Prompt the user to select a context if mapping value is missing + else { + localConfig.clusterContextMapping[cluster] = await this.promptForContext(task, cluster); + } } } - } - this.parent.logger.info('Update local configuration...'); - await localConfig.write(); - }); - }); + this.parent.logger.info('Update local configuration...'); + await localConfig.write(); + }); + }, + }; } - private async getSelectedContext(task, selectedCluster, localConfig, isQuiet) { + private async getSelectedContext( + task: SoloListrTaskWrapper, + selectedCluster: string, + localConfig: LocalConfig, + isQuiet: boolean, + ) { let selectedContext; if (isQuiet) { selectedContext = this.parent.getK8().getKubeConfig().getCurrentContext(); @@ -95,7 +109,7 @@ export class ClusterCommandTasks { return selectedContext; } - private async promptForContext(task, cluster) { + private async promptForContext(task: SoloListrTaskWrapper, cluster: string) { const kubeContexts = this.parent.getK8().getContexts(); return flags.context.prompt( task, @@ -104,14 +118,19 @@ export class ClusterCommandTasks { ); } - private async selectContextForFirstCluster(task, clusters, localConfig, isQuiet) { + private async selectContextForFirstCluster( + task: SoloListrTaskWrapper, + clusters: string[], + localConfig: LocalConfig, + isQuiet: boolean, + ) { const selectedCluster = clusters[0]; if (localConfig.clusterContextMapping[selectedCluster]) { return localConfig.clusterContextMapping[selectedCluster]; } - // If cluster does not exist in LocalConfig mapping prompt the user to select a context or use the current one + // If a cluster does not exist in LocalConfig mapping prompt the user to select a context or use the current one else { return this.getSelectedContext(task, selectedCluster, localConfig, isQuiet); } @@ -160,69 +179,72 @@ export class ClusterCommandTasks { ); } - selectContext(argv) { - return new Task('Read local configuration settings', async (ctx: any, task: ListrTaskWrapper) => { - this.parent.logger.info('Read local configuration settings...'); - const configManager = this.parent.getConfigManager(); - const isQuiet = configManager.getFlag(flags.quiet); - const deploymentName: string = configManager.getFlag(flags.namespace); - let clusters = splitFlagInput(configManager.getFlag(flags.clusterName)); - const contexts = splitFlagInput(configManager.getFlag(flags.context)); - const localConfig = this.parent.getLocalConfig(); - let selectedContext; - - // If one or more contexts are provided use the first one - if (contexts.length) { - selectedContext = contexts[0]; - } - - // If one or more clusters are provided use the first one to determine the context - // from the mapping in the LocalConfig - else if (clusters.length) { - selectedContext = await this.selectContextForFirstCluster(task, clusters, localConfig, isQuiet); - } + selectContext(): SoloListrTask { + return { + title: 'Read local configuration settings', + task: async (_, task) => { + this.parent.logger.info('Read local configuration settings...'); + const configManager = this.parent.getConfigManager(); + const isQuiet = configManager.getFlag(flags.quiet); + const deploymentName: string = configManager.getFlag(flags.namespace); + let clusters = splitFlagInput(configManager.getFlag(flags.clusterName)); + const contexts = splitFlagInput(configManager.getFlag(flags.context)); + const localConfig = this.parent.getLocalConfig(); + let selectedContext: string; - // If a deployment name is provided get the clusters associated with the deployment from the LocalConfig - // and select the context from the mapping, corresponding to the first deployment cluster - else if (deploymentName) { - const deployment = localConfig.deployments[deploymentName]; + // If one or more contexts are provided, use the first one + if (contexts.length) { + selectedContext = contexts[0]; + } - if (deployment && deployment.clusters.length) { - selectedContext = await this.selectContextForFirstCluster(task, deployment.clusters, localConfig, isQuiet); + // If one or more clusters are provided, use the first one to determine the context + // from the mapping in the LocalConfig + else if (clusters.length) { + selectedContext = await this.selectContextForFirstCluster(task, clusters, localConfig, isQuiet); } - // The provided deployment does not exist in the LocalConfig - else { - // Add the deployment to the LocalConfig with the currently selected cluster and context in KubeConfig - if (isQuiet) { - selectedContext = this.parent.getK8().getKubeConfig().getCurrentContext(); - const selectedCluster = this.parent.getK8().getKubeConfig().getCurrentCluster().name; - localConfig.deployments[deploymentName] = { - clusters: [selectedCluster], - }; - - if (!localConfig.clusterContextMapping[selectedCluster]) { - localConfig.clusterContextMapping[selectedCluster] = selectedContext; - } + // If a deployment name is provided, get the clusters associated with the deployment from the LocalConfig + // and select the context from the mapping, corresponding to the first deployment cluster + else if (deploymentName) { + const deployment = localConfig.deployments[deploymentName]; + + if (deployment && deployment.clusters.length) { + selectedContext = await this.selectContextForFirstCluster(task, deployment.clusters, localConfig, isQuiet); } - // Prompt user for clusters and contexts + // The provided deployment does not exist in the LocalConfig else { - clusters = splitFlagInput(await flags.clusterName.prompt(task, clusters)); - - for (const cluster of clusters) { - if (!localConfig.clusterContextMapping[cluster]) { - localConfig.clusterContextMapping[cluster] = await this.promptForContext(task, cluster); + // Add the deployment to the LocalConfig with the currently selected cluster and context in KubeConfig + if (isQuiet) { + selectedContext = this.parent.getK8().getKubeConfig().getCurrentContext(); + const selectedCluster = this.parent.getK8().getKubeConfig().getCurrentCluster().name; + localConfig.deployments[deploymentName] = { + clusters: [selectedCluster], + }; + + if (!localConfig.clusterContextMapping[selectedCluster]) { + localConfig.clusterContextMapping[selectedCluster] = selectedContext; } } - selectedContext = localConfig.clusterContextMapping[clusters[0]]; + // Prompt user for clusters and contexts + else { + clusters = splitFlagInput(await flags.clusterName.prompt(task, clusters)); + + for (const cluster of clusters) { + if (!localConfig.clusterContextMapping[cluster]) { + localConfig.clusterContextMapping[cluster] = await this.promptForContext(task, cluster); + } + } + + selectedContext = localConfig.clusterContextMapping[clusters[0]]; + } } } - } - this.parent.getK8().getKubeConfig().setCurrentContext(selectedContext); - }); + this.parent.getK8().getKubeConfig().setCurrentContext(selectedContext); + }, + }; } initialize(argv: any, configInit: ConfigBuilder) { diff --git a/src/commands/deployment.ts b/src/commands/deployment.ts index 74deb0f06..3f2883077 100644 --- a/src/commands/deployment.ts +++ b/src/commands/deployment.ts @@ -23,15 +23,26 @@ import {Templates} from '../core/templates.js'; import chalk from 'chalk'; import {RemoteConfigTasks} from '../core/config/remote/remote_config_tasks.js'; import {ListrLease} from '../core/lease/listr_lease.js'; +import {ClusterCommandTasks} from './cluster/tasks.js'; import type {Namespace} from '../core/config/remote/types.js'; -import {type ContextClusterStructure} from '../types/config_types.js'; -import {type CommandFlag} from '../types/flag_types.js'; -import {type CommandBuilder} from '../types/aliases.js'; +import type {ContextClusterStructure} from '../types/config_types.js'; +import type {CommandFlag} from '../types/flag_types.js'; +import type {CommandBuilder} from '../types/aliases.js'; +import type {Opts} from '../types/command_types.js'; export class DeploymentCommand extends BaseCommand { + readonly tasks: ClusterCommandTasks; + + constructor(opts: Opts) { + super(opts); + + this.tasks = new ClusterCommandTasks(this, this.k8); + } + private static get DEPLOY_FLAGS_LIST(): CommandFlag[] { return [ flags.quiet, + flags.context, flags.namespace, flags.userEmailAddress, flags.deploymentClusters, @@ -44,6 +55,7 @@ export class DeploymentCommand extends BaseCommand { const lease = await self.leaseManager.create(); interface Config { + context: string; namespace: Namespace; contextClusterUnparsed: string; contextCluster: ContextClusterStructure; @@ -56,7 +68,7 @@ export class DeploymentCommand extends BaseCommand { [ { title: 'Initialize', - task: async (ctx, task): Promise> => { + task: async (ctx, task) => { self.configManager.update(argv); self.logger.debug('Updated config with argv', {config: self.configManager.config}); @@ -87,7 +99,7 @@ export class DeploymentCommand extends BaseCommand { this.localConfig.promptLocalConfigTask(self.k8), { title: 'Validate cluster connections', - task: async (ctx, task): Promise> => { + task: async (ctx, task) => { const subTasks = []; for (const cluster of Object.keys(ctx.config.contextCluster)) { diff --git a/test/unit/commands/cluster.test.ts b/test/unit/commands/cluster.test.ts index a8a8dffef..930f4cabf 100644 --- a/test/unit/commands/cluster.test.ts +++ b/test/unit/commands/cluster.test.ts @@ -206,8 +206,11 @@ describe('ClusterCommand unit tests', () => { describe('updateLocalConfig', () => { async function runUpdateLocalConfigTask(opts) { command = new ClusterCommand(opts); + // @ts-ignore tasks = new ClusterCommandTasks(command, opts.k8); + // @ts-ignore const taskObj = tasks.updateLocalConfig({}); + // @ts-ignore await taskObj.task({config: {}}, sandbox.stub() as unknown as ListrTaskWrapper); return command; } @@ -344,8 +347,12 @@ describe('ClusterCommand unit tests', () => { describe('selectContext', () => { async function runSelectContextTask(opts) { command = new ClusterCommand(opts); + // @ts-ignore tasks = new ClusterCommandTasks(command, opts.k8); + // @ts-ignore const taskObj = tasks.selectContext({}); + + // @ts-ignore await taskObj.task({config: {}}, sandbox.stub() as unknown as ListrTaskWrapper); return command; } From 16fee5d7f3907224dc403ce90e45db962d0d4d76 Mon Sep 17 00:00:00 2001 From: instamenta Date: Mon, 13 Jan 2025 13:42:47 +0200 Subject: [PATCH 2/5] add tasks to the 'deployment create' command, and fix logic around localconfig for not prompting the user for email Signed-off-by: instamenta --- src/commands/deployment.ts | 2 ++ src/core/config/local_config.ts | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/commands/deployment.ts b/src/commands/deployment.ts index 3f2883077..1a97260ce 100644 --- a/src/commands/deployment.ts +++ b/src/commands/deployment.ts @@ -122,6 +122,8 @@ export class DeploymentCommand extends BaseCommand { }, }, RemoteConfigTasks.createRemoteConfig.bind(this)(), + this.tasks.selectContext(), + this.tasks.updateLocalConfig(), ], { concurrent: false, diff --git a/src/core/config/local_config.ts b/src/core/config/local_config.ts index 9dbfb910d..8455bc14a 100644 --- a/src/core/config/local_config.ts +++ b/src/core/config/local_config.ts @@ -35,6 +35,7 @@ import {splitFlagInput} from '../helpers.js'; import {inject, injectable} from 'tsyringe-neo'; import {patchInject} from '../container_helper.js'; import type {SoloListrTask, SoloListrTaskWrapper} from '../../types/index.js'; +import {AnyObject} from '../../types/aliases.js'; @injectable() export class LocalConfig implements LocalConfigData { @@ -169,7 +170,7 @@ export class LocalConfig implements LocalConfigData { title: 'Prompt local configuration', skip: this.skipPromptTask, task: async (_: any, task: SoloListrTaskWrapper): Promise => { - if (self.configFileExists) { + if (self.configFileExists()) { self.configManager.setFlag(flags.userEmailAddress, self.userEmailAddress); } @@ -206,10 +207,10 @@ export class LocalConfig implements LocalConfigData { if (parsedContexts.length < parsedClusters.length) { if (!isQuiet) { - const promptedContexts = []; + const promptedContexts: string[] = []; for (const cluster of parsedClusters) { const kubeContexts = k8.getContexts(); - const context = await flags.context.prompt( + const context: string = await flags.context.prompt( task, kubeContexts.map(c => c.name), cluster, From 3e33c494a49781a759d671def3db3e0e858289fb Mon Sep 17 00:00:00 2001 From: instamenta Date: Mon, 13 Jan 2025 14:01:21 +0200 Subject: [PATCH 3/5] fixed linter error and circular dependency Signed-off-by: instamenta --- src/commands/cluster/configs.ts | 2 +- src/commands/cluster/tasks.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/commands/cluster/configs.ts b/src/commands/cluster/configs.ts index 6569b5d6b..d1f9ece95 100644 --- a/src/commands/cluster/configs.ts +++ b/src/commands/cluster/configs.ts @@ -20,7 +20,7 @@ import {Flags as flags} from '../flags.js'; import * as constants from '../../core/constants.js'; import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; import {SoloError} from '../../core/errors.js'; -import {Namespace} from '../../core/config/remote/types.js'; +import {type Namespace} from '../../core/config/remote/types.js'; export const CONNECT_CONFIGS_NAME = 'connectConfig'; diff --git a/src/commands/cluster/tasks.ts b/src/commands/cluster/tasks.ts index 728dc9025..0ff4e27bd 100644 --- a/src/commands/cluster/tasks.ts +++ b/src/commands/cluster/tasks.ts @@ -28,8 +28,6 @@ import {type K8} from '../../core/k8.js'; import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; import type {SoloListrTask, SoloListrTaskWrapper} from '../../types/index.js'; import type {SelectClusterContextContext} from './configs.js'; -import type {ClusterCommand} from './index.js'; -import type {DeploymentCommand} from '../deployment.js'; import type {Namespace} from '../../core/config/remote/types.js'; import type {LocalConfig} from '../../core/config/local_config.js'; @@ -37,7 +35,7 @@ export class ClusterCommandTasks { private readonly parent: BaseCommand; constructor( - parent: ClusterCommand | DeploymentCommand, + parent, private readonly k8: K8, ) { this.parent = parent; From 66d6a1c44dd3b864b88474e4fccd8a4e2846eb46 Mon Sep 17 00:00:00 2001 From: instamenta Date: Tue, 14 Jan 2025 10:33:12 +0200 Subject: [PATCH 4/5] remove unusued import, and switched @ts-ignore tags for @ts-expect-error as requested Signed-off-by: instamenta --- src/core/config/local_config.ts | 1 - test/unit/commands/cluster.test.ts | 48 ++++++++++++++++-------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/core/config/local_config.ts b/src/core/config/local_config.ts index 8455bc14a..93d9f5065 100644 --- a/src/core/config/local_config.ts +++ b/src/core/config/local_config.ts @@ -35,7 +35,6 @@ import {splitFlagInput} from '../helpers.js'; import {inject, injectable} from 'tsyringe-neo'; import {patchInject} from '../container_helper.js'; import type {SoloListrTask, SoloListrTaskWrapper} from '../../types/index.js'; -import {AnyObject} from '../../types/aliases.js'; @injectable() export class LocalConfig implements LocalConfigData { diff --git a/test/unit/commands/cluster.test.ts b/test/unit/commands/cluster.test.ts index 930f4cabf..6b5135ba6 100644 --- a/test/unit/commands/cluster.test.ts +++ b/test/unit/commands/cluster.test.ts @@ -147,7 +147,8 @@ describe('ClusterCommand unit tests', () => { const getBaseCommandOpts = ( sandbox: sinon.SinonSandbox, remoteConfig: any = {}, - // @ts-ignore + + // @ts-expect-error - TS2344: Type CommandFlag does not satisfy the constraint string | number | symbol stubbedFlags: Record[] = [], ) => { const loggerStub = sandbox.createStubInstance(SoloLogger); @@ -206,12 +207,13 @@ describe('ClusterCommand unit tests', () => { describe('updateLocalConfig', () => { async function runUpdateLocalConfigTask(opts) { command = new ClusterCommand(opts); - // @ts-ignore + tasks = new ClusterCommandTasks(command, opts.k8); - // @ts-ignore + + // @ts-expect-error - TS2554: Expected 0 arguments, but got 1. const taskObj = tasks.updateLocalConfig({}); - // @ts-ignore - await taskObj.task({config: {}}, sandbox.stub() as unknown as ListrTaskWrapper); + + await taskObj.task({config: {}} as any, sandbox.stub() as unknown as ListrTaskWrapper); return command; } @@ -249,7 +251,7 @@ describe('ClusterCommand unit tests', () => { }, }; const opts = getBaseCommandOpts(sandbox, remoteConfig, []); - command = await runUpdateLocalConfigTask(opts); // @ts-ignore + command = await runUpdateLocalConfigTask(opts); localConfig = new LocalConfig(filePath); expect(localConfig.currentDeploymentName).to.equal('deployment'); @@ -267,7 +269,7 @@ describe('ClusterCommand unit tests', () => { }, }; const opts = getBaseCommandOpts(sandbox, remoteConfig, [[flags.context, 'provided-context']]); - command = await runUpdateLocalConfigTask(opts); // @ts-ignore + command = await runUpdateLocalConfigTask(opts); localConfig = new LocalConfig(filePath); expect(localConfig.currentDeploymentName).to.equal('deployment'); @@ -289,7 +291,7 @@ describe('ClusterCommand unit tests', () => { const opts = getBaseCommandOpts(sandbox, remoteConfig, [ [flags.context, 'provided-context-2,provided-context-3,provided-context-4'], ]); - command = await runUpdateLocalConfigTask(opts); // @ts-ignore + command = await runUpdateLocalConfigTask(opts); localConfig = new LocalConfig(filePath); expect(localConfig.currentDeploymentName).to.equal('deployment'); @@ -310,7 +312,7 @@ describe('ClusterCommand unit tests', () => { }, }; const opts = getBaseCommandOpts(sandbox, remoteConfig, [[flags.quiet, true]]); - command = await runUpdateLocalConfigTask(opts); // @ts-ignore + command = await runUpdateLocalConfigTask(opts); localConfig = new LocalConfig(filePath); expect(localConfig.currentDeploymentName).to.equal('deployment'); @@ -331,7 +333,7 @@ describe('ClusterCommand unit tests', () => { }; const opts = getBaseCommandOpts(sandbox, remoteConfig, []); - command = await runUpdateLocalConfigTask(opts); // @ts-ignore + command = await runUpdateLocalConfigTask(opts); localConfig = new LocalConfig(filePath); expect(localConfig.currentDeploymentName).to.equal('deployment'); @@ -347,13 +349,13 @@ describe('ClusterCommand unit tests', () => { describe('selectContext', () => { async function runSelectContextTask(opts) { command = new ClusterCommand(opts); - // @ts-ignore + tasks = new ClusterCommandTasks(command, opts.k8); - // @ts-ignore + + // @ts-expect-error - TS2554: Expected 0 arguments, but got 1 const taskObj = tasks.selectContext({}); - // @ts-ignore - await taskObj.task({config: {}}, sandbox.stub() as unknown as ListrTaskWrapper); + await taskObj.task({config: {}} as any, sandbox.stub() as unknown as ListrTaskWrapper); return command; } @@ -387,21 +389,21 @@ describe('ClusterCommand unit tests', () => { [flags.context, 'provided-context-1,provided-context-2,provided-context-3'], ]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('provided-context-1'); }); it('should use local config mapping to connect to first provided cluster', async () => { const opts = getBaseCommandOpts(sandbox, {}, [[flags.clusterName, 'cluster-2,cluster-3']]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-2'); }); it('should prompt for context if selected cluster is not found in local config mapping', async () => { const opts = getBaseCommandOpts(sandbox, {}, [[flags.clusterName, 'cluster-3']]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-3'); }); @@ -411,21 +413,21 @@ describe('ClusterCommand unit tests', () => { [flags.quiet, true], ]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-from-kubeConfig'); }); it('should use context from local config mapping for the first cluster from the selected deployment', async () => { const opts = getBaseCommandOpts(sandbox, {}, [[flags.namespace, 'deployment-2']]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-2'); }); it('should prompt for context if selected deployment is found in local config but the context is not', async () => { const opts = getBaseCommandOpts(sandbox, {}, [[flags.namespace, 'deployment-3']]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-3'); }); @@ -435,14 +437,14 @@ describe('ClusterCommand unit tests', () => { [flags.quiet, true], ]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-from-kubeConfig'); }); it('should prompt for clusters and contexts if selected deployment is not found in local config', async () => { const opts = getBaseCommandOpts(sandbox, {}, [[flags.namespace, 'deployment-4']]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-3'); }); @@ -452,7 +454,7 @@ describe('ClusterCommand unit tests', () => { [flags.quiet, true], ]); - command = await runSelectContextTask(opts); // @ts-ignore + command = await runSelectContextTask(opts); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-from-kubeConfig'); }); }); From 08fbb028350ab65176ddbdd251500a474a7775b9 Mon Sep 17 00:00:00 2001 From: instamenta Date: Wed, 15 Jan 2025 17:44:43 +0200 Subject: [PATCH 5/5] validate context Signed-off-by: instamenta --- src/commands/deployment.ts | 34 +++++++++++++-------------------- src/core/config/local_config.ts | 5 +++++ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/commands/deployment.ts b/src/commands/deployment.ts index 881b3c0e5..31716fdd8 100644 --- a/src/commands/deployment.ts +++ b/src/commands/deployment.ts @@ -14,7 +14,7 @@ * limitations under the License. * */ -import {Listr, type ListrTaskWrapper} from 'listr2'; +import {Listr} from 'listr2'; import {SoloError} from '../core/errors.js'; import {BaseCommand} from './base.js'; import {Flags as flags} from './flags.js'; @@ -97,32 +97,24 @@ export class DeploymentCommand extends BaseCommand { }, }, this.localConfig.promptLocalConfigTask(self.k8), + RemoteConfigTasks.createRemoteConfig.bind(this)(), + this.tasks.selectContext(), { - title: 'Validate cluster connections', + title: 'Validate context', task: async (ctx, task) => { - const subTasks = []; - - for (const cluster of Object.keys(ctx.config.contextCluster)) { - subTasks.push({ - title: `Testing connection to cluster: ${chalk.cyan(cluster)}`, - task: async (_: Context, task: ListrTaskWrapper) => { - if (!(await self.k8.testClusterConnection(cluster))) { - task.title = `${task.title} - ${chalk.red('Cluster connection failed')}`; - - throw new SoloError(`Cluster connection failed for: ${cluster}`); - } - }, - }); + ctx.config.context = ctx.config.context ?? self.configManager.getFlag(flags.context); + const availableContexts = self.k8.getContextNames(); + + if (availableContexts.includes(ctx.config.context)) { + task.title += ` - context: ${chalk.green(ctx.config.context)} is valid`; + return; } - return task.newListr(subTasks, { - concurrent: true, - rendererOptions: {collapseSubtasks: false}, - }); + throw new SoloError( + `Context with name ${ctx.config.context} not found, available contexts include ${availableContexts.join(', ')}`, + ); }, }, - RemoteConfigTasks.createRemoteConfig.bind(this)(), - this.tasks.selectContext(), this.tasks.updateLocalConfig(), ], { diff --git a/src/core/config/local_config.ts b/src/core/config/local_config.ts index 93d9f5065..697e524f8 100644 --- a/src/core/config/local_config.ts +++ b/src/core/config/local_config.ts @@ -216,6 +216,8 @@ export class LocalConfig implements LocalConfigData { ); self.clusterContextMapping[cluster] = context; promptedContexts.push(context); + + self.configManager.setFlag(flags.context, context); } self.configManager.setFlag(flags.context, promptedContexts.join(',')); } else { @@ -229,12 +231,15 @@ export class LocalConfig implements LocalConfigData { for (let i = 0; i < parsedClusters.length; i++) { const cluster = parsedClusters[i]; self.clusterContextMapping[cluster] = parsedContexts[i]; + + self.configManager.setFlag(flags.context, parsedContexts[i]); } } self.userEmailAddress = userEmailAddress; self.deployments = deployments; self.currentDeploymentName = deploymentName; + self.validate(); await self.write(); },