UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

641 lines 35.4 kB
"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