From f6064471bba7f745da52521eea41ef603606294e Mon Sep 17 00:00:00 2001 From: iliapolo Date: Sat, 31 Aug 2024 17:39:14 +0300 Subject: [PATCH 1/5] mid work --- .projen/deps.json | 16 ++++++------- .projen/tasks.json | 4 ++-- .projenrc.ts | 4 ++-- lib/aws.ts | 29 +++++++++++++++++------- lib/private/handlers/container-images.ts | 8 +++++-- package.json | 4 ++-- 6 files changed, 41 insertions(+), 24 deletions(-) diff --git a/.projen/deps.json b/.projen/deps.json index 9d04a7f..2b38d7d 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -1,13 +1,5 @@ { "dependencies": [ - { - "name": "@smithy/config-resolver", - "type": "build" - }, - { - "name": "@smithy/node-config-provider", - "type": "build" - }, { "name": "@smithy/types", "type": "build" @@ -159,6 +151,14 @@ "name": "@aws-sdk/lib-storage", "type": "runtime" }, + { + "name": "@smithy/config-resolver", + "type": "runtime" + }, + { + "name": "@smithy/node-config-provider", + "type": "runtime" + }, { "name": "archiver", "type": "runtime" diff --git a/.projen/tasks.json b/.projen/tasks.json index 228f63e..5c9f46c 100644 --- a/.projen/tasks.json +++ b/.projen/tasks.json @@ -271,13 +271,13 @@ }, "steps": [ { - "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --dep=dev,peer,prod,optional --filter=@smithy/config-resolver,@smithy/node-config-provider,@smithy/types,@types/archiver,@types/glob,@types/jest,@types/mime,@types/yargs,aws-sdk-client-mock,aws-sdk-client-mock-jest,eslint-config-prettier,eslint-import-resolver-typescript,eslint-plugin-import,eslint-plugin-prettier,fs-extra,graceful-fs,jest,jszip,prettier,projen,ts-jest,ts-node,typescript,@aws-cdk/cloud-assembly-schema,@aws-cdk/cx-api,@aws-sdk/client-ecr,@aws-sdk/client-s3,@aws-sdk/client-secrets-manager,@aws-sdk/client-sts,@aws-sdk/credential-providers,@aws-sdk/lib-storage,archiver,glob,mime,yargs" + "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --dep=dev,peer,prod,optional --filter=@smithy/types,@types/archiver,@types/glob,@types/jest,@types/mime,@types/yargs,aws-sdk-client-mock,aws-sdk-client-mock-jest,eslint-config-prettier,eslint-import-resolver-typescript,eslint-plugin-import,eslint-plugin-prettier,fs-extra,graceful-fs,jest,jszip,prettier,projen,ts-jest,ts-node,typescript,@aws-cdk/cloud-assembly-schema,@aws-cdk/cx-api,@aws-sdk/client-ecr,@aws-sdk/client-s3,@aws-sdk/client-secrets-manager,@aws-sdk/client-sts,@aws-sdk/credential-providers,@aws-sdk/lib-storage,@smithy/config-resolver,@smithy/node-config-provider,archiver,glob,mime,yargs" }, { "exec": "yarn install --check-files" }, { - "exec": "yarn upgrade @smithy/config-resolver @smithy/node-config-provider @smithy/types @types/archiver @types/glob @types/jest @types/mime @types/node @types/yargs @typescript-eslint/eslint-plugin @typescript-eslint/parser aws-sdk-client-mock aws-sdk-client-mock-jest commit-and-tag-version constructs eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint fs-extra graceful-fs jest jest-junit jszip prettier projen ts-jest ts-node typescript @aws-cdk/cloud-assembly-schema @aws-cdk/cx-api @aws-sdk/client-ecr @aws-sdk/client-s3 @aws-sdk/client-secrets-manager @aws-sdk/client-sts @aws-sdk/credential-providers @aws-sdk/lib-storage archiver glob mime yargs" + "exec": "yarn upgrade @smithy/types @types/archiver @types/glob @types/jest @types/mime @types/node @types/yargs @typescript-eslint/eslint-plugin @typescript-eslint/parser aws-sdk-client-mock aws-sdk-client-mock-jest commit-and-tag-version constructs eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint fs-extra graceful-fs jest jest-junit jszip prettier projen ts-jest ts-node typescript @aws-cdk/cloud-assembly-schema @aws-cdk/cx-api @aws-sdk/client-ecr @aws-sdk/client-s3 @aws-sdk/client-secrets-manager @aws-sdk/client-sts @aws-sdk/credential-providers @aws-sdk/lib-storage @smithy/config-resolver @smithy/node-config-provider archiver glob mime yargs" }, { "exec": "npx projen" diff --git a/.projenrc.ts b/.projenrc.ts index 7ee3532..adc2d3b 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -35,14 +35,14 @@ const project = new typescript.TypeScriptProject({ '@aws-sdk/client-sts', '@aws-sdk/credential-providers', '@aws-sdk/lib-storage', + '@smithy/config-resolver', + '@smithy/node-config-provider', 'glob', 'mime', 'yargs', ], description: 'CDK Asset Publishing Tool', devDeps: [ - '@smithy/config-resolver', - '@smithy/node-config-provider', '@smithy/types', '@types/archiver', '@types/glob', diff --git a/lib/aws.ts b/lib/aws.ts index b2ccadc..b09fcc1 100644 --- a/lib/aws.ts +++ b/lib/aws.ts @@ -1,13 +1,20 @@ import * as os from 'os'; import { ECRClient } from '@aws-sdk/client-ecr'; -import { CompleteMultipartUploadCommandOutput, PutObjectCommandInput, S3Client } from '@aws-sdk/client-s3'; +import { + CompleteMultipartUploadCommandOutput, + PutObjectCommandInput, + S3Client, +} from '@aws-sdk/client-s3'; import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; import { GetCallerIdentityCommand, STSClient, STSClientConfig } from '@aws-sdk/client-sts'; import { fromNodeProviderChain, fromTemporaryCredentials } from '@aws-sdk/credential-providers'; import { Upload } from '@aws-sdk/lib-storage'; -import { NODE_REGION_CONFIG_FILE_OPTIONS, NODE_REGION_CONFIG_OPTIONS } from '@smithy/config-resolver'; +import { + NODE_REGION_CONFIG_FILE_OPTIONS, + NODE_REGION_CONFIG_OPTIONS, +} from '@smithy/config-resolver'; import { loadConfig } from '@smithy/node-config-provider'; -import { AwsCredentialIdentityProvider } from '@smithy/types'; +import type { AwsCredentialIdentityProvider } from '@smithy/types'; /** * AWS SDK operations required by Asset Publishing @@ -21,7 +28,10 @@ export interface IAws { s3Client(options: ClientOptions): Promise; ecrClient(options: ClientOptions): Promise; secretsManagerClient(options: ClientOptions): Promise; - upload(params: PutObjectCommandInput, options?: ClientOptions): Promise; + upload( + params: PutObjectCommandInput, + options?: ClientOptions + ): Promise; } export interface ClientOptions { @@ -68,7 +78,7 @@ export class DefaultAwsClient implements IAws { process.env.AWS_PROFILE = profile; const clientConfig: STSClientConfig = { customUserAgent: USER_AGENT, - } + }; this.config = { clientConfig, credentials: fromNodeProviderChain({ @@ -82,19 +92,22 @@ export class DefaultAwsClient implements IAws { return new S3Client(await this.awsOptions(options)); } - public async upload(params: PutObjectCommandInput, options: ClientOptions = {}): Promise { + public async upload( + params: PutObjectCommandInput, + options: ClientOptions = {} + ): Promise { try { const upload = new Upload({ client: await this.s3Client(options), params, }); - return upload.done(); + return await upload.done(); } catch (e) { // TODO: add something more useful here console.log(e); throw e; - } + } } public async ecrClient(options: ClientOptions): Promise { diff --git a/lib/private/handlers/container-images.ts b/lib/private/handlers/container-images.ts index ea349b0..a155e6e 100644 --- a/lib/private/handlers/container-images.ts +++ b/lib/private/handlers/container-images.ts @@ -1,6 +1,10 @@ import * as path from 'path'; import { DockerImageDestination } from '@aws-cdk/cloud-assembly-schema'; -import { DescribeImagesCommand, DescribeRepositoriesCommand, type ECRClient } from '@aws-sdk/client-ecr'; +import { + DescribeImagesCommand, + DescribeRepositoriesCommand, + type ECRClient, +} from '@aws-sdk/client-ecr'; import { DockerImageManifestEntry } from '../../asset-manifest'; import { EventType } from '../../progress'; import { IAssetHandler, IHandlerHost, IHandlerOptions } from '../asset-handler'; @@ -274,7 +278,7 @@ async function imageExists(ecr: ECRClient, repositoryName: string, imageTag: str async function repositoryUri(ecr: ECRClient, repositoryName: string): Promise { try { const command = new DescribeRepositoriesCommand({ - repositoryNames: [ repositoryName ], + repositoryNames: [repositoryName], }); const response = await ecr.send(command); diff --git a/package.json b/package.json index 07c81fb..35bf825 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,6 @@ "projen": "npx projen" }, "devDependencies": { - "@smithy/config-resolver": "^3.0.5", - "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", "@types/archiver": "^5.3.4", "@types/glob": "^7.2.0", @@ -72,6 +70,8 @@ "@aws-sdk/client-sts": "^3.637.0", "@aws-sdk/credential-providers": "^3.637.0", "@aws-sdk/lib-storage": "^3.637.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/node-config-provider": "^3.1.4", "archiver": "^5.3.2", "glob": "^7.2.3", "mime": "^2.6.0", From ba5c48b161d480bf926d1555cf825eea9a09fe68 Mon Sep 17 00:00:00 2001 From: iliapolo Date: Sat, 31 Aug 2024 18:02:48 +0300 Subject: [PATCH 2/5] mid work --- lib/aws.ts | 27 +++++++++++++++-- lib/private/handlers/container-images.ts | 5 +++- lib/private/handlers/files.ts | 38 +++++++++++++++++++----- test/docker-images.test.ts | 8 +++++ test/files.test.ts | 4 +-- 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/lib/aws.ts b/lib/aws.ts index b09fcc1..0f18424 100644 --- a/lib/aws.ts +++ b/lib/aws.ts @@ -6,7 +6,12 @@ import { S3Client, } from '@aws-sdk/client-s3'; import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; -import { GetCallerIdentityCommand, STSClient, STSClientConfig } from '@aws-sdk/client-sts'; +import { + GetCallerIdentityCommand, + STSClient, + STSClientConfig, + AssumeRoleRequest, +} from '@aws-sdk/client-sts'; import { fromNodeProviderChain, fromTemporaryCredentials } from '@aws-sdk/credential-providers'; import { Upload } from '@aws-sdk/lib-storage'; import { @@ -16,6 +21,12 @@ import { import { loadConfig } from '@smithy/node-config-provider'; import type { AwsCredentialIdentityProvider } from '@smithy/types'; +// Partial because `RoleSessionName` is required in STS, but we have a default value for it. +export type AssumeRoleAdditionalOptions = Partial< + // cloud-assembly-schema validates that `ExternalId` and `RoleArn` are not configured + Omit +>; + /** * AWS SDK operations required by Asset Publishing */ @@ -38,6 +49,7 @@ export interface ClientOptions { region?: string; assumeRoleArn?: string; assumeRoleExternalId?: string; + assumeRoleAdditionalOptions?: AssumeRoleAdditionalOptions; quiet?: boolean; } @@ -134,7 +146,7 @@ export class DefaultAwsClient implements IAws { } public async discoverTargetAccount(options: ClientOptions): Promise { - return this.getAccount(await this.awsOptions(options)); + return this.getAccount(options); } private async getAccount(options?: ClientOptions): Promise { @@ -158,11 +170,22 @@ export class DefaultAwsClient implements IAws { if (options) { config.region = options.region; if (options.assumeRoleArn) { + if ( + options.assumeRoleAdditionalOptions?.Tags && + options.assumeRoleAdditionalOptions.Tags.length > 0 && + !options.assumeRoleAdditionalOptions.TransitiveTagKeys + ) { + options.assumeRoleAdditionalOptions.TransitiveTagKeys = + // for some reason t.Key is marked as optional in the SDK .d.ts + // so we have to "!". + options.assumeRoleAdditionalOptions.Tags.map((t) => t.Key!); + } config.credentials = fromTemporaryCredentials({ params: { RoleArn: options.assumeRoleArn, ExternalId: options.assumeRoleExternalId, RoleSessionName: `${USER_AGENT}-${safeUsername()}`, + ...(options.assumeRoleAdditionalOptions ?? {}), }, clientConfig: this.config.clientConfig, }); diff --git a/lib/private/handlers/container-images.ts b/lib/private/handlers/container-images.ts index a155e6e..50f589d 100644 --- a/lib/private/handlers/container-images.ts +++ b/lib/private/handlers/container-images.ts @@ -109,7 +109,10 @@ export class ContainerImageAssetHandler implements IAssetHandler { const destination = await replaceAwsPlaceholders(this.asset.destination, this.host.aws); const ecr = await this.host.aws.ecrClient({ - ...destination, + assumeRoleExternalId: destination.assumeRoleExternalId, + assumeRoleArn: destination.assumeRoleArn, + assumeRoleAdditionalOptions: destination.assumeRoleAdditionalOptions, + region: destination.region, quiet: options.quiet, }); const account = async () => (await this.host.aws.discoverCurrentAccount())?.accountId; diff --git a/lib/private/handlers/files.ts b/lib/private/handlers/files.ts index 78c1237..e657e20 100644 --- a/lib/private/handlers/files.ts +++ b/lib/private/handlers/files.ts @@ -1,6 +1,12 @@ import { createReadStream, promises as fs } from 'fs'; import * as path from 'path'; import { FileAssetPackaging, FileSource } from '@aws-cdk/cloud-assembly-schema'; +import { + GetBucketEncryptionCommand, + GetBucketLocationCommand, + ListObjectsV2Command, + S3Client, +} from '@aws-sdk/client-s3'; import * as mime from 'mime'; import { FileManifestEntry } from '../../asset-manifest'; import { EventType } from '../../progress'; @@ -9,7 +15,6 @@ import { IAssetHandler, IHandlerHost } from '../asset-handler'; import { pathExists } from '../fs-extra'; import { replaceAwsPlaceholders } from '../placeholders'; import { shell } from '../shell'; -import { GetBucketEncryptionCommand, GetBucketLocationCommand, ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'; /** * The size of an empty zip file is 22 bytes @@ -36,7 +41,10 @@ export class FileAssetHandler implements IAssetHandler { const s3Url = `s3://${destination.bucketName}/${destination.objectKey}`; try { const s3 = await this.host.aws.s3Client({ - ...destination, + region: destination.region, + assumeRoleArn: destination.assumeRoleArn, + assumeRoleExternalId: destination.assumeRoleExternalId, + assumeRoleAdditionalOptions: destination.assumeRoleAdditionalOptions, quiet: true, }); this.host.emitMessage(EventType.CHECK, `Check ${s3Url}`); @@ -54,14 +62,27 @@ export class FileAssetHandler implements IAssetHandler { public async publish(): Promise { const destination = await replaceAwsPlaceholders(this.asset.destination, this.host.aws); const s3Url = `s3://${destination.bucketName}/${destination.objectKey}`; - const s3 = await this.host.aws.s3Client(destination); + const s3 = await this.host.aws.s3Client({ + region: destination.region, + assumeRoleArn: destination.assumeRoleArn, + assumeRoleExternalId: destination.assumeRoleExternalId, + assumeRoleAdditionalOptions: destination.assumeRoleAdditionalOptions, + }); this.host.emitMessage(EventType.CHECK, `Check ${s3Url}`); const bucketInfo = BucketInformation.for(this.host); // A thunk for describing the current account. Used when we need to format an error // message, not in the success case. - const account = async () => (await this.host.aws.discoverTargetAccount(destination))?.accountId; + const account = async () => + ( + await this.host.aws.discoverTargetAccount({ + region: destination.region, + assumeRoleArn: destination.assumeRoleArn, + assumeRoleExternalId: destination.assumeRoleExternalId, + assumeRoleAdditionalOptions: destination.assumeRoleAdditionalOptions, + }) + )?.accountId; switch (await bucketInfo.bucketOwnership(s3, destination.bucketName)) { case BucketOwnership.MINE: break; @@ -203,7 +224,7 @@ async function objectExists(s3: S3Client, bucket: string, key: string) { * never retry building those assets without users having to manually clear * their bucket, which is a bad experience. */ - const command = new ListObjectsV2Command({ + const command = new ListObjectsV2Command({ Bucket: bucket, Prefix: key, MaxKeys: 1, @@ -270,7 +291,7 @@ class BucketInformation { private async _bucketOwnership(s3: S3Client, bucket: string): Promise { try { - const command = new GetBucketLocationCommand({ + const command = new GetBucketLocationCommand({ Bucket: bucket, }); await s3.send(command); @@ -293,8 +314,9 @@ class BucketInformation { const l = encryption?.ServerSideEncryptionConfiguration?.Rules?.length ?? 0; if (l > 0) { const apply = - encryption?.ServerSideEncryptionConfiguration?.Rules?.at(0) - ?.ApplyServerSideEncryptionByDefault; + encryption?.ServerSideEncryptionConfiguration?.Rules?.at( + 0 + )?.ApplyServerSideEncryptionByDefault; let ssealgo = apply?.SSEAlgorithm; if (ssealgo === 'AES256') return { type: 'aes256' }; if (ssealgo === 'aws:kms') return { type: 'kms', kmsKeyId: apply?.KMSMasterKeyID }; diff --git a/test/docker-images.test.ts b/test/docker-images.test.ts index 4872705..a15cf46 100644 --- a/test/docker-images.test.ts +++ b/test/docker-images.test.ts @@ -40,6 +40,10 @@ beforeEach(() => { theDestination: { region: 'us-north-50', assumeRoleArn: 'arn:aws:role', + assumeRoleExternalId: 'external-id', + assumeRoleAdditionalOptions: { + Tags: [{ Key: 'Departement', Value: 'Engineering' }], + }, repositoryName: 'repo', imageTag: 'abcdef', }, @@ -362,6 +366,10 @@ test('pass destination properties to AWS client', async () => { imageTag: 'abcdef', region: 'us-north-50', assumeRoleArn: 'arn:aws:role', + assumeRoleExternalId: 'external-id', + assumeRoleAdditionalOptions: { + Tags: [{ Key: 'Departement', Value: 'Engineering' }], + }, repositoryName: 'repo', }); }); diff --git a/test/files.test.ts b/test/files.test.ts index cf3b30c..ee18821 100644 --- a/test/files.test.ts +++ b/test/files.test.ts @@ -2,7 +2,7 @@ jest.mock('child_process'); import 'aws-sdk-client-mock-jest'; -import { Manifest } from '@aws-cdk/cloud-assembly-schema'; +import { FileDestination, Manifest } from '@aws-cdk/cloud-assembly-schema'; import { GetBucketEncryptionCommand, ListObjectsV2Command } from '@aws-sdk/client-s3'; import { FakeListener } from './fake-listener'; import { MockAws, mockS3 } from './mock-aws'; @@ -12,7 +12,7 @@ import { AssetPublishing, AssetManifest, IAws } from '../lib'; const ABS_PATH = '/simple/cdk.out/some_external_file'; -const DEFAULT_DESTINATION = { +const DEFAULT_DESTINATION: FileDestination = { region: 'us-north-50', assumeRoleArn: 'arn:aws:role', bucketName: 'some_bucket', From be13b276b1689069b6dc24cab3ea7f482ee535ec Mon Sep 17 00:00:00 2001 From: iliapolo Date: Sat, 31 Aug 2024 18:09:48 +0300 Subject: [PATCH 3/5] mid work --- test/docker-images.test.ts | 8 ++++---- test/placeholders.test.ts | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/test/docker-images.test.ts b/test/docker-images.test.ts index a15cf46..59d9e8d 100644 --- a/test/docker-images.test.ts +++ b/test/docker-images.test.ts @@ -363,14 +363,13 @@ test('pass destination properties to AWS client', async () => { await pub.publish(); expect(ecrClient).toHaveBeenCalledWith({ - imageTag: 'abcdef', region: 'us-north-50', assumeRoleArn: 'arn:aws:role', assumeRoleExternalId: 'external-id', assumeRoleAdditionalOptions: { Tags: [{ Key: 'Departement', Value: 'Engineering' }], }, - repositoryName: 'repo', + quiet: undefined, }); }); @@ -706,10 +705,11 @@ describe('external assets', () => { await pub.publish(); expect(ecrClient).toHaveBeenCalledWith({ - imageTag: 'ghijkl', region: 'us-north-50', assumeRoleArn: 'arn:aws:role', - repositoryName: 'repo', + assumeRoleAdditionalOptions: undefined, + assumeRoleExternalId: undefined, + quiet: undefined, }); expectAllSpawns(); diff --git a/test/placeholders.test.ts b/test/placeholders.test.ts index 00b26e4..ac42f4e 100644 --- a/test/placeholders.test.ts +++ b/test/placeholders.test.ts @@ -68,9 +68,10 @@ test('correct calls are made', async () => { await pub.publish(); expect(s3Client).toHaveBeenCalledWith({ + assumeRoleAdditionalOptions: undefined, + assumeRoleExternalId: undefined, + region: undefined, assumeRoleArn: 'arn:aws:role-current_account', - bucketName: 'some_bucket-current_account-current_region', - objectKey: 'some_key-current_account-current_region', }); expect(mockS3).toHaveReceivedCommandWith(ListObjectsV2Command, { @@ -81,10 +82,10 @@ test('correct calls are made', async () => { expect(ecrClient).toHaveBeenCalledWith({ assumeRoleArn: 'arn:aws:role-current_account', - imageTag: 'abcdef', quiet: undefined, + assumeRoleAdditionalOptions: undefined, + assumeRoleExternalId: undefined, region: 'explicit_region', - repositoryName: 'repo-current_account-explicit_region', }); expect(mockEcr).toHaveReceivedCommandWith(DescribeImagesCommand, { From 933a194e3893506defa0e83b7babeaa6f3f2cdbb Mon Sep 17 00:00:00 2001 From: iliapolo Date: Sun, 1 Sep 2024 08:40:44 +0300 Subject: [PATCH 4/5] mid work --- lib/private/docker-credentials.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/private/docker-credentials.ts b/lib/private/docker-credentials.ts index 4d1add3..f30dd3c 100644 --- a/lib/private/docker-credentials.ts +++ b/lib/private/docker-credentials.ts @@ -1,10 +1,10 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { ECRClient, GetAuthorizationTokenCommand } from '@aws-sdk/client-ecr'; +import { GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; import { Logger } from './shell'; import { IAws } from '../aws'; -import { GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; -import { ECRClient, GetAuthorizationTokenCommand } from '@aws-sdk/client-ecr'; export interface DockerCredentials { readonly Username: string; @@ -107,7 +107,7 @@ export async function obtainEcrCredentials(ecr: ECRClient, logger?: Logger) { if (logger) { logger('Fetching ECR authorization token'); } - + const authData = (await ecr.send(new GetAuthorizationTokenCommand({}))).authorizationData || []; if (authData.length === 0) { throw new Error('No authorization data received from ECR'); From dc6f510e9dbf3b48c63f88d26e71ef345b5617b5 Mon Sep 17 00:00:00 2001 From: iliapolo Date: Sun, 1 Sep 2024 09:57:17 +0300 Subject: [PATCH 5/5] mid work --- test/aws.test.ts | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 test/aws.test.ts diff --git a/test/aws.test.ts b/test/aws.test.ts new file mode 100644 index 0000000..0400721 --- /dev/null +++ b/test/aws.test.ts @@ -0,0 +1,76 @@ +import { fromTemporaryCredentials } from '@aws-sdk/credential-providers'; +import { DefaultAwsClient } from '../lib'; + +// putting this either inside the test or in a beforeEach function +// breaks mocking. not sure why. +jest.mock('@aws-sdk/credential-providers', () => ({ + fromTemporaryCredentials: jest.fn(), + fromNodeProviderChain: jest.fn(), +})); + +afterEach(() => { + jest.resetAllMocks(); +}); + +test('additional options are passed to STS when assuming roles', async () => { + const aws = new DefaultAwsClient(); + await aws.ecrClient({ + assumeRoleArn: 'some-role', + assumeRoleAdditionalOptions: { + Tags: [ + { Key: 'Departement1', Value: 'Engineering' }, + { Key: 'Departement2', Value: 'Engineering2' }, + ], + TransitiveTagKeys: ['Departement1'], + DurationSeconds: 3600, + RoleSessionName: 'some-session', + }, + }); + + expect(fromTemporaryCredentials).toHaveBeenCalledWith({ + params: { + RoleArn: 'some-role', + DurationSeconds: 3600, + RoleSessionName: 'some-session', + Tags: [ + { Key: 'Departement1', Value: 'Engineering' }, + { Key: 'Departement2', Value: 'Engineering2' }, + ], + TransitiveTagKeys: ['Departement1'], + }, + clientConfig: { + customUserAgent: 'cdk-assets', + }, + }); +}); + +test('TransitiveTagKeys defaults to all Tag keys when assuming roles', async () => { + const aws = new DefaultAwsClient(); + await aws.ecrClient({ + assumeRoleArn: 'some-role', + assumeRoleAdditionalOptions: { + DurationSeconds: 3600, + RoleSessionName: 'some-session', + Tags: [ + { Key: 'Departement1', Value: 'Engineering' }, + { Key: 'Departement2', Value: 'Engineering2' }, + ], + }, + }); + + expect(fromTemporaryCredentials).toHaveBeenCalledWith({ + params: { + RoleArn: 'some-role', + DurationSeconds: 3600, + RoleSessionName: 'some-session', + Tags: [ + { Key: 'Departement1', Value: 'Engineering' }, + { Key: 'Departement2', Value: 'Engineering2' }, + ], + TransitiveTagKeys: ['Departement1', 'Departement2'], + }, + clientConfig: { + customUserAgent: 'cdk-assets', + }, + }); +});