Skip to content

Commit 2dca095

Browse files
authored
feat(local-remote-config): added solo version to local config and remote config (#1384)
Signed-off-by: instamenta <[email protected]>
1 parent 23e17aa commit 2dca095

21 files changed

+351
-158
lines changed

src/commands/cluster/tasks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class ClusterCommandTasks {
125125
const localDeployments = localConfig.deployments;
126126
const remoteClusterList: string[] = [];
127127
let deploymentName;
128-
const remoteNamespace = remoteConfig.metadata.name;
128+
const remoteNamespace = remoteConfig.metadata.namespace;
129129
for (const deployment in localConfig.deployments) {
130130
if (localConfig.deployments[deployment].namespace === remoteNamespace) {
131131
deploymentName = deployment;

src/commands/network.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,11 @@ export class NetworkCommand extends BaseCommand {
539539
}
540540
}
541541

542-
async prepareConfig(task: any, argv: any) {
542+
async prepareConfig(task: any, argv: any, promptForNodeAliases: boolean = false) {
543543
this.configManager.update(argv);
544544
this.logger.debug('Updated config with argv', {config: this.configManager.config});
545545

546-
// disable the prompts that we don't want to prompt the user for
547-
flags.disablePrompts([
546+
const flagsWithDisabledPrompts = [
548547
flags.apiPermissionProperties,
549548
flags.app,
550549
flags.applicationEnv,
@@ -557,7 +556,6 @@ export class NetworkCommand extends BaseCommand {
557556
flags.debugNodeAlias,
558557
flags.loadBalancerEnabled,
559558
flags.log4j2Xml,
560-
flags.nodeAliasesUnparsed,
561559
flags.persistentVolumeClaims,
562560
flags.profileName,
563561
flags.profileFile,
@@ -574,7 +572,12 @@ export class NetworkCommand extends BaseCommand {
574572
flags.gcsEndpoint,
575573
flags.gcsBucket,
576574
flags.gcsBucketPrefix,
577-
]);
575+
];
576+
577+
if (promptForNodeAliases) flagsWithDisabledPrompts.push(flags.nodeAliasesUnparsed);
578+
579+
// disable the prompts that we don't want to prompt the user for
580+
flags.disablePrompts(flagsWithDisabledPrompts);
578581

579582
await this.configManager.executePrompt(task, NetworkCommand.DEPLOY_FLAGS_LIST);
580583
let namespace = await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task);
@@ -714,7 +717,7 @@ export class NetworkCommand extends BaseCommand {
714717
{
715718
title: 'Initialize',
716719
task: async (ctx, task) => {
717-
ctx.config = await self.prepareConfig(task, argv);
720+
ctx.config = await self.prepareConfig(task, argv, true);
718721
return ListrLease.newAcquireLeaseTask(lease, task);
719722
},
720723
},

src/core/config/local_config.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* SPDX-License-Identifier: Apache-2.0
33
*/
4-
import {IsEmail, IsNotEmpty, IsObject, validateSync} from 'class-validator';
4+
import {IsEmail, IsNotEmpty, IsObject, IsString, validateSync} from 'class-validator';
55
import fs from 'fs';
66
import * as yaml from 'yaml';
77
import {Flags as flags} from '../../commands/flags.js';
@@ -10,10 +10,10 @@ import {MissingArgumentError, SoloError} from '../errors.js';
1010
import {type SoloLogger} from '../logging.js';
1111
import {IsClusterRefs, IsDeployments} from '../validator_decorators.js';
1212
import {type ConfigManager} from '../config_manager.js';
13-
import {type ClusterRefs, type DeploymentName, type EmailAddress} from './remote/types.js';
13+
import {type DeploymentName, type EmailAddress, type Version, type ClusterRefs} from './remote/types.js';
1414
import {ErrorMessages} from '../error_messages.js';
1515
import {type K8Factory} from '../kube/k8_factory.js';
16-
import {splitFlagInput} from '../helpers.js';
16+
import * as helpers from '../helpers.js';
1717
import {inject, injectable} from 'tsyringe-neo';
1818
import {patchInject} from '../dependency_injection/container_helper.js';
1919
import {type SoloListrTask} from '../../types/index.js';
@@ -30,6 +30,10 @@ export class LocalConfig implements LocalConfigData {
3030
)
3131
userEmailAddress: EmailAddress;
3232

33+
@IsString({message: ErrorMessages.LOCAL_CONFIG_INVALID_SOLO_VERSION})
34+
@IsNotEmpty({message: ErrorMessages.LOCAL_CONFIG_INVALID_SOLO_VERSION})
35+
soloVersion: Version;
36+
3337
// The string is the name of the deployment, will be used as the namespace,
3438
// so it needs to be available in all targeted clusters
3539
@IsDeployments({
@@ -60,7 +64,7 @@ export class LocalConfig implements LocalConfigData {
6064

6165
if (!this.filePath || this.filePath === '') throw new MissingArgumentError('a valid filePath is required');
6266

63-
const allowedKeys = ['userEmailAddress', 'deployments', 'clusterRefs'];
67+
const allowedKeys = ['userEmailAddress', 'deployments', 'clusterRefs', 'soloVersion'];
6468
if (this.configFileExists()) {
6569
const fileContent = fs.readFileSync(filePath, 'utf8');
6670
const parsedConfig = yaml.parse(fileContent);
@@ -109,6 +113,12 @@ export class LocalConfig implements LocalConfigData {
109113
return this;
110114
}
111115

116+
public setSoloVersion(version: Version): this {
117+
this.soloVersion = version;
118+
this.validate();
119+
return this;
120+
}
121+
112122
public configFileExists(): boolean {
113123
return fs.existsSync(this.filePath);
114124
}
@@ -118,6 +128,7 @@ export class LocalConfig implements LocalConfigData {
118128
userEmailAddress: this.userEmailAddress,
119129
deployments: this.deployments,
120130
clusterRefs: this.clusterRefs,
131+
soloVersion: this.soloVersion,
121132
});
122133
await fs.promises.writeFile(this.filePath, yamlContent);
123134

@@ -159,7 +170,7 @@ export class LocalConfig implements LocalConfigData {
159170
self.configManager.setFlag(flags.deploymentClusters, deploymentClusters);
160171
}
161172

162-
const parsedClusterRefs = splitFlagInput(deploymentClusters);
173+
const parsedClusterRefs = helpers.splitFlagInput(deploymentClusters);
163174

164175
const deployments: Deployments = {
165176
[deploymentName]: {
@@ -168,7 +179,7 @@ export class LocalConfig implements LocalConfigData {
168179
},
169180
};
170181

171-
const parsedContexts = splitFlagInput(contexts);
182+
const parsedContexts = helpers.splitFlagInput(contexts);
172183

173184
if (parsedContexts.length < parsedClusterRefs.length) {
174185
if (!isQuiet) {
@@ -200,6 +211,7 @@ export class LocalConfig implements LocalConfigData {
200211

201212
self.userEmailAddress = userEmailAddress;
202213
self.deployments = deployments;
214+
self.soloVersion = helpers.getSoloVersion();
203215

204216
self.validate();
205217
await self.write();

src/core/config/local_config_data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type DeploymentName,
88
type EmailAddress,
99
type NamespaceNameAsString,
10+
type Version,
1011
} from './remote/types.js';
1112

1213
export interface DeploymentStructure {
@@ -26,4 +27,7 @@ export interface LocalConfigData {
2627

2728
// Every cluster must have a kubectl context associated to it, which is used to establish a connection.
2829
clusterRefs: ClusterRefs;
30+
31+
// Solo CLI version
32+
soloVersion: Version;
2933
}

src/core/config/remote/cluster.ts

Lines changed: 20 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,45 @@
22
* SPDX-License-Identifier: Apache-2.0
33
*/
44
import {type ToObject} from '../../../types/index.js';
5-
import {type ClusterRef, type ICluster, type NamespaceNameAsString} from './types.js';
5+
import {type ClusterRef, type DeploymentName, type ICluster, type NamespaceNameAsString} from './types.js';
66
import {SoloError} from '../../errors.js';
77

88
export class Cluster implements ICluster, ToObject<ICluster> {
9-
private readonly _name: string;
10-
private readonly _namespace: string;
11-
private readonly _dnsBaseDomain: string = 'cluster.local'; // example: 'us-west-2.gcp.charlie.sphere'`
12-
private readonly _dnsConsensusNodePattern: string = 'network-${nodeAlias}-svc.${namespace}.svc'; // example: '${nodeId}.consensus.prod'`
13-
149
public constructor(
15-
name: string,
16-
namespace: NamespaceNameAsString,
17-
dnsBaseDomain?: string,
18-
dnsConsensusNodePattern?: string,
10+
public readonly name: string,
11+
public readonly namespace: NamespaceNameAsString,
12+
public readonly deployment: DeploymentName,
13+
public readonly dnsBaseDomain: string = 'cluster.local', // example: 'us-west-2.gcp.charlie.sphere'`
14+
public readonly dnsConsensusNodePattern: string = 'network-${nodeAlias}-svc.${namespace}.svc', // example: '${nodeId}.consensus.prod'`
1915
) {
20-
if (!name) {
21-
throw new SoloError('name is required');
22-
}
23-
24-
if (typeof name !== 'string') {
25-
throw new SoloError(`Invalid type for name: ${typeof name}`);
26-
}
27-
28-
if (!namespace) {
29-
throw new SoloError('namespace is required');
30-
}
31-
32-
if (typeof namespace !== 'string') {
33-
throw new SoloError(`Invalid type for namespace: ${typeof namespace}`);
34-
}
16+
if (!name) throw new SoloError('name is required');
17+
if (typeof name !== 'string') throw new SoloError(`Invalid type for name: ${typeof name}`);
3518

36-
this._name = name;
37-
this._namespace = namespace;
38-
39-
if (dnsBaseDomain) {
40-
this._dnsBaseDomain = dnsBaseDomain;
41-
}
42-
43-
if (dnsConsensusNodePattern) {
44-
this._dnsConsensusNodePattern = dnsConsensusNodePattern;
45-
}
46-
}
47-
48-
public get name(): string {
49-
return this._name;
50-
}
51-
52-
public get namespace(): string {
53-
return this._namespace;
54-
}
55-
56-
public get dnsBaseDomain(): string {
57-
return this._dnsBaseDomain;
58-
}
19+
if (!namespace) throw new SoloError('namespace is required');
20+
if (typeof namespace !== 'string') throw new SoloError(`Invalid type for namespace: ${typeof namespace}`);
5921

60-
public get dnsConsensusNodePattern(): string {
61-
return this._dnsConsensusNodePattern;
22+
if (!deployment) throw new SoloError('deployment is required');
23+
if (typeof deployment !== 'string') throw new SoloError(`Invalid type for deployment: ${typeof deployment}`);
6224
}
6325

6426
public toObject(): ICluster {
6527
return {
6628
name: this.name,
6729
namespace: this.namespace,
30+
deployment: this.deployment,
6831
dnsBaseDomain: this.dnsBaseDomain,
6932
dnsConsensusNodePattern: this.dnsConsensusNodePattern,
7033
};
7134
}
7235

7336
public static fromObject(cluster: ICluster) {
74-
return new Cluster(cluster.name, cluster.namespace, cluster.dnsBaseDomain, cluster.dnsConsensusNodePattern);
37+
return new Cluster(
38+
cluster.name,
39+
cluster.namespace,
40+
cluster.deployment,
41+
cluster.dnsBaseDomain,
42+
cluster.dnsConsensusNodePattern,
43+
);
7544
}
7645

7746
public static toClustersMapObject(clustersMap: Record<ClusterRef, Cluster>) {

src/core/config/remote/metadata.ts

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {Migration} from './migration.js';
55
import {SoloError} from '../../errors.js';
66
import * as k8s from '@kubernetes/client-node';
77
import {
8+
type DeploymentName,
89
type EmailAddress,
910
type NamespaceNameAsString,
1011
type RemoteConfigMetadataStructure,
@@ -22,20 +23,21 @@ import {type Optional, type ToObject, type Validate} from '../../../types/index.
2223
export class RemoteConfigMetadata
2324
implements RemoteConfigMetadataStructure, Validate, ToObject<RemoteConfigMetadataStructure>
2425
{
25-
private readonly _name: NamespaceNameAsString;
26-
private readonly _lastUpdatedAt: Date;
27-
private readonly _lastUpdateBy: EmailAddress;
2826
private _migration?: Migration;
2927

3028
public constructor(
31-
name: NamespaceNameAsString,
32-
lastUpdatedAt: Date,
33-
lastUpdateBy: EmailAddress,
29+
public readonly namespace: NamespaceNameAsString,
30+
public readonly deploymentName: DeploymentName,
31+
public readonly lastUpdatedAt: Date,
32+
public readonly lastUpdateBy: EmailAddress,
33+
public readonly soloVersion: Version,
34+
public soloChartVersion: Version = '',
35+
public hederaPlatformVersion: Version = '',
36+
public hederaMirrorNodeChartVersion: Version = '',
37+
public hederaExplorerChartVersion: Version = '',
38+
public hederaJsonRpcRelayChartVersion: Version = '',
3439
migration?: Migration,
3540
) {
36-
this._name = name;
37-
this._lastUpdatedAt = lastUpdatedAt;
38-
this._lastUpdateBy = lastUpdateBy;
3941
this._migration = migration;
4042
this.validate();
4143
}
@@ -49,29 +51,14 @@ export class RemoteConfigMetadata
4951

5052
/* -------- Getters -------- */
5153

52-
/** Retrieves the namespace */
53-
public get name(): NamespaceNameAsString {
54-
return this._name;
55-
}
56-
57-
/** Retrieves the date when the remote config metadata was last updated */
58-
public get lastUpdatedAt(): Date {
59-
return this._lastUpdatedAt;
60-
}
61-
62-
/** Retrieves the email of the user who last updated the remote config metadata */
63-
public get lastUpdateBy(): EmailAddress {
64-
return this._lastUpdateBy;
65-
}
66-
6754
/** Retrieves the migration if such exists */
6855
public get migration(): Optional<Migration> {
6956
return this._migration;
7057
}
7158

7259
/* -------- Utilities -------- */
7360

74-
/** Handles conversion from plain object to instance */
61+
/** Handles conversion from a plain object to instance */
7562
public static fromObject(metadata: RemoteConfigMetadataStructure): RemoteConfigMetadata {
7663
let migration: Optional<Migration> = undefined;
7764

@@ -82,12 +69,32 @@ export class RemoteConfigMetadata
8269
migration = new Migration(new Date(migratedAt), migratedBy, fromVersion);
8370
}
8471

85-
return new RemoteConfigMetadata(metadata.name, new Date(metadata.lastUpdatedAt), metadata.lastUpdateBy, migration);
72+
return new RemoteConfigMetadata(
73+
metadata.namespace,
74+
metadata.deploymentName,
75+
new Date(metadata.lastUpdatedAt),
76+
metadata.lastUpdateBy,
77+
metadata.soloVersion,
78+
metadata.soloChartVersion,
79+
metadata.hederaPlatformVersion,
80+
metadata.hederaMirrorNodeChartVersion,
81+
metadata.hederaExplorerChartVersion,
82+
metadata.hederaJsonRpcRelayChartVersion,
83+
migration,
84+
);
8685
}
8786

8887
public validate(): void {
89-
if (!this.name || !(typeof this.name === 'string')) {
90-
throw new SoloError(`Invalid name: ${this.name}, is type string: ${typeof this.name === 'string'}`);
88+
if (!this.namespace || !(typeof this.namespace === 'string')) {
89+
throw new SoloError(
90+
`Invalid namespace: ${this.namespace}, is type string: ${typeof this.namespace === 'string'}`,
91+
);
92+
}
93+
94+
if (!this.deploymentName || !(typeof this.deploymentName === 'string')) {
95+
throw new SoloError(
96+
`Invalid deploymentName: ${this.deploymentName}, is type string: ${typeof this.deploymentName === 'string'}`,
97+
);
9198
}
9299

93100
if (!(this.lastUpdatedAt instanceof Date)) {
@@ -98,16 +105,27 @@ export class RemoteConfigMetadata
98105
throw new SoloError(`Invalid lastUpdateBy: ${this.lastUpdateBy}`);
99106
}
100107

108+
if (!this.soloVersion || typeof this.soloVersion !== 'string') {
109+
throw new SoloError(`Invalid soloVersion: ${this.soloVersion}`);
110+
}
111+
101112
if (this.migration && !(this.migration instanceof Migration)) {
102113
throw new SoloError(`Invalid migration: ${this.migration}`);
103114
}
104115
}
105116

106117
public toObject(): RemoteConfigMetadataStructure {
107118
const data = {
108-
name: this.name,
119+
namespace: this.namespace,
120+
deploymentName: this.deploymentName,
109121
lastUpdatedAt: new k8s.V1MicroTime(this.lastUpdatedAt),
110122
lastUpdateBy: this.lastUpdateBy,
123+
soloChartVersion: this.soloChartVersion,
124+
hederaPlatformVersion: this.hederaPlatformVersion,
125+
hederaMirrorNodeChartVersion: this.hederaMirrorNodeChartVersion,
126+
hederaExplorerChartVersion: this.hederaExplorerChartVersion,
127+
hederaJsonRpcRelayChartVersion: this.hederaJsonRpcRelayChartVersion,
128+
soloVersion: this.soloVersion,
111129
} as RemoteConfigMetadataStructure;
112130

113131
if (this.migration) data.migration = this.migration.toObject() as any;

0 commit comments

Comments
 (0)