Skip to content

Commit 25d5d60

Browse files
authoredMay 16, 2023
feat(cli): assets can now depend on stacks (aws#25536)
Introduce a work graph, in which building assets, publishing assets, and deploying stacks are nodes. Each can have their own sets of dependencies which will be respected in a parallel deployment. This change supports an upcoming change for asset publishing. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent d11021d commit 25d5d60

37 files changed

+1695
-778
lines changed
 

‎packages/@aws-cdk/cx-api/jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module.exports = {
44
coverageThreshold: {
55
global: {
66
...baseConfig.coverageThreshold.global,
7-
branches: 75,
7+
branches: 70,
88
},
99
},
1010
};

‎packages/aws-cdk-lib/cloud-assembly-schema/lib/manifest.ts

+10
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ export interface LoadManifestOptions {
4848
* @default false
4949
*/
5050
readonly skipEnumCheck?: boolean;
51+
52+
/**
53+
* Topologically sort all artifacts
54+
*
55+
* This parameter is only respected by the constructor of `CloudAssembly`. The
56+
* property lives here for backwards compatibility reasons.
57+
*
58+
* @default true
59+
*/
60+
readonly topoSort?: boolean;
5161
}
5262

5363
/**

‎packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts

+35
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,30 @@ import { CloudArtifact } from '../cloud-artifact';
55
import type { CloudAssembly } from '../cloud-assembly';
66
import { Environment, EnvironmentUtils } from '../environment';
77

8+
const CLOUDFORMATION_STACK_ARTIFACT_SYM = Symbol.for('@aws-cdk/cx-api.CloudFormationStackArtifact');
9+
810
export class CloudFormationStackArtifact extends CloudArtifact {
11+
/**
12+
* Checks if `art` is an instance of this class.
13+
*
14+
* Use this method instead of `instanceof` to properly detect `CloudFormationStackArtifact`
15+
* instances, even when the construct library is symlinked.
16+
*
17+
* Explanation: in JavaScript, multiple copies of the `cx-api` library on
18+
* disk are seen as independent, completely different libraries. As a
19+
* consequence, the class `CloudFormationStackArtifact` in each copy of the `cx-api` library
20+
* is seen as a different class, and an instance of one class will not test as
21+
* `instanceof` the other class. `npm install` will not create installations
22+
* like this, but users may manually symlink construct libraries together or
23+
* use a monorepo tool: in those cases, multiple copies of the `cx-api`
24+
* library can be accidentally installed, and `instanceof` will behave
25+
* unpredictably. It is safest to avoid using `instanceof`, and using
26+
* this type-testing method instead.
27+
*/
28+
public static isCloudFormationStackArtifact(art: any): art is CloudFormationStackArtifact {
29+
return art && typeof art === 'object' && art[CLOUDFORMATION_STACK_ARTIFACT_SYM];
30+
}
31+
932
/**
1033
* The file name of the template.
1134
*/
@@ -183,3 +206,15 @@ export class CloudFormationStackArtifact extends CloudArtifact {
183206
return ret;
184207
}
185208
}
209+
210+
/**
211+
* Mark all instances of 'CloudFormationStackArtifact'
212+
*
213+
* Why not put this in the constructor? Because this is a class property,
214+
* not an instance property. It applies to all instances of the class.
215+
*/
216+
Object.defineProperty(CloudFormationStackArtifact.prototype, CLOUDFORMATION_STACK_ARTIFACT_SYM, {
217+
value: true,
218+
enumerable: false,
219+
writable: false,
220+
});

‎packages/aws-cdk-lib/cx-api/lib/artifacts/nested-cloud-assembly-artifact.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,33 @@ import * as cxschema from '../../../cloud-assembly-schema';
33
import { CloudArtifact } from '../cloud-artifact';
44
import type { CloudAssembly } from '../cloud-assembly';
55

