Skip to content

Commit 12a4ea6

Browse files
authored
Merge branch 'main' into bobertzh/apigwv1-validation
2 parents 40c72c6 + 1beaf83 commit 12a4ea6

File tree

22 files changed

+12707
-320
lines changed

22 files changed

+12707
-320
lines changed

packages/@aws-cdk-testing/cli-integ/lib/aws.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@ import { SNSClient } from '@aws-sdk/client-sns';
2020
import { SSOClient } from '@aws-sdk/client-sso';
2121
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
2222
import { fromIni } from '@aws-sdk/credential-providers';
23-
import type { AwsCredentialIdentityProvider } from '@smithy/types';
23+
import type { AwsCredentialIdentity, AwsCredentialIdentityProvider } from '@smithy/types';
2424
import { ConfiguredRetryStrategy } from '@smithy/util-retry';
2525
interface ClientConfig {
26-
readonly credentials?: AwsCredentialIdentityProvider;
26+
readonly credentials?: AwsCredentialIdentityProvider | AwsCredentialIdentity;
2727
readonly region: string;
2828
readonly retryStrategy: ConfiguredRetryStrategy;
2929
}
3030

3131
export class AwsClients {
32+
public static async forIdentity(region: string, identity: AwsCredentialIdentity, output: NodeJS.WritableStream) {
33+
return new AwsClients(region, output, identity);
34+
}
35+
3236
public static async forRegion(region: string, output: NodeJS.WritableStream) {
3337
return new AwsClients(region, output);
3438
}
@@ -45,9 +49,12 @@ export class AwsClients {
4549
public readonly lambda: LambdaClient;
4650
public readonly sts: STSClient;
4751

48-
constructor(public readonly region: string, private readonly output: NodeJS.WritableStream) {
52+
constructor(
53+
public readonly region: string,
54+
private readonly output: NodeJS.WritableStream,
55+
public readonly identity?: AwsCredentialIdentity) {
4956
this.config = {
50-
credentials: chainableCredentials(this.region),
57+
credentials: this.identity ?? chainableCredentials(this.region),
5158
region: this.region,
5259
retryStrategy: new ConfiguredRetryStrategy(9, (attempt: number) => attempt ** 500),
5360
};

packages/@aws-cdk-testing/cli-integ/lib/integ-test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ if (SKIP_TESTS) {
1313

1414
export interface TestContext {
1515
readonly randomString: string;
16+
readonly name: string;
1617
readonly output: NodeJS.WritableStream;
1718
log(s: string): void;
1819
};
@@ -51,6 +52,7 @@ export function integTest(
5152
return await callback({
5253
output,
5354
randomString: randomString(),
55+
name,
5456
log(s: string) {
5557
output.write(`${s}\n`);
5658
},

packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts

+55-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
1+
import { AtmosphereClient } from '@cdklabs/cdk-atmosphere-client';
12
import { AwsClients } from './aws';
23
import { TestContext } from './integ-test';
34
import { ResourcePool } from './resource-pool';
45
import { DisableBootstrapContext } from './with-cdk-app';
56

7+
export function atmosphereEnabled(): boolean {
8+
const enabled = process.env.CDK_INTEG_ATMOSPHERE_ENABLED;
9+
return enabled === 'true' || enabled === '1';
10+
}
11+
12+
export function atmosphereEndpoint(): string {
13+
const value = process.env.CDK_INTEG_ATMOSPHERE_ENDPOINT;
14+
if (!value) {
15+
throw new Error('CDK_INTEG_ATMOSPHERE_ENDPOINT is not defined');
16+
}
17+
return value;
18+
}
19+
20+
export function atmospherePool() {
21+
const value = process.env.CDK_INTEG_ATMOSPHERE_POOL;
22+
if (!value) {
23+
throw new Error('CDK_INTEG_ATMOSPHERE_POOL is not defined');
24+
}
25+
return value;
26+
}
27+
628
export type AwsContext = { readonly aws: AwsClients };
729

830
/**
@@ -14,12 +36,40 @@ export function withAws<A extends TestContext>(
1436
block: (context: A & AwsContext & DisableBootstrapContext) => Promise<void>,
1537
disableBootstrap: boolean = false,
1638
): (context: A) => Promise<void> {
17-
return (context: A) => regionPool().using(async (region) => {
18-
const aws = await AwsClients.forRegion(region, context.output);
19-
await sanityCheck(aws);
39+
return async (context: A) => {
40+
41+
if (atmosphereEnabled()) {
42+
const atmosphere = new AtmosphereClient(atmosphereEndpoint());
43+
const allocation = await atmosphere.acquire({ pool: atmospherePool(), requester: context.name });
44+
const aws = await AwsClients.forIdentity(allocation.environment.region, {
45+
accessKeyId: allocation.credentials.accessKeyId,
46+
secretAccessKey: allocation.credentials.secretAccessKey,
47+
sessionToken: allocation.credentials.sessionToken,
48+
accountId: allocation.environment.account,
49+
}, context.output);
50+
51+
await sanityCheck(aws);
52+
53+
let outcome = 'success';
54+
try {
55+
return await block({ ...context, disableBootstrap, aws });
56+
} catch (e: any) {
57+
outcome = 'failure';
58+
throw e;
59+
} finally {
60+
await atmosphere.release(allocation.id, outcome);
61+
}
62+
63+
} else {
64+
return regionPool().using(async (region) => {
65+
const aws = await AwsClients.forRegion(region, context.output);
66+
await sanityCheck(aws);
67+
68+
return block({ ...context, disableBootstrap, aws });
69+
});
70+
}
2071

21-
return block({ ...context, disableBootstrap, aws });
22-
});
72+
};
2373
}
2474

2575
let _regionPool: undefined | ResourcePool;

packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts

+52-32
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { IPackageSource } from './package-sources/source';
1111
import { packageSourceInSubprocess } from './package-sources/subprocess';
1212
import { RESOURCES_DIR } from './resources';
1313
import { shell, ShellOptions, ShellHelper, rimraf } from './shell';
14-
import { AwsContext, withAws } from './with-aws';
14+
import { AwsContext, atmosphereEnabled, withAws } from './with-aws';
1515
import { withTimeout } from './with-timeout';
1616

1717
export const DEFAULT_TEST_TIMEOUT_S = 20 * 60;
@@ -498,13 +498,22 @@ export class TestFixture extends ShellHelper {
498498

499499
await this.packages.makeCliAvailable();
500500

501+
// if tests are using an explicit aws identity already (i.e creds)
502+
// force every cdk command to use the same identity.
503+
const awsCreds: Record<string, string> = this.aws.identity ? {
504+
AWS_ACCESS_KEY_ID: this.aws.identity.accessKeyId,
505+
AWS_SECRET_ACCESS_KEY: this.aws.identity.secretAccessKey,
506+
AWS_SESSION_TOKEN: this.aws.identity.sessionToken!,
507+
} : {};
508+
501509
return this.shell(['cdk', ...(verbose ? ['-v'] : []), ...args], {
502510
...options,
503511
modEnv: {
504512
AWS_REGION: this.aws.region,
505513
AWS_DEFAULT_REGION: this.aws.region,
506514
STACK_NAME_PREFIX: this.stackNamePrefix,
507515
PACKAGE_LAYOUT_VERSION: this.packages.majorVersion(),
516+
...awsCreds,
508517
...options.modEnv,
509518
},
510519
});
@@ -555,37 +564,44 @@ export class TestFixture extends ShellHelper {
555564
* Cleanup leftover stacks and bootstrapped resources
556565
*/
557566
public async dispose(success: boolean) {
558-
const stacksToDelete = await this.deleteableStacks(this.stackNamePrefix);
559-
560-
this.sortBootstrapStacksToTheEnd(stacksToDelete);
561-
562-
// Bootstrap stacks have buckets that need to be cleaned
563-
const bucketNames = stacksToDelete.map(stack => outputFromStack('BucketName', stack)).filter(defined);
564-
// Parallelism will be reasonable
565-
// eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism
566-
await Promise.all(bucketNames.map(b => this.aws.emptyBucket(b)));
567-
// The bootstrap bucket has a removal policy of RETAIN by default, so add it to the buckets to be cleaned up.
568-
this.bucketsToDelete.push(...bucketNames);
569-
570-
// Bootstrap stacks have ECR repositories with images which should be deleted
571-
const imageRepositoryNames = stacksToDelete.map(stack => outputFromStack('ImageRepositoryName', stack)).filter(defined);
572-
// Parallelism will be reasonable
573-
// eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism
574-
await Promise.all(imageRepositoryNames.map(r => this.aws.deleteImageRepository(r)));
575-
576-
await this.aws.deleteStacks(
577-
...stacksToDelete.map((s) => {
578-
if (!s.StackName) {
579-
throw new Error('Stack name is required to delete a stack.');
580-
}
581-
return s.StackName;
582-
}),
583-
);
584567

585-
// We might have leaked some buckets by upgrading the bootstrap stack. Be
586-
// sure to clean everything.
587-
for (const bucket of this.bucketsToDelete) {
588-
await this.aws.deleteBucket(bucket);
568+
// when using the atmosphere service, it does resource cleanup on our behalf
569+
// so we don't have to wait for it.
570+
if (!atmosphereEnabled()) {
571+
572+
const stacksToDelete = await this.deleteableStacks(this.stackNamePrefix);
573+
574+
this.sortBootstrapStacksToTheEnd(stacksToDelete);
575+
576+
// Bootstrap stacks have buckets that need to be cleaned
577+
const bucketNames = stacksToDelete.map(stack => outputFromStack('BucketName', stack)).filter(defined);
578+
// Parallelism will be reasonable
579+
// eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism
580+
await Promise.all(bucketNames.map(b => this.aws.emptyBucket(b)));
581+
// The bootstrap bucket has a removal policy of RETAIN by default, so add it to the buckets to be cleaned up.
582+
this.bucketsToDelete.push(...bucketNames);
583+
584+
// Bootstrap stacks have ECR repositories with images which should be deleted
585+
const imageRepositoryNames = stacksToDelete.map(stack => outputFromStack('ImageRepositoryName', stack)).filter(defined);
586+
// Parallelism will be reasonable
587+
// eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism
588+
await Promise.all(imageRepositoryNames.map(r => this.aws.deleteImageRepository(r)));
589+
590+
await this.aws.deleteStacks(
591+
...stacksToDelete.map((s) => {
592+
if (!s.StackName) {
593+
throw new Error('Stack name is required to delete a stack.');
594+
}
595+
return s.StackName;
596+
}),
597+
);
598+
599+
// We might have leaked some buckets by upgrading the bootstrap stack. Be
600+
// sure to clean everything.
601+
for (const bucket of this.bucketsToDelete) {
602+
await this.aws.deleteBucket(bucket);
603+
}
604+
589605
}
590606

591607
// If the tests completed successfully, happily delete the fixture
@@ -662,7 +678,11 @@ export async function ensureBootstrapped(fixture: TestFixture) {
662678
},
663679
});
664680

665-
ALREADY_BOOTSTRAPPED_IN_THIS_RUN.add(envSpecifier);
681+
// when using the atmosphere service, every test needs to bootstrap
682+
// its own environment.
683+
if (!atmosphereEnabled()) {
684+
ALREADY_BOOTSTRAPPED_IN_THIS_RUN.add(envSpecifier);
685+
}
666686
}
667687

668688
function defined<A>(x: A): x is NonNullable<A> {

packages/@aws-cdk-testing/cli-integ/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@aws-sdk/client-sso": "3.632.0",
5151
"@aws-sdk/client-sts": "3.632.0",
5252
"@aws-sdk/credential-providers": "3.632.0",
53+
"@cdklabs/cdk-atmosphere-client": "0.0.1",
5354
"@smithy/util-retry": "3.0.8",
5455
"@smithy/types": "3.6.0",
5556
"axios": "1.7.7",
@@ -86,4 +87,4 @@
8687
"publishConfig": {
8788
"tag": "latest"
8889
}
89-
}
90+
}

0 commit comments

Comments
 (0)