UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

362 lines 13.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Dashboard = exports.Kubeconfig = exports.Mk8sCmd = void 0; const fs = require("fs"); const jwt = require("jwt-decode"); const open = require("open"); const os = require("os"); const path = require("path"); const options_1 = require("./options"); const generic_1 = require("./generic"); const resolver_1 = require("./resolver"); const command_1 = require("../cli/command"); const child_process_1 = require("child_process"); const tags_1 = require("./tags"); const logger_1 = require("../util/logger"); const names_1 = require("../util/names"); const functions_1 = require("../util/functions"); const js_yaml_1 = require("js-yaml"); const generic_2 = require("./generic"); const AUTH_VERSION = 'client.authentication.k8s.io/v1'; class Mk8sCmd extends command_1.Command { constructor() { super(...arguments); this.command = 'mk8s'; this.describe = 'Manage an mk8s cluster'; } builder(yargs) { const resolver = (0, resolver_1.kindResolver)('mk8s'); const opts = [options_1.withStandardOptions, options_1.withOrgOptions]; const commandName = 'mk8s cluster'; const commandNamePlural = 'mk8s clusters'; const commandNameA = 'an mk8s cluster'; return (yargs .demandCommand() .version(false) .help() // generic .command(new generic_1.Get(commandName, resolver, ...opts).toYargs()) .command(new generic_1.Edit(commandName, resolver, ...opts).toYargs()) .command(new generic_1.Patch(commandName, resolver, ...opts).toYargs()) .command(new generic_1.Query(commandNamePlural, resolver, undefined, ...opts).toYargs()) .command(new generic_1.Delete(commandNamePlural, resolver, ...opts).toYargs()) .command(new generic_1.Eventlog(commandName, resolver, ...opts).toYargs()) .command(new generic_1.Tag(commandNamePlural, resolver, ...opts).toYargs()) .command(new generic_1.ListPermissions(commandNameA, resolver, ...opts).toYargs()) .command(new generic_1.ViewAccessReport(commandName, resolver, ...opts).toYargs()) .command(new generic_1.Clone(commandName, resolver, ...opts).toYargs()) .command(new generic_1.Update(commandName, resolver, [ { path: 'description', }, { path: 'tags.<key>', }, { path: 'spec.version', }, // TODO support dynamic nested objects // { // path: 'spec.provider.<key>.<key>', // }, ], ...opts).toYargs()) // specific .command(new Kubeconfig(resolver).toYargs()) //.command(new Kubectl(resolver).toYargs()) // TODO .command(new Auth().toYargs()) .command(new Dashboard(resolver).toYargs()) .command(new Join(resolver).toYargs())); } handle() { } } exports.Mk8sCmd = Mk8sCmd; class Kubeconfig extends command_1.Command { constructor(resolve) { super(); this.resolve = resolve; this.command = 'kubeconfig <ref>'; this.describe = `Create a kubeconfig for a cluster.`; } options(yargs) { return yargs.options({ file: { describe: 'file to save kubeconfig to, default is $KUBECONFIG if set, otherwise ~/.kube/config. Use `--file -` to dump to stdout. New kubeconfig will be merged into the existing one', alias: 'f', requiresArg: true, demandOption: false, }, }); } builder(yargs) { return (0, functions_1.pipe)( // this.options, generic_2.withSingleRef, options_1.withAllOptions)(yargs); } async handle(args) { const link = this.resolve.resourceLink(args.ref, this.session.context); const getCluster = await this.client.get(link); const getCacerts = await this.client.get(link + '/-cacerts'); // this.session.outFormat(body); // this.session.outFormat(cacerts); const spec = { server: getCluster.status.serverUrl, name: `${this.session.context.org}/${getCluster.name}/${getCluster.alias}`, }; let username = 'mk8s-user-' + new Date().getTime(); const template = { apiVersion: 'v1', kind: 'Config', clusters: [ { cluster: { 'certificate-authority-data': Buffer.from(getCacerts.cacerts).toString('base64'), server: spec.server, }, name: spec.name, }, ], contexts: [ { context: { cluster: spec.name, user: username, }, name: spec.name, }, ], 'current-context': spec.name, users: [], }; const p = (0, names_1.parseSaToken)(this.session.request.token); // if token passed on the CLI, or the profile points to a SA if (args.token || p) { if (p) { username = `${spec.name}/sa:${p.name}`; } else { username = 'cli-token'; } template.users.push({ name: username, user: { token: this.session.request.token, }, }); // replace user in the context template.contexts[0].context.user = username; } else { const parsed = parseJwt(this.session.request.token); if (parsed === null || parsed === void 0 ? void 0 : parsed.email) { username = `${this.session.context.org}/${parsed.email}`; template.contexts[0].context.user = username; } template.users.push({ name: username, user: { exec: { apiVersion: AUTH_VERSION, command: 'cpln', args: ['mk8s', 'auth', getCluster.name], env: [ { name: 'CPLN_PROFILE', value: this.session.context.profile, }, { name: 'CPLN_ORG', value: this.session.context.org, }, ], provideClusterInfo: false, interactiveMode: 'IfAvailable', }, }, }); } const kubeconfigYaml = (0, js_yaml_1.safeDump)(template, { indent: 2, noRefs: true, skipInvalid: true, lineWidth: 2048, }); let destinationKubecfg = args.file; if (args.file == '-') { // dump to stdout this.session.out(kubeconfigYaml); return; } else if (args.file) { destinationKubecfg = args.file; } else { destinationKubecfg = process.env.KUBECONFIG; if (!destinationKubecfg) { destinationKubecfg = path.join(os.homedir(), '.kube', 'config'); logger_1.logger.info(`Defaulted to ${destinationKubecfg} for KUBECONFIG`); } else { logger_1.logger.info(`Current KUBECONFIG set to ${destinationKubecfg}`); } } this.mergeKubeconfig(destinationKubecfg, kubeconfigYaml); } mergeKubeconfig(destinationKubeconfig, contents) { const now = new Date().getTime(); // create parent directory if it doesn't exist const dirPath = path.dirname(destinationKubeconfig); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } // create backup file if (fs.existsSync(destinationKubeconfig)) { const backupName = destinationKubeconfig + '.cpln-' + now + '.bak'; logger_1.logger.info('Found existing kubeconfig, creating backup to ' + backupName); fs.copyFileSync(destinationKubeconfig, backupName); } // save new file to temp location const tmpFile = destinationKubeconfig + '.tmp-' + now; fs.writeFileSync(tmpFile, contents, { encoding: 'utf-8' }); // Determine KUBECONFIG env value based on platform let kubeconfigEnv = `${destinationKubeconfig}:${tmpFile}`; if (os.platform() == 'win32') { kubeconfigEnv = `${destinationKubeconfig};${tmpFile}`; } // merge destination and new const mergedFile = destinationKubeconfig + '.merged-' + now; (0, child_process_1.execSync)(`kubectl config view --flatten > "${mergedFile}"`, { env: { ...process.env, // passing a list of :-separated kubeconfigs KUBECONFIG: kubeconfigEnv, }, }); // replace destination with the merged version fs.copyFileSync(mergedFile, destinationKubeconfig); // remove temp try { fs.unlinkSync(tmpFile); } catch (ignore) { } // remove merged try { fs.unlinkSync(mergedFile); } catch (ignore) { } } } exports.Kubeconfig = Kubeconfig; class Auth extends command_1.Command { constructor() { super(); this.command = 'auth <ref>'; this.describe = false; // suppress showing in the help screen } builder(yargs) { return (0, functions_1.pipe)( // generic_2.withSingleRef, options_1.withOrgOptions, options_1.withProfileOptions)(yargs); } async handle(args) { var _a; // prime the token await this.client.get(`/org/${this.session.context.org}`); const response = { apiVersion: AUTH_VERSION, kind: 'ExecCredential', status: { token: '', expirationTimestamp: undefined, }, }; let token = (_a = this.session.request.token) !== null && _a !== void 0 ? _a : ''; token = token.replace('Bearer ', ''); response.status.token = token; const parsed = parseJwt(token); if (parsed === null || parsed === void 0 ? void 0 : parsed.exp) { response.status.expirationTimestamp = new Date(parsed.exp * 1000).toISOString(); } else { // must be a SA token } this.session.out(JSON.stringify(response, null, 2)); } } function parseJwt(t) { if (!t) { return null; } t = t.replace('Bearer ', ''); try { return jwt(t !== null && t !== void 0 ? t : ''); } catch (e) { } } class Dashboard extends command_1.Command { constructor(resolve) { super(); this.resolve = resolve; this.command = 'dashboard <ref>'; this.describe = 'Open the k8s dashboard for an mk8s cluster'; } builder(yargs) { return (0, functions_1.pipe)( // generic_2.withSingleRef, generic_1.withTagOptions, options_1.withAllOptions)(yargs); } async handle(args) { var _a, _b, _c; const link = this.resolve.resourceLink(args.ref, this.session.context); const body = await this.client.get(link); if ((_c = (_b = (_a = body.status) === null || _a === void 0 ? void 0 : _a.addOns) === null || _b === void 0 ? void 0 : _b.dashboard) === null || _c === void 0 ? void 0 : _c.url) { open(body.status.addOns.dashboard.url, { wait: false, }); } } } exports.Dashboard = Dashboard; class Join extends command_1.Command { constructor(resolve) { super(); this.resolve = resolve; this.command = 'join <ref>'; this.describe = 'Join compute nodes to a cluster'; // suppress showing in the help screen } options(yargs) { return yargs.options({ type: { describe: `Type of join configuration to produce: * cloud-init: produces a cloud-init script suitable for cloud deployments. * join-script: results in a simple script that can be evaluated as root on a node`, demandOption: true, requiresArg: true, choices: ['cloud-init', 'join-script'], }, options: { description: 'Some providers support extra options (e.g., --options nodepool=ingress)', requiresArg: true, multiple: true, }, }); } builder(yargs) { return (0, functions_1.pipe)( // generic_2.withSingleRef, options_1.withOrgOptions, options_1.withProfileOptions, options_1.withDebugOptions, options_1.withRequestOptions, this.options)(yargs); } async handle(args) { const link = this.resolve.resourceLink(args.ref, this.session.context); let script = ''; if (args.type == 'cloud-init') { const getCloudInit = await this.client.post(link + '/-cloudinit', (0, tags_1.expressionToTags)(args.options)); script = getCloudInit.script; } else if (args.type == 'join-script') { const getJoin = await this.client.post(link + '/-join', (0, tags_1.expressionToTags)(args.options)); script = getJoin.script; } this.session.out(script); } } //# sourceMappingURL=mk8s.js.map