-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
asset-publishing.ts
195 lines (170 loc) · 5.77 KB
/
asset-publishing.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import * as cxapi from '@aws-cdk/cx-api';
import * as AWS from 'aws-sdk';
import * as cdk_assets from 'cdk-assets';
import { Mode } from '../api/aws-auth/credentials';
import { ISDK } from '../api/aws-auth/sdk';
import { SdkProvider } from '../api/aws-auth/sdk-provider';
import { debug, error, print } from '../logging';
export interface PublishAssetsOptions {
/**
* Print progress at 'debug' level
*/
readonly quiet?: boolean;
/**
* Whether to build assets before publishing.
*
* @default true To remain backward compatible.
*/
readonly buildAssets?: boolean;
}
/**
* Use cdk-assets to publish all assets in the given manifest.
*/
export async function publishAssets(
manifest: cdk_assets.AssetManifest,
sdk: SdkProvider,
targetEnv: cxapi.Environment,
options: PublishAssetsOptions = {},
) {
// This shouldn't really happen (it's a programming error), but we don't have
// the types here to guide us. Do an runtime validation to be super super sure.
if (
targetEnv.account === undefined ||
targetEnv.account === cxapi.UNKNOWN_ACCOUNT ||
targetEnv.region === undefined ||
targetEnv.account === cxapi.UNKNOWN_REGION
) {
throw new Error(`Asset publishing requires resolved account and region, got ${JSON.stringify(targetEnv)}`);
}
const publisher = new cdk_assets.AssetPublishing(manifest, {
aws: new PublishingAws(sdk, targetEnv),
progressListener: new PublishingProgressListener(options.quiet ?? false),
throwOnError: false,
publishInParallel: true,
buildAssets: options.buildAssets ?? true,
publishAssets: true,
});
await publisher.publish();
if (publisher.hasFailures) {
throw new Error('Failed to publish one or more assets. See the error messages above for more information.');
}
}
export interface BuildAssetsOptions {
/**
* Print progress at 'debug' level
*/
readonly quiet?: boolean;
}
/**
* Use cdk-assets to build all assets in the given manifest.
*/
export async function buildAssets(
manifest: cdk_assets.AssetManifest,
sdk: SdkProvider,
targetEnv: cxapi.Environment,
options: BuildAssetsOptions = {},
) {
// This shouldn't really happen (it's a programming error), but we don't have
// the types here to guide us. Do an runtime validation to be super super sure.
if (
targetEnv.account === undefined ||
targetEnv.account === cxapi.UNKNOWN_ACCOUNT ||
targetEnv.region === undefined ||
targetEnv.account === cxapi.UNKNOWN_REGION
) {
throw new Error(`Asset building requires resolved account and region, got ${JSON.stringify(targetEnv)}`);
}
const publisher = new cdk_assets.AssetPublishing(manifest, {
aws: new PublishingAws(sdk, targetEnv),
progressListener: new PublishingProgressListener(options.quiet ?? false),
throwOnError: false,
publishInParallel: true,
buildAssets: true,
publishAssets: false,
});
await publisher.publish();
if (publisher.hasFailures) {
throw new Error('Failed to build one or more assets. See the error messages above for more information.');
}
}
class PublishingAws implements cdk_assets.IAws {
private sdkCache: Map<String, ISDK> = new Map();
constructor(
/**
* The base SDK to work with
*/
private readonly aws: SdkProvider,
/**
* Environment where the stack we're deploying is going
*/
private readonly targetEnv: cxapi.Environment) {
}
public async discoverPartition(): Promise<string> {
return (await this.aws.baseCredentialsPartition(this.targetEnv, Mode.ForWriting)) ?? 'aws';
}
public async discoverDefaultRegion(): Promise<string> {
return this.targetEnv.region;
}
public async discoverCurrentAccount(): Promise<cdk_assets.Account> {
const account = await this.aws.defaultAccount();
return account ?? {
accountId: '<unknown account>',
partition: 'aws',
};
}
public async discoverTargetAccount(options: cdk_assets.ClientOptions): Promise<cdk_assets.Account> {
return (await this.sdk(options)).currentAccount();
}
public async s3Client(options: cdk_assets.ClientOptions): Promise<AWS.S3> {
return (await this.sdk(options)).s3();
}
public async ecrClient(options: cdk_assets.ClientOptions): Promise<AWS.ECR> {
return (await this.sdk(options)).ecr();
}
public async secretsManagerClient(options: cdk_assets.ClientOptions): Promise<AWS.SecretsManager> {
return (await this.sdk(options)).secretsManager();
}
/**
* Get an SDK appropriate for the given client options
*/
private async sdk(options: cdk_assets.ClientOptions): Promise<ISDK> {
const env = {
...this.targetEnv,
region: options.region ?? this.targetEnv.region, // Default: same region as the stack
};
const cacheKey = JSON.stringify({
env, // region, name, account
assumeRuleArn: options.assumeRoleArn,
assumeRoleExternalId: options.assumeRoleExternalId,
});
const maybeSdk = this.sdkCache.get(cacheKey);
if (maybeSdk) {
return maybeSdk;
}
const sdk = (await this.aws.forEnvironment(env, Mode.ForWriting, {
assumeRoleArn: options.assumeRoleArn,
assumeRoleExternalId: options.assumeRoleExternalId,
})).sdk;
this.sdkCache.set(cacheKey, sdk);
return sdk;
}
}
const EVENT_TO_LOGGER: Record<cdk_assets.EventType, (x: string) => void> = {
build: debug,
cached: debug,
check: debug,
debug,
fail: error,
found: debug,
start: print,
success: print,
upload: debug,
};
class PublishingProgressListener implements cdk_assets.IPublishProgressListener {
constructor(private readonly quiet: boolean) {
}
public onPublishEvent(type: cdk_assets.EventType, event: cdk_assets.IPublishProgress): void {
const handler = this.quiet && type !== 'fail' ? debug : EVENT_TO_LOGGER[type];
handler(`[${event.percentComplete}%] ${type}: ${event.message}`);
}
}