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: handle multiple contexts during network deploy #1369

Merged
merged 6 commits into from
Feb 12, 2025
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
83 changes: 79 additions & 4 deletions src/commands/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import paths from 'path';
import path from 'path';
import {MissingArgumentError, SoloError} from '../core/errors.js';
import {ShellRunner} from '../core/shell_runner.js';
import {type LeaseManager} from '../core/lease/lease_manager.js';
Expand All @@ -17,11 +18,12 @@ import {type Opts} from '../types/command_types.js';
import {type CommandFlag} from '../types/flag_types.js';
import {type Lease} from '../core/lease/lease.js';
import {Listr} from 'listr2';
import path from 'path';
import * as constants from '../core/constants.js';
import fs from 'fs';
import {Task} from '../core/task.js';
import {ConsensusNode} from '../core/model/consensus_node.js';
import {type ClusterRef, type ClusterRefs} from '../core/config/remote/types.js';
import {Flags} from './flags.js';

export interface CommandHandlers {
parent: BaseCommand;
Expand Down Expand Up @@ -72,9 +74,7 @@ export abstract class BaseCommand extends ShellRunner {
return `${chartRepo}/${chartReleaseName}`;
}

// TODO @Lenin, this is in the base so it will be used by everyone, which might be good because they won't have to duplicate the code
// perhaps we should clone this method and have the new method return an object Record<ClusterRef, valuesFileArg>
// need to support: --values-file aws-cluster=aws/solo-values.yaml,aws-cluster=aws/solo-values2.yaml,gcp-cluster=gcp/solo-values.yaml,gcp-cluster=gcp/solo-values2.yaml
// FIXME @Deprecated. Use prepareValuesFilesMap instead to support multi-cluster
public prepareValuesFiles(valuesFile: string) {
let valuesArg = '';
if (valuesFile) {
Expand All @@ -88,6 +88,80 @@ export abstract class BaseCommand extends ShellRunner {
return valuesArg;
}

/**
* Prepare the values files map for each cluster
*
* <p> Order of precedence:
* <ol>
* <li> Chart's default values file (if chartDirectory is set) </li>
* <li> Profile values file </li>
* <li> User's values file </li>
* </ol>
* @param contextRefs - the map of cluster references
* @param valuesFileInput - the values file input string
* @param chartDirectory - the chart directory
* @param profileValuesFile - the profile values file
*/
static prepareValuesFilesMap(
contextRefs: ClusterRefs,
chartDirectory?: string,
profileValuesFile?: string,
valuesFileInput?: string,
): Record<ClusterRef, string> {
// initialize the map with an empty array for each cluster-ref
const valuesFiles: Record<ClusterRef, string> = {};
Object.entries(contextRefs).forEach(([clusterRef]) => {
valuesFiles[clusterRef] = '';
});

// add the chart's default values file for each cluster-ref if chartDirectory is set
// this should be the first in the list of values files as it will be overridden by user's input
if (chartDirectory) {
const chartValuesFile = path.join(chartDirectory, 'solo-deployment', 'values.yaml');
for (const clusterRef in valuesFiles) {
valuesFiles[clusterRef] += ` --values ${chartValuesFile}`;
}
}

if (profileValuesFile) {
const parsed = Flags.parseValuesFilesInput(profileValuesFile);
Object.entries(parsed).forEach(([clusterRef, files]) => {
let vf = '';
files.forEach(file => {
vf += ` --values ${file}`;
});

if (clusterRef === Flags.KEY_COMMON) {
Object.entries(valuesFiles).forEach(([cf]) => {
valuesFiles[cf] += vf;
});
} else {
valuesFiles[clusterRef] += vf;
}
});
}

if (valuesFileInput) {
const parsed = Flags.parseValuesFilesInput(valuesFileInput);
Object.entries(parsed).forEach(([clusterRef, files]) => {
let vf = '';
files.forEach(file => {
vf += ` --values ${file}`;
});

if (clusterRef === Flags.KEY_COMMON) {
Object.entries(valuesFiles).forEach(([clusterRef]) => {
valuesFiles[clusterRef] += vf;
});
} else {
valuesFiles[clusterRef] += vf;
}
});
}

return valuesFiles;
}

public getConfigManager(): ConfigManager {
return this.configManager;
}
Expand All @@ -107,6 +181,7 @@ export abstract class BaseCommand extends ShellRunner {
// build the dynamic class that will keep track of which properties are used
const NewConfigClass = class {
private usedConfigs: Map<string, number>;

constructor() {
// the map to keep track of which properties are used
this.usedConfigs = new Map();
Expand Down
76 changes: 58 additions & 18 deletions src/commands/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer';
import * as helpers from '../core/helpers.js';
import validator from 'validator';
import {type AnyObject} from '../types/aliases.js';
import {type ClusterRef} from '../core/config/remote/types.js';

export class Flags {
public static KEY_COMMON = '_COMMON_';

private static async prompt(
type: string,
task: ListrTaskWrapper<any, any, any>,
Expand Down Expand Up @@ -173,33 +176,69 @@ export class Flags {
},
};

/**
* Parse the values files input string that includes the cluster reference and the values file path
* <p>It supports input as below:
* <p>--values-file aws-cluster=aws/solo-values.yaml,aws-cluster=aws/solo-values2.yaml,gcp-cluster=gcp/solo-values.yaml,gcp-cluster=gcp/solo-values2.yaml
* @param input
*/
static parseValuesFilesInput(input: string): Record<ClusterRef, Array<string>> {
const valuesFiles: Record<ClusterRef, Array<string>> = {};
if (input) {
const inputItems = input.split(',');
inputItems.forEach(v => {
const parts = v.split('=');

let clusterRef = '';
let valuesFile = '';
if (parts.length !== 2) {
valuesFile = path.resolve(v);
clusterRef = Flags.KEY_COMMON;
} else {
clusterRef = parts[0];
valuesFile = path.resolve(parts[1]);
}

if (!valuesFiles[clusterRef]) {
valuesFiles[clusterRef] = [];
}
valuesFiles[clusterRef].push(valuesFile);
});
}

return valuesFiles;
}

static readonly valuesFile: CommandFlag = {
constName: 'valuesFile',
name: 'values-file',
definition: {
describe: 'Comma separated chart values files',
describe: 'Comma separated chart values file',
defaultValue: '',
alias: 'f',
type: 'string',
},
prompt: async function promptValuesFile(task: ListrTaskWrapper<any, any, any>, input: any) {
try {
if (input && !fs.existsSync(input)) {
input = await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'text',
default: Flags.valuesFile.definition.defaultValue,
message: 'Enter path to values.yaml: ',
});

if (!fs.existsSync(input)) {
throw new IllegalArgumentError('Invalid values.yaml file', input);
}
}
return input; // no prompt is needed for values file
},
};

return input;
} catch (e: Error | any) {
throw new SoloError(`input failed: ${Flags.valuesFile.name}`, e);
static readonly networkDeploymentValuesFile: CommandFlag = {
constName: 'valuesFile',
name: 'values-file',
definition: {
describe:
'Comma separated chart values file paths for each cluster (e.g. values.yaml,cluster-1=./a/b/values1.yaml,cluster-2=./a/b/values2.yaml)',
defaultValue: '',
alias: 'f',
type: 'string',
},
prompt: async function promptValuesFile(task: ListrTaskWrapper<any, any, any>, input: any) {
if (input) {
Flags.parseValuesFilesInput(input); // validate input as early as possible by parsing it
}

return input; // no prompt is needed for values file
},
};

Expand Down Expand Up @@ -345,8 +384,8 @@ export class Flags {
};

/*
Deploy cert manager CRDs separately from cert manager itself. Cert manager
CRDs are required for cert manager to deploy successfully.
Deploy cert manager CRDs separately from cert manager itself. Cert manager
CRDs are required for cert manager to deploy successfully.
*/
static readonly deployCertManagerCrds: CommandFlag = {
constName: 'deployCertManagerCrds',
Expand Down Expand Up @@ -1925,6 +1964,7 @@ export class Flags {
Flags.mirrorNodeVersion,
Flags.mirrorStaticIp,
Flags.namespace,
Flags.networkDeploymentValuesFile,
Flags.newAccountNumber,
Flags.newAdminKey,
Flags.nodeAlias,
Expand Down
Loading
Loading