diff --git a/package-lock.json b/package-lock.json index 63b42b7d..532f7537 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,8 @@ "license": "MIT", "dependencies": { "@aws-sdk/client-cloudformation": "^3.451.0", - "@aws-sdk/client-cloudwatch-events": "^3.451.0", "@aws-sdk/client-ec2": "^3.451.0", + "@aws-sdk/client-eventbridge": "3.451.0", "@aws-sdk/client-iam": "^3.451.0", "@aws-sdk/client-organizations": "^3.451.0", "@aws-sdk/client-s3": "^3.451.0", @@ -272,10 +272,10 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-cloudwatch-events": { + "node_modules/@aws-sdk/client-cognito-identity": { "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-events/-/client-cloudwatch-events-3.451.0.tgz", - "integrity": "sha512-GsN5oFBGUHJZZfAndTPJnsbZ1vKBqSCourLTV030GjE/bkw8W7Cs+FlDAO2MZ2TzNn15NQHnBQJZ852tR3sBZQ==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.451.0.tgz", + "integrity": "sha512-xoImUiGoaXJZpOCgbWcdrU4vHJ8HG5KluaCkc32kuFobM277sjQimaUIHOGHL24M5vyo4QxcJD9CT/IhX63Vlg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -321,10 +321,10 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.451.0.tgz", - "integrity": "sha512-xoImUiGoaXJZpOCgbWcdrU4vHJ8HG5KluaCkc32kuFobM277sjQimaUIHOGHL24M5vyo4QxcJD9CT/IhX63Vlg==", + "node_modules/@aws-sdk/client-ec2": { + "version": "3.452.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ec2/-/client-ec2-3.452.0.tgz", + "integrity": "sha512-/Oy6bWkd7MnmbExOQaB4fZODCCammsKPPDUWIJrCR3uYkCBhV7MMjIdl7pY16EsCgxbd19NGEu3Adj8eoSEW7A==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -334,6 +334,7 @@ "@aws-sdk/middleware-host-header": "3.451.0", "@aws-sdk/middleware-logger": "3.451.0", "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-sdk-ec2": "3.451.0", "@aws-sdk/middleware-signing": "3.451.0", "@aws-sdk/middleware-user-agent": "3.451.0", "@aws-sdk/region-config-resolver": "3.451.0", @@ -364,16 +365,19 @@ "@smithy/util-endpoints": "^1.0.4", "@smithy/util-retry": "^2.0.6", "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" + "@smithy/util-waiter": "^2.0.13", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0", + "uuid": "^8.3.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-ec2": { - "version": "3.452.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ec2/-/client-ec2-3.452.0.tgz", - "integrity": "sha512-/Oy6bWkd7MnmbExOQaB4fZODCCammsKPPDUWIJrCR3uYkCBhV7MMjIdl7pY16EsCgxbd19NGEu3Adj8eoSEW7A==", + "node_modules/@aws-sdk/client-eventbridge": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-eventbridge/-/client-eventbridge-3.451.0.tgz", + "integrity": "sha512-v1AjQCMSCLIMK0r6VY7tALSfQEQVzSG+lUuaEID9PPgkBR5ff979EE1dg1shLj3P/wCBK0RfDi0pM20pVtftaQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -383,10 +387,10 @@ "@aws-sdk/middleware-host-header": "3.451.0", "@aws-sdk/middleware-logger": "3.451.0", "@aws-sdk/middleware-recursion-detection": "3.451.0", - "@aws-sdk/middleware-sdk-ec2": "3.451.0", "@aws-sdk/middleware-signing": "3.451.0", "@aws-sdk/middleware-user-agent": "3.451.0", "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/signature-v4-multi-region": "3.451.0", "@aws-sdk/types": "3.451.0", "@aws-sdk/util-endpoints": "3.451.0", "@aws-sdk/util-user-agent-browser": "3.451.0", @@ -414,10 +418,7 @@ "@smithy/util-endpoints": "^1.0.4", "@smithy/util-retry": "^2.0.6", "@smithy/util-utf8": "^2.0.2", - "@smithy/util-waiter": "^2.0.13", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" diff --git a/package.json b/package.json index d246e304..418fde53 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "license": "MIT", "dependencies": { "@aws-sdk/client-cloudformation": "^3.451.0", - "@aws-sdk/client-cloudwatch-events": "^3.451.0", "@aws-sdk/client-ec2": "^3.451.0", + "@aws-sdk/client-eventbridge": "3.451.0", "@aws-sdk/client-iam": "^3.451.0", "@aws-sdk/client-organizations": "^3.451.0", "@aws-sdk/client-s3": "^3.451.0", diff --git a/src/aws-provider/aws-events.ts b/src/aws-provider/aws-events.ts index 7e3632f6..3342c561 100644 --- a/src/aws-provider/aws-events.ts +++ b/src/aws-provider/aws-events.ts @@ -1,5 +1,6 @@ -import { PutEventsCommand, CloudWatchEventsClient } from '@aws-sdk/client-cloudwatch-events'; +import { PutEventsCommand } from '@aws-sdk/client-eventbridge'; import { ConsoleUtil } from '../util/console-util'; +import { AwsUtil } from '~util/aws-util'; const eventSource = 'oc.org-formation'; const eventDetailType = 'events.org-formation.com'; @@ -48,7 +49,7 @@ export class AwsEvents { } public static async PutEvent(command: PutEventsCommand): Promise { - const events = new CloudWatchEventsClient({region: 'us-east-1'}); + const events = AwsUtil.GetEventBridgeService(undefined, 'us-east-1'); await events.send(command); } } diff --git a/src/aws-provider/aws-organization-writer.ts b/src/aws-provider/aws-organization-writer.ts index c4136dbe..73a6f8f8 100644 --- a/src/aws-provider/aws-organization-writer.ts +++ b/src/aws-provider/aws-organization-writer.ts @@ -43,7 +43,7 @@ export class AwsOrganizationWriter { await org.send(enablePolicyTypeCommand); ConsoleUtil.LogDebug('enabled service control policies'); } catch (err) { - if (err && err.code === 'PolicyTypeAlreadyEnabledException') { + if (err && err.name === 'PolicyTypeAlreadyEnabledException') { // do nothing } else { throw err; @@ -66,7 +66,7 @@ export class AwsOrganizationWriter { ConsoleUtil.LogDebug(`SCP Created ${scpId}`); return scpId; } catch (err) { - if (err.code === 'DuplicatePolicyException') { + if (err.name === 'DuplicatePolicyException') { const existingPolicy: AWSPolicy = this.organization.policies.find(x => x.Name === resource.policyName); const scpId = existingPolicy!.Id; await this.updatePolicy(resource, scpId); @@ -91,7 +91,7 @@ export class AwsOrganizationWriter { await this.ensureSCPEnabled(); await org.send(attachPolicyCommand); } catch (err) { - if (err && err.code === 'PolicyTypeNotEnabledException') { + if (err && err.name === 'PolicyTypeNotEnabledException') { await this.ensureSCPEnabled(); await org.send(attachPolicyCommand); } else { @@ -99,7 +99,7 @@ export class AwsOrganizationWriter { } } } catch (err) { - if (err && err.code !== 'DuplicatePolicyAttachmentException') { + if (err && err.name !== 'DuplicatePolicyAttachmentException') { throw err; } } @@ -115,7 +115,7 @@ export class AwsOrganizationWriter { try { await this.organizationsService.send(detachPolicyCommand); } catch (err) { - if (err && err.code !== 'PolicyNotAttachedException' && err.code !== 'PolicyNotFoundException') { + if (err && err.name !== 'PolicyNotAttachedException' && err.name !== 'PolicyNotFoundException') { // 'ConcurrentModificationException' ?? throw err; } @@ -143,7 +143,7 @@ export class AwsOrganizationWriter { try { await this.organizationsService.send(deletePolicyCommand); } catch (err) { - if (err && err.code !== 'PolicyNotFoundException' && err.code !== 'PolicyInUseException') { + if (err && err.name !== 'PolicyNotFoundException' && err.name !== 'PolicyInUseException') { // 'ConcurrentModificationException' ?? throw err; } @@ -335,7 +335,7 @@ export class AwsOrganizationWriter { try { await this.updateAccount(resource, accountId); } catch (err) { - if (err.code === 'AccessDenied' && retryCountAccessDenied < 3) { + if (err.name === 'AccessDenied' && retryCountAccessDenied < 3) { shouldRetry = true; retryCountAccessDenied = retryCountAccessDenied + 1; await sleep(3000); @@ -374,7 +374,7 @@ export class AwsOrganizationWriter { await this.updateAccount(resource, result.AccountId); await partitionWriter.updateAccount(resource, result.GovCloudAccountId); } catch (err) { - if (err.code === 'AccessDenied' && retryCountAccessDenied < 3) { + if (err.name === 'AccessDenied' && retryCountAccessDenied < 3) { shouldRetry = true; retryCountAccessDenied = retryCountAccessDenied + 1; await sleep(3000); @@ -413,7 +413,7 @@ export class AwsOrganizationWriter { }); await iam.send(deleteAccountAliasCommand); } catch (err) { - if (err && err.code !== 'NoSuchEntity') { + if (err && err.name !== 'NoSuchEntity') { throw err; } } @@ -430,7 +430,7 @@ export class AwsOrganizationWriter { if (current.AccountAliases.find(x => x === alias)) { return; } - if (err && err.code === 'EntityAlreadyExists') { + if (err && err.name === 'EntityAlreadyExists') { throw new OrgFormationError(`The account alias ${alias} already exists. Most likely someone else already registered this alias to some other account.`); } } @@ -484,7 +484,7 @@ export class AwsOrganizationWriter { try { await iam.send(new IAM.DeleteAccountPasswordPolicyCommand({})); } catch (err) { - if (err && err.code !== 'NoSuchEntity') { + if (err && err.name !== 'NoSuchEntity') { throw err; } } diff --git a/src/aws-provider/util.ts b/src/aws-provider/util.ts index c43de898..1a02f5c9 100644 --- a/src/aws-provider/util.ts +++ b/src/aws-provider/util.ts @@ -8,11 +8,11 @@ export const performAndRetryIfNeeded = async (fn: () => Promi try { return await fn(); } catch (err) { - if (err && (err.code === 'ConcurrentModificationException' || err.code === 'TooManyRequestsException') && retryCount < 30) { + if (err && (err.name === 'ConcurrentModificationException' || err.name === 'TooManyRequestsException') && retryCount < 30) { retryCount = retryCount + 1; shouldRetry = true; const wait = retryCount + (0.5 * Math.random()); - ConsoleUtil.LogDebug(`received retryable error ${err.code}. wait ${wait} and retry-count ${retryCount}`); + ConsoleUtil.LogDebug(`received retryable error ${err.name}. wait ${wait} and retry-count ${retryCount}`); await sleep(wait * 1000); continue; } diff --git a/src/cfn-binder/cfn-task-provider.ts b/src/cfn-binder/cfn-task-provider.ts index a5c291b0..69c58e93 100644 --- a/src/cfn-binder/cfn-task-provider.ts +++ b/src/cfn-binder/cfn-task-provider.ts @@ -167,7 +167,7 @@ export class CfnTaskProvider { customViaRoleArn: binding.customViaRoleArn, }); } catch (err) { - if (err.code !== 'OptInRequired') { + if (err.name !== 'OptInRequired') { ConsoleUtil.LogError(`error updating CloudFormation stack ${stackName} in account ${binding.accountId} (${binding.region}). \n${err.message}`); } try { @@ -269,11 +269,11 @@ export const performAndRetryIfNeeded = async (fn: () => Promi try { return await fn(); } catch (err) { - if (err && (err.code === 'ThrottlingException') && retryCount < 10) { + if (err && (err.name === 'ThrottlingException') && retryCount < 10) { retryCount = retryCount + 1; shouldRetry = true; const wait = retryCount + (0.5 * Math.random()); - ConsoleUtil.LogDebug(`received retryable error ${err.code}. wait ${wait} and retry-count ${retryCount}`); + ConsoleUtil.LogDebug(`received retryable error ${err.name}. wait ${wait} and retry-count ${retryCount}`); await sleep(wait * 1000); continue; } diff --git a/src/cfn-binder/cfn-validate-task-provider.ts b/src/cfn-binder/cfn-validate-task-provider.ts index 6aab34e1..17cdf9d7 100644 --- a/src/cfn-binder/cfn-validate-task-provider.ts +++ b/src/cfn-binder/cfn-validate-task-provider.ts @@ -80,7 +80,7 @@ export class CfnValidateTaskProvider { throw new OrgFormationError(`template expects parameter(s) ${missingParameters.join(', ')} which have not been provided`); } } catch (err) { - if (err.code === 'AccessDenied') { + if (err.name === 'AccessDenied') { ConsoleUtil.LogWarning(`access denied when running validate stack: ${err}`); return; } diff --git a/src/commands/base-command.ts b/src/commands/base-command.ts index f3985dc4..87d6ad53 100644 --- a/src/commands/base-command.ts +++ b/src/commands/base-command.ts @@ -118,7 +118,7 @@ export abstract class BaseCliCommand { command.state = state; return state; } catch (err) { - if (err && err.code === 'NoSuchBucket') { + if (err && err.name === 'NoSuchBucket') { throw new OrgFormationError(`unable to load previously committed state, reason: bucket '${storageProvider.bucketName}' does not exist in current account.`); } throw err; @@ -133,8 +133,8 @@ export abstract class BaseCliCommand { if (err instanceof OrgFormationError) { ConsoleUtil.LogError(err.message); } else { - if (err.code && err.requestId) { - ConsoleUtil.LogError(`error: ${err.code}, aws-request-id: ${err.requestId}`); + if (err.name && err.requestId) { + ConsoleUtil.LogError(`error: ${err.name}, aws-request-id: ${err.requestId}`); ConsoleUtil.LogError(err.message); } else { @@ -202,7 +202,7 @@ export abstract class BaseCliCommand { try { await storageProvider.create(region); } catch (err) { - if (err && err.code === 'BucketAlreadyOwnedByYou') { + if (err && err.name === 'BucketAlreadyOwnedByYou') { return storageProvider; } throw err; diff --git a/src/core/generic-task-runner.ts b/src/core/generic-task-runner.ts index 8cd573a4..7af2f72e 100644 --- a/src/core/generic-task-runner.ts +++ b/src/core/generic-task-runner.ts @@ -172,7 +172,7 @@ export class GenericTaskRunner { task.running = false; ConsoleUtil.LogInfo(`${delegate.getName(task)} ${delegate.getVerb(task)} successful.`); } catch (err) { - if ((err.code === 'Throttling' || err.code === 'OptInRequired') && retryAttemptRateLimited < 5) { + if ((err.name === 'Throttling' || err.name === 'OptInRequired') && retryAttemptRateLimited < 5) { retryWhenRateLimited = true; retryAttemptRateLimited = retryAttemptRateLimited + 1; await sleep(Math.pow(retryAttemptRateLimited, 2) + Math.random()); diff --git a/src/state/storage-provider.ts b/src/state/storage-provider.ts index 8a077e46..9c260b88 100644 --- a/src/state/storage-provider.ts +++ b/src/state/storage-provider.ts @@ -51,7 +51,7 @@ export class S3StorageProvider implements IStorageProvider { const s3client = AwsUtil.GetS3Service(undefined, region); try { - await s3client.send(new S3.CreateBucketCommand(request)); + await s3client.send(new S3.CreateBucketCommand(request)); await s3client.send(new S3.PutPublicAccessBlockCommand({ Bucket: this.bucketName, PublicAccessBlockConfiguration: { BlockPublicAcls: true, @@ -66,13 +66,13 @@ export class S3StorageProvider implements IStorageProvider { }, })); } catch (err) { - if (err && err.code === 'IllegalLocationConstraintException') { + if (err && err.name === 'IllegalLocationConstraintException') { throw new OrgFormationError(`Unable to create bucket in region ${region}. Is the region spelled correctly?\nIf a bucket with the same name was recently deleted from a different region it could take up to a couple of hours for you to be able to create the same bucket in a different region.`); } - if (err && err.code === 'BucketAlreadyOwnedByYou') { + if (err && err.name === 'BucketAlreadyOwnedByYou') { return; } - if (err && !throwOnAccessDenied && err.code === 'AccessDenied') { + if (err && !throwOnAccessDenied && err.name === 'AccessDenied') { return; // assume bucket has been set up properly } throw err; @@ -98,16 +98,16 @@ export class S3StorageProvider implements IStorageProvider { try { const response = await s3client.send(new S3.GetObjectCommand(request)); if (!response.Body) { return undefined; } - const contents = response.Body.transformToString(); + const contents = await response.Body.transformToString('utf-8'); return contents; } catch (err) { - if (err && err.code === 'NoSuchKey') { + if (err && err.name === 'NoSuchKey') { return undefined; } - if (err && err.code === 'NoSuchBucket') { + if (err && err.name === 'NoSuchBucket') { return undefined; } - throw err; + throw err; } } diff --git a/src/util/aws-util.ts b/src/util/aws-util.ts index 1a8ecba4..8669461b 100644 --- a/src/util/aws-util.ts +++ b/src/util/aws-util.ts @@ -12,6 +12,7 @@ import { CreateBucketCommand, DeleteObjectCommand, PutBucketEncryptionCommand, P import { CloudFormationClient, CreateStackCommandInput, UpdateStackCommandInput, ValidateTemplateCommandInput, DescribeStacksCommandOutput, UpdateStackCommand, waitUntilStackUpdateComplete, DescribeStacksCommand, CreateStackCommand, waitUntilStackCreateComplete, DeleteStackCommand, waitUntilStackDeleteComplete, paginateListExports, CloudFormationServiceException, CloudFormationClientConfig } from '@aws-sdk/client-cloudformation'; import { defaultProvider } from '@aws-sdk/credential-provider-node'; import { chain } from '@smithy/property-provider'; +import { EventBridgeClient, EventBridgeClientConfig } from '@aws-sdk/client-eventbridge'; import { OrgFormationError } from '../org-formation-error'; import { ClientCredentialsConfig } from './aws-types'; import { ConsoleUtil } from './console-util'; @@ -61,6 +62,7 @@ export class AwsUtil { AwsUtil.SupportServiceCache = {}; AwsUtil.S3ServiceCache = {}; AwsUtil.EC2ServiceCache = {}; + AwsUtil.EventBridgeServiceCache = {}; } private static organization: DescribeOrganizationCommandOutput; @@ -410,6 +412,29 @@ export class AwsUtil { return this.CfnServiceCache[cacheKey]; } + public static GetEventBridgeService(accountId: string, region: string, roleInTargetAccount?: string, viaRoleArn?: string, isPartition?: boolean): CloudFormationClient { + AwsUtil.throwIfNowInitiazized(); + const { cacheKey, provider } = AwsUtil.GetCredentialProviderWithRoleAssumptions({ + accountId, + region, + roleInTargetAccount, + viaRoleArn, + isPartition, + }); + const config: EventBridgeClientConfig = { + region: (isPartition) ? this.partitionRegion : region ?? AwsUtil.GetDefaultRegion(), + credentials: provider, + defaultsMode: 'standard', + retryMode: 'standard', + maxAttempts: 6, + }; + if (this.EventBridgeServiceCache[cacheKey]) { + return this.EventBridgeServiceCache[cacheKey]; + } + this.EventBridgeServiceCache[cacheKey] = new EventBridgeClient(config); + return this.EventBridgeServiceCache[cacheKey]; + } + /** * we don't assume a role if we are running the master account AND the roleInTarget account is OrganizationAccessRoleName */ @@ -570,6 +595,7 @@ export class AwsUtil { private static CfnServiceCache: Record = {}; private static S3ServiceCache: Record = {}; private static EC2ServiceCache: Record = {}; + private static EventBridgeServiceCache: Record = {}; private static CfnExportsCache: Record = {}; private static isPartition = false; private static initialized = false; @@ -661,7 +687,7 @@ export class CfnUtil { }) ); } catch (err) { - if (err && (err.code !== 'BucketAlreadyOwnedByYou')) { + if (err && (err.name !== 'BucketAlreadyOwnedByYou')) { throw new OrgFormationError(`unable to create bucket ${bucketName} in account ${binding.accountId}, error: ${err}`); } } @@ -728,14 +754,14 @@ export class CfnUtil { } } catch (err) { // ConsoleUtil.LogError(`ADDITIONAL ${updateStackInput.StackName}: ${inspect(err)}`); - if (err && (err.code === 'OptInRequired' || err.code === 'InvalidClientTokenId')) { + if (err && (err.name === 'OptInRequired' || err.name === 'InvalidClientTokenId')) { if (retryAccountIsBeingInitializedCount >= 20) { // 20 * 30 sec = 10 minutes throw new OrgFormationError('Account seems stuck initializing.'); } retryAccountIsBeingInitializedCount += 1; await sleep(26 + (4 * Math.random())); retryAccountIsBeingInitialized = true; - } else if (err && (err.code === 'ValidationError' && err.message) || (err.code === 'ResourceNotReady')) { + } else if (err && (err.name === 'ValidationError' && err.message) || (err.name === 'ResourceNotReady')) { const message = err.message as string; if (message.includes('ROLLBACK_COMPLETE') || message.includes('DELETE_FAILED')) { // await deleteStack({ StackName: updateStackInput.StackName, RoleARN: updateStackInput.RoleARN }).promise(); @@ -782,7 +808,7 @@ export class CfnUtil { (-1 !== message.indexOf('is in CREATE_COMPLETE_CLEANUP_IN_PROGRESS state and can not be updated.')) || (-1 !== message.indexOf('is in CREATE_IN_PROGRESS state and can not be updated.')) || (-1 !== message.indexOf('is in DELETE_IN_PROGRESS state and can not be updated.')) || - (err.code === 'ResourceNotReady' && err.originalError?.code === 'Throttling')) { + (err.name === 'ResourceNotReady' && err.originalError?.code === 'Throttling')) { if (retryStackIsBeingUpdatedCount >= 20) { // 20 * 30 sec = 10 minutes throw new OrgFormationError(`Stack ${updateStackInput.StackName} seems stuck in UPDATE_IN_PROGRESS (or similar) state.`); } diff --git a/test/integration-tests/base-integration-test.ts b/test/integration-tests/base-integration-test.ts index cb2cd043..0dcb8f23 100644 --- a/test/integration-tests/base-integration-test.ts +++ b/test/integration-tests/base-integration-test.ts @@ -40,7 +40,7 @@ export const baseBeforeAll = async (profileName: string = profileForIntegrationT const stackName = `a${Math.floor(Math.random() * 10000)}`; const command = { stateBucketName, stateObject: 'state.json', logicalName: 'default', stackName, profile: profileForIntegrationTests, verbose: true, maxConcurrentStacks: 10, failedStacksTolerance: 0, maxConcurrentTasks: 10, failedTasksTolerance: 0 } as any; - const s3ClientThatDoesntAssumeRole = new S3Client({ credentials: credentialsChain, region: 'eu-west-1' }); + const s3ClientThatDoesntAssumeRole = new S3Client({ credentials: credentialsChain, region: 'eu-west-1', followRegionRedirects: true }); const cfnClientThatDoesntAssumeRole = new CloudFormationClient({ credentials: credentialsChain, region: 'eu-west-1' }) return { diff --git a/test/integration-tests/scenario-no-bucket.test.ts b/test/integration-tests/scenario-no-bucket.test.ts index 16a1cac9..112e10f4 100644 --- a/test/integration-tests/scenario-no-bucket.test.ts +++ b/test/integration-tests/scenario-no-bucket.test.ts @@ -31,7 +31,8 @@ describe('when calling org-formation perform tasks', () => { const obj = await context.s3client.send(new GetObjectCommand({ Bucket: context.stateBucketName, Key: 'state.json'})); expect(obj).toBeDefined(); expect(obj.Body).toBeDefined(); - const object = JSON.parse(obj.Body.toString()) as IState; + const objectString = await obj.Body.transformToString('utf-8') + const object = JSON.parse(objectString) as IState; expect(object.masterAccountId).toBe('102625093955'); expect(object.stacks["integration-test-my-role"]).toBeDefined(); expect(object.stacks["integration-test-my-role"]["340381375986"]).toBeDefined();