diff --git a/packages/cdktf-cli/bin/cmds/ui/models/terraform-cloud.ts b/packages/cdktf-cli/bin/cmds/ui/models/terraform-cloud.ts index 958aa22783..6f795f6e93 100644 --- a/packages/cdktf-cli/bin/cmds/ui/models/terraform-cloud.ts +++ b/packages/cdktf-cli/bin/cmds/ui/models/terraform-cloud.ts @@ -7,6 +7,7 @@ import { TerraformJsonConfigBackendRemote } from '../terraform-json' import * as TerraformCloudClient from '@skorfmann/terraform-cloud' import archiver from 'archiver'; import { WritableStreamBuffer } from 'stream-buffers'; +import { logger } from '../../../../lib/logging'; export class TerraformCloudPlan implements TerraformPlan { constructor(public readonly planFile: string, public readonly plan: { [key: string]: any }, public readonly url: string) { } @@ -145,6 +146,9 @@ export class TerraformCloud implements Terraform { if (!zipBuffer) throw new Error("Couldn't upload directory to Terraform Cloud"); await this.client.ConfigurationVersion.upload(version.attributes.uploadUrl, zipBuffer) + + // we might get an HTTP 422 error if we don't wait for the processing and try to run too early. see #647 + await this.waitForConfigurationVersionToBeReady(); } @BeautifyErrors @@ -279,4 +283,39 @@ export class TerraformCloud implements Terraform { throw e; } } + + private async isConfigurationVersionReady(): Promise { + if (!this.configurationVersionId) throw new Error('No ConfigurationVersionId known. Cannot wait for ConfigurationVersion to become ready'); + const configVersion = await this.client.ConfigurationVersion.show(this.configurationVersionId); + const { status } = configVersion.attributes; + switch (status) { + case 'uploaded': return true; + case 'errored': { + const { error, errorMessage } = configVersion.attributes; + logger.error(`ConfigurationVersion in Terraform Cloud has status "${status}". Returned config version was ${JSON.stringify(configVersion)}`); + throw new Error(`Terraform Cloud ConfigurationVersion ${this.configurationVersionId} has status "errored" ${JSON.stringify({ error, errorMessage })}`); + } + case 'pending': // fallthrough + default: + logger.debug(`ConfigurationVersion in Terraform Cloud not considered ready with status "${status}".`) + return false; + } + } + + private async waitForConfigurationVersionToBeReady(timeoutMs = 60_000): Promise { + logger.debug('waiting for Configuration Version to be ready in Terraform Cloud'); + let timeoutReached = false; + const ready = async () => { + while (!(await this.isConfigurationVersionReady() && !timeoutReached)) { + await wait(1000); + } + }; + const timeout = async () => { + await wait(timeoutMs); + timeoutReached = true; + throw new Error(`Waiting for Terraform Cloud ConfigurationVersion to become ready timed out (timeout was ${timeoutMs}ms)`); + }; + await Promise.race([ready(), timeout()]); + logger.debug('Configuration Version is ready in Terraform Cloud'); + } } diff --git a/packages/cdktf-cli/package.json b/packages/cdktf-cli/package.json index 86968ab783..dc7062b088 100644 --- a/packages/cdktf-cli/package.json +++ b/packages/cdktf-cli/package.json @@ -29,7 +29,7 @@ "dependencies": { "@cdktf/hcl2json": "0.0.0", "@skorfmann/ink-confirm-input": "^3.0.0", - "@skorfmann/terraform-cloud": "^1.9.1", + "@skorfmann/terraform-cloud": "^1.10.0", "@types/node": "^14.0.26", "archiver": "^5.1.0", "cdktf": "0.0.0", diff --git a/yarn.lock b/yarn.lock index 00cb595aea..ea302a618b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1406,6 +1406,14 @@ prop-types "^15.5.10" yn "^3.1.1" +"@skorfmann/terraform-cloud@^1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@skorfmann/terraform-cloud/-/terraform-cloud-1.10.0.tgz#cba669213dacf92aa1a80c8e8d112f0b5e422cc9" + integrity sha512-Yd5WWmmUjFYBpQpsAnAntwQMerilNRpHILNPA7x0EkwHhf+5KTSKYZwzYL/bOY1QfGJX6DTnOSHXIRStHZDUgg== + dependencies: + axios "^0.21.1" + camelcase-keys "^6.2.2" + "@skorfmann/terraform-cloud@^1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@skorfmann/terraform-cloud/-/terraform-cloud-1.9.1.tgz#6fbdf6846efd6fdeb3405126cc91cd7d5c846eff"