6+
const NESTED_CLOUD_ASSEMBLY_SYM = Symbol.for('@aws-cdk/cx-api.NestedCloudAssemblyArtifact');
7+
68
/**
79
* Asset manifest is a description of a set of assets which need to be built and published
810
*/
911
export class NestedCloudAssemblyArtifact extends CloudArtifact {
12+
/**
13+
* Checks if `art` is an instance of this class.
14+
*
15+
* Use this method instead of `instanceof` to properly detect `NestedCloudAssemblyArtifact`
16+
* instances, even when the construct library is symlinked.
17+
*
18+
* Explanation: in JavaScript, multiple copies of the `cx-api` library on
19+
* disk are seen as independent, completely different libraries. As a
20+
* consequence, the class `NestedCloudAssemblyArtifact` in each copy of the `cx-api` library
21+
* is seen as a different class, and an instance of one class will not test as
22+
* `instanceof` the other class. `npm install` will not create installations
23+
* like this, but users may manually symlink construct libraries together or
24+
* use a monorepo tool: in those cases, multiple copies of the `cx-api`
25+
* library can be accidentally installed, and `instanceof` will behave
26+
* unpredictably. It is safest to avoid using `instanceof`, and using
27+
* this type-testing method instead.
28+
*/
29+
public static isNestedCloudAssemblyArtifact(art: any): art is NestedCloudAssemblyArtifact {
30+
return art && typeof art === 'object' && art[NESTED_CLOUD_ASSEMBLY_SYM];
31+
}
32+
1033
/**
1134
* The relative directory name of the asset manifest
1235
*/
@@ -40,4 +63,16 @@ export interface NestedCloudAssemblyArtifact {
4063
readonly nestedAssembly: CloudAssembly;
4164

4265
// Declared in a different file
43-
}
66+
}
67+
68+
/**
69+
* Mark all instances of 'NestedCloudAssemblyArtifact'
70+
*
71+
* Why not put this in the constructor? Because this is a class property,
72+
* not an instance property. It applies to all instances of the class.
73+
*/
74+
Object.defineProperty(NestedCloudAssemblyArtifact.prototype, NESTED_CLOUD_ASSEMBLY_SYM, {
75+
value: true,
76+
enumerable: false,
77+
writable: false,
78+
});

‎packages/aws-cdk-lib/cx-api/lib/artifacts/tree-cloud-artifact.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,30 @@ import * as cxschema from '../../../cloud-assembly-schema';
22
import { CloudArtifact } from '../cloud-artifact';
33
import { CloudAssembly } from '../cloud-assembly';
44

5+
const TREE_CLOUD_ARTIFACT_SYM = Symbol.for('@aws-cdk/cx-api.TreeCloudArtifact');
6+
57
export class TreeCloudArtifact extends CloudArtifact {
8+
/**
9+
* Checks if `art` is an instance of this class.
10+
*
11+
* Use this method instead of `instanceof` to properly detect `TreeCloudArtifact`
12+
* instances, even when the construct library is symlinked.
13+
*
14+
* Explanation: in JavaScript, multiple copies of the `cx-api` library on
15+
* disk are seen as independent, completely different libraries. As a
16+
* consequence, the class `TreeCloudArtifact` in each copy of the `cx-api` library
17+
* is seen as a different class, and an instance of one class will not test as
18+
* `instanceof` the other class. `npm install` will not create installations
19+
* like this, but users may manually symlink construct libraries together or
20+
* use a monorepo tool: in those cases, multiple copies of the `cx-api`
21+
* library can be accidentally installed, and `instanceof` will behave
22+
* unpredictably. It is safest to avoid using `instanceof`, and using
23+
* this type-testing method instead.
24+
*/
25+
public static isTreeCloudArtifact(art: any): art is TreeCloudArtifact {
26+
return art && typeof art === 'object' && art[TREE_CLOUD_ARTIFACT_SYM];
27+
}
28+
629
public readonly file: string;
730

831
constructor(assembly: CloudAssembly, name: string, artifact: cxschema.ArtifactManifest) {
@@ -14,4 +37,16 @@ export class TreeCloudArtifact extends CloudArtifact {
1437
}
1538
this.file = properties.file;
1639
}
17-
}
40+
}
41+
42+
/**
43+
* Mark all instances of 'TreeCloudArtifact'
44+
*
45+
* Why not put this in the constructor? Because this is a class property,
46+
* not an instance property. It applies to all instances of the class.
47+
*/
48+
Object.defineProperty(TreeCloudArtifact.prototype, TREE_CLOUD_ARTIFACT_SYM, {
49+
value: true,
50+
enumerable: false,
51+
writable: false,
52+
});

