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

feat: solo deployment create should use the context and cluster provided for where to save the remote config #1142

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
11 changes: 11 additions & 0 deletions src/commands/cluster/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {type Namespace} from '../../core/config/remote/types.js';

export const CONNECT_CONFIGS_NAME = 'connectConfig';

Expand Down Expand Up @@ -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[];
};
}
4 changes: 2 additions & 2 deletions src/commands/cluster/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
this.tasks.initialize(argv, connectConfigBuilder.bind(this)),
this.tasks.setupHomeDirectory(),
this.parent.getLocalConfig().promptLocalConfigTask(this.parent.getK8()),
this.tasks.selectContext(argv),
this.tasks.selectContext(),

Check warning on line 48 in src/commands/cluster/handlers.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/cluster/handlers.ts#L48

Added line #L48 was not covered by tests
RemoteConfigTasks.loadRemoteConfig.bind(this)(argv),
this.tasks.updateLocalConfig(argv),
this.tasks.updateLocalConfig(),

Check warning on line 50 in src/commands/cluster/handlers.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/cluster/handlers.ts#L50

Added line #L50 was not covered by tests
],
{
concurrent: false,
Expand Down
210 changes: 115 additions & 95 deletions src/commands/cluster/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ 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 {Namespace} from '../../core/config/remote/types.js';
import type {LocalConfig} from '../../core/config/local_config.js';

export class ClusterCommandTasks {
private readonly parent: BaseCommand;
Expand All @@ -37,54 +41,62 @@ export class ClusterCommandTasks {
this.parent = parent;
}

updateLocalConfig(argv) {
return new Task('Update local configuration', async (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
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<SelectClusterContextContext> {
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<SelectClusterContextContext>,
selectedCluster: string,
localConfig: LocalConfig,
isQuiet: boolean,
) {
let selectedContext;
if (isQuiet) {
selectedContext = this.parent.getK8().getKubeConfig().getCurrentContext();
Expand All @@ -95,7 +107,7 @@ export class ClusterCommandTasks {
return selectedContext;
}

private async promptForContext(task, cluster) {
private async promptForContext(task: SoloListrTaskWrapper<SelectClusterContextContext>, cluster: string) {
const kubeContexts = this.parent.getK8().getContexts();
return flags.context.prompt(
task,
Expand All @@ -104,14 +116,19 @@ export class ClusterCommandTasks {
);
}

private async selectContextForFirstCluster(task, clusters, localConfig, isQuiet) {
private async selectContextForFirstCluster(
task: SoloListrTaskWrapper<SelectClusterContextContext>,
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);
}
Expand Down Expand Up @@ -160,69 +177,72 @@ export class ClusterCommandTasks {
);
}

selectContext(argv) {
return new Task('Read local configuration settings', async (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
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<SelectClusterContextContext> {
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<boolean>(flags.quiet);
const deploymentName: string = configManager.getFlag<Namespace>(flags.namespace);
let clusters = splitFlagInput(configManager.getFlag<string>(flags.clusterName));
const contexts = splitFlagInput(configManager.getFlag<string>(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) {
Expand Down
24 changes: 19 additions & 5 deletions src/commands/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -56,7 +68,7 @@ export class DeploymentCommand extends BaseCommand {
[
{
title: 'Initialize',
task: async (ctx, task): Promise<Listr<Context, any, any>> => {
task: async (ctx, task) => {
self.configManager.update(argv);
self.logger.debug('Updated config with argv', {config: self.configManager.config});

Expand Down Expand Up @@ -87,7 +99,7 @@ export class DeploymentCommand extends BaseCommand {
this.localConfig.promptLocalConfigTask(self.k8),
{
title: 'Validate cluster connections',
task: async (ctx, task): Promise<Listr<Context, any, any>> => {
task: async (ctx, task) => {
const subTasks = [];

for (const cluster of Object.keys(ctx.config.contextCluster)) {
Expand All @@ -110,6 +122,8 @@ export class DeploymentCommand extends BaseCommand {
},
},
RemoteConfigTasks.createRemoteConfig.bind(this)(),
this.tasks.selectContext(),
this.tasks.updateLocalConfig(),
],
{
concurrent: false,
Expand Down
6 changes: 3 additions & 3 deletions src/core/config/local_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
title: 'Prompt local configuration',
skip: this.skipPromptTask,
task: async (_: any, task: SoloListrTaskWrapper<any>): Promise<void> => {
if (self.configFileExists) {
if (self.configFileExists()) {

Check warning on line 172 in src/core/config/local_config.ts

View check run for this annotation

Codecov / codecov/patch

src/core/config/local_config.ts#L172

Added line #L172 was not covered by tests
self.configManager.setFlag(flags.userEmailAddress, self.userEmailAddress);
}

Expand Down Expand Up @@ -206,10 +206,10 @@

if (parsedContexts.length < parsedClusters.length) {
if (!isQuiet) {
const promptedContexts = [];
const promptedContexts: string[] = [];

Check warning on line 209 in src/core/config/local_config.ts

View check run for this annotation

Codecov / codecov/patch

src/core/config/local_config.ts#L209

Added line #L209 was not covered by tests
for (const cluster of parsedClusters) {
const kubeContexts = k8.getContexts();
const context = await flags.context.prompt(
const context: string = await flags.context.prompt(

Check warning on line 212 in src/core/config/local_config.ts

View check run for this annotation

Codecov / codecov/patch

src/core/config/local_config.ts#L212

Added line #L212 was not covered by tests
task,
kubeContexts.map(c => c.name),
cluster,
Expand Down
Loading
Loading