Skip to content

Commit

Permalink
chore: add feature flag report to package (#32607)
Browse files Browse the repository at this point in the history
Include a file `recommended-feature-flags.json` into the `aws-cdk-lib` package. 

At build time, the CLI will copy the current `aws-cdk-lib` and `constructs` version, as well as the feature flag report, into its own package directory, and it will use those to initialize a new project.

This decouples the init version number from the CLI version number, and it removes the need for the CLI to read `cx-api` at init time.

Closes aws/aws-cdk-cli#7

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
rix0rrr authored Dec 23, 2024
1 parent 0546ec2 commit cf18cf6
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 27 deletions.
1 change: 1 addition & 0 deletions packages/@aws-cdk/cli-lib-alpha/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc');
baseConfig.parserOptions.project = __dirname + '/tsconfig.json';
baseConfig.ignorePatterns.push('**/*.template.ts');

baseConfig.rules['import/no-extraneous-dependencies'] = ['error', { devDependencies: true, peerDependencies: true } ];
baseConfig.rules['import/order'] = 'off';
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cli-lib-alpha/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
*.js.map
*.d.ts
*.gz
!lib/init-templates/**/javascript/**/*
lib/init-templates/
node_modules
dist
.jsii
Expand Down
12 changes: 12 additions & 0 deletions packages/@aws-cdk/cli-lib-alpha/build-tools/copy-cli-resources.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
set -euo pipefail

aws_cdk="$1"

# Copy all resources that aws_cdk/generate.sh produced, and some othersCall the generator for the
cp $aws_cdk/build-info.json ./
cp -R $aws_cdk/lib/init-templates ./lib/

mkdir -p ./lib/api/bootstrap/ && cp $aws_cdk/lib/api/bootstrap/bootstrap-template.yaml ./lib/api/bootstrap/
cp $aws_cdk/lib/index_bg.wasm ./lib/
cp $aws_cdk/db.json.gz ./
8 changes: 4 additions & 4 deletions packages/@aws-cdk/cli-lib-alpha/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"main": "lib/main.js",
"types": "lib/index.d.ts",
"jsii": {
"excludeTypescript": [
"**/*.template.ts"
],
"outdir": "dist",
"targets": {
"dotnet": {
Expand Down Expand Up @@ -52,7 +55,7 @@
"build+test+package": "yarn build+test && yarn package",
"bundle": "esbuild --bundle lib/index.ts --target=node18 --platform=node --external:fsevents --minify-whitespace --outfile=lib/main.js",
"compat": "cdk-compat",
"gen": "../../../packages/aws-cdk/generate.sh",
"gen": "build-tools/copy-cli-resources.sh ../../../packages/aws-cdk",
"lint": "cdk-lint",
"package": "cdk-package",
"pkglint": "pkglint -f",
Expand All @@ -63,9 +66,6 @@
"cdk-build": {
"post": [
"yarn attribute",
"mkdir -p ./lib/api/bootstrap/ && cp ../../aws-cdk/lib/api/bootstrap/bootstrap-template.yaml ./lib/api/bootstrap/",
"cp ../../../node_modules/cdk-from-cfn/index_bg.wasm ./lib/",
"cp ../../../node_modules/@aws-cdk/aws-service-spec/db.json.gz ./",
"yarn bundle",
"node ./lib/main.js >/dev/null 2>/dev/null </dev/null"
],
Expand Down
6 changes: 6 additions & 0 deletions packages/@aws-cdk/integ-runner/build-tools/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
set -euo pipefail

# Copy the recommended-feature-flags.json file out from aws-cdk-lib.
path=$(node -p 'require.resolve("aws-cdk-lib/recommended-feature-flags.json")')
cp $path lib/recommended-feature-flags.json
14 changes: 12 additions & 2 deletions packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import * as path from 'path';
import { CdkCliWrapper, ICdk } from '@aws-cdk/cdk-cli-wrapper';
import { TestCase, DefaultCdkOptions } from '@aws-cdk/cloud-assembly-schema';
import { AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY, TARGET_PARTITIONS, NEW_PROJECT_CONTEXT } from '@aws-cdk/cx-api';
import { AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY, TARGET_PARTITIONS } from '@aws-cdk/cx-api';
import * as fs from 'fs-extra';
import { IntegTestSuite, LegacyIntegTestSuite } from './integ-test-suite';
import { IntegTest } from './integration-tests';
Expand Down Expand Up @@ -365,7 +365,7 @@ export abstract class IntegRunner {

protected getContext(additionalContext?: Record<string, any>): Record<string, any> {
return {
...NEW_PROJECT_CONTEXT,
...currentlyRecommendedAwsCdkLibFlags(),
...this.legacyContext,
...additionalContext,

Expand Down Expand Up @@ -432,3 +432,13 @@ export const DEFAULT_SYNTH_OPTIONS = {
CDK_INTEG_SUBNET_ID: 'subnet-0dff1a399d8f6f92c',
},
};

/**
* Return the currently recommended flags for `aws-cdk-lib`.
*
* These have been built into the CLI at build time.
*/
export function currentlyRecommendedAwsCdkLibFlags() {
const recommendedFlagsFile = path.join(__dirname, '..', 'recommended-feature-flags.json');
return JSON.parse(fs.readFileSync(recommendedFlagsFile, { encoding: 'utf-8' }));
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/integ-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"integ-runner": "bin/integ-runner"
},
"scripts": {
"gen": "./build-tools/generate.sh",
"build": "cdk-build",
"lint": "cdk-lint",
"package": "cdk-package",
Expand Down
9 changes: 8 additions & 1 deletion packages/aws-cdk-lib/cx-api/build-tools/flag-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ async function main() {
diff: changedFlags(),
migratejson: migrateJson(),
});

// Write to the package root
await updateRecommendedFlagsFile(path.join(__dirname, '..', '..', 'recommended-feature-flags.json'));
}

function flagsTable() {
Expand Down Expand Up @@ -117,7 +120,7 @@ function oldBehavior(flag: FlagInfo): string | undefined {
function recommendedJson() {
return [
'```json',
JSON.stringify({ context: feats.NEW_PROJECT_CONTEXT }, undefined, 2),
JSON.stringify({ context: feats.CURRENTLY_RECOMMENDED_FLAGS }, undefined, 2),
'```',
].join('\n');
}
Expand Down Expand Up @@ -206,6 +209,10 @@ async function updateMarkdownFile(filename: string, sections: Record<string, str
await fs.writeFile(filename, contents, { encoding: 'utf-8' });
}

async function updateRecommendedFlagsFile(filename: string) {
await fs.writeFile(filename, JSON.stringify(feats.CURRENTLY_RECOMMENDED_FLAGS, undefined, 2), { encoding: 'utf-8' });
}

function firstCmp(...xs: number[]) {
return xs.find(x => x !== 0) ?? 0;
}
Expand Down
13 changes: 8 additions & 5 deletions packages/aws-cdk-lib/cx-api/lib/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1317,10 +1317,13 @@ export const CURRENT_VERSION_EXPIRED_FLAGS: string[] = Object.entries(FLAGS)
* Add a flag in here (typically with the value `true`), to enable
* backwards-breaking behavior changes only for new projects. New projects
* generated through `cdk init` will include these flags in their generated
* project config.
*
* Tests must cover the default (disabled) case and the future (enabled) case.
*
* Going forward, this should *NOT* be consumed directly anymore.
*/
export const NEW_PROJECT_CONTEXT = Object.fromEntries(
export const CURRENTLY_RECOMMENDED_FLAGS = Object.fromEntries(
Object.entries(FLAGS)
.filter(([_, flag]) => flag.recommendedValue !== flag.defaults?.[CURRENT_MV] && flag.introducedIn[CURRENT_MV])
.map(([name, flag]) => [name, flag.recommendedValue]),
Expand Down Expand Up @@ -1352,10 +1355,10 @@ export function futureFlagDefault(flag: string): boolean {
/** @deprecated use CURRENT_VERSION_EXPIRED_FLAGS instead */
export const FUTURE_FLAGS_EXPIRED = CURRENT_VERSION_EXPIRED_FLAGS;

/** @deprecated use NEW_PROJECT_CONTEXT instead */
export const FUTURE_FLAGS = Object.fromEntries(Object.entries(NEW_PROJECT_CONTEXT)
/** @deprecated do not use at all! */
export const FUTURE_FLAGS = Object.fromEntries(Object.entries(CURRENTLY_RECOMMENDED_FLAGS)
.filter(([_, v]) => typeof v === 'boolean'));

/** @deprecated use NEW_PROJECT_CONTEXT instead */
export const NEW_PROJECT_DEFAULT_CONTEXT = Object.fromEntries(Object.entries(NEW_PROJECT_CONTEXT)
/** @deprecated do not use at all! */
export const NEW_PROJECT_DEFAULT_CONTEXT = Object.fromEntries(Object.entries(CURRENTLY_RECOMMENDED_FLAGS)
.filter(([_, v]) => typeof v !== 'boolean'));
2 changes: 1 addition & 1 deletion packages/aws-cdk-lib/cx-api/lib/private/flag-modeling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface FlagInfoBase {
/** Version number the flag was introduced in each version line. `undefined` means flag does not exist in that line. */
readonly introducedIn: { v1?: string; v2?: string };
/** Default value, if flag is unset by user. Adding a flag with a default may not change behavior after GA! */
readonly defaults?: { v2?: any };
readonly defaults?: { v1?: any; v2?: any };
/** Default in new projects */
readonly recommendedValue: any;
};
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@
"./pipelines/.warnings.jsii.js": "./pipelines/.warnings.jsii.js",
"./pipelines/lib/helpers-internal": "./pipelines/lib/helpers-internal/index.js",
"./pipelines/package.json": "./pipelines/package.json",
"./recommended-feature-flags.json": "./recommended-feature-flags.json",
"./region-info": "./region-info/index.js",
"./triggers": "./triggers/index.js"
},
Expand Down
63 changes: 63 additions & 0 deletions packages/aws-cdk-lib/recommended-feature-flags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true
}
2 changes: 2 additions & 0 deletions packages/aws-cdk/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
!lib/init-templates/**/javascript/**/*
lib/init-templates/**/*.hook.js
lib/init-templates/**/*.hook.d.ts
lib/init-templates/.*.json
node_modules
dist

Expand Down Expand Up @@ -40,5 +41,6 @@ test/integ/cli/*.d.ts

junit.xml


lib/**/*.wasm
db.json.gz
9 changes: 9 additions & 0 deletions packages/aws-cdk/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ cat > build-info.json <<HERE
"commit": "${commit:0:7}"
}
HERE

# Copy the current 'aws-cdk-lib' version out from the monorepo.
cdk_version=$(node -p 'require("aws-cdk-lib/package.json").version')
constructs_range=$(node -p 'require("aws-cdk-lib/package.json").peerDependencies.constructs')
echo '{"aws-cdk-lib": "'"$cdk_version"'", "constructs": "'"$constructs_range"'"}' > lib/init-templates/.init-version.json

# Copy the recommended-feature-flags.json file out from aws-cdk-lib.
path=$(node -p 'require.resolve("aws-cdk-lib/recommended-feature-flags.json")')
cp $path lib/init-templates/.recommended-feature-flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
},
"exclude": [
"node_modules",
"cdk.out"
"cdk.out",
]
}
52 changes: 45 additions & 7 deletions packages/aws-cdk/lib/init.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as childProcess from 'child_process';
import * as path from 'path';
import * as cxapi from '@aws-cdk/cx-api';
import * as chalk from 'chalk';
import * as fs from 'fs-extra';
import { invokeBuiltinHooks } from './init-hooks';
Expand Down Expand Up @@ -126,6 +125,7 @@ export class InitTemplate {
const projectInfo: ProjectInfo = {
name: decamelize(path.basename(path.resolve(targetDirectory))),
stackName,
versions: await loadInitVersions(),
};

const sourceDirectory = path.join(this.basePath, language);
Expand Down Expand Up @@ -173,11 +173,9 @@ export class InitTemplate {
}

private expand(template: string, language: string, project: ProjectInfo) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const manifest = require(path.join(rootDir(), 'package.json'));
const MATCH_VER_BUILD = /\+[a-f0-9]+$/; // Matches "+BUILD" in "x.y.z-beta+BUILD"
const cdkVersion = manifest.version.replace(MATCH_VER_BUILD, '');
let constructsVersion = manifest.devDependencies.constructs.replace(MATCH_VER_BUILD, '');
const cdkVersion = project.versions['aws-cdk-lib'];
let constructsVersion = project.versions.constructs;

switch (language) {
case 'java':
case 'csharp':
Expand Down Expand Up @@ -222,7 +220,7 @@ export class InitTemplate {
const config = await fs.readJson(cdkJson);
config.context = {
...config.context,
...cxapi.NEW_PROJECT_CONTEXT,
...await currentlyRecommendedAwsCdkLibFlags(),
};

await fs.writeJson(cdkJson, config, { spaces: 2 });
Expand All @@ -248,6 +246,8 @@ interface ProjectInfo {
/** The value used for %name% */
readonly name: string;
readonly stackName?: string;

readonly versions: Versions;
}

export async function availableInitTemplates(): Promise<InitTemplate[]> {
Expand Down Expand Up @@ -471,3 +471,41 @@ async function execute(cmd: string, args: string[], { cwd }: { cwd: string }) {
});
});
}

interface Versions {
['aws-cdk-lib']: string;
constructs: string;
}

/**
* Return the 'aws-cdk-lib' version we will init
*
* This has been built into the CLI at build time.
*/
async function loadInitVersions(): Promise<Versions> {
const recommendedFlagsFile = path.join(__dirname, './init-templates/.init-version.json');
const contents = JSON.parse(await fs.readFile(recommendedFlagsFile, { encoding: 'utf-8' }));

const ret = {
'aws-cdk-lib': contents['aws-cdk-lib'],
'constructs': contents.constructs,
};
for (const [key, value] of Object.entries(ret)) {
/* istanbul ignore next */
if (!value) {
throw new Error(`Missing init version from ${recommendedFlagsFile}: ${key}`);
}
}

return ret;
}

/**
* Return the currently recommended flags for `aws-cdk-lib`.
*
* These have been built into the CLI at build time.
*/
export async function currentlyRecommendedAwsCdkLibFlags() {
const recommendedFlagsFile = path.join(__dirname, './init-templates/.recommended-feature-flags.json');
return JSON.parse(await fs.readFile(recommendedFlagsFile, { encoding: 'utf-8' }));
}
Loading

0 comments on commit cf18cf6

Please sign in to comment.