Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/aws-cdk-lib/core/lib/private/feature-flag-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ArtifactType, FeatureFlag } from '@aws-cdk/cloud-assembly-schema';
import { IConstruct } from 'constructs';
import { CloudAssemblyBuilder } from '../../../cx-api';
import * as feats from '../../../cx-api/lib/features';
import { FlagInfo } from '../../../cx-api/lib/private/flag-modeling';

/**
* Creates a FeatureFlag object based on flag information given.
*/
function parseFeatureFlagInfo(flagName: string, info: FlagInfo, root: IConstruct): FeatureFlag {
const userValue = root.node.tryGetContext(flagName) ?? undefined;

let parsedFlag: FeatureFlag = {
userValue: userValue,
recommendedValue: info.recommendedValue,
explanation: info.summary,
};

return parsedFlag;
}

/**
* Iterate through all feature flags, retrieve the user's context,
* and create a Feature Flag report.
*/
export function generateFeatureFlagReport(builder: CloudAssemblyBuilder, root: IConstruct): void {
const featureFlags: Record<string, FeatureFlag> = {};
for (const [flagName, flagInfo] of Object.entries(feats.FLAGS)) {
featureFlags[flagName] = parseFeatureFlagInfo(flagName, flagInfo, root);
}

builder.addArtifact('aws-cdk-lib/feature-flag-report', {
type: ArtifactType.FEATURE_FLAG_REPORT,
properties: {
module: 'aws-cdk-lib',
flags: featureFlags,
},
});
}
3 changes: 3 additions & 0 deletions packages/aws-cdk-lib/core/lib/private/synthesis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Stack } from '../stack';
import { ISynthesisSession } from '../stack-synthesizers/types';
import { Stage, StageSynthesisOptions } from '../stage';
import { IPolicyValidationPluginBeta1 } from '../validation';
import { generateFeatureFlagReport } from './feature-flag-report';
import { ConstructTree } from '../validation/private/construct-tree';
import { PolicyValidationReportFormatter, NamedValidationPluginReport } from '../validation/private/report';

Expand Down Expand Up @@ -64,6 +65,8 @@ export function synthesize(root: IConstruct, options: SynthesisOptions = { }): c
// stacks to add themselves to the synthesized cloud assembly.
synthesizeTree(root, builder, options.validateOnSynthesis);

generateFeatureFlagReport(builder, root);

const assembly = builder.buildAssembly();

invokeValidationPlugins(root, builder.outdir, assembly);
Expand Down
51 changes: 51 additions & 0 deletions packages/aws-cdk-lib/core/test/feature-flag-report.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FeatureFlagReportProperties } from '@aws-cdk/cloud-assembly-schema';
import * as cxapi from '../../cx-api';
import { App } from '../lib';
import { generateFeatureFlagReport } from '../lib/private/feature-flag-report';

describe('generate feature flag report', () => {
test('feature flag report can be retrieved from CloudAssembly using its artifact ID', () => {
const app = new App();
const assembly = app.synth();
expect(assembly.manifest.artifacts?.['aws-cdk-lib/feature-flag-report']).toBeDefined();
});
test('report contains context values that represent the feature flags', () => {
const app = new App({
context: {
'@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault': true,
'@aws-cdk/core:aspectStabilization': false,
},
});
const assembly = app.synth();
const report = assembly.manifest.artifacts?.['aws-cdk-lib/feature-flag-report'];
expect(report).toEqual(expect.objectContaining({
type: 'cdk:feature-flag-report',
properties: expect.objectContaining({
module: 'aws-cdk-lib',
flags: expect.objectContaining({
'@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault': expect.objectContaining({
userValue: true,
}),
'@aws-cdk/core:aspectStabilization': expect.objectContaining({
userValue: false,
}),
}),
}),
}));
});
test('defaults userValue to undefined when not set in context', () => {
const app = new App();
const builder = new cxapi.CloudAssemblyBuilder('/tmp/test');
const spy = jest.spyOn(builder, 'addArtifact');

generateFeatureFlagReport(builder, app);

const flags = (spy.mock.calls[0][1].properties as FeatureFlagReportProperties).flags;
expect(flags).toEqual(expect.objectContaining({
'@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault': expect.objectContaining({
userValue: undefined,
}),
}));
});
});

6 changes: 3 additions & 3 deletions packages/aws-cdk-lib/core/test/stage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ describe('stage', () => {
const rootAssembly = app.synth();

// THEN
expect(rootAssembly.manifest.artifacts).toEqual({
expect(rootAssembly.manifest.artifacts).toMatchObject({
'assembly-StageLevel1': {
type: 'cdk:cloud-assembly',
properties: {
Expand All @@ -263,7 +263,7 @@ describe('stage', () => {
});

const assemblyLevel1 = rootAssembly.getNestedAssembly('assembly-StageLevel1');
expect(assemblyLevel1.manifest.artifacts).toEqual({
expect(assemblyLevel1.manifest.artifacts).toMatchObject({
'assembly-StageLevel1-StageLevel2': {
type: 'cdk:cloud-assembly',
properties: {
Expand All @@ -274,7 +274,7 @@ describe('stage', () => {
});

const assemblyLevel2 = assemblyLevel1.getNestedAssembly('assembly-StageLevel1-StageLevel2');
expect(assemblyLevel2.manifest.artifacts).toEqual({
expect(assemblyLevel2.manifest.artifacts).toMatchObject({
'assembly-StageLevel1-StageLevel2-StageLevel3': {
type: 'cdk:cloud-assembly',
properties: {
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk-lib/core/test/synthesis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('synthesis', () => {
// THEN
expect(app.synth()).toEqual(session); // same session if we synth() again
expect(list(session.directory)).toEqual(['cdk.out', 'manifest.json', 'tree.json']);
expect(readJson(session.directory, 'manifest.json').artifacts).toEqual({
expect(readJson(session.directory, 'manifest.json').artifacts).toMatchObject({
Tree: {
type: 'cdk:tree',
properties: { file: 'tree.json' },
Expand Down
Loading