Skip to content

Commit 5b3d68a

Browse files
authored
feat: handle multiple contexts during network deploy (#1369)
Signed-off-by: Lenin Mehedy <[email protected]>
1 parent ea24bc4 commit 5b3d68a

File tree

10 files changed

+404
-141
lines changed

10 files changed

+404
-141
lines changed

src/commands/base.ts

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import paths from 'path';
6+
import path from 'path';
67
import {MissingArgumentError, SoloError} from '../core/errors.js';
78
import {ShellRunner} from '../core/shell_runner.js';
89
import {type LeaseManager} from '../core/lease/lease_manager.js';
@@ -17,11 +18,12 @@ import {type Opts} from '../types/command_types.js';
1718
import {type CommandFlag} from '../types/flag_types.js';
1819
import {type Lease} from '../core/lease/lease.js';
1920
import {Listr} from 'listr2';
20-
import path from 'path';
2121
import * as constants from '../core/constants.js';
2222
import fs from 'fs';
2323
import {Task} from '../core/task.js';
2424
import {ConsensusNode} from '../core/model/consensus_node.js';
25+
import {type ClusterRef, type ClusterRefs} from '../core/config/remote/types.js';
26+
import {Flags} from './flags.js';
2527

2628
export interface CommandHandlers {
2729
parent: BaseCommand;
@@ -72,9 +74,7 @@ export abstract class BaseCommand extends ShellRunner {
7274
return `${chartRepo}/${chartReleaseName}`;
7375
}
7476

75-
// 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
76-
// perhaps we should clone this method and have the new method return an object Record<ClusterRef, valuesFileArg>
77-
// 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
77+
// FIXME @Deprecated. Use prepareValuesFilesMap instead to support multi-cluster
7878
public prepareValuesFiles(valuesFile: string) {
7979
let valuesArg = '';
8080
if (valuesFile) {
@@ -88,6 +88,80 @@ export abstract class BaseCommand extends ShellRunner {
8888
return valuesArg;
8989
}
9090

91+
/**
92+
* Prepare the values files map for each cluster
93+
*
94+
* <p> Order of precedence:
95+
* <ol>
96+
* <li> Chart's default values file (if chartDirectory is set) </li>
97+
* <li> Profile values file </li>
98+
* <li> User's values file </li>
99+
* </ol>
100+
* @param contextRefs - the map of cluster references
101+
* @param valuesFileInput - the values file input string
102+
* @param chartDirectory - the chart directory
103+
* @param profileValuesFile - the profile values file
104+
*/
105+
static prepareValuesFilesMap(
106+
contextRefs: ClusterRefs,
107+
chartDirectory?: string,
108+
profileValuesFile?: string,
109+
valuesFileInput?: string,
110+
): Record<ClusterRef, string> {
111+
// initialize the map with an empty array for each cluster-ref
112+
const valuesFiles: Record<ClusterRef, string> = {};
113+
Object.entries(contextRefs).forEach(([clusterRef]) => {
114+
valuesFiles[clusterRef] = '';
115+
});
116+
117+
// add the chart's default values file for each cluster-ref if chartDirectory is set
118+
// this should be the first in the list of values files as it will be overridden by user's input
119+
if (chartDirectory) {
120+
const chartValuesFile = path.join(chartDirectory, 'solo-deployment', 'values.yaml');
121+
for (const clusterRef in valuesFiles) {
122+
valuesFiles[clusterRef] += ` --values ${chartValuesFile}`;
123+
}
124+
}
125+
126+
if (profileValuesFile) {
127+
const parsed = Flags.parseValuesFilesInput(profileValuesFile);
128+
Object.entries(parsed).forEach(([clusterRef, files]) => {
129+
let vf = '';
130+
files.forEach(file => {
131+
vf += ` --values ${file}`;
132+
});
133+
134+
if (clusterRef === Flags.KEY_COMMON) {
135+
Object.entries(valuesFiles).forEach(([cf]) => {
136+
valuesFiles[cf] += vf;
137+
});
138+
} else {
139+
valuesFiles[clusterRef] += vf;
140+
}
141+
});
142+
}
143+
144+
if (valuesFileInput) {
145+
const parsed = Flags.parseValuesFilesInput(valuesFileInput);
146+
Object.entries(parsed).forEach(([clusterRef, files]) => {
147+
let vf = '';
148+
files.forEach(file => {
149+
vf += ` --values ${file}`;
150+
});
151+
152+
if (clusterRef === Flags.KEY_COMMON) {
153+
Object.entries(valuesFiles).forEach(([clusterRef]) => {
154+
valuesFiles[clusterRef] += vf;
155+
});
156+
} else {
157+
valuesFiles[clusterRef] += vf;
158+
}
159+
});
160+
}
161+
162+
return valuesFiles;
163+
}
164+
91165
public getConfigManager(): ConfigManager {
92166
return this.configManager;
93167
}
@@ -107,6 +181,7 @@ export abstract class BaseCommand extends ShellRunner {
107181
// build the dynamic class that will keep track of which properties are used
108182
const NewConfigClass = class {
109183
private usedConfigs: Map<string, number>;
184+
110185
constructor() {
111186
// the map to keep track of which properties are used
112187
this.usedConfigs = new Map();

src/commands/flags.ts

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer';
1212
import * as helpers from '../core/helpers.js';
1313
import validator from 'validator';
1414
import {type AnyObject} from '../types/aliases.js';
15+
import {type ClusterRef} from '../core/config/remote/types.js';
1516

1617
export class Flags {
18+
public static KEY_COMMON = '_COMMON_';
19+
1720
private static async prompt(
1821
type: string,
1922
task: ListrTaskWrapper<any, any, any>,
@@ -173,33 +176,69 @@ export class Flags {
173176
},
174177
};
175178

179+
/**
180+
* Parse the values files input string that includes the cluster reference and the values file path
181+
* <p>It supports input as below:
182+
* <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
183+
* @param input
184+
*/
185+
static parseValuesFilesInput(input: string): Record<ClusterRef, Array<string>> {
186+
const valuesFiles: Record<ClusterRef, Array<string>> = {};
187+
if (input) {
188+
const inputItems = input.split(',');
189+
inputItems.forEach(v => {
190+
const parts = v.split('=');
191+
192+
let clusterRef = '';
193+
let valuesFile = '';
194+
if (parts.length !== 2) {
195+
valuesFile = path.resolve(v);
196+
clusterRef = Flags.KEY_COMMON;
197+
} else {
198+
clusterRef = parts[0];
199+
valuesFile = path.resolve(parts[1]);
200+
}
201+
202+
if (!valuesFiles[clusterRef]) {
203+
valuesFiles[clusterRef] = [];
204+
}
205+
valuesFiles[clusterRef].push(valuesFile);
206+
});
207+
}
208+
209+
return valuesFiles;
210+
}
211+
176212
static readonly valuesFile: CommandFlag = {
177213
constName: 'valuesFile',
178214
name: 'values-file',
179215
definition: {
180-
describe: 'Comma separated chart values files',
216+
describe: 'Comma separated chart values file',
181217
defaultValue: '',
182218
alias: 'f',
183219
type: 'string',
184220
},
185221
prompt: async function promptValuesFile(task: ListrTaskWrapper<any, any, any>, input: any) {
186-
try {
187-
if (input && !fs.existsSync(input)) {
188-
input = await task.prompt(ListrEnquirerPromptAdapter).run({
189-
type: 'text',
190-
default: Flags.valuesFile.definition.defaultValue,
191-
message: 'Enter path to values.yaml: ',
192-
});
193-
194-
if (!fs.existsSync(input)) {
195-
throw new IllegalArgumentError('Invalid values.yaml file', input);
196-
}
197-
}
222+
return input; // no prompt is needed for values file
223+
},
224+
};
198225

199-
return input;
200-
} catch (e: Error | any) {
201-
throw new SoloError(`input failed: ${Flags.valuesFile.name}`, e);
226+
static readonly networkDeploymentValuesFile: CommandFlag = {
227+
constName: 'valuesFile',
228+
name: 'values-file',
229+
definition: {
230+
describe:
231+
'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)',
232+
defaultValue: '',
233+
alias: 'f',
234+
type: 'string',
235+
},
236+
prompt: async function promptValuesFile(task: ListrTaskWrapper<any, any, any>, input: any) {
237+
if (input) {
238+
Flags.parseValuesFilesInput(input); // validate input as early as possible by parsing it
202239
}
240+
241+
return input; // no prompt is needed for values file
203242
},
204243
};
205244

@@ -345,8 +384,8 @@ export class Flags {
345384
};
346385

347386
/*
348-
Deploy cert manager CRDs separately from cert manager itself. Cert manager
349-
CRDs are required for cert manager to deploy successfully.
387+
Deploy cert manager CRDs separately from cert manager itself. Cert manager
388+
CRDs are required for cert manager to deploy successfully.
350389
*/
351390
static readonly deployCertManagerCrds: CommandFlag = {
352391
constName: 'deployCertManagerCrds',
@@ -1925,6 +1964,7 @@ export class Flags {
19251964
Flags.mirrorNodeVersion,
19261965
Flags.mirrorStaticIp,
19271966
Flags.namespace,
1967+
Flags.networkDeploymentValuesFile,
19281968
Flags.newAccountNumber,
19291969
Flags.newAdminKey,
19301970
Flags.nodeAlias,

0 commit comments

Comments
 (0)