‎packages/aws-cdk-lib/cx-api/lib/cloud-assembly.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { NestedCloudAssemblyArtifact } from './artifacts/nested-cloud-assembly-a
66
import { TreeCloudArtifact } from './artifacts/tree-cloud-artifact';
77
import { CloudArtifact } from './cloud-artifact';
88
import { topologicalSort } from './toposort';
9-
import { LoadManifestOptions } from '../../cloud-assembly-schema';
109
import * as cxschema from '../../cloud-assembly-schema';
1110

1211
/**
@@ -47,12 +46,12 @@ export class CloudAssembly {
4746
* Reads a cloud assembly from the specified directory.
4847
* @param directory The root directory of the assembly.
4948
*/
50-
constructor(directory: string, loadOptions?: LoadManifestOptions) {
49+
constructor(directory: string, loadOptions?: cxschema.LoadManifestOptions) {
5150
this.directory = directory;
5251

5352
this.manifest = cxschema.Manifest.loadAssemblyManifest(path.join(directory, MANIFEST_FILE), loadOptions);
5453
this.version = this.manifest.version;
55-
this.artifacts = this.renderArtifacts();
54+
this.artifacts = this.renderArtifacts(loadOptions?.topoSort ?? true);
5655
this.runtime = this.manifest.runtime || { libraries: { } };
5756

5857
// force validation of deps by accessing 'depends' on all artifacts
@@ -219,7 +218,7 @@ export class CloudAssembly {
219218
}
220219
}
221220

222-
private renderArtifacts() {
221+
private renderArtifacts(topoSort: boolean) {
223222
const result = new Array<CloudArtifact>();
224223
for (const [name, artifact] of Object.entries(this.manifest.artifacts || { })) {
225224
const cloudartifact = CloudArtifact.fromManifest(this, name, artifact);
@@ -228,7 +227,7 @@ export class CloudAssembly {
228227
}
229228
}
230229

231-
return topologicalSort(result, x => x.id, x => x._dependencyIDs);
230+
return topoSort ? topologicalSort(result, x => x.id, x => x._dependencyIDs) : result;
232231
}
233232
}
234233

@@ -357,6 +356,13 @@ export class CloudAssemblyBuilder {
357356
parentBuilder: this,
358357
});
359358
}
359+
360+
/**
361+
* Delete the cloud assembly directory
362+
*/
363+
public delete() {
364+
fs.rmSync(this.outdir, { recursive: true, force: true });
365+
}
360366
}
361367

