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: Create a local config for storing configurations on the users machine #803

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fdc8678
feat: add LocalCOnfiguration class
Ivo-Yankov Oct 30, 2024
0eda60f
feat: finalize LocalConfig class and tests
Ivo-Yankov Nov 1, 2024
a5e0812
chore: code cleanup
Ivo-Yankov Nov 1, 2024
dac5104
chore: rename class
Ivo-Yankov Nov 1, 2024
dd07de0
wip: LocalConfig prompting
Ivo-Yankov Nov 4, 2024
cf9bada
Merge branch 'main' into 00590-create-a-local-config-for-storing-conf…
Ivo-Yankov Nov 4, 2024
7116e6e
feat: LocalConfig prompts
Ivo-Yankov Nov 5, 2024
baa94b3
test: add yaml file for e2e tests
Ivo-Yankov Nov 6, 2024
584032b
Merge branch 'main' into 00590-create-a-local-config-for-storing-conf…
Ivo-Yankov Nov 6, 2024
3905f5d
debug: e2e tests
Ivo-Yankov Nov 6, 2024
2601d5a
debug: e2e tests
Ivo-Yankov Nov 6, 2024
658775a
feat: inject config into BaseCommand
Ivo-Yankov Nov 7, 2024
4ba7b58
chore: rename types
Ivo-Yankov Nov 7, 2024
55a1202
chore: refactor LocalConfig class
Ivo-Yankov Nov 8, 2024
f50cfb7
chore: eslint
Ivo-Yankov Nov 8, 2024
03d7c06
Merge branch 'main' into 00590-create-a-local-config-for-storing-conf…
Ivo-Yankov Nov 8, 2024
4b6cad1
fix: circular dependency
Ivo-Yankov Nov 8, 2024
6838f6d
chore: eslint
Ivo-Yankov Nov 8, 2024
18255cc
nit: cleanup
Ivo-Yankov Nov 8, 2024
ba50a7b
Merge branch 'main' into 00590-create-a-local-config-for-storing-conf…
Ivo-Yankov Nov 8, 2024
c74f2e7
nit: cleanup
Ivo-Yankov Nov 8, 2024
e049a36
fix: comment out injectable code in BaseCommand
Ivo-Yankov Nov 8, 2024
cddf073
removed inversifyjs
Ivo-Yankov Nov 11, 2024
e9f4c6b
Merge branch 'main' into 00590-create-a-local-config-for-storing-conf…
Ivo-Yankov Nov 12, 2024
428e81d
chore: fix formatting
Ivo-Yankov Nov 12, 2024
a3fb43b
fix: integration test
Ivo-Yankov Nov 12, 2024
4a1dc81
feat: add LocalCOnfiguration class
Ivo-Yankov Oct 30, 2024
4e247ee
feat: finalize LocalConfig class and tests
Ivo-Yankov Nov 1, 2024
d5f08b2
chore: code cleanup
Ivo-Yankov Nov 1, 2024
76bee22
chore: rename class
Ivo-Yankov Nov 1, 2024
b6f3395
wip: LocalConfig prompting
Ivo-Yankov Nov 4, 2024
1a9face
feat: LocalConfig prompts
Ivo-Yankov Nov 5, 2024
b77c55e
test: add yaml file for e2e tests
Ivo-Yankov Nov 6, 2024
ad3195f
debug: e2e tests
Ivo-Yankov Nov 6, 2024
ef45975
debug: e2e tests
Ivo-Yankov Nov 6, 2024
329b6a6
feat: inject config into BaseCommand
Ivo-Yankov Nov 7, 2024
a6381df
chore: rename types
Ivo-Yankov Nov 7, 2024
ac314f0
chore: refactor LocalConfig class
Ivo-Yankov Nov 8, 2024
b0d1d5d
chore: eslint
Ivo-Yankov Nov 8, 2024
de4be41
fix: circular dependency
Ivo-Yankov Nov 8, 2024
e8a37dd
chore: eslint
Ivo-Yankov Nov 8, 2024
fb3a294
nit: cleanup
Ivo-Yankov Nov 8, 2024
767984c
nit: cleanup
Ivo-Yankov Nov 8, 2024
ec3d535
fix: comment out injectable code in BaseCommand
Ivo-Yankov Nov 8, 2024
a00e908
removed inversifyjs
Ivo-Yankov Nov 11, 2024
3845d15
chore: fix formatting
Ivo-Yankov Nov 12, 2024
d7db2cb
fix: integration test
Ivo-Yankov Nov 12, 2024
b227e2f
update package.json
jeromy-cannon Nov 19, 2024
325ad06
Merge remote-tracking branch 'origin/00590-create-a-local-config-for-…
Ivo-Yankov Nov 21, 2024
2307c38
chore: renamed files
Ivo-Yankov Nov 21, 2024
18a1a03
Merge branch 'main' into 00590-create-a-local-config-for-storing-conf…
Ivo-Yankov Nov 21, 2024
2068797
chore: import js files
Ivo-Yankov Nov 21, 2024
4acdece
chore: regenerate package-lock.json
Ivo-Yankov Nov 21, 2024
bb58abc
nit: addressing comments
Ivo-Yankov Nov 21, 2024
a536e9d
chore: fix path building
Ivo-Yankov Nov 21, 2024
d655d08
chore: remove clusterMappings
Ivo-Yankov Nov 22, 2024
00c5800
chore: remove custom deployments validation
Ivo-Yankov Nov 22, 2024
30ac6ad
add isDeployments decorator
Ivo-Yankov Nov 22, 2024
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
380 changes: 293 additions & 87 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
"@peculiar/x509": "^1.12.3",
"adm-zip": "^0.5.16",
"chalk": "^5.3.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dot-object": "^2.1.5",
"dotenv": "^16.4.5",
"enquirer": "^2.4.1",
Expand Down
4 changes: 4 additions & 0 deletions src/commands/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { MissingArgumentError } from '../core/errors.js'
import { ShellRunner } from '../core/shell_runner.js'
import type { ChartManager, ConfigManager, Helm, K8, DependencyManager, LeaseManager } from '../core/index.js'
import type { CommandFlag, Opts } from '../types/index.js'
import { type LocalConfig } from '../core/config/local_config.js'

export class BaseCommand extends ShellRunner {
protected readonly helm: Helm
Expand All @@ -29,6 +30,7 @@ export class BaseCommand extends ShellRunner {
protected readonly depManager: DependencyManager
protected readonly leaseManager: LeaseManager
protected readonly _configMaps = new Map<string, any>()
protected readonly localConfig: LocalConfig

constructor (opts: Opts) {
if (!opts || !opts.logger) throw new Error('An instance of core/SoloLogger is required')
Expand All @@ -37,6 +39,7 @@ export class BaseCommand extends ShellRunner {
if (!opts || !opts.chartManager) throw new Error('An instance of core/ChartManager is required')
if (!opts || !opts.configManager) throw new Error('An instance of core/ConfigManager is required')
if (!opts || !opts.depManager) throw new Error('An instance of core/DependencyManager is required')
if (!opts || !opts.localConfig) throw new Error('An instance of core/LocalConfig is required')

super(opts.logger)

Expand All @@ -46,6 +49,7 @@ export class BaseCommand extends ShellRunner {
this.configManager = opts.configManager
this.depManager = opts.depManager
this.leaseManager = opts.leaseManager
this.localConfig = opts.localConfig
}

async prepareChartPath (chartDir: string, chartRepo: string, chartReleaseName: string) {
Expand Down
35 changes: 33 additions & 2 deletions src/commands/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@

/**
* Set flag from the flag option
* @param y instance of yargs

Check warning on line 25 in src/commands/flags.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

tsdoc-param-tag-missing-hyphen: The @param block should be followed by a parameter name and then a hyphen
* @param commandFlags a set of command flags

Check warning on line 26 in src/commands/flags.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

tsdoc-param-tag-missing-hyphen: The @param block should be followed by a parameter name and then a hyphen
*
*/
export function setCommandFlags (y: any, ...commandFlags: CommandFlag[]) {
Expand Down Expand Up @@ -708,6 +708,36 @@
}
}

export const userEmailAddress: CommandFlag = {
constName: 'userEmailAddress',
name: 'email',
definition: {
describe: 'User email address used for local configuration',
defaultValue: '',
type: 'string'
}
}

export const deploymentName: CommandFlag = {
constName: 'deploymentName',
name: 'deployment-name',
definition: {
describe: 'Solo deployment name',
defaultValue: '',
type: 'string'
}
}

export const deploymentClusters: CommandFlag = {
constName: 'deploymentClusters',
name: 'deployment-clusters',
definition: {
describe: 'Solo deployment cluster list (comma separated)',
defaultValue: '',
type: 'string'
}
}

//* ------------- Node Proxy Certificates ------------- !//

export const grpcTlsCertificatePath: CommandFlag = {
Expand Down Expand Up @@ -782,6 +812,8 @@
deployCertManagerCrds,
deployHederaExplorer,
deployJsonRpcRelay,
deploymentClusters,
deploymentName,
deployMinio,
deployPrometheusStack,
devMode,
Expand Down Expand Up @@ -823,11 +855,10 @@
tlsPrivateKey,
tlsPublicKey,
updateAccountKeys,
userEmailAddress,
valuesFile,
mirrorNodeVersion,
hederaExplorerVersion,
inputDir,
outputDir,
grpcTlsCertificatePath,
grpcWebTlsCertificatePath,
grpcTlsKeyPath,
Expand Down
34 changes: 34 additions & 0 deletions src/commands/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as helpers from '../core/helpers.js'
import { hederaExplorerVersion, resetDisabledPrompts } from './flags.js'
import type { ListrTaskWrapper } from 'listr2'
import { type CommandFlag } from '../types/index.js'
import validator from 'validator'

async function prompt (type: string, task: ListrTaskWrapper<any, any, any>, input: any, defaultValue: any, promptMessage: string, emptyCheckMessage: string | null, flagName: string) {
try {
Expand Down Expand Up @@ -366,6 +367,38 @@ export async function promptUpdateAccountKeys (task: ListrTaskWrapper<any, any,
flags.updateAccountKeys.name)
}

export async function promptUserEmailAddress (task: ListrTaskWrapper<any, any, any>, input: any) {
const promptForInput = async () => {
return await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'text',
message: 'Please enter your email address:'
})
}

input = await promptForInput()
while (!validator.isEmail(input)) {
input = await promptForInput()
}

return input
}

export async function promptDeploymentName (task: ListrTaskWrapper<any, any, any>, input: any) {
return await promptText(task, input,
flags.deploymentName.definition.defaultValue,
'Enter the Solo deployment name: ',
null,
flags.deploymentName.name)
}

export async function promptDeploymentClusters (task: ListrTaskWrapper<any, any, any>, input: any) {
return await promptText(task, input,
flags.deploymentClusters.definition.defaultValue,
'Enter the Solo deployment cluster names (comma separated): ',
null,
flags.deploymentClusters.name)
}

export async function promptPrivateKey (task: ListrTaskWrapper<any, any, any>, input: any) {
return await promptText(task, input,
flags.ed25519PrivateKey.definition.defaultValue,
Expand Down Expand Up @@ -531,6 +564,7 @@ export function getPromptMap (): Map<string, Function> {
.set(flags.replicaCount.name, promptReplicaCount)
.set(flags.tlsClusterIssuerType.name, promptTlsClusterIssuerType)
.set(flags.updateAccountKeys.name, promptUpdateAccountKeys)
.set(flags.userEmailAddress.name, promptUserEmailAddress)
.set(flags.valuesFile.name, promptValuesFile)
.set(flags.nodeAlias.name, promptNewNodeAlias)
.set(flags.gossipEndpoints.name, promptGossipEndpoints)
Expand Down
146 changes: 146 additions & 0 deletions src/core/config/local_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { IsEmail, IsNotEmpty, IsObject, IsString, validateSync } from 'class-validator'
import { type ListrTask } from 'listr2'
import fs from 'fs'
import * as yaml from 'yaml'
import { flags } from '../../commands/index.js'
import { type Deployment, type Deployments, type LocalConfigData } from './local_config_data.js'
import { MissingArgumentError, SoloError } from '../errors.js'
import { promptDeploymentClusters, promptDeploymentName, promptUserEmailAddress } from '../../commands/prompts.js'
import { type SoloLogger } from '../logging.js'
import { Task } from '../task.js'
import { IsDeployments } from '../validator_decorators.js'

export class LocalConfig implements LocalConfigData {
@IsNotEmpty()
@IsEmail()
userEmailAddress: string

// The string is the name of the deployment, will be used as the namespace,
// so it needs to be available in all targeted clusters
@IsNotEmpty()
@IsObject()
@IsDeployments()
deployments: Deployments

@IsNotEmpty()
@IsString()
currentDeploymentName : string

private readonly skipPromptTask: boolean = false

constructor (private readonly filePath: string, private readonly logger: SoloLogger) {
if (!filePath || filePath === '') throw new MissingArgumentError('a valid filePath is required')
if (!logger) throw new Error('An instance of core/SoloLogger is required')

const allowedKeys = ['userEmailAddress', 'deployments', 'currentDeploymentName']
if (this.configFileExists()) {
const fileContent = fs.readFileSync(filePath, 'utf8')

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

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/core/config/local_config.ts#L53

The application dynamically constructs file or path information.
const parsedConfig = yaml.parse(fileContent)

for(const key in parsedConfig) {
if (!allowedKeys.includes(key)) {
throw new SoloError('Validation of local config failed')
}
this[key] = parsedConfig[key]
}

this.validate()
this.skipPromptTask = true
}
}

private validate () {
const genericMessage = 'Validation of local config failed'
const errors = validateSync(this, {})

if (errors.length) {
throw new SoloError(genericMessage)
}

try {
// Custom validations:
if (!this.deployments[this.currentDeploymentName]) {
throw new SoloError(genericMessage)
}
}
catch(e: any) { throw new SoloError(genericMessage) }
}

public setUserEmailAddress (emailAddress: string): this {
this.userEmailAddress = emailAddress
this.validate()
return this
}

public setDeployments (deployments: Deployments): this {
this.deployments = deployments
this.validate()
return this
}

public setCurrentDeployment (deploymentName: string): this {
this.currentDeploymentName = deploymentName
this.validate()
return this
}

public getCurrentDeployment (): Deployment {
return this.deployments[this.currentDeploymentName]
}

private configFileExists (): boolean {
return fs.existsSync(this.filePath)

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

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/core/config/local_config.ts#L108

The application dynamically constructs file or path information.
}

public async write (): Promise<void> {
const yamlContent = yaml.stringify({
userEmailAddress: this.userEmailAddress,
deployments: this.deployments,
currentDeploymentName: this.currentDeploymentName
})
await fs.promises.writeFile(this.filePath, yamlContent)
this.logger.info(`Wrote local config to ${this.filePath}`)
}

public promptLocalConfigTask (k8, argv): ListrTask<any, any, any>[] {
return new Task('Prompt local configuration', async (ctx, task) => {
let userEmailAddress = argv[flags.userEmailAddress.name]
if (!userEmailAddress) userEmailAddress = await promptUserEmailAddress(task, userEmailAddress)

let deploymentName = argv[flags.deploymentName.name]
if (!deploymentName) deploymentName = await promptDeploymentName(task, deploymentName)

let deploymentClusters = argv[flags.deploymentClusters.name]
if (!deploymentClusters) deploymentClusters = await promptDeploymentClusters(task, deploymentClusters)

const deployments = {}
deployments[deploymentName] = {
clusterAliases: deploymentClusters.split(',')
}

this.userEmailAddress = userEmailAddress
this.deployments = deployments
this.currentDeploymentName = deploymentName
this.validate()
await this.write()

return this
}, this.skipPromptTask) as ListrTask<any, any, any>[]
}
}
30 changes: 30 additions & 0 deletions src/core/config/local_config_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

export interface Deployment {
clusterAliases : string[]
}

// an alias for the cluster, provided during the configuration
// of the deployment, must be unique
export type Deployments = Record<string, Deployment>;

export interface LocalConfigData {
userEmailAddress: string;
deployments: Deployments;
currentDeploymentName: string;
}
2 changes: 2 additions & 0 deletions src/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,5 @@ export const RELAY_PODS_RUNNING_DELAY = +process.env.RELAY_PODS_RUNNING_DELAY ||
export const RELAY_PODS_READY_MAX_ATTEMPTS = +process.env.RELAY_PODS_READY_MAX_ATTEMPTS || 100
export const RELAY_PODS_READY_DELAY = +process.env.RELAY_PODS_READY_DELAY || 1_000


export const DEFAULT_LOCAL_CONFIG_FILE = 'local-config.yaml'
4 changes: 3 additions & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { DependencyManager } from './dependency_managers/index.js'
import { AccountManager } from './account_manager.js'
import { LeaseManager } from './lease/lease_manager.js'
import { CertificateManager } from './certificate_manager.js'
import { LocalConfig } from './config/local_config.js'

// Expose components from the core module
export {
Expand All @@ -54,5 +55,6 @@ export {
DependencyManager,
AccountManager,
LeaseManager,
CertificateManager
CertificateManager,
LocalConfig
}
Loading
Loading