aws2-5 Exercise 3: The Edges: No Credentials, Aspect Errors, Escape Hatches, Construct IDs and Feature Flags ============================================================================================================= Run with Node 24.18.0 on Windows 11 in a scratch folder (not installed globally): aws-cdk 2.1145.0 (CLI), aws-cdk-lib 2.273.0, constructs 10.8.1, typescript 7.0.2, jest 30.5.2, @types/jest, @types/node; cfn-lint 1.57.2 from the chapter 4 environment. Setup: npm install --save-exact aws-cdk@2.1145.0 aws-cdk-lib@2.273.0 constructs@10.8.1 typescript@7.0.2 jest@30.5.2 @types/jest @types/node ; build with: node node_modules/typescript/bin/tsc -p tsconfig.json ; synth with: node node_modules/aws-cdk/bin/cdk synth -c environment=dev ; test with: node node_modules/jest/bin/jest.js. VERIFICATION LEVEL: 1 (run here; no AWS account, no AWS credentials, nothing deployed). Outputs below are saved from the real runs of 2026-10-13 because some steps (the failing test, the narrowed grants, the snapshot update) happen in sequence and cannot be replayed by one command. --- bin/bad.ts --- import * as cdk from "aws-cdk-lib"; import * as s3 from "aws-cdk-lib/aws-s3"; import { RequireBucketVersioning } from "../lib/aspects"; // an app that breaks the organisation rule: a bucket without versioning const app = new cdk.App(); const stack = new cdk.Stack(app, "Unversioned"); new s3.Bucket(stack, "Plain"); cdk.Aspects.of(app).add(new RequireBucketVersioning()); --- bin/lookup.ts --- import * as cdk from "aws-cdk-lib"; import * as ec2 from "aws-cdk-lib/aws-ec2"; // an environment-specific stack that asks AWS for something at synthesis time (a context lookup) const app = new cdk.App(); const stack = new cdk.Stack(app, "Lookup", { env: { account: "111122223333", region: "eu-west-1" } }); const vpc = ec2.Vpc.fromLookup(stack, "DefaultVpc", { isDefault: true }); new cdk.CfnOutput(stack, "VpcId", { value: vpc.vpcId }); --- bin/hatch.ts --- import * as cdk from "aws-cdk-lib"; import * as s3 from "aws-cdk-lib/aws-s3"; import { RequireBucketVersioning } from "../lib/aspects"; // escape hatches: reach the L1 resource inside an L2 and rewrite it with raw overrides const app = new cdk.App(); const stack = new cdk.Stack(app, "Hatch"); const bucket = new s3.Bucket(stack, "Reports", { versioned: true }); const cfn = bucket.node.defaultChild as s3.CfnBucket; cfn.addPropertyOverride("VersioningConfiguration.Status", "Suspended"); // raw override: bypasses the typed API cfn.addPropertyOverride("ObjectLockEnabled", true); // a property the L2 above never set cfn.addMetadata("reviewed-by", "platform-team"); // template metadata cfn.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN; // a CloudFormation option cdk.Aspects.of(app).add(new RequireBucketVersioning()); // the organisation rule from lib/aspects.ts --- bin/ids.ts --- import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; import * as s3 from "aws-cdk-lib/aws-s3"; // logical IDs are generated from the construct path; this prints them for the same bucket in two places in the tree class Storage extends Construct { constructor(scope: Construct, id: string) { super(scope, id); new s3.Bucket(this, "ReportBucket"); } } function ids(build: (stack: cdk.Stack) => void): string[] { const app = new cdk.App(); const stack = new cdk.Stack(app, "S"); build(stack); const t = app.synth().getStackByName("S").template; return Object.keys(t.Resources); } console.log("bucket directly in the stack :", ids((s) => new s3.Bucket(s, "ReportBucket"))); console.log("same code run again :", ids((s) => new s3.Bucket(s, "ReportBucket"))); console.log("bucket inside a construct :", ids((s) => new Storage(s, "Storage"))); console.log("construct renamed to Archive :", ids((s) => new Storage(s, "Archive"))); console.log("bucket id changed to Reports :", ids((s) => new s3.Bucket(s, "Reports"))); --- cdk diff / deploy / bootstrap / destroy with no credentials, a context lookup, and the aspect error (all exit status 1) (saved output of the real run) --- == cdk diff (no AWS credentials on this machine) -> exit status 1 AI agent detected Need to perform AWS calls for account 111122223333, but no credentials have been configured Need to perform AWS calls for account 111122223333, but no credentials have been configured == cdk deploy (no AWS credentials on this machine) -> exit status 1 AI agent detected ✨ Synthesis time: 8.17s Need to perform AWS calls for account 111122223333, but no credentials have been configured == cdk bootstrap (no AWS credentials on this machine) -> exit status 1 AI agent detected ⏳ Bootstrapping environment aws://111122223333/eu-west-1... ❌ Environment aws://111122223333/eu-west-1 failed bootstrapping: NoCredentials: Need to perform AWS calls for account 111122223333, but no credentials have been configured Need to perform AWS calls for account 111122223333, but no credentials have been configured == cdk destroy (no AWS credentials on this machine) -> exit status 1 AI agent detected Reports-dev: destroying... [1/1] ❌ Reports-dev: destroy failed NoCredentials: Need to perform AWS calls for account 111122223333, but no credentials have been configured Need to perform AWS calls for account 111122223333, but no credentials have been configured == cdk synth of an app that does a context lookup (Vpc.fromLookup) -> exit status 1 AI agent detected ERROR Need to perform AWS calls for account 111122223333, but no credentials have been configured (Construct Annotations) Lookup Rule Annotation::error-annotation Synthesis finished with errors (the cloud assembly files were still written: cdk.out Lookup.assets.json Lookup.metadata.json Lookup.template.json manifest.json tree.json validation-report.json ) == cdk synth of an app that breaks the organisation rule (unversioned bucket) -> exit status 1 AI agent detected ERROR Bucket versioning must be Enabled (organisation rule) (Construct Annotations) Unversioned/Plain/Resource Rule Annotation::error-annotation Synthesis finished with errors (the cloud assembly files were still written: cdk.out manifest.json tree.json Unversioned.assets.json Unversioned.metadata.json Unversioned.template.json validation-report.json ) --- cdk synth --app 'node dist/bin/hatch.js' (exit status 0; first 13 lines of the template) (saved output of the real run) --- Resources: Reports65402A0A: Type: AWS::S3::Bucket Properties: ObjectLockEnabled: true VersioningConfiguration: Status: Suspended UpdateReplacePolicy: Retain DeletionPolicy: Retain Metadata: aws:cdk:path: Hatch/Reports/Resource reviewed-by: platform-team CDKMetadata: --- node dist/bin/ids.js (saved output of the real run) --- bucket directly in the stack : [ 'ReportBucket577F0FCD' ] same code run again : [ 'ReportBucket577F0FCD' ] bucket inside a construct : [ 'StorageReportBucketC5ACC6CD' ] construct renamed to Archive : [ 'ArchiveReportBucketFA2C8983' ] bucket id changed to Reports : [ 'Reports65402A0A' ] --- cdk doctor (saved output of the real run) --- AI agent detected ℹ️ CDK CLI Version: 2.1145.0 (build 13877dc) normal verbosity, no debugging ℹ️ AWS environment variables: - AWS_EC2_METADATA_DISABLED = true ℹ️ No CDK environment variables --- cdk flags --unstable=flags (first 8 lines; the full table has 84 unset flags) (saved output of the real run) --- AI agent detected Unconfigured feature flags ┌──────────────────────────────────────────────────────────────────────────────────────────────┬─────────────┬─────────┬───────────┐ │ Feature Flag │ Recommended │ User │ Effective │ ├──────────────────────────────────────────────────────────────────────────────────────────────┼─────────────┼─────────┼───────────┤ │ Module: aws-cdk-lib │ │ │ │ │ @aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver │ true │ │ false │ │ @aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId │ true │ │ false │ --- effect of -c '@aws-cdk/aws-iam:minimizePolicies=true' on the worker role's policy (saved output of the real run) --- flag unset (default) 6 statements: 5 actions, 1 actions, 3 actions, 2 actions, 6 actions, 4 actions minimizePolicies = true 4 statements: 5 actions, 5 actions, 3 actions, 6 actions actions listed (with repeats): 21 -> 19; distinct: 19 -> 19 WHY THIS WORKS AS AN ANSWER --------------------------- Without credentials every account-dependent command printed the same message and exited with status 1, and a synthesis that needs a lookup failed the same way: the exercise shows where the account boundary is. The aspect run exited with status 1 but still wrote the cloud assembly including a template, so a pipeline must test the exit status. The escape-hatch run exited with status 0 and wrote Status: Suspended for a bucket created with versioned: true, together with ObjectLockEnabled and the extra metadata; the aspect did not object because it reads the typed property, which shows that raw overrides bypass the aspect. The ID run shows identical IDs for identical code and new IDs when the bucket moves into a wrapper, the wrapper is renamed or the id changes, which CloudFormation would treat as a remove plus an add. The flags run found 84 unset flags with an effective value of false; setting one of them (minimizePolicies) changed 6 statements into 4 with the same 19 distinct actions. Not run: cdk init, refactor, watch, drift, import, any command with credentials.