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(cli): implement cluster setup command #452

Merged
merged 19 commits into from
Oct 26, 2023
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
5 changes: 4 additions & 1 deletion fullstack-network-manager/src/commands/base.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,13 @@ export const BaseCommand = class BaseCommand {
/**
* Run the specified bash command
* @param cmd is a bash command including args
* @returns {Promise<unknown>}
* @returns {Promise<string>}
*/
runExec(cmd) {
let self = this

return new Promise((resolve, reject) => {
self.logger.debug(`Invoking '${cmd}'...`)
exec(cmd, (error, stdout, stderr) => {
if (error) {
reject(error)
Expand Down
147 changes: 128 additions & 19 deletions fullstack-network-manager/src/commands/cluster.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import chalk from "chalk";
const clusterNameFlag = {
describe: 'Name of the cluster',
default: core.constants.CLUSTER_NAME,
alias: 'n',
alias: 'c',
type: 'string'
}

const namespaceFlag = {
describe: 'Name of the namespace',
default: core.constants.NAMESPACE_NAME,
alias: 's',
type: 'string'
}

Expand All @@ -19,21 +26,22 @@ export const ClusterCommand = class extends BaseCommand {

/**
* List available clusters
* @returns {Promise<boolean>}
* @returns {Promise<string[]>}
*/
async getClusters() {
let cmd = `kind get clusters`

try {
let cmd = `kind get clusters -q`

let output = await this.runExec(cmd)
this.logger.showUser("\nList of available clusters \n--------------------------\n%s", output)
return true

return output.split(/\r?\n/)
} catch (e) {
this.logger.error("%s", e)
this.logger.showUser(e.message)
}

return false
return []
}

/**
Expand All @@ -42,11 +50,13 @@ export const ClusterCommand = class extends BaseCommand {
* @returns {Promise<boolean>}
*/
async getClusterInfo(argv) {
let cmd = `kubectl cluster-info --context kind-${argv.name}`

try {
let clusterName = argv.clusterName
let cmd = `kubectl cluster-info --context kind-${clusterName}`

let output = await this.runExec(cmd)
this.logger.showUser(output)

return true
} catch (e) {
this.logger.error("%s", e)
Expand All @@ -62,14 +72,14 @@ export const ClusterCommand = class extends BaseCommand {
* @returns {Promise<boolean>}
*/
async create(argv) {
let cmd = `kind create cluster -n ${argv.name} --config ${core.constants.RESOURCES_DIR}/dev-cluster.yaml`

try {
this.logger.showUser(chalk.cyan('Creating cluster:'), chalk.yellow(`${argv.name}...`))
this.logger.debug(`Invoking '${cmd}'...`)
let clusterName = argv.clusterName
let cmd = `kind create cluster -n ${clusterName} --config ${core.constants.RESOURCES_DIR}/dev-cluster.yaml`

this.logger.showUser(chalk.cyan('Creating cluster:'), chalk.yellow(`${clusterName}...`))
let output = await this.runExec(cmd)
this.logger.debug(output)
this.logger.showUser(chalk.green('Created cluster:'), chalk.yellow(argv.name))
this.logger.showUser(chalk.green('Created cluster:'), chalk.yellow(clusterName))

// show all clusters and cluster-info
await this.getClusters()
Expand All @@ -90,10 +100,11 @@ export const ClusterCommand = class extends BaseCommand {
* @returns {Promise<boolean>}
*/
async delete(argv) {
let cmd = `kind delete cluster -n ${argv.name}`
try {
this.logger.debug(`Invoking '${cmd}'...`)
this.logger.showUser(chalk.cyan('Deleting cluster:'), chalk.yellow(`${argv.name}...`))
let clusterName = argv.clusterName
let cmd = `kind delete cluster -n ${clusterName}`

this.logger.showUser(chalk.cyan('Deleting cluster:'), chalk.yellow(`${clusterName}...`))
await this.runExec(cmd)
await this.getClusters()

Expand All @@ -106,6 +117,66 @@ export const ClusterCommand = class extends BaseCommand {
return false
}

/**
* List available clusters
* @returns {Promise<string[]>}
*/
async getInstalledCharts(argv) {
try {
let namespaceName = argv.namespace
let cmd = `helm list -n ${namespaceName} -q`

let output = await this.runExec(cmd)
this.logger.showUser("\nList of installed charts\n--------------------------\n%s", output)

return output.split(/\r?\n/)
} catch (e) {
this.logger.error("%s", e)
this.logger.showUser(e.message)
}

return []
}

/**
* Setup cluster with shared components
* @param argv
* @returns {Promise<boolean>}
*/
async setup(argv) {
try {
let clusterName = argv.clusterName
let releaseName = "fullstack-cluster-setup"
let namespaceName = argv.namespace
let chartPath = `${core.constants.FST_HOME_DIR}/full-stack-testing/charts/fullstack-cluster-setup`

this.logger.showUser(chalk.cyan(`Setting up cluster ${clusterName}...`))

let charts= await this.getInstalledCharts(argv)

if (!charts.includes(releaseName)) {
// install fullstack-cluster-setup chart
let cmd = `helm install -n ${namespaceName} ${releaseName} ${chartPath}`
this.logger.showUser(chalk.cyan("Installing fullstack-cluster-setup chart"))
this.logger.debug(`Invoking '${cmd}'...`)

let output = await this.runExec(cmd)
this.logger.showUser(chalk.green('OK'), `chart '${releaseName}' is installed`)
} else {
this.logger.showUser(chalk.green('OK'), `chart '${releaseName}' is already installed`)
}

this.logger.showUser(chalk.yellow("Chart setup complete"))

return true
} catch (e) {
this.logger.error("%s", e.stack)
this.logger.showUser(e.message)
}

return false
}

/**
* Return Yargs command definition for 'cluster' command
* @param clusterCmd an instance of ClusterCommand
Expand All @@ -120,45 +191,83 @@ export const ClusterCommand = class extends BaseCommand {
command: 'create',
desc: 'Create a cluster',
builder: yargs => {
yargs.option('name', clusterNameFlag)
yargs.option('cluster-name', clusterNameFlag)
},
handler: argv => {
clusterCmd.logger.debug("==== Running 'cluster create' ===")
clusterCmd.logger.debug(argv)

clusterCmd.create(argv).then(r => {
if (!r) process.exit(1)
})

clusterCmd.logger.debug("==== Finished running `cluster create`====")
}
})
.command({
command: 'delete',
desc: 'Delete a cluster',
builder: yargs => {
yargs.option('name', clusterNameFlag)
yargs.option('cluster-name', clusterNameFlag)
},
handler: argv => {
clusterCmd.logger.debug("==== Running 'cluster delete' ===")
clusterCmd.logger.debug(argv)

clusterCmd.delete(argv).then(r => {
if (!r) process.exit(1)
})

clusterCmd.logger.debug("==== Finished running `cluster delete`====")
}
})
.command({
command: 'list',
desc: 'List all clusters',
handler: argv => {
clusterCmd.logger.debug("==== Running 'cluster list' ===")
clusterCmd.logger.debug(argv)

clusterCmd.getClusters().then(r => {
if (!r) process.exit(1)
})

clusterCmd.logger.debug("==== Finished running `cluster list`====")
}
})
.command({
command: 'info',
desc: 'Get cluster info',
builder: yargs => {
yargs.option('name', clusterNameFlag)
yargs.option('cluster-name', clusterNameFlag)
},
handler: argv => {
clusterCmd.logger.debug("==== Running 'cluster info' ===")
clusterCmd.logger.debug(argv)

clusterCmd.getClusterInfo(argv).then(r => {
if (!r) process.exit(1)
})

clusterCmd.logger.debug("==== Finished running `cluster info`====")
}
})
.command({
command: 'setup',
desc: 'Setup cluster with shared components',
builder: yargs => {
yargs.option('cluster-name', clusterNameFlag)
yargs.option('namespace', namespaceFlag)
},
handler: argv => {
clusterCmd.logger.debug("==== Running 'cluster setup' ===")
clusterCmd.logger.debug(argv)

clusterCmd.setup(argv).then(r => {
if (!r) process.exit(1)
})

clusterCmd.logger.debug("==== Finished running `cluster setup`====")
}
})
.demand(1, 'Select a cluster command')
Expand Down
3 changes: 3 additions & 0 deletions fullstack-network-manager/src/commands/index.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ClusterCommand} from "./cluster.mjs";
import {InitCommand} from "./init.mjs";
import {NetworkCommand} from "./network.mjs"

/*
* Return a list of Yargs command builder to be exposed through CLI
Expand All @@ -8,10 +9,12 @@ import {InitCommand} from "./init.mjs";
function Initialize(opts) {
const initCmd = new InitCommand(opts)
const clusterCmd = new ClusterCommand(opts)
const networkCmd = new NetworkCommand(opts)

return [
InitCommand.getCommandDefinition(initCmd),
ClusterCommand.getCommandDefinition(clusterCmd),
NetworkCommand.getCommandDefinition(networkCmd),
]
}

Expand Down
63 changes: 63 additions & 0 deletions fullstack-network-manager/src/commands/network.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {BaseCommand} from "./base.mjs";
import chalk from "chalk";


export const NetworkCommand = class NetworkCommand extends BaseCommand {
async deployShared(argv) {
this.logger.showUser(chalk.green("Deploying FST network....%s"), chalk.yellow(JSON.stringify(argv)))
return false
}

async deploy(argv) {
this.logger.showUser(chalk.green("Deploying FST network....%s"), chalk.yellow(JSON.stringify(argv)))
return false
}

static getCommandDefinition(networkCmd) {
return {
command: 'network',
desc: 'Manage FST network deployment',
builder: yargs => {
return yargs
.command({
command: 'deploy',
desc: 'Deploy a FST network',
builder: yargs => {
yargs.option('haproxy', {
describe: 'Deploy HAProxy',
default: true,
alias: 'p',
type: 'boolean'
})

yargs.option('envoy-proxy', {
describe: 'Deploy Envoy proxy',
default: true,
alias: 'e',
type: 'boolean'
})

yargs.option('mirror-node', {
describe: 'Deploy mirror node',
default: true,
alias: 'm',
type: 'boolean'
})
yargs.option('hedera-explorer', {
describe: 'Deploy hedera explorer',
default: true,
alias: 'x',
type: 'boolean'
})
},
handler: argv => {
networkCmd.deploy(argv).then(r => {
if (!r) process.exit(1)
})
}
})
.demand(1, 'Select a network command')
}
}
}
}
4 changes: 3 additions & 1 deletion fullstack-network-manager/src/core/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ const CUR_FILE_DIR = dirname(fileURLToPath(import.meta.url))
const USER = `${process.env.USER}`
export const constants = {
USER: `${USER}`,
CLUSTER_NAME: `fst-${USER}`,
CLUSTER_NAME: `fst`,
RELEASE_NAME: `fst`,
NAMESPACE_NAME: `fst-${USER}`,
HELM: 'helm',
KIND: 'kind',
KUBECTL: 'kubectl',
Expand Down