@controlplane/cli
Version:
Control Plane Corporation CLI
362 lines • 13.9 kB
JavaScript
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
;