362368
/**

‎packages/aws-cdk-lib/cx-api/test/cloud-assembly.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ test('can read assembly with asset manifest', () => {
168168
expect(assembly.artifacts).toHaveLength(2);
169169
});
170170

171+
test('can toposort assembly with asset dependency', () => {
172+
const assembly = new CloudAssembly(path.join(FIXTURES, 'asset-depends'));
173+
expect(assembly.stacks).toHaveLength(2);
174+
expect(assembly.artifacts).toHaveLength(3);
175+
expect(assembly.artifacts[0].id).toEqual('StagingStack');
176+
});
177+
171178
test('getStackArtifact retrieves a stack by artifact id from a nested assembly', () => {
172179
const assembly = new CloudAssembly(path.join(FIXTURES, 'nested-assemblies'));
173180

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"version": "0.0.0",
3+
"artifacts": {
4+
"MyStackName": {
5+
"type": "aws:cloudformation:stack",
6+
"environment": "aws://37736633/us-region-1",
7+
"properties": {
8+
"templateFile": "template.json"
9+
},
10+
"dependencies": ["AssetManifest"],
11+
"metadata": {
12+
}
13+
},
14+
"AssetManifest": {
15+
"type": "cdk:asset-manifest",
16+
"properties": {
17+
"file": "asset.json"
18+
},
19+
"dependencies": ["StagingStack"]
20+
},
21+
"StagingStack": {
22+
"type": "aws:cloudformation:stack",
23+
"environment": "aws://1111/us-region-1",
24+
"properties": {
25+
"templateFile": "template.json"
26+
}
27+
}
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"Resources": {
3+
"MyBucket": {
4+
"Type": "AWS::S3::Bucket"
5+
}
6+
}
7+
}

‎packages/aws-cdk/THIRD_PARTY_LICENSES

-80
Original file line numberDiff line numberDiff line change
@@ -1143,32 +1143,6 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
11431143
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11441144

11451145

1146-
----------------
1147-
1148-
** eventemitter3@4.0.7 - https://www.npmjs.com/package/eventemitter3/v/4.0.7 | MIT
1149-
The MIT License (MIT)
1150-
1151-
Copyright (c) 2014 Arnout Kazemier
1152-
1153-
Permission is hereby granted, free of charge, to any person obtaining a copy
1154-
of this software and associated documentation files (the "Software"), to deal
1155-
in the Software without restriction, including without limitation the rights
1156-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1157-
copies of the Software, and to permit persons to whom the Software is
1158-
furnished to do so, subject to the following conditions:
1159-
1160-
The above copyright notice and this permission notice shall be included in all
1161-
copies or substantial portions of the Software.
1162-
1163-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1164-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1165-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1166-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1167-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1168-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1169-
SOFTWARE.
1170-
1171-
11721146
----------------
11731147

11741148
** fast-deep-equal@3.1.3 - https://www.npmjs.com/package/fast-deep-equal/v/3.1.3 | MIT
@@ -2300,60 +2274,6 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
23002274
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23012275

23022276

2303-
----------------
2304-
2305-
** p-finally@1.0.0 - https://www.npmjs.com/package/p-finally/v/1.0.0 | MIT
2306-
The MIT License (MIT)
2307-
2308-
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
2309-
2310-
Permission is hereby granted, free of charge, to any person obtaining a copy
2311-
of this software and associated documentation files (the "Software"), to deal
2312-
in the Software without restriction, including without limitation the rights
2313-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2314-
copies of the Software, and to permit persons to whom the Software is
2315-
furnished to do so, subject to the following conditions:
2316-
2317-
The above copyright notice and this permission notice shall be included in
2318-
all copies or substantial portions of the Software.
2319-
2320-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2321-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2322-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2323-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2324-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2325-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2326-
THE SOFTWARE.
2327-
2328-
2329-
----------------
2330-
2331-
** p-queue@6.6.2 - https://www.npmjs.com/package/p-queue/v/6.6.2 | MIT
2332-
MIT License
2333-
2334-
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
2335-
2336-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2337-
2338-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2339-
2340-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2341-
2342-
2343-
----------------
2344-
2345-
** p-timeout@3.2.0 - https://www.npmjs.com/package/p-timeout/v/3.2.0 | MIT
2346-
MIT License
2347-
2348-
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
2349-
2350-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2351-
2352-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2353-
2354-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2355-
2356-
23572277
----------------
23582278

23592279
** pac-proxy-agent@5.0.0 - https://www.npmjs.com/package/pac-proxy-agent/v/5.0.0 | MIT

‎packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts

+1
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export class SdkProvider {
176176
options?: CredentialsOptions,
177177
): Promise<SdkForEnvironment> {
178178
const env = await this.resolveEnvironment(environment);
179+
179180
const baseCreds = await this.obtainBaseCredentials(env.account, mode);
180181

181182
// At this point, we need at least SOME credentials

‎packages/aws-cdk/lib/api/cxapp/exec.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom
130130
*/
131131
export function createAssembly(appDir: string) {
132132
try {
133-
return new cxapi.CloudAssembly(appDir);
133+
return new cxapi.CloudAssembly(appDir, {
134+
// We sort as we deploy
135+
topoSort: false,
136+
});
134137
} catch (error: any) {
135138
if (error.message.includes(cxschema.VERSION_MISMATCH)) {
136139
// this means the CLI version is too old.

0 commit comments

Comments
 (0)