@controlplane/cli
Version:
Control Plane Corporation CLI
641 lines • 35.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HelmReleaseRevisionManager = void 0;
const objects_1 = require("../util/objects");
const workload_1 = require("../util/workload");
const resolver_1 = require("../commands/resolver");
const lodash_1 = require("lodash");
const logger_1 = require("../util/logger");
const functions_1 = require("../util/functions");
const errors_1 = require("../util/errors");
const helm_base_1 = require("./helm-base");
const format_1 = require("../util/format");
const functions_2 = require("./functions");
const constants_1 = require("./constants");
class HelmReleaseRevisionManager extends helm_base_1.HelmBase {
constructor(client, session, args, ensureDeletion, secret) {
super(client, session, args, ensureDeletion);
// Required parameters
this.secret = secret;
// Parameters dependant
this.release = (0, functions_2.decodeReleaseFromSecret)(secret);
this.fetchedGvcs = new Set();
this.customResourceTags = {};
this.customResourceTags[constants_1.CPLN_HELM_RESOURCE_TAG_KEY] = this.secret.name;
this.createdResources = [];
this.secretSelfLink = (0, resolver_1.resolveToLink)('secret', secret.name, this.session.context);
}
// Public Methods //
async install(templateResources, historyManager) {
// Prepare before install or upgrade
await this.prepareBeforeApply(templateResources);
// Install template resources as part of the release
const applyResponse = await this.applyTemplateResources(templateResources);
// Mark this release as deployed; which will also update the secret with the modified release
await this.updateStatus('deployed');
// Wait for workloads to be ready if the "wait" option is set to true
await this.awaitWorkloadsReadiness(applyResponse.appliedWorkloadLinks);
// Notify user that the release is successful
this.session.out(`\nRelease '${this.release.name}' has been installed successfully!`);
// Print release notes if found
if (this.release.info.notes) {
this.session.out(`\n${this.release.info.notes}`);
}
}
async upgrade(templateResources, historyManager) {
var _a;
// Prepare before install or upgrade
await this.prepareBeforeApply(templateResources);
// Install template resources as part of the release
const applyResponse = await this.applyTemplateResources(templateResources, (_a = historyManager.deployedReleaseRevision) === null || _a === void 0 ? void 0 : _a.release);
// Scan previous releases for resources that are no longer OR not part of the current release and delete them
await this.performCleanup(applyResponse.appliedResourceLinks, historyManager.releaseRevisionSecrets);
// Mark the deployed release as superseded
if (historyManager.deployedReleaseRevision) {
await historyManager.deployedReleaseRevision.updateStatus('superseded');
}
// Mark this release as deployed; which will also update the secret with the modified release
await this.updateStatus('deployed');
// Wait for workloads to be ready if the "wait" option is set to true
await this.awaitWorkloadsReadiness(applyResponse.appliedWorkloadLinks);
// Notify user that the release is successful
this.session.out(`\nRelease '${this.release.name}' has been upgraded successfully!`);
// Print release notes if found
if (this.release.info.notes) {
this.session.out(`\n${this.release.info.notes}`);
}
}
async rollback(target, historyManager) {
// Reset created resources array just in case
this.createdResources = [];
// Extract resources from the rollback target
const resources = target.release.info.resources.map((helmResource) => helmResource.template);
// Prepare before rollback
await this.prepareBeforeApply(resources);
// Install target resources as part of the release
const applyResponse = await this.applyRollbackResources(target.release.info.resources, historyManager.deployedReleaseRevision ? historyManager.deployedReleaseRevision.release : undefined);
// Scan previous releases for resources that are no longer OR not part of the current release and delete them
await this.performCleanup(applyResponse.appliedResourceLinks, historyManager.releaseRevisionSecrets);
// Mark the deployed release as superseded
if (historyManager.deployedReleaseRevision) {
await historyManager.deployedReleaseRevision.updateStatus('superseded');
}
// Mark this release as deployed; which will also update the secret with the modified release
await this.updateStatus('deployed');
// Wait for workloads to be ready if the "wait" option is set to true
await this.awaitWorkloadsReadiness(applyResponse.appliedWorkloadLinks);
// Notify user that the rollback is successful
this.session.out(`\nRollback to revision '${target.release.version}' has been successful!`);
}
async uninstall() {
// Delete all resources of this release
await this.deleteResources(this.release.info.resources);
// Delete the secret of this release
await this.deleteSecret();
}
async deleteSecret() {
try {
await this.client.axios.delete(this.secretSelfLink);
}
catch (e) {
this.session.err(`ERROR: Unable to delete the secret '${this.secretSelfLink}' for the release '${this.release.name}'.`);
this.session.abort({ error: e });
}
}
getManifest() {
// If the release does not have a manifest or it is empty, that is probably because of the migration, let's return resources instead
if (!this.release.manifest) {
// Convert each item in the array to YAML and collect the results
const docs = this.release.info.resources.map((item) => (0, format_1.toSortedYamlString)(item));
// Join the individual YAML documents using the '---' separator
return docs.join('---\n');
}
// Return the manifest of the release as is
return this.release.manifest;
}
async markCleanupChecked() {
this.secret.tags[constants_1.CPLN_HELM_SCANNED_FOR_DELETION_TAG_KEY] = 'true';
// Update secret safely
try {
// Apply using the local secret object
await this.client.put(this.secretParentLink, this.secret);
}
catch (e) {
this.session.err(`ERROR: Unable to update the state '${this.release.name}'.`);
this.session.abort({ error: e });
}
}
// Private Methods //
async prepareBeforeApply(resources) {
var _a, _b, _c;
// Declare a secret name to hash map
const secretHashMap = {};
// Process secrets in the template resources list and compute their hashes
for (const templateResource of resources) {
// Ignore non-secret resources
if (templateResource.kind !== 'secret') {
continue;
}
// Parse template resource to a Secret object
const secret = templateResource;
// Ignore secrets that are invalid, we will catch this later on apply
if (!secret.name || !secret.data) {
continue;
}
// Map secret name to its data hash
secretHashMap[secret.name] = (0, objects_1.computeHash)(secret.data);
}
// Process workloads and add/update their tag that is responsible for secrets
for (const resource of resources) {
// Ignore non-workload resources
if (resource.kind !== 'workload') {
continue;
}
// Parse template resource to a Workload object
const workload = resource;
// Extract secret names from the workload
const secretNames = (0, workload_1.extractSecretNames)(workload);
// Iterate over secret names and update workload tags accordingly
for (const secretName of secretNames) {
// Fetch secret from API if not already in the list
if (!(secretName in secretHashMap)) {
// Attempt to fetch the secret, and handle error accordingly
try {
// Resolve the self-link of the secret
const secretLink = (0, resolver_1.resolveToLink)('secret', secretName, this.session.context);
// Fetch the secret
const fetchedSecret = await this.fetchResource('secret', secretLink);
// Compute the hash of the secret and map it to the secret name
secretHashMap[secretName] = (0, objects_1.computeHash)(fetchedSecret.data);
}
catch (e) {
let errorMessage = '';
// Handle when the secret does not exist
if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
// Let's map a constant value for non-existing secrets
secretHashMap[secretName] = constants_1.CPLN_HELM_SECRET_TAG_DEFAULT_VALUE;
}
else if ((_c = (_b = e.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) {
// Throw the error message along with the status code
errorMessage = `ERROR: Unable to fetch Secret '${secretName}' that is referenced by Workload '${workload.name}'. Status: ${e.response.status}, Message: ${e.response.data.message}.`;
}
else {
// Something unexpected happened, throw accordingly
errorMessage = `ERROR: Unable to fetch Secret '${secretName}' that is referenced by Workload '${workload.name}'. Message: ${e.message}.`;
}
// Mark deployment as failed and throw error if an error message was found
if (errorMessage) {
this.setReleaseStatus('failed', errorMessage);
await this.updateSecret();
// Attempt to clean up on fail if requested
await this.cleanupOnFail();
this.session.err(`ERROR: Unable to apply ${resource.kind} '${resource.name}'.`);
this.session.abort({ error: e });
}
}
}
// Initialize tags if not found
if (workload.tags === undefined) {
workload.tags = {};
}
// Update workload tags
workload.tags[`${constants_1.CPLN_HELM_RESOURCE_SECRET_KEY_PREFIX}:${secretName}`] = secretHashMap[secretName];
}
}
}
async applyTemplateResources(templateResources, deployedRelease) {
const appliedResourceLinks = [];
const appliedWorkloadLinks = [];
// Iterate over template resources and apply them individually
for (const templateResource of templateResources) {
// Apply the resource
const resourceSelfLink = await this.applyResource(this.session.context, templateResource, deployedRelease);
// Collect workload links so we can wait for them to be ready if requested
if (templateResource.kind === 'workload') {
appliedWorkloadLinks.push(resourceSelfLink);
}
// Confirm that the resource has been deployed
appliedResourceLinks.push(resourceSelfLink);
}
// Return a response instance
return {
appliedResourceLinks,
appliedWorkloadLinks,
};
}
async applyRollbackResources(targetResources, deployedRelease) {
const appliedResourceLinks = [];
const appliedWorkloadLinks = [];
// Iterate over target resources and apply them individually
for (const targetResource of targetResources) {
// Dismantle the self link to extract the context
const dismantledSelfLink = (0, resolver_1.dismantleSelfLink)(this.session.context.org, targetResource.link);
// Apply the resource
const resourceSelfLink = await this.applyResource(dismantledSelfLink.ctx, targetResource.template, deployedRelease);
// Collect workload links so we can wait for them to be ready if requested
if (targetResource.template.kind === 'workload') {
appliedWorkloadLinks.push(resourceSelfLink);
}
// Confirm that the resource has been deployed
appliedResourceLinks.push(resourceSelfLink);
}
// Return a response instance
return {
appliedResourceLinks,
appliedWorkloadLinks,
};
}
async applyResource(ctx, resource, deployedRelease) {
var _a, _b, _c;
// Modify context if it has been specified in the resource
if (resource.gvc) {
ctx.gvc = resource.gvc;
}
// Prepare relation links
const selfLink = (0, resolver_1.resolveToLink)(resource.kind, resource.name, ctx);
const parentLink = (0, resolver_1.kindResolver)(resource.kind).parentLink(ctx);
// Assume that this resource is not to be created until proven otherwise
let isCreated = false;
try {
// Avoid applying GVC related resources if the GVC itself does not exist
await this.ensureGvcExists(resource, ctx);
// Check if this resource belongs to a different release and throw error accordingly
await this.validateResourceReleaseTag(selfLink);
// Declare the variable for the original resource that will be fetched
let originalResource = null;
// Safely fetch the resource if it exists
try {
originalResource = await this.client.get(selfLink);
}
catch (e) {
if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.status) !== 404) {
throw e;
}
}
// Update the tags only if we are not rolling back
if (this.release.info.status !== 'pending-rollback') {
resource.tags = {
...this.release.labels,
...resource.tags,
...this.customResourceTags, // Helm specific tags that we force on every resource applied through cpln helm (overrides resource specific tags)
};
}
// A variable to determine whether we should apply template resource or not
let shouldApplyResource = await this.shouldApplyResource(deployedRelease, originalResource, selfLink, resource);
// Apply template resource if needed
if (shouldApplyResource) {
const copyResource = JSON.parse(JSON.stringify(resource));
// Remove the GVC property before applying if it exists
if (copyResource.gvc) {
delete copyResource.gvc;
}
// Perform apply
const axiosResponse = await this.client.axios.put(parentLink, copyResource);
// Print apply status message
if (axiosResponse.status == 201) {
this.session.out(`Created ${axiosResponse.headers.location}`);
// Mark this resource as created
isCreated = true;
}
else {
this.session.out(`Updated ${selfLink}`);
}
}
else {
this.session.out(`Unchanged ${selfLink}`);
}
// Fetch the resource to extract some details from it
const fetchedResource = await this.client.get(selfLink);
// Construct the helm release resource
const releaseResource = {
id: fetchedResource.id,
kind: fetchedResource.kind,
version: fetchedResource.version,
link: selfLink,
template: resource,
};
// If this resource has been created, let's add it to the created resources array
if (isCreated) {
this.createdResources.push(releaseResource);
}
// Add the resource to the release
this.release.info.resources.push(releaseResource);
return selfLink;
}
catch (e) {
this.setReleaseStatus('failed', ((_c = (_b = e.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) || e.message);
await this.updateSecret();
// Attempt to clean up on fail if requested
await this.cleanupOnFail();
this.session.err(`ERROR: Unable to apply '${selfLink}'.`);
this.session.abort({ error: e });
}
}
async updateStatus(status) {
// Mark this release as superseded
this.setReleaseStatus(status);
// Update the secret with the modified release
await this.updateSecret();
}
async updateSecret() {
// Modify the existing secret object
this.secret.tags.status = this.release.info.status;
this.secret.data.payload = await (0, functions_2.encodeRelease)(this.release);
// Update secret safely
try {
// Apply using the local secret object
await this.client.put(this.secretParentLink, this.secret);
}
catch (e) {
this.session.err(`ERROR: Unable to update the state '${this.release.name}'.`);
this.session.abort({ error: e });
}
}
async performCleanup(appliedResourceLinks, releaseRevisionSecrets) {
var _a, _b;
// A variable to hold resources that need to be deleted
let resourcesToDelete = [];
// A list to track post cleanup promises that we will await for after cleanup
const toMarkScannedForCleanup = [];
// Iterate over all release revisions and find resources that are not or no longer part of the current release
for (const secret of releaseRevisionSecrets) {
// Skip the secret if it has already been scanned for deletion
if (secret.tags[constants_1.CPLN_HELM_SCANNED_FOR_DELETION_TAG_KEY] === 'true') {
continue;
}
// Reveal the secret
const releaseRevision = await this.loadReleaseRevision(secret.name);
// Iterate over all resources in the release revision
for (const resource of releaseRevision.release.info.resources) {
// Check if the resource is not part of the current release
if (!appliedResourceLinks.includes(resource.link)) {
// Add the resource to the list of resources to delete
resourcesToDelete.push(resource);
}
}
// Accumulate the release revision to the post cleanup list
toMarkScannedForCleanup.push(releaseRevision.markCleanupChecked());
}
// If there were resources to delete, let's delete them
if (resourcesToDelete.length > 0) {
// Print a message to indicate that we are performing cleanup
this.session.out('\nPerforming cleanup...\n');
// Safely delete resources
try {
// Delete the accumulated resources
await this.deleteResources(resourcesToDelete);
}
catch (e) {
// Mark this release as failed
this.setReleaseStatus('failed', ((_b = (_a = e.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || e.message);
// Update the secret with the modified release
await this.updateSecret();
// Attempt to clean up on fail if requested
await this.cleanupOnFail();
// Abort the session with an error message
this.session.abort({ error: e });
}
}
// Post cleanup; mark all release revisions that we scanned for cleanup as scanned so we don't have to visit them again
await Promise.all(toMarkScannedForCleanup);
}
async cleanupOnFail() {
if (this.args.cleanupOnFail && this.createdResources.length > 0) {
this.session.out(`Rollback was not successful, deleting created resources...`);
await this.deleteResources(this.createdResources);
}
}
async deleteResources(resourceLinks) {
var _a, _b;
let currentKindToEnsureDeletion;
let resourceLinksToEnsureDeletion = [];
// Sort by priority reverse
(0, objects_1.sortIHasKindByPriority)(resourceLinks, true);
for (const resource of resourceLinks) {
try {
// Skip resource deleteion if specified
if (((_a = resource.template.tags) === null || _a === void 0 ? void 0 : _a[constants_1.CPLN_HELM_KEEP_RESOURCE_KEY]) === constants_1.CPLN_HELM_KEEP_RESOURCE_VALUE) {
this.session.out(`Skipped deletion of ${resource.link} because it has the 'helm.sh/resource-policy' tag set to 'keep'`);
continue;
}
// Ensure the deletion of a group of resources of the same kind
if (currentKindToEnsureDeletion && currentKindToEnsureDeletion !== resource.kind) {
currentKindToEnsureDeletion = undefined;
await Promise.all(resourceLinksToEnsureDeletion.map((link) => this.ensureDeletion(link)));
resourceLinksToEnsureDeletion = [];
}
// Consider these kinds of resources to ensure their deletion later
if (resource.kind == 'volumeset' || resource.kind == 'workload') {
currentKindToEnsureDeletion = resource.kind;
resourceLinksToEnsureDeletion.push(resource.link);
}
// Perform the delete operation
await this.client.axios.delete(resource.link);
this.session.out(`Deleted ${resource.link}`);
}
catch (e) {
if (((_b = e.response) === null || _b === void 0 ? void 0 : _b.status) !== 404) {
this.session.err(`ERROR: Unable to uninstall '${resource.link}'. Please try again.`);
this.session.abort({ error: e });
}
}
}
}
async ensureGvcExists(resource, ctx) {
var _a, _b, _c;
if (objects_1.gvcRelatedKinds.includes(resource.kind)) {
// Check if GVC was already ensured, and avoid checking again
if (this.fetchedGvcs.has(ctx.gvc)) {
return;
}
// Attempt to fetch the GVC, if there is an issue, throw errors accordingly
try {
// Construct the self link for the GVC
const gvcLink = (0, resolver_1.resolveToLink)('gvc', ctx.gvc, ctx);
// Fetch the GVC
await this.client.get(gvcLink);
// If successful, we will get here, and we will take record of this GVC
this.fetchedGvcs.add(ctx.gvc);
}
catch (e) {
// Handle when GVC does not exist
if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
throw new Error(`ERROR: You attempted to apply ${resource.kind} '${resource.name}' while GVC '${ctx.gvc || 'UNKNOWN'}' does not exist.`);
}
else if ((_c = (_b = e.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) {
// Throw the error message along with the status code
throw new Error(`ERROR: Unable to fetch GVC '${ctx.gvc || 'UNKNOWN'}' for ${resource.kind} '${resource.name}'. Status: ${e.response.status}, Message: ${e.response.data.message}.`);
}
else {
// Something unexpected happened, throw accordingly
throw new Error(`ERROR: Unable to fetch GVC '${ctx.gvc || 'UNKNOWN'}' for ${resource.kind} '${resource.name}'. Message: ${e.message}.`);
}
}
}
}
async validateResourceReleaseTag(resourceSelfLink) {
var _a, _b;
try {
const resourceToCheck = await this.client.get(resourceSelfLink);
// Skip the release tag check for specific kinds
if (constants_1.CPLN_HELM_KINDS_TO_SKIP_RELEASE_TAG_CHECK.includes(resourceToCheck.kind)) {
return;
}
// Throw an error if the resource does not have the CPLN_HELM_RESOURCE_TAG tag
if (!resourceToCheck.tags || !resourceToCheck.tags.hasOwnProperty(constants_1.CPLN_HELM_RESOURCE_TAG_KEY)) {
throw new Error(`ERROR: The resource '${resourceSelfLink}' cannot be updated because it is missing the '${constants_1.CPLN_HELM_RESOURCE_TAG_KEY}' tag. This tag is required to ensure the resource is managed by the correct release. Please add the tag '${constants_1.CPLN_HELM_RESOURCE_TAG_KEY}' with the value '${this.secret.name}' to proceed.`);
}
// Throw an error if the resource does not belong to this release.
if (resourceToCheck.tags[constants_1.CPLN_HELM_RESOURCE_TAG_KEY] !== (0, functions_2.getReleaseSecretNameLegacy)(this.release.name) &&
!resourceToCheck.tags[constants_1.CPLN_HELM_RESOURCE_TAG_KEY].startsWith((0, functions_2.getReleaseSecretNameWithoutRevision)(this.release.name))) {
// Extract the secret name of the different release that this resource belongs to
const differentReleaseSecretName = resourceToCheck.tags[constants_1.CPLN_HELM_RESOURCE_TAG_KEY];
// Construct the self link of the different release secret
const differentReleaseSecretSelfLink = (0, resolver_1.resolveToLink)('secret', differentReleaseSecretName, this.session.context);
// Declare a variable that will hold the secret object value after successful fetch
let differentReleaseRevisionSecret;
// Attempt to fetch the secret
try {
// Fetch the release secret of the resource we are checking so we can extract the different release name
differentReleaseRevisionSecret = await this.client.get(differentReleaseSecretSelfLink);
}
catch (e) {
throw new Error(`ERROR: The resource '${resourceSelfLink}' cannot be updated because it is being managed by a different release, the secret that manages this release is '${differentReleaseSecretName}'. This measure is in place to prevent updates to resources that already belong to a different release.`);
}
// Extract the different release name that this resource belongs to
let differentReleaseName = (_a = differentReleaseRevisionSecret.tags) === null || _a === void 0 ? void 0 : _a.name;
// If the name tag is not on the secret object, then this must be a legacy release secret that we just fetched, in this case let's just extract the release name from the secret name
if (differentReleaseSecretName.startsWith(constants_1.CPLN_RELEASE_NAME_PREFIX_LEGACY)) {
differentReleaseName = differentReleaseSecretName.slice(`${constants_1.CPLN_RELEASE_NAME_PREFIX_LEGACY}-`.length);
}
// If the different release name is still undefined, let's throw a different error
if (!differentReleaseName) {
throw new Error(`ERROR: The resource '${resourceSelfLink}' cannot be updated because it is being managed by a different release, the secret that manages this release is '${differentReleaseSecretName}'. This measure is in place to prevent updates to resources that already belong to a different release.`);
}
// Throw an error explaining that the resource does not belong to this release
throw new Error(`ERROR: The resource '${resourceSelfLink}' cannot be updated because it is being managed by a different release '${differentReleaseName}'. This measure is in place to prevent updates to resources that already belong to a different release.`);
}
}
catch (e) {
if (((_b = e.response) === null || _b === void 0 ? void 0 : _b.status) !== 404) {
throw e;
}
}
}
async shouldApplyResource(deployedRelease, originalResource, templateResourceSelfLink, templateResource) {
// If there is a last deployed deployment, let's compare resources
if (deployedRelease && originalResource) {
// Attempt to find this template resource in the latest deployed deployment
const deployedResource = deployedRelease.info.resources.find((resource) => resource.link === templateResourceSelfLink);
// If this resource exists in the last deployed deployment, then let's make a comparison
if (deployedResource) {
try {
// Copy both resources
const templateResourceCopy = JSON.parse(JSON.stringify(templateResource));
const deployedResourceCopy = JSON.parse(JSON.stringify(deployedResource.template));
// Remove the 'cpln/release' tag from both since it is gonna be different because of the revision number
if (templateResourceCopy.tags) {
delete templateResourceCopy.tags[constants_1.CPLN_HELM_RESOURCE_TAG_KEY];
}
if (deployedResourceCopy.tags) {
delete deployedResourceCopy.tags[constants_1.CPLN_HELM_RESOURCE_TAG_KEY];
}
// Comparison is only valid if both versions are identical.
// Otherwise, we apply the template because differences are likely intentional.
// Comparing is tricky since the data service adds fields (e.g., "description")
// that may not exist in the template or were unset by the user.
if (originalResource.version === deployedResource.version && (0, lodash_1.isEqual)(deployedResourceCopy, templateResourceCopy)) {
return false;
}
}
catch (e) {
// We don't really want to do anything here;
// If we are unable to compare, then let's just apply the template resource as is.
}
}
}
return true;
}
async awaitWorkloadsReadiness(appliedWorkloadLinks) {
if (this.args.wait && appliedWorkloadLinks.length > 0) {
this.session.out(`\nWaiting for workloads to be ready...`);
// Wait for workloads to be ready
await this.monitorWorkloadsReadiness(this.args.timeout, appliedWorkloadLinks);
this.session.out('\nAll workloads are ready.');
}
}
async monitorWorkloadsReadiness(timeoutSeconds, appliedWorkloadLinks) {
const promises = [];
// Accumulate promises in order to wait for them in parallel
for (const workloadLink of appliedWorkloadLinks) {
promises.push(this.monitorWorkloadReadiness(workloadLink));
}
let timeoutId;
const promisesToRace = [Promise.allSettled(promises)];
if (timeoutSeconds) {
const { timeoutPromise, timeoutId: _timeoutId } = (0, functions_1.timeoutAsync)(timeoutSeconds * 1000);
timeoutId = _timeoutId;
promisesToRace.push(timeoutPromise);
}
try {
// Wait for workloads to be ready in parallel
await Promise.race(promisesToRace);
}
catch (e) {
if (e instanceof errors_1.TimeoutError) {
this.session.abort({
message: `Timeout Error: Not all workloads were ready within ${timeoutSeconds} seconds.\nRelease ${this.release.name} has not been installed successfully!`,
});
}
// Handle any thrown error that occurred while monitoring a workload's readiness
this.session.abort({ error: e });
}
// Clear timeout if all workloads were ready
if (timeoutId) {
clearTimeout(timeoutId);
}
}
async monitorWorkloadReadiness(workloadLink) {
logger_1.logger.info(`Waiting for workload '${workloadLink}' to be ready...`);
await (0, functions_1.retryFn)(async () => {
try {
const workload = await this.client.get(workloadLink);
const deployments = await this.client.get(workloadLink + '/deployment');
const workloadHealth = (0, workload_1.getWorkloadHealth)(deployments.items, workload);
if (!workloadHealth.readyLatest) {
throw new Error(`Deployments are not ready yet.`);
}
}
catch (e) {
logger_1.logger.info(`Workload '${workloadLink}' is not ready yet, retrying. Error: ${e.message}`);
throw e;
}
}, {
onFailure() {
logger_1.logger.debug(`Timed out, the workload ${workloadLink} is still not ready.`);
},
repeatTimes: 9999,
});
}
setReleaseStatus(status, errorMessage) {
if (!this.release.info.description) {
let description = '';
switch (status) {
case 'deployed':
description = this.release.info.status == 'pending-install' ? 'Install complete' : 'Upgrade complete';
break;
case 'failed':
description = this.release.info.status == 'pending-install' ? 'Install failed' : 'Upgrade failed';
if (errorMessage) {
description = `${description}: ${errorMessage}`;
}
break;
}
this.release.info.description = description;
}
this.release.info.status = status;
this.release.info.lastDeployed = new Date().toISOString();
}
}
exports.HelmReleaseRevisionManager = HelmReleaseRevisionManager;
//# sourceMappingURL=helm-release-revision-manager.js.map