@controlplane/cli
Version:
Control Plane Corporation CLI
672 lines • 27.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HelmGetValues = exports.HelmGetNotes = exports.HelmGetManifest = exports.HelmGetAll = exports.HelmGet = exports.HelmHistory = exports.HelmList = exports.HelmUninstall = exports.HelmRollback = exports.HelmUpgrade = exports.HelmInstall = exports.HelmTemplate = exports.HelmCmd = void 0;
const command_1 = require("../cli/command");
const functions_1 = require("../util/functions");
const generic_1 = require("./generic");
const constants_1 = require("../helm/constants");
const options_1 = require("./options");
const functions_2 = require("../helm/functions");
const k8s_crd_exporter_1 = require("../k8s-crd-exporter");
const helpers_1 = require("../k8s-crd-exporter/helpers");
const helm_release_orchestrator_1 = require("../helm/helm-release-orchestrator");
const resolver_1 = require("./resolver");
const resultFetcher_1 = require("../rest/resultFetcher");
const helm_release_history_manager_1 = require("../helm/helm-release-history-manager");
const helm_release_migrator_1 = require("../helm/helm-release-migrator");
function withSingleRelease(yargs) {
return yargs.positional('release', {
type: 'string',
describe: 'The release name',
demandOption: false,
});
}
function withSingleChart(yargs) {
return yargs.positional('chart', {
type: 'string',
describe: 'Path to chart',
demandOption: true,
});
}
function withSingleRevision(yargs) {
return yargs.positional('revision', {
type: 'string',
describe: `Revision (version) number. If this argument is omitted or set to 0, it will roll back to the previous release. To see revision numbers, run 'cpln helm history RELEASE'.`,
});
}
function withHelmUpgradeOptions(yargs) {
return yargs.options({
'history-limit': {
type: 'number',
default: 10,
describe: 'Maximum number of revisions saved per release. Use 0 for no limit',
},
});
}
function withHelmTemplateOptions(yargs) {
return yargs.options({
'dependency-update': {
type: 'boolean',
describe: 'Update dependencies if they are missing before installing the chart',
},
description: {
type: 'string',
describe: 'Add a custom description',
alias: 'desc',
},
'generate-name': {
type: 'boolean',
describe: 'Generate the name (and omit the NAME parameter)',
alias: 'g',
},
'post-renderer': {
type: 'string',
describe: 'The path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path',
},
'post-renderer-args': {
default: [],
multiple: true,
describe: 'An argument to the post-renderer (can specify multiple or separate values: --post-renderer-args arg1 --post-renderer-args arg2) (default [])',
},
repo: {
type: 'string',
describe: 'Chart repository url where to locate the requested chart',
},
set: {
multiple: true,
describe: 'Set values on the command line (can specify multiple or separate values: --set key1=val1 --set key2=val2)',
},
'set-string': {
multiple: true,
describe: 'Set STRING values on the command line (can specify multiple or separate values: --set-string key1=val1 --set-string key2=val2)',
},
'set-file': {
multiple: true,
describe: 'Set values from respective files specified via the command line (can specify multiple or separate values: --set-file key1=path1 --set-file key2=path2)',
},
values: {
type: 'string',
multiple: true,
describe: 'Specify values in a YAML file or a URL (can specify multiple or separate values: --values value1.yaml --values values2.yaml)',
alias: 'f',
},
verify: {
type: 'boolean',
describe: 'Verify the package before using it',
},
});
}
function withHelmGetOptions(yargs) {
return yargs.options({
revision: {
type: 'number',
describe: 'get the named release with revision',
},
});
}
function withHelmGetValuesOptions(yargs) {
return yargs.options({
all: {
type: 'boolean',
describe: 'dump all (computed) values',
alias: 'a',
},
});
}
function withWaitOptions(yargs) {
return yargs.options({
wait: {
type: 'boolean',
describe: `If set, will wait until all Workloads are in a ready state before marking the release as successful. It will wait for as long as --timeout`,
},
timeout: {
type: 'number',
default: 5 * 60,
describe: 'The amount of seconds to wait for workloads to be ready before timing out. Works only if the "wait" option is set to true.',
},
});
}
function withCleanupOnFail(yargs) {
return yargs.options({
'cleanup-on-fail': {
type: 'boolean',
default: false,
describe: `allow deletion of new resources created in this rollback when rollback fails`,
},
});
}
function withStateTagRemovableOptions(yargs) {
return yargs.options({
'state-tag': {
description: 'Attach tags to the Helm secret (e.g., --state-tag drink=water)',
requiresArg: true,
multiple: true,
},
'remove-state-tag': {
description: 'Remove tags from the Helm secret (e.g., --remove-state-tag tagname)',
requiresArg: true,
multiple: true,
},
});
}
// Commands //
class HelmCmd extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'helm';
this.describe = 'Manage helm releases on cpln';
}
builder(yargs) {
return yargs
.demandCommand()
.version(false)
.help()
.command(new HelmGet().toYargs())
.command(new HelmHistory().toYargs())
.command(new HelmInstall().toYargs())
.command(new HelmList().toYargs())
.command(new HelmRollback().toYargs())
.command(new HelmTemplate().toYargs())
.command(new HelmUninstall().toYargs())
.command(new HelmUpgrade().toYargs());
}
handle() { }
}
exports.HelmCmd = HelmCmd;
class HelmTemplate extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'template [release] [chart]';
this.describe = 'Generate cpln resources from a template';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withSingleChart, withHelmTemplateOptions, helpers_1.withK8sCrdExporterOptions, options_1.withAllOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Process Helm arguments
try {
(0, functions_2.processHelmArgs)(args);
}
catch (e) {
this.session.abort({ message: e.message });
}
// Output the template in different formats
switch (args.output) {
case 'crd':
const templateResources = (0, functions_2.getTemplateResources)(this.session, args, this.session.context.org, this.session.context.gvc);
// Create a new CRD exporter
const exporter = new k8s_crd_exporter_1.K8sCrdExporter(this.session);
// Construct the batch request
const batchRequest = {
resources: templateResources,
};
// Perform the batch request
const batchResponse = await exporter.batch(batchRequest, args.namespace, args.keep);
// Determine output format
let outputFormat;
// Extract and show resources in the default format (defaults to JSON if no default is specified)
switch (this.session.profile.format.output) {
case 'json':
case 'json-slim':
case 'yaml':
case 'yaml-slim':
outputFormat = this.session.profile.format.output || 'yaml';
break;
default:
outputFormat = 'yaml';
break;
}
// Output the items in the determined format
this.session.outFormat(batchResponse.items, {
output: outputFormat,
color: this.session.profile.format.color,
_separateDocs: true,
});
break;
case 'json':
case 'json-slim':
case 'names':
this.session.outFormat((0, functions_2.generateHelmTemplate)(args, this.session.context.org, this.session.context.gvc, args.debug || args.verbose));
break;
default:
this.session.out((0, functions_2.generateHelmTemplate)(args, this.session.context.org, this.session.context.gvc, args.debug || args.verbose));
break;
}
}
}
exports.HelmTemplate = HelmTemplate;
class HelmInstall extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'install [release] [chart]';
this.aliases = ['apply'];
this.describe = 'Install a release';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withSingleChart, withWaitOptions, withHelmTemplateOptions, generic_1.withTagRemovableOptions, withStateTagRemovableOptions, options_1.withAllOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Safely process Helm arguments
try {
(0, functions_2.processHelmArgs)(args);
}
catch (e) {
this.session.abort({ message: e.message });
}
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Install the specified release
await helmOrchestrator.install();
}
}
exports.HelmInstall = HelmInstall;
class HelmUpgrade extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'upgrade [release] [chart]';
this.describe = 'Upgrade a release';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withSingleChart, withWaitOptions, withHelmUpgradeOptions, withHelmTemplateOptions, generic_1.withTagRemovableOptions, withStateTagRemovableOptions, options_1.withAllOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Process Helm arguments
try {
(0, functions_2.processHelmArgs)(args);
}
catch (e) {
this.session.abort({ message: e.message });
}
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Upgrade the specified release
await helmOrchestrator.upgrade();
}
}
exports.HelmUpgrade = HelmUpgrade;
class HelmRollback extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'rollback <release> [revision]';
this.describe = 'Roll back a release to a previous revision';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withSingleRevision, withCleanupOnFail, withWaitOptions, options_1.withAllOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Rollback to a specific release
await helmOrchestrator.rollback();
}
}
exports.HelmRollback = HelmRollback;
class HelmUninstall extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'uninstall <release>';
this.aliases = ['destroy', 'del', 'delete', 'un'];
this.describe = 'Uninstall a release';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, options_1.withRequestOptions, options_1.withAllOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Uninstall the release and its revisions
await helmOrchestrator.uninstall();
}
}
exports.HelmUninstall = HelmUninstall;
class HelmList extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'list';
this.describe = 'List releases';
}
// Public Methods //
builder(yargs) {
return (0, functions_1.pipe)(
//
options_1.withOrgOptions, options_1.withStandardOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Migrate legacy releases if found, this is so we can have them in the list later on
await this.migrateLegacyReleases(args);
// The final items for the table display
const items = await this.collectTableItems(args);
// Prepare accumulator
const accumulator = {
kind: 'list',
itemKind: constants_1.CPLN_HELM_LIST_KIND,
items: items,
links: [],
};
if (this.session.format.max) {
accumulator.items = accumulator.items.slice(0, this.session.format.max);
}
// Output the deployment list with format
await this.session.outFormat(accumulator);
}
// Private Methods //
async migrateLegacyReleases(args) {
// A variable to hold all migration promises to await for in parallel
const promises = [];
// Construct the parent link of the secrets
const secretParentLink = (0, resolver_1.kindResolver)('secret').parentLink(this.session.context);
// Fetch all secrets that start with the legacy cpln helm release prefix
const secretList = await this.client.post(`${secretParentLink}/-query`, {
kind: 'secret',
spec: {
match: 'any',
terms: [
{
op: '~',
property: 'name',
value: constants_1.CPLN_RELEASE_NAME_PREFIX_LEGACY,
},
],
},
});
// Continue fetching all secrets that match the query terms
await (0, resultFetcher_1.fetchPages)(this.client, 0, secretList);
// Treat the secret list items as an array of secrets
const secrets = secretList.items;
// If there were no legacy releases found, skip migration
if (secrets.length === 0) {
return;
}
// Iterate over each secret and accumulate migrate promises so we can await migrations in parallel
for (const secret of secrets) {
promises.push(this.migrateRelease(secret, args));
}
// Migrate all accumulated migration promises in parallel
await Promise.all(promises);
}
async collectTableItems(args) {
// The final items for the table display
const items = [];
// A variable to hold all release history manager instances to await for in parallel
const promises = [];
// Get all release names
const releaseNames = await this.getAllReleaseNames();
// Iterate over the release names and collect release history manager initialization promises,
// so we can await for their initialization in parallel
for (const releaseName of releaseNames) {
// Clone base arguments and specify the current release name
const newArgs = { ...args, release: releaseName };
// Collect the initialize promise
promises.push(helm_release_history_manager_1.HelmReleaseHistoryManager.initialize(this.client, this.session, newArgs, this.ensureDeletion));
}
// Await for the initialization of all release history managers in parallel
const historyManagers = await Promise.all(promises);
// Iterate over history managers and create table items
for (const historyManager of historyManagers) {
// Placeholder for the selected release revision
let releaseRevision;
// Prefer the deployed revision if available, otherwise use the latest revision
if (historyManager.deployedReleaseRevision) {
releaseRevision = historyManager.deployedReleaseRevision;
}
else {
releaseRevision = historyManager.latestReleaseRevision;
}
// Extract key details and append to the items list for display
items.push({
release: releaseRevision.release.name,
gvc: releaseRevision.release.gvc,
revision: releaseRevision.release.version,
updated: releaseRevision.release.info.lastDeployed,
status: releaseRevision.release.info.status,
chart: releaseRevision.release.chart.metadata.name,
appVersion: releaseRevision.release.chart.metadata.appVersion,
});
}
// Return the items for table display
return items;
}
async getAllReleaseNames() {
// The unique set of release names
const releaseNames = new Set();
// Construct the parent link of the secrets
const secretParentLink = (0, resolver_1.kindResolver)('secret').parentLink(this.session.context);
// Fetch all secrets that start with the cpln helm release prefix
const secretList = await this.client.post(`${secretParentLink}/-query`, {
kind: 'secret',
spec: {
match: 'any',
terms: [
{
op: '~',
property: 'name',
value: constants_1.CPLN_RELEASE_NAME_PREFIX,
},
],
},
});
// Continue fetching all secrets that match the query terms
await (0, resultFetcher_1.fetchPages)(this.client, 0, secretList);
// Treat the secret list items as an array of secrets
const secrets = secretList.items;
// Since each release could have multiple revisions (secrets), then we should uniqule accumulate release names from the secrets
for (const secret of secrets) {
// Skip secret if it is an invalid release revision secret
if (!secret.tags || !secret.tags.hasOwnProperty('name')) {
continue;
}
// Extract the release name from the secret tags
const releaseName = secret.tags.name;
// Skip if the release name has already been collected
if (releaseNames.has(releaseName)) {
continue;
}
// Add the release name to the release names set
releaseNames.add(releaseName);
}
// No limit requested: return the full set
if (this.session.format.max === undefined || this.session.format.max < 1) {
return releaseNames;
}
// Convert the Set to an array, take the first `max` items, and wrap them back into a new Set
const slicedArray = [...releaseNames].slice(0, this.session.format.max);
// Return the limited Set
return new Set(slicedArray);
}
async migrateRelease(secret, args) {
// Construct the self link of the secret
const selfLink = (0, resolver_1.resolveToLink)('secret', secret.name, this.session.context);
// Reveal the legacy secret
const revealedSecret = await this.client.get(`${selfLink}/-reveal`);
// If we got here, then that legacy release exists and we need to migrate it
const helmMigrator = new helm_release_migrator_1.HelmReleaseMigrator(this.client, this.session, args, this.ensureDeletion, revealedSecret);
// Migrate the release
await helmMigrator.migrate();
}
}
exports.HelmList = HelmList;
class HelmHistory extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'history <release>';
this.describe = 'Fetch release history';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, options_1.withAllOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// The final items for the table display
const items = [];
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Get release revisions
const releaseRevisions = await helmOrchestrator.getRevisions();
// Throw an error if deployments were empty
if (releaseRevisions.length == 0) {
this.session.abort({ message: `ERROR: Release '${args.release}' has no revisions.` });
}
// Iterate over each release revision and construct a table item
for (const releaseRevision of releaseRevisions) {
items.push({
revision: releaseRevision.release.version,
updated: releaseRevision.release.info.lastDeployed,
status: releaseRevision.release.info.status,
chart: releaseRevision.release.chart.metadata.name,
appVersion: releaseRevision.release.chart.metadata.appVersion,
description: releaseRevision.release.info.description,
});
}
// Prepare accumulator
const accumulator = {
kind: 'list',
itemKind: constants_1.CPLN_HELM_DEPLOYMENT_KIND,
items: items,
links: [],
};
if (this.session.format.max) {
accumulator.items = accumulator.items.slice(0, this.session.format.max);
}
// Output the state with format
await this.session.outFormat(accumulator);
}
}
exports.HelmHistory = HelmHistory;
class HelmGet extends command_1.Command {
constructor() {
super();
this.command = 'get';
this.describe = 'Download extended information of a named release';
}
builder(yargs) {
return (yargs
.demandCommand()
.version(false)
.help()
// specific
.command(new HelmGetAll().toYargs())
.command(new HelmGetManifest().toYargs())
.command(new HelmGetNotes().toYargs())
.command(new HelmGetValues().toYargs()));
}
handle() { }
}
exports.HelmGet = HelmGet;
class HelmGetAll extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'all <release>';
this.describe = 'Download all information for a named release';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withHelmGetOptions, options_1.withOrgOptions, options_1.withStandardOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Get the release object of the specified release
const release = await helmOrchestrator.getAll();
// Output the release with format
await this.session.outFormat(release);
}
}
exports.HelmGetAll = HelmGetAll;
class HelmGetManifest extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'manifest <release>';
this.describe = 'Download the manifest for a named release';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withHelmGetOptions, options_1.withOrgOptions, options_1.withStandardOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Get the manifest of the specified release
const manifest = await helmOrchestrator.getManifest();
// Output the state with format
await this.session.outFormat(manifest);
}
}
exports.HelmGetManifest = HelmGetManifest;
class HelmGetNotes extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'notes <release>';
this.describe = 'Download the notes for a named release';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withHelmGetOptions, options_1.withOrgOptions, options_1.withStandardOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Get the release object of the specified release
const release = await helmOrchestrator.getAll();
// Output the notes
this.session.out(release.info.notes);
}
}
exports.HelmGetNotes = HelmGetNotes;
class HelmGetValues extends command_1.Command {
constructor() {
super(...arguments);
this.command = 'values <release>';
this.describe = 'Download the values file for a named release';
}
builder(yargs) {
return (0, functions_1.pipe)(
//
withSingleRelease, withHelmGetValuesOptions, withHelmGetOptions, options_1.withOrgOptions, options_1.withStandardOptions)(yargs);
}
async handle(args) {
// Org is required
this.requireOrg();
// Initialize the Helm Orchestrator
const helmOrchestrator = await helm_release_orchestrator_1.HelmReleaseOrchestrator.initialize(this.client, this.session, args, this.ensureDeletion);
// Get the config of the specified release
const config = await helmOrchestrator.getConfig();
// Output the state with format
await this.session.outFormat(config);
}
}
exports.HelmGetValues = HelmGetValues;
//# sourceMappingURL=helm.js.map