Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: list stack dependencies #28995

Merged
merged 50 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
72964a4
feat: list stack dependencies
vinayak-kukreja Feb 6, 2024
e0c5952
update PR
vinayak-kukreja Feb 6, 2024
3c0000b
update PR
vinayak-kukreja Feb 6, 2024
7daf9d2
update Readme
vinayak-kukreja Feb 6, 2024
76cec8a
Add unit tests
SankyRed Feb 7, 2024
43ce6f6
Add unit tests
SankyRed Feb 8, 2024
0d2311a
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Feb 8, 2024
8a8de26
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Feb 9, 2024
0a3d0d8
route through typescript
vinayak-kukreja Feb 9, 2024
90bf23b
update PR
vinayak-kukreja Feb 9, 2024
e6c6d7d
update PR
vinayak-kukreja Feb 9, 2024
7f56705
Updating unit tests
SankyRed Feb 12, 2024
5019136
Sample test to trigger the test pipeline for the cli integ tests
SankyRed Feb 13, 2024
2bc1e86
fix up the build issue
SankyRed Feb 14, 2024
b59dec6
Fix pipeline build issue
SankyRed Feb 14, 2024
794042b
MockCloudExecutable for unit tests
SankyRed Feb 14, 2024
1acabfe
Merge branch 'main' into vkukreja/list-stack-deps
SankyRed Feb 14, 2024
01769ec
debugging build pipeline issue
SankyRed Feb 19, 2024
dba03df
Merge branch 'main' into vkukreja/list-stack-deps
SankyRed Feb 19, 2024
35dcf65
Merge branch 'main' into vkukreja/list-stack-deps
SankyRed Feb 20, 2024
2d3b970
fix unit test
SankyRed Feb 20, 2024
190ce40
Merge branch 'main' into vkukreja/list-stack-deps
SankyRed Feb 20, 2024
24835e0
revert
SankyRed Feb 20, 2024
c2a16e7
build fix
SankyRed Feb 21, 2024
aaffa68
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Feb 21, 2024
0393edb
empty commit for rerunning build
vinayak-kukreja Feb 21, 2024
6c094b0
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Feb 21, 2024
546e396
debug code
vinayak-kukreja Feb 21, 2024
4fddb87
debug code
vinayak-kukreja Feb 21, 2024
1425923
unit test
SankyRed Feb 22, 2024
5a145f6
mocking cloud executable
SankyRed Feb 22, 2024
64cfbac
unit test fix
SankyRed Feb 22, 2024
29668da
skip test
SankyRed Feb 22, 2024
5f90d9e
Updating integ and unit tests
SankyRed Feb 23, 2024
ab8a01f
Merge branch 'main' into vkukreja/list-stack-deps
SankyRed Feb 23, 2024
41377d1
unit test and debugging integ test
SankyRed Feb 23, 2024
30898c5
remove validation as it would be breaking behavior
vinayak-kukreja Feb 26, 2024
02da73c
remove debug vars
vinayak-kukreja Feb 26, 2024
fb09666
remove debug vars
vinayak-kukreja Feb 26, 2024
d7e9b9d
remove update to access specifier
vinayak-kukreja Feb 26, 2024
89aafeb
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Feb 26, 2024
10586ed
Add new integ tests
SankyRed Feb 26, 2024
9fe8d17
clean up
SankyRed Feb 27, 2024
189ead6
clean up
SankyRed Feb 27, 2024
bf27129
Merge branch 'main' into vkukreja/list-stack-deps
SankyRed Feb 27, 2024
3e2e075
address feedback
vinayak-kukreja Mar 1, 2024
eab7a24
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Mar 1, 2024
f262295
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Mar 4, 2024
ade7c03
empty commit for rerunning build
SankyRed Mar 4, 2024
39ae15c
Merge branch 'main' into vkukreja/list-stack-deps
vinayak-kukreja Mar 5, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ if (process.env.PACKAGE_LAYOUT_VERSION === '1') {
aws_sns: sns,
aws_sqs: sqs,
aws_lambda: lambda,
aws_ecr_assets: docker
aws_ecr_assets: docker,
Stack
} = require('aws-cdk-lib');
}

Expand Down Expand Up @@ -65,6 +66,59 @@ class YourStack extends cdk.Stack {
}
}

class ListMultipleDependentStack extends Stack {
constructor(scope, id) {
super(scope, id);

const dependentStack1 = new DependentStack1(this, 'DependentStack1');
const dependentStack2 = new DependentStack2(this, 'DependentStack2');

this.addDependency(dependentStack1);
this.addDependency(dependentStack2);
}
}

class DependentStack1 extends Stack {
constructor(scope, id) {
super(scope, id);

}
}

class DependentStack2 extends Stack {
constructor(scope, id) {
super(scope, id);

}
}

class ListStack extends Stack {
constructor(scope, id) {
super(scope, id);

const dependentStack = new DependentStack(this, 'DependentStack');

this.addDependency(dependentStack);
}
}

class DependentStack extends Stack {
constructor(scope, id) {
super(scope, id);

const innerDependentStack = new InnerDependentStack(this, 'InnerDependentStack');

this.addDependency(innerDependentStack);
}
}

class InnerDependentStack extends Stack {
constructor(scope, id) {
super(scope, id);

}
}

class MigrateStack extends cdk.Stack {
constructor(parent, id, props) {
super(parent, id, props);
Expand Down Expand Up @@ -498,6 +552,8 @@ switch (stackSet) {

new StackWithNestedStack(app, `${stackPrefix}-with-nested-stack`);
new StackWithNestedStackUsingParameters(app, `${stackPrefix}-with-nested-stack-using-parameters`);
new ListStack(app, `${stackPrefix}-list-stacks`)
new ListMultipleDependentStack(app, `${stackPrefix}-list-multiple-dependent-stacks`);

new YourStack(app, `${stackPrefix}-termination-protection`, {
terminationProtection: process.env.TERMINATION_PROTECTION !== 'FALSE' ? true : false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,139 @@ integTest('cdk ls', withDefaultFixture(async (fixture) => {
}
}));

/**
* Type to store stack dependencies recursively
*/
type DependencyDetails = {
id: string;
dependencies: DependencyDetails[];
};

type StackDetails = {
id: string;
dependencies: DependencyDetails[];
};

integTest('cdk ls --show-dependencies --json', withDefaultFixture(async (fixture) => {
const listing = await fixture.cdk(['ls --show-dependencies --json'], { captureStderr: false });

const expectedStacks = [
{
id: 'test-1',
dependencies: [],
},
{
id: 'order-providing',
dependencies: [],
},
{
id: 'order-consuming',
dependencies: [
{
id: 'order-providing',
dependencies: [],
},
],
},
{
id: 'with-nested-stack',
dependencies: [],
},
{
id: 'list-stacks',
dependencies: [
{
id: 'liststacksDependentStack',
dependencies: [
{
id: 'liststacksDependentStackInnerDependentStack',
dependencies: [],
},
],
},
],
},
{
id: 'list-multiple-dependent-stacks',
dependencies: [
{
id: 'listmultipledependentstacksDependentStack1',
dependencies: [],
},
{
id: 'listmultipledependentstacksDependentStack2',
dependencies: [],
},
],
},
];

function validateStackDependencies(stack: StackDetails) {
expect(listing).toContain(stack.id);

function validateDependencies(dependencies: DependencyDetails[]) {
for (const dependency of dependencies) {
expect(listing).toContain(dependency.id);
if (dependency.dependencies.length > 0) {
validateDependencies(dependency.dependencies);
}
}
}

if (stack.dependencies.length > 0) {
validateDependencies(stack.dependencies);
}
}

for (const stack of expectedStacks) {
validateStackDependencies(stack);
}
}));

integTest('cdk ls --show-dependencies --json --long', withDefaultFixture(async (fixture) => {
const listing = await fixture.cdk(['ls --show-dependencies --json --long'], { captureStderr: false });

const expectedStacks = [
{
id: 'order-providing',
name: 'order-providing',
enviroment: {
account: 'unknown-account',
region: 'unknown-region',
name: 'aws://unknown-account/unknown-region',
},
dependencies: [],
},
{
id: 'order-consuming',
name: 'order-consuming',
enviroment: {
account: 'unknown-account',
region: 'unknown-region',
name: 'aws://unknown-account/unknown-region',
},
dependencies: [
{
id: 'order-providing',
dependencies: [],
},
],
},
];

for (const stack of expectedStacks) {
expect(listing).toContain(fixture.fullStackName(stack.id));
expect(listing).toContain(fixture.fullStackName(stack.name));
expect(listing).toContain(stack.enviroment.account);
expect(listing).toContain(stack.enviroment.name);
expect(listing).toContain(stack.enviroment.region);
for (const dependency of stack.dependencies) {
expect(listing).toContain(fixture.fullStackName(dependency.id));
}
}

}));

integTest('synthing a stage with errors leads to failure', withDefaultFixture(async (fixture) => {
const output = await fixture.cdk(['synth'], {
allowErrExit: true,
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The AWS CDK Toolkit provides the `cdk` command-line interface that can be used t
| ------------------------------------- | ---------------------------------------------------------------------------------- |
| [`cdk docs`](#cdk-docs) | Access the online documentation |
| [`cdk init`](#cdk-init) | Start a new CDK project (app or library) |
| [`cdk list`](#cdk-list) | List stacks in an application |
| [`cdk list`](#cdk-list) | List stacks and their dependencies in an application |
| [`cdk synth`](#cdk-synthesize) | Synthesize a CDK app to CloudFormation template(s) |
| [`cdk diff`](#cdk-diff) | Diff stacks against current state |
| [`cdk deploy`](#cdk-deploy) | Deploy a stack into an AWS account |
Expand Down Expand Up @@ -74,7 +74,7 @@ $ cdk init lib --language=typescript

### `cdk list`

Lists the stacks modeled in the CDK app.
Lists the stacks and their dependencies modeled in the CDK app.

```console
$ # List all stacks in the CDK app 'node bin/main.js'
Comment on lines 79 to 80
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update these examples once CLI team decides on the UX for this command.

Expand Down
40 changes: 31 additions & 9 deletions packages/aws-cdk/lib/cdk-toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { StackActivityProgress } from './api/util/cloudformation/stack-activity-
import { generateCdkApp, generateStack, readFromPath, readFromStack, setEnvironment, parseSourceOptions, generateTemplate, FromScan, TemplateSourceOptions, GenerateTemplateOutput, CfnTemplateGeneratorProvider, writeMigrateJsonFile, buildGenertedTemplateOutput, buildCfnClient, appendWarningsToReadme, isThereAWarning } from './commands/migrate';
import { printSecurityDiff, printStackDiff, RequireApproval } from './diff';
import { ResourceImporter, removeNonImportResources } from './import';
import { listStacks } from './list-stacks';
import { data, debug, error, highlight, print, success, warning, withCorkedLogging } from './logging';
import { deserializeStructure, serializeStructure } from './serialize';
import { Configuration, PROJECT_CONFIG } from './settings';
Expand Down Expand Up @@ -613,16 +614,37 @@ export class CdkToolkit {
}
}

public async list(selectors: string[], options: { long?: boolean; json?: boolean } = { }): Promise<number> {
const stacks = await this.selectStacksForList(selectors);
public async list(selectors: string[], options: { long?: boolean; json?: boolean; showDeps?: boolean } = { }): Promise<number> {
const stacks = await listStacks(this, {
selectors: selectors,
});

if (options.long && options.showDeps) {
data(serializeStructure(stacks, options.json ?? false));
return 0;
}

if (options.showDeps) {
const stackDeps = [];

for (const stack of stacks) {
stackDeps.push({
id: stack.id,
dependencies: stack.dependencies,
});
}

data(serializeStructure(stackDeps, options.json ?? false));
return 0;
}

// if we are in "long" mode, emit the array as-is (JSON/YAML)
if (options.long) {
const long = [];
for (const stack of stacks.stackArtifacts) {

for (const stack of stacks) {
long.push({
id: stack.hierarchicalId,
name: stack.stackName,
id: stack.id,
name: stack.name,
environment: stack.environment,
});
}
Expand All @@ -631,8 +653,8 @@ export class CdkToolkit {
}

// just print stack IDs
for (const stack of stacks.stackArtifacts) {
data(stack.hierarchicalId);
for (const stack of stacks) {
data(stack.id);
}

return 0; // exit-code
Expand Down Expand Up @@ -905,7 +927,7 @@ export class CdkToolkit {
return assembly.stackById(stacks.firstStack.id);
}

private assembly(cacheCloudAssembly?: boolean): Promise<CloudAssembly> {
public assembly(cacheCloudAssembly?: boolean): Promise<CloudAssembly> {
return this.props.cloudExecutable.synthesize(cacheCloudAssembly);
}

Expand Down
5 changes: 3 additions & 2 deletions packages/aws-cdk/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ async function parseCommandLineArguments(args: string[]) {
.option('no-color', { type: 'boolean', desc: 'Removes colors and other style from console output', default: false })
.option('ci', { type: 'boolean', desc: 'Force CI detection. If CI=true then logs will be sent to stdout instead of stderr', default: process.env.CI !== undefined })
.command(['list [STACKS..]', 'ls [STACKS..]'], 'Lists all stacks in the app', (yargs: Argv) => yargs
.option('long', { type: 'boolean', default: false, alias: 'l', desc: 'Display environment information for each stack' }),
.option('long', { type: 'boolean', default: false, alias: 'l', desc: 'Display environment information for each stack' })
.option('show-dependencies', { type: 'boolean', default: false, alias: 'd', desc: 'Display stack dependency information for each stack' }),
)
.command(['synthesize [STACKS..]', 'synth [STACKS..]'], 'Synthesizes and prints the CloudFormation template for this stack', (yargs: Argv) => yargs
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only synthesize requested stacks, don\'t include dependencies' })
Expand Down Expand Up @@ -498,7 +499,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n

case 'ls':
case 'list':
return cli.list(args.STACKS, { long: args.long, json: argv.json });
return cli.list(args.STACKS, { long: args.long, json: argv.json, showDeps: args.showDependencies });

case 'diff':
const enableDiffNoFail = isFeatureEnabled(configuration, cxapi.ENABLE_DIFF_NO_FAIL_CONTEXT);
Expand Down
Loading
Loading