Skip to content

Commit 14458da

Browse files
committed
feat: assets can have a display name
Add a new field to the asset schema: `displayName`. If supplied, it will be used instead of the asset ID for display purposes in `cdk-assets` and in the CLI. This adds support for display names to the contract and to the CLI. A future PR in `aws-cdk-lib` will make it possible to configure display names on Asset objects, and add the same support to CDK Pipelines.
1 parent c913ff1 commit 14458da

File tree

9 files changed

+100
-21
lines changed

9 files changed

+100
-21
lines changed

packages/@aws-cdk/cloud-assembly-schema/lib/assets/docker-image-asset.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@ import { AwsDestination } from './aws-destination';
55
*/
66
export interface DockerImageAsset {
77
/**
8-
* Source description for file assets
8+
* A display name for this asset
9+
*
10+
* @default - The identifier will be used as the display name
11+
*/
12+
readonly displayName?: string;
13+
14+
/**
15+
* Source description for container assets
916
*/
1017
readonly source: DockerImageSource;
1118

1219
/**
13-
* Destinations for this file asset
20+
* Destinations for this container asset
1421
*/
1522
readonly destinations: { [id: string]: DockerImageDestination };
1623
}

packages/@aws-cdk/cloud-assembly-schema/lib/assets/file-asset.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { AwsDestination } from './aws-destination';
44
* A file asset
55
*/
66
export interface FileAsset {
7+
/**
8+
* A display name for this asset
9+
*
10+
* @default - The identifier will be used as the display name
11+
*/
12+
readonly displayName?: string;
13+
714
/**
815
* Source description for file assets
916
*/

packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
"description": "A file asset",
3333
"type": "object",
3434
"properties": {
35+
"displayName": {
36+
"description": "A display name for this asset (Default - The identifier will be used as the display name)",
37+
"type": "string"
38+
},
3539
"source": {
3640
"$ref": "#/definitions/FileSource",
3741
"description": "Source description for file assets"
@@ -113,12 +117,16 @@
113117
"description": "A file asset",
114118
"type": "object",
115119
"properties": {
120+
"displayName": {
121+
"description": "A display name for this asset (Default - The identifier will be used as the display name)",
122+
"type": "string"
123+
},
116124
"source": {
117125
"$ref": "#/definitions/DockerImageSource",
118-
"description": "Source description for file assets"
126+
"description": "Source description for container assets"
119127
},
120128
"destinations": {
121-
"description": "Destinations for this file asset",
129+
"description": "Destinations for this container asset",
122130
"type": "object",
123131
"additionalProperties": {
124132
"$ref": "#/definitions/DockerImageDestination"
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"schemaHash": "1063bafc562a50e6dafeb8664275f5d61ff631542d47992963bfaf5cf277ab6e",
3-
"revision": 40
2+
"schemaHash": "ecd34aeb9a63b778241f095d26b8b7aeafd69cefef0498a331a4e0d81025d96a",
3+
"revision": 41
44
}

packages/aws-cdk/lib/api/deployments/deployments.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ export class Deployments {
634634
const publisher = this.cachedPublisher(assetManifest, resolvedEnvironment, options.stackName);
635635
await publisher.buildEntry(asset);
636636
if (publisher.hasFailures) {
637-
throw new ToolkitError(`Failed to build asset ${asset.id}`);
637+
throw new ToolkitError(`Failed to build asset ${asset.displayName(false)}`);
638638
}
639639
}
640640

@@ -652,7 +652,7 @@ export class Deployments {
652652
const publisher = this.cachedPublisher(assetManifest, stackEnv, options.stackName);
653653
await publisher.publishEntry(asset, { allowCrossAccount: await this.allowCrossAccountAssetPublishingForEnv(options.stack) });
654654
if (publisher.hasFailures) {
655-
throw new ToolkitError(`Failed to publish asset ${asset.id}`);
655+
throw new ToolkitError(`Failed to publish asset ${asset.displayName(true)}`);
656656
}
657657
}
658658

packages/aws-cdk/lib/api/work-graph/work-graph-builder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class WorkGraphBuilder {
6262
const node: AssetBuildNode = {
6363
type: 'asset-build',
6464
id: buildId,
65-
note: assetId,
65+
note: asset.displayName(false),
6666
dependencies: new Set([
6767
...this.stackArtifactIds(assetManifestArtifact.dependencies),
6868
// If we disable prebuild, then assets inherit (stack) dependencies from their parent stack
@@ -83,7 +83,7 @@ export class WorkGraphBuilder {
8383
this.graph.addNodes({
8484
type: 'asset-publish',
8585
id: publishId,
86-
note: `${asset.id}`,
86+
note: asset.displayName(true),
8787
dependencies: new Set([
8888
buildId,
8989
]),

packages/cdk-assets/lib/asset-manifest.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ export class AssetManifest {
144144
}
145145

146146
function makeEntries<A, B, C>(
147-
assets: Record<string, { source: A; destinations: Record<string, B> }>,
148-
ctor: new (id: DestinationIdentifier, source: A, destination: B) => C,
147+
assets: Record<string, { source: A; displayName?: string; destinations: Record<string, B> }>,
148+
ctor: new (id: DestinationIdentifier, displayName: string | undefined, source: A, destination: B) => C,
149149
): C[] {
150150
const ret = new Array<C>();
151151
for (const [assetId, asset] of Object.entries(assets)) {
152152
for (const [destId, destination] of Object.entries(asset.destinations)) {
153-
ret.push(new ctor(new DestinationIdentifier(assetId, destId), asset.source, destination));
153+
ret.push(new ctor(new DestinationIdentifier(assetId, destId), asset.displayName, asset.source, destination));
154154
}
155155
}
156156
return ret;
@@ -183,6 +183,20 @@ export interface IManifestEntry {
183183
* Type-dependent destination data
184184
*/
185185
readonly genericDestination: unknown;
186+
187+
/**
188+
* Return a display name for this asset
189+
*
190+
* The `includeDestination` parameter controls whether or not to include the
191+
* destination ID in the display name.
192+
*
193+
* - Pass `false` if you are displaying notifications about building the
194+
* asset, or if you are describing the work of building the asset and publishing
195+
* to all destinations at the same time.
196+
* - Pass `true` if you are displaying notifications about publishing to a
197+
* specific destination.
198+
*/
199+
displayName(includeDestination: boolean): string;
186200
}
187201

188202
/**
@@ -196,6 +210,7 @@ export class FileManifestEntry implements IManifestEntry {
196210
constructor(
197211
/** Identifier for this asset */
198212
public readonly id: DestinationIdentifier,
213+
private readonly _displayName: string | undefined,
199214
/** Source of the file asset */
200215
public readonly source: FileSource,
201216
/** Destination for the file asset */
@@ -204,6 +219,14 @@ export class FileManifestEntry implements IManifestEntry {
204219
this.genericSource = source;
205220
this.genericDestination = destination;
206221
}
222+
223+
public displayName(includeDestination: boolean): string {
224+
if (includeDestination) {
225+
return this._displayName ? `${this._displayName} (${this.id.destinationId})` : `${this.id}`;
226+
} else {
227+
return this._displayName ? this._displayName : this.id.assetId;
228+
}
229+
}
207230
}
208231

209232
/**
@@ -217,6 +240,7 @@ export class DockerImageManifestEntry implements IManifestEntry {
217240
constructor(
218241
/** Identifier for this asset */
219242
public readonly id: DestinationIdentifier,
243+
private readonly _displayName: string | undefined,
220244
/** Source of the file asset */
221245
public readonly source: DockerImageSource,
222246
/** Destination for the file asset */
@@ -225,13 +249,28 @@ export class DockerImageManifestEntry implements IManifestEntry {
225249
this.genericSource = source;
226250
this.genericDestination = destination;
227251
}
252+
253+
public displayName(includeDestination: boolean): string {
254+
if (includeDestination) {
255+
return this._displayName ? `${this._displayName} (${this.id.destinationId})` : `${this.id}`;
256+
} else {
257+
return this._displayName ? this._displayName : this.id.assetId;
258+
}
259+
}
228260
}
229261

230262
/**
231263
* Identify an asset destination in an asset manifest
232264
*
233-
* When stringified, this will be a combination of the source
234-
* and destination IDs.
265+
* This class is used to identify both an asset to be built as well as a
266+
* destination where an asset will be published. However, when reasoning about
267+
* building assets the destination part can be ignored, because the same asset
268+
* being sent to multiple destinations will only need to be built once and their
269+
* assetIds are all the same.
270+
*
271+
* When stringified, this will be a combination of the source and destination
272+
* IDs; if a string representation of the source is necessary, use `id.assetId`
273+
* instead.
235274
*/
236275
export class DestinationIdentifier {
237276
/**

packages/cdk-assets/lib/publishing.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export class AssetPublishing implements IPublishProgress {
153153
*/
154154
public async buildEntry(asset: IManifestEntry) {
155155
try {
156-
if (this.progressEvent(EventType.START, `Building ${asset.id}`)) {
156+
if (this.progressEvent(EventType.START, `Building ${asset.displayName(false)}`)) {
157157
return false;
158158
}
159159

@@ -165,7 +165,7 @@ export class AssetPublishing implements IPublishProgress {
165165
}
166166

167167
this.completedOperations++;
168-
if (this.progressEvent(EventType.SUCCESS, `Built ${asset.id}`)) {
168+
if (this.progressEvent(EventType.SUCCESS, `Built ${asset.displayName(false)}`)) {
169169
return false;
170170
}
171171
} catch (e: any) {
@@ -184,7 +184,7 @@ export class AssetPublishing implements IPublishProgress {
184184
*/
185185
public async publishEntry(asset: IManifestEntry, options: PublishOptions = {}) {
186186
try {
187-
if (this.progressEvent(EventType.START, `Publishing ${asset.id}`)) {
187+
if (this.progressEvent(EventType.START, `Publishing ${asset.displayName(true)}`)) {
188188
return false;
189189
}
190190

@@ -196,7 +196,7 @@ export class AssetPublishing implements IPublishProgress {
196196
}
197197

198198
this.completedOperations++;
199-
if (this.progressEvent(EventType.SUCCESS, `Published ${asset.id}`)) {
199+
if (this.progressEvent(EventType.SUCCESS, `Published ${asset.displayName(true)}`)) {
200200
return false;
201201
}
202202
} catch (e: any) {
@@ -225,7 +225,7 @@ export class AssetPublishing implements IPublishProgress {
225225
*/
226226
private async publishAsset(asset: IManifestEntry, options: PublishOptions = {}) {
227227
try {
228-
if (this.progressEvent(EventType.START, `Publishing ${asset.id}`)) {
228+
if (this.progressEvent(EventType.START, `Publishing ${asset.displayName(true)}`)) {
229229
return false;
230230
}
231231

@@ -244,7 +244,7 @@ export class AssetPublishing implements IPublishProgress {
244244
}
245245

246246
this.completedOperations++;
247-
if (this.progressEvent(EventType.SUCCESS, `Published ${asset.id}`)) {
247+
if (this.progressEvent(EventType.SUCCESS, `Published ${asset.displayName(true)}`)) {
248248
return false;
249249
}
250250
} catch (e: any) {

packages/cdk-assets/test/manifest.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,25 @@ test('.entries() iterates over all destinations', () => {
6060
expect(manifest.entries).toEqual([
6161
new FileManifestEntry(
6262
new DestinationIdentifier('asset1', 'dest1'),
63+
undefined,
6364
{ path: 'S1' },
6465
{ bucketName: 'D1', objectKey: 'X' },
6566
),
6667
new FileManifestEntry(
6768
new DestinationIdentifier('asset1', 'dest2'),
69+
undefined,
6870
{ path: 'S1' },
6971
{ bucketName: 'D2', objectKey: 'X' },
7072
),
7173
new DockerImageManifestEntry(
7274
new DestinationIdentifier('asset2', 'dest1'),
75+
undefined,
7376
{ directory: 'S2' },
7477
{ repositoryName: 'D3', imageTag: 'X' },
7578
),
7679
new DockerImageManifestEntry(
7780
new DestinationIdentifier('asset2', 'dest2'),
81+
undefined,
7882
{ directory: 'S2' },
7983
{ repositoryName: 'D4', imageTag: 'X' },
8084
),
@@ -136,6 +140,20 @@ test('parse *:DEST the same as :DEST', () => {
136140
expect(DestinationPattern.parse('*:a')).toEqual(DestinationPattern.parse(':a'));
137141
});
138142

143+
test.each([
144+
['Display Name', false, 'Display Name'],
145+
['Display Name', true, 'Display Name (dest2)'],
146+
[undefined, false, 'asset1'],
147+
[undefined, true, 'asset1:dest2'],
148+
])('with displayName %p and including destination %p => %p', (displayName, includeDestination, expected) => {
149+
expect(new FileManifestEntry(
150+
new DestinationIdentifier('asset1', 'dest2'),
151+
displayName,
152+
{ path: 'S1' },
153+
{ bucketName: 'D2', objectKey: 'X' },
154+
).displayName(includeDestination)).toEqual(expected);
155+
});
156+
139157
function f(obj: unknown, ...keys: string[]): any {
140158
for (const k of keys) {
141159
if (typeof obj === 'object' && obj !== null && k in obj) {

0 commit comments

Comments
 (0)