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 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
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;
};

Check warning on line 31 in src/commands/context/configs.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/context/configs.ts#L23-L31

Added lines #L23 - L31 were not covered by tests

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,
],
};
15 changes: 12 additions & 3 deletions src/commands/context/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,32 @@
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),

Check warning on line 47 in src/commands/context/handlers.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/context/handlers.ts#L44-L47

Added lines #L44 - L47 were not covered by tests
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
177 changes: 134 additions & 43 deletions src/commands/context/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
*
*/
import {Task} from '../../core/task.js';
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';
import {splitFlagInput} from '../../core/helpers.js';

export class ContextCommandTasks {
private readonly parent: BaseCommand;
Expand All @@ -29,62 +30,150 @@

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 => {
// Update current deployment with cluster list from remoteConfig
const localConfig = this.parent.getLocalConfig();
const localDeployments = localConfig.deployments;
const remoteClusterList = [];
for (const cluster of Object.keys(remoteConfig.clusters)) {
if (localConfig.currentDeploymentName === remoteConfig.clusters[cluster]) {
remoteClusterList.push(cluster);
}
}
ctx.config.clusters = remoteClusterList;
localDeployments[localConfig.currentDeploymentName].clusters = ctx.config.clusters;
localConfig.setDeployments(localDeployments);

let currentDeploymentName = argv[flags.namespace.name];
let clusters = Templates.parseClusterAliases(argv[flags.clusterName.name]);
let contextName = argv[flags.context.name];
const contexts = splitFlagInput(configManager.getFlag(flags.context));

const kubeContexts = await this.parent.getK8().getContexts();
for (let i = 0; i < ctx.config.clusters.length; i++) {
const cluster = ctx.config.clusters[i];
const context = contexts[i];

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();
// If a context is provided use it to update the mapping
if (context) {
localConfig.clusterContextMapping[cluster] = context;
} else if (!localConfig.clusterContextMapping[cluster]) {
// In quiet mode use the currently selected context to update the mapping
if (isQuiet) {
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';
}
} 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),
);
}
if (!currentDeploymentName) {
const prompt = flags.namespace.prompt;
currentDeploymentName = await prompt(task, currentDeploymentName);
// Prompt the user to select a context if mapping value is missing
else {
localConfig.clusterContextMapping[cluster] = await this.promptForContext(task, cluster);
}
}
}
this.parent.logger.info('Update local configuration...');
await localConfig.write();
});
});
}

private async getSelectedContext(task, selectedCluster, localConfig, isQuiet) {
let selectedContext;
if (isQuiet) {
selectedContext = this.parent.getK8().getKubeConfig().getCurrentContext();
} else {
selectedContext = await this.promptForContext(task, selectedCluster);
localConfig.clusterContextMapping[selectedCluster] = selectedContext;
}
return selectedContext;
}

private async promptForContext(task, cluster) {
const kubeContexts = this.parent.getK8().getContexts();
return flags.context.prompt(
task,
kubeContexts.map(c => c.name),
cluster,
);
}

private async selectContextForFirstCluster(task, clusters, localConfig, isQuiet) {
const selectedCluster = clusters[0];

if (localConfig.clusterContextMapping[selectedCluster]) {
return localConfig.clusterContextMapping[selectedCluster];
}

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

selectContext(argv) {
jeromy-cannon marked this conversation as resolved.
Show resolved Hide resolved
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 = splitFlagInput(configManager.getFlag(flags.clusterName));
const contexts = splitFlagInput(configManager.getFlag(flags.context));
const localConfig = this.parent.getLocalConfig();
let selectedContext;

// If one or more contexts are provided use the first one
if (contexts.length) {
selectedContext = contexts[0];
}

// Select current deployment
this.parent.getLocalConfig().setCurrentDeployment(currentDeploymentName);
// 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) {
selectedContext = await this.selectContextForFirstCluster(task, clusters, localConfig, isQuiet);
}

// 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 deployment = localConfig.deployments[deploymentName];

if (deployment && deployment.clusters.length) {
selectedContext = await this.selectContextForFirstCluster(task, deployment.clusters, localConfig, isQuiet);
}

// Set clusters for active deployment
const deployments = this.parent.getLocalConfig().deployments;
deployments[currentDeploymentName].clusters = clusters;
this.parent.getLocalConfig().setDeployments(deployments);
// 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],
};

this.parent.getK8().getKubeConfig().setCurrentContext(contextName);
if (!localConfig.clusterContextMapping[selectedCluster]) {
localConfig.clusterContextMapping[selectedCluster] = selectedContext;
}
}

this.parent.logger.info(
`Save LocalConfig file: [currentDeploymentName: ${currentDeploymentName}, contextName: ${contextName}, clusters: ${clusters.join(' ')}]`,
);
await this.parent.getLocalConfig().write();
// Prompt user for clusters and contexts
else {
clusters = splitFlagInput(await flags.clusterName.prompt(task, clusters));

for (const cluster of clusters) {
if (!localConfig.clusterContextMapping[cluster]) {
localConfig.clusterContextMapping[cluster] = await this.promptForContext(task, 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 +182,8 @@
if (argv[flags.devMode.name]) {
this.parent.logger.setDevMode(true);
}

ctx.config = await configInit(argv, ctx, task);

Check warning on line 186 in src/commands/context/tasks.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/context/tasks.ts#L185-L186

Added lines #L185 - L186 were not covered by tests
});
}
}
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