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: update solo context connect to connect to single remote cluster #993

Merged
Show file tree
Hide file tree
Changes from 10 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ An opinionated CLI tool to deploy and manage standalone test networks.

### Hardware Requirements

To run a three-node network, you will need to set up Docker Desktop with at least 8GB of memory and 4 CPUs.
To run a three-node network, you will need to set up Docker Desktop with at least 8GB of memory and 4 CPUs.

![alt text](/docs/content/User/DockerDesktop.png)

Expand Down
7 changes: 0 additions & 7 deletions docs/content/User/Env.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,3 @@ User can configure the following environment variables to customize the behavior
| `RELAY_PODS_READY_MAX_ATTEMPTS` | The maximum number of attempts to check if relay pods are ready. | `100` |
| `RELAY_PODS_READY_DELAY` | The interval between attempts to check if relay pods are ready, in the unit of milliseconds. | `120` |
| `NETWORK_DESTROY_WAIT_TIMEOUT` | The period of time to wait for network to be destroyed, in the unit of milliseconds. | `60000` |







3 changes: 2 additions & 1 deletion docs/content/User/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ You can run `solo account init` anytime after `solo node start`

### Where can I find the default account keys ?

It is the well known default genesis key [Link](https://github.com/hashgraph/hedera-services/blob/develop/hedera-node/data/onboard/GenesisPrivKey.txt)
It is the well known default genesis key [Link](https://github.com/hashgraph/hedera-services/blob/develop/hedera-node/data/onboard/GenesisPrivKey.txt)

### How do I get the key for an account?

Use the following command to get account balance and private key of the account `0.0.1007`:

```bash
# get account info of 0.0.1007 and also show the private key
solo account get --account-id 0.0.1007 -n solo-e2e --private-key
Expand Down
1 change: 1 addition & 0 deletions docs/content/User/GetStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For Hedera extended users:
* [Using Environment Variables](Env.md)

FAQ:

* [Frequently Asked Questions](FAQ.md)

For curious mind:
Expand Down
8 changes: 8 additions & 0 deletions src/commands/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ export abstract class BaseCommand extends ShellRunner {
return valuesArg;
}

getConfigManager(): ConfigManager {
return this.configManager;
}

/**
* Dynamically builds a class with properties from the provided list of flags
* and extra properties, will keep track of which properties are used. Call
Expand Down Expand Up @@ -185,6 +189,10 @@ export abstract class BaseCommand extends ShellRunner {
return this.localConfig;
}

getRemoteConfigManager() {
return this.remoteConfigManager;
}

abstract close(): Promise<void>;

commandActionBuilder(actionTasks: any, options: any, errorString: string, lease: Lease | null) {
Expand Down
41 changes: 41 additions & 0 deletions src/commands/context/configs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 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 {type NodeAlias} from '../../types/aliases.js';

export const CONNECT_CONFIGS_NAME = 'connectConfig';

export const connectConfigBuilder = async function (argv, ctx, task) {
const config = this.getConfig(CONNECT_CONFIGS_NAME, argv.flags, [
'currentDeploymentName',
]) as ContextConnectConfigClass;

// set config in the context for later tasks to use
ctx.config = config;

return ctx.config;
};

export interface ContextConnectConfigClass {
app: string;
cacheDir: string;
devMode: boolean;
namespace: string;
nodeAlias: NodeAlias;
context: string;
clusterName: string;
}
9 changes: 8 additions & 1 deletion src/commands/context/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,12 @@ import {Flags as flags} from '../flags.js';
export const USE_FLAGS = {
requiredFlags: [],
requiredFlagsWithDisabledPrompt: [],
optionalFlags: [flags.devMode, flags.quiet, flags.clusterName, flags.context, flags.force, flags.namespace],
optionalFlags: [
flags.devMode,
flags.quiet,
flags.clusterName,
flags.context,
flags.namespace,
flags.userEmailAddress,
],
};
16 changes: 13 additions & 3 deletions src/commands/context/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,33 @@ import {type ContextCommandTasks} from './tasks.js';
import * as helpers from '../../core/helpers.js';
import * as constants from '../../core/constants.js';
import * as ContextFlags from './flags.js';
import {RemoteConfigTasks} from '../../core/config/remote/remote_config_tasks.js';
import type {RemoteConfigManager} from '../../core/config/remote/remote_config_manager.js';
import {connectConfigBuilder} from './configs.js';

export class ContextCommandHandlers implements CommandHandlers {
readonly parent: BaseCommand;
readonly tasks: ContextCommandTasks;
public readonly remoteConfigManager: RemoteConfigManager;
private getConfig: any;

constructor(parent: BaseCommand, tasks: ContextCommandTasks) {
constructor(parent: BaseCommand, tasks: ContextCommandTasks, remoteConfigManager: RemoteConfigManager) {
this.parent = parent;
this.tasks = tasks;
this.remoteConfigManager = remoteConfigManager;
this.getConfig = parent.getConfig.bind(parent);
}

async connect(argv: any) {
argv = helpers.addFlagsToArgv(argv, ContextFlags.USE_FLAGS);

const action = this.parent.commandActionBuilder(
[
this.tasks.initialize(argv),
this.parent.getLocalConfig().promptLocalConfigTask(),
this.tasks.initialize(argv, connectConfigBuilder.bind(this)),
this.parent.getLocalConfig().promptLocalConfigTask(this.parent.getK8()),
this.tasks.selectContext(argv),
RemoteConfigTasks.loadRemoteConfig.bind(this)(argv),
// todo validate remoteConfig
this.tasks.updateLocalConfig(argv),
],
{
Expand Down
2 changes: 1 addition & 1 deletion src/commands/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class ContextCommand extends BaseCommand {
constructor(opts: Opts) {
super(opts);

this.handlers = new ContextCommandHandlers(this, new ContextCommandTasks(this));
this.handlers = new ContextCommandHandlers(this, new ContextCommandTasks(this), this.remoteConfigManager);
}

getCommandDefinition() {
Expand Down
180 changes: 142 additions & 38 deletions src/commands/context/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import {Templates} from '../../core/templates.js';
import {Flags as flags} from '../flags.js';
import type {ListrTaskWrapper} from 'listr2';
import type {ConfigBuilder} from '../../types/aliases.js';
import {type BaseCommand} from '../base.js';

export class ContextCommandTasks {
Expand All @@ -29,62 +30,163 @@

updateLocalConfig(argv) {
return new Task('Update local configuration', async (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
this.parent.logger.info('Updating local configuration...');
this.parent.logger.info('Compare local and remote configuration...');
const configManager = this.parent.getConfigManager();
const isQuiet = configManager.getFlag(flags.quiet);

const isQuiet = !!argv[flags.quiet.name];
await this.parent.getRemoteConfigManager().modify(async remoteConfig => {
const localConfig = this.parent.getLocalConfig();
const localDeployments = localConfig.deployments;
const remoteClusterList = [];

let currentDeploymentName = argv[flags.namespace.name];
let clusters = Templates.parseClusterAliases(argv[flags.clusterName.name]);
let contextName = argv[flags.context.name];
for (const cluster of Object.keys(remoteConfig.clusters)) {
if (localConfig.currentDeploymentName === remoteConfig.clusters[cluster]) {

Check failure on line 43 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `··`
remoteClusterList.push(cluster);

Check failure on line 44 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `····`
}

Check failure on line 45 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `··`
}

const kubeContexts = await this.parent.getK8().getContexts();
ctx.config.clusters = remoteClusterList;
localDeployments[localConfig.currentDeploymentName].clusters = ctx.config.clusters;
localConfig.setDeployments(localDeployments);

if (isQuiet) {
const currentCluster = await this.parent.getK8().getKubeConfig().getCurrentCluster();
if (!clusters.length) clusters = [currentCluster.name];
if (!contextName) contextName = await this.parent.getK8().getKubeConfig().getCurrentContext();
const contexts = Templates.parseCommaSeparatedList(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;
}

Check failure on line 61 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Replace `⏎⏎⏎··········else·if·(!localConfig.clusterContextMapping[cluster])·{⏎` with `·else·if·(!localConfig.clusterContextMapping[cluster])·{`


else if (!localConfig.clusterContextMapping[cluster]) {

// In quiet mode use the currently selected context to update the mapping
if (isQuiet) {
//default
localConfig.clusterContextMapping[cluster] = this.parent.getK8().getKubeConfig().getCurrentContext();
}

if (!currentDeploymentName) {
const selectedContext = kubeContexts.find(e => e.name === contextName);
currentDeploymentName = selectedContext && selectedContext.namespace ? selectedContext.namespace : 'default';
// Prompt the user to select a context if mapping value is missing
else {

Check failure on line 73 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `·`
const kubeContexts = this.parent.getK8().getContexts();

Check failure on line 74 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `··`
localConfig.clusterContextMapping[cluster] = await flags.context.prompt(

Check failure on line 75 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `··`
task,

Check failure on line 76 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `····`
kubeContexts.map(c => c.name),

Check failure on line 77 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Replace `····················` with `················`
cluster,

Check failure on line 78 in src/commands/context/tasks.ts

View workflow job for this annotation

GitHub Actions / Code Style / Standard

Delete `····`
);
}
}
}
this.parent.logger.info('Update local configuration...');
await localConfig.write();
});
});
}

async _getSelectedContext(task, selectedCluster, localConfig, isQuiet) {
let selectedContext;
if (isQuiet) {
selectedContext = this.parent.getK8().getKubeConfig().getCurrentContext();
} else {
if (!clusters.length) {
const prompt = flags.clusterName.prompt;
const unparsedClusterAliases = await prompt(task, clusters);
clusters = Templates.parseClusterAliases(unparsedClusterAliases);
}
if (!contextName) {
const prompt = flags.context.prompt;
contextName = await prompt(
task,
kubeContexts.map(c => c.name),
const kubeContexts = this.parent.getK8().getContexts();
selectedContext = await flags.context.prompt(
task,
kubeContexts.map(c => c.name),
selectedCluster,
);
localConfig.clusterContextMapping[selectedCluster] = selectedContext;
}
return selectedContext
}

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 = Templates.parseCommaSeparatedList(configManager.getFlag(flags.clusterName));
const contexts = Templates.parseCommaSeparatedList(configManager.getFlag(flags.context));
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) {
const selectedCluster = clusters[0];
const localConfig = this.parent.getLocalConfig();

if (localConfig.clusterContextMapping[selectedCluster]) {
selectedContext = localConfig.clusterContextMapping[selectedCluster];
}
if (!currentDeploymentName) {
const prompt = flags.namespace.prompt;
currentDeploymentName = await prompt(task, currentDeploymentName);

// If cluster does not exist in LocalConfig mapping prompt the user to select a context or use the current one
else {
selectedContext = await this._getSelectedContext(task, selectedCluster, localConfig, isQuiet)
}
}

// Select current deployment
this.parent.getLocalConfig().setCurrentDeployment(currentDeploymentName);
// 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 localConfig = this.parent.getLocalConfig();
const deployment = localConfig.deployments[deploymentName];

if (deployment && deployment.clusters.length) {
const selectedCluster = deployment.clusters[0];
selectedContext = localConfig.clusterContextMapping[selectedCluster];
if (!selectedContext) {
selectedContext = await this._getSelectedContext(task, selectedCluster, 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],
};

// Set clusters for active deployment
const deployments = this.parent.getLocalConfig().deployments;
deployments[currentDeploymentName].clusters = clusters;
this.parent.getLocalConfig().setDeployments(deployments);
if (!localConfig.clusterContextMapping[selectedCluster]) {
localConfig.clusterContextMapping[selectedCluster] = selectedContext;
}
}

this.parent.getK8().getKubeConfig().setCurrentContext(contextName);
// Prompt user for clusters and contexts
else {
clusters = Templates.parseCommaSeparatedList(await flags.clusterName.prompt(task, clusters));

this.parent.logger.info(
`Save LocalConfig file: [currentDeploymentName: ${currentDeploymentName}, contextName: ${contextName}, clusters: ${clusters.join(' ')}]`,
);
await this.parent.getLocalConfig().write();
const kubeContexts = this.parent.getK8().getContexts();
for (const cluster of clusters) {
if (!localConfig.clusterContextMapping[cluster]) {
localConfig.clusterContextMapping[cluster] = await flags.context.prompt(
task,
kubeContexts.map(c => c.name),
cluster,
);
}
}

selectedContext = localConfig.clusterContextMapping[clusters[0]];
}
}
}

this.parent.getK8().getKubeConfig().setCurrentContext(selectedContext);
});
}

initialize(argv: any) {
initialize(argv: any, configInit: ConfigBuilder) {
const {requiredFlags, optionalFlags} = argv;

argv.flags = [...requiredFlags, ...optionalFlags];
Expand All @@ -93,6 +195,8 @@
if (argv[flags.devMode.name]) {
this.parent.logger.setDevMode(true);
}

ctx.config = await configInit(argv, ctx, task);
});
}
}
2 changes: 1 addition & 1 deletion src/commands/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class DeploymentCommand extends BaseCommand {
return ListrLease.newAcquireLeaseTask(lease, task);
},
},
this.localConfig.promptLocalConfigTask(),
this.localConfig.promptLocalConfigTask(self.k8),
{
title: 'Validate cluster connections',
task: async (ctx, task): Promise<Listr<Context, any, any>> => {
Expand Down
Loading
Loading