sst 2.18.4 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cdk/asset-publishing.d.ts +74 -0
- package/cdk/asset-publishing.js +147 -0
- package/cdk/deployments.d.ts +1 -1
- package/cdk/deployments.js +1 -3
- package/cdk-assets/private/asset-handler.d.ts +29 -0
- package/cdk-assets/private/asset-handler.js +1 -0
- package/cdk-assets/private/docker.d.ts +94 -0
- package/cdk-assets/private/docker.js +237 -0
- package/cdk-assets/private/handlers/container-images.d.ts +22 -0
- package/cdk-assets/private/handlers/container-images.js +231 -0
- package/cdk-assets/private/handlers/index.d.ts +3 -0
- package/cdk-assets/private/handlers/index.js +18 -0
- package/cdk-assets/publishing.d.ts +113 -0
- package/cdk-assets/publishing.js +194 -0
- package/cli/commands/dev.js +25 -1
- package/constructs/Function.d.ts +4 -0
- package/constructs/Function.js +43 -11
- package/package.json +1 -1
- package/runtime/handlers/container.d.ts +1 -0
- package/runtime/handlers/container.js +124 -0
- package/runtime/handlers.d.ts +4 -0
- package/runtime/handlers.js +1 -0
- package/runtime/workers.js +1 -0
- package/sst.mjs +1218 -318
- package/stacks/synth.js +2 -0
- package/support/bridge/Dockerfile +3 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { EventType } from "cdk-assets/lib/progress.js";
|
|
3
|
+
import { replaceAwsPlaceholders } from "cdk-assets/lib/private/placeholders.js";
|
|
4
|
+
import { shell } from "cdk-assets/lib/private/shell.js";
|
|
5
|
+
export class ContainerImageAssetHandler {
|
|
6
|
+
workDir;
|
|
7
|
+
asset;
|
|
8
|
+
host;
|
|
9
|
+
options;
|
|
10
|
+
init;
|
|
11
|
+
constructor(workDir, asset, host,
|
|
12
|
+
// TODO: remove after PR is merged
|
|
13
|
+
options) {
|
|
14
|
+
this.workDir = workDir;
|
|
15
|
+
this.asset = asset;
|
|
16
|
+
this.host = host;
|
|
17
|
+
this.options = options;
|
|
18
|
+
}
|
|
19
|
+
async build() {
|
|
20
|
+
const initOnce = await this.initOnce();
|
|
21
|
+
if (initOnce.destinationAlreadyExists) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (this.host.aborted) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const dockerForBuilding = await this.host.dockerFactory.forBuild({
|
|
28
|
+
repoUri: initOnce.repoUri,
|
|
29
|
+
logger: (m) => this.host.emitMessage(EventType.DEBUG, m),
|
|
30
|
+
ecr: initOnce.ecr,
|
|
31
|
+
});
|
|
32
|
+
const builder = new ContainerImageBuilder(dockerForBuilding, this.workDir, this.asset, this.host,
|
|
33
|
+
// TODO: remove after PR is merged
|
|
34
|
+
{
|
|
35
|
+
quiet: this.options.quiet,
|
|
36
|
+
});
|
|
37
|
+
const localTagName = await builder.build();
|
|
38
|
+
if (localTagName === undefined || this.host.aborted) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (this.host.aborted) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
await dockerForBuilding.tag(localTagName, initOnce.imageUri);
|
|
45
|
+
}
|
|
46
|
+
async isPublished() {
|
|
47
|
+
try {
|
|
48
|
+
const initOnce = await this.initOnce({ quiet: true });
|
|
49
|
+
return initOnce.destinationAlreadyExists;
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
this.host.emitMessage(EventType.DEBUG, `${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
async publish() {
|
|
57
|
+
const initOnce = await this.initOnce();
|
|
58
|
+
if (initOnce.destinationAlreadyExists) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (this.host.aborted) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const dockerForPushing = await this.host.dockerFactory.forEcrPush({
|
|
65
|
+
repoUri: initOnce.repoUri,
|
|
66
|
+
logger: (m) => this.host.emitMessage(EventType.DEBUG, m),
|
|
67
|
+
ecr: initOnce.ecr,
|
|
68
|
+
});
|
|
69
|
+
if (this.host.aborted) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.host.emitMessage(EventType.UPLOAD, `Push ${initOnce.imageUri}`);
|
|
73
|
+
// TODO: remove after PR is merged
|
|
74
|
+
await dockerForPushing.push({
|
|
75
|
+
tag: initOnce.imageUri,
|
|
76
|
+
quiet: this.options.quiet,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async initOnce(options = {}) {
|
|
80
|
+
if (this.init) {
|
|
81
|
+
return this.init;
|
|
82
|
+
}
|
|
83
|
+
const destination = await replaceAwsPlaceholders(this.asset.destination, this.host.aws);
|
|
84
|
+
const ecr = await this.host.aws.ecrClient({
|
|
85
|
+
...destination,
|
|
86
|
+
quiet: options.quiet,
|
|
87
|
+
});
|
|
88
|
+
const account = async () => (await this.host.aws.discoverCurrentAccount())?.accountId;
|
|
89
|
+
const repoUri = await repositoryUri(ecr, destination.repositoryName);
|
|
90
|
+
if (!repoUri) {
|
|
91
|
+
throw new Error(`No ECR repository named '${destination.repositoryName}' in account ${await account()}. Is this account bootstrapped?`);
|
|
92
|
+
}
|
|
93
|
+
const imageUri = `${repoUri}:${destination.imageTag}`;
|
|
94
|
+
this.init = {
|
|
95
|
+
imageUri,
|
|
96
|
+
ecr,
|
|
97
|
+
repoUri,
|
|
98
|
+
destinationAlreadyExists: await this.destinationAlreadyExists(ecr, destination, imageUri),
|
|
99
|
+
};
|
|
100
|
+
return this.init;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check whether the image already exists in the ECR repo
|
|
104
|
+
*
|
|
105
|
+
* Use the fields from the destination to do the actual check. The imageUri
|
|
106
|
+
* should correspond to that, but is only used to print Docker image location
|
|
107
|
+
* for user benefit (the format is slightly different).
|
|
108
|
+
*/
|
|
109
|
+
async destinationAlreadyExists(ecr, destination, imageUri) {
|
|
110
|
+
this.host.emitMessage(EventType.CHECK, `Check ${imageUri}`);
|
|
111
|
+
if (await imageExists(ecr, destination.repositoryName, destination.imageTag)) {
|
|
112
|
+
this.host.emitMessage(EventType.FOUND, `Found ${imageUri}`);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
class ContainerImageBuilder {
|
|
119
|
+
docker;
|
|
120
|
+
workDir;
|
|
121
|
+
asset;
|
|
122
|
+
host;
|
|
123
|
+
options;
|
|
124
|
+
constructor(docker, workDir, asset, host,
|
|
125
|
+
// TODO: remove after PR is merged
|
|
126
|
+
options) {
|
|
127
|
+
this.docker = docker;
|
|
128
|
+
this.workDir = workDir;
|
|
129
|
+
this.asset = asset;
|
|
130
|
+
this.host = host;
|
|
131
|
+
this.options = options;
|
|
132
|
+
}
|
|
133
|
+
async build() {
|
|
134
|
+
return this.asset.source.executable
|
|
135
|
+
? this.buildExternalAsset(this.asset.source.executable)
|
|
136
|
+
: this.buildDirectoryAsset();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Build a (local) Docker asset from a directory with a Dockerfile
|
|
140
|
+
*
|
|
141
|
+
* Tags under a deterministic, unique, local identifier wich will skip
|
|
142
|
+
* the build if it already exists.
|
|
143
|
+
*/
|
|
144
|
+
async buildDirectoryAsset() {
|
|
145
|
+
const localTagName = `cdkasset-${this.asset.id.assetId.toLowerCase()}`;
|
|
146
|
+
if (!(await this.isImageCached(localTagName))) {
|
|
147
|
+
if (this.host.aborted) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
await this.buildImage(localTagName);
|
|
151
|
+
}
|
|
152
|
+
return localTagName;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Build a (local) Docker asset by running an external command
|
|
156
|
+
*
|
|
157
|
+
* External command is responsible for deduplicating the build if possible,
|
|
158
|
+
* and is expected to return the generated image identifier on stdout.
|
|
159
|
+
*/
|
|
160
|
+
async buildExternalAsset(executable, cwd) {
|
|
161
|
+
const assetPath = cwd ?? this.workDir;
|
|
162
|
+
this.host.emitMessage(EventType.BUILD, `Building Docker image using command '${executable}'`);
|
|
163
|
+
if (this.host.aborted) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
return (await shell(executable, { cwd: assetPath, quiet: true })).trim();
|
|
167
|
+
}
|
|
168
|
+
async buildImage(localTagName) {
|
|
169
|
+
const source = this.asset.source;
|
|
170
|
+
if (!source.directory) {
|
|
171
|
+
throw new Error(`'directory' is expected in the DockerImage asset source, got: ${JSON.stringify(source)}`);
|
|
172
|
+
}
|
|
173
|
+
const fullPath = path.resolve(this.workDir, source.directory);
|
|
174
|
+
this.host.emitMessage(EventType.BUILD, `Building Docker image at ${fullPath}`);
|
|
175
|
+
await this.docker.build({
|
|
176
|
+
directory: fullPath,
|
|
177
|
+
tag: localTagName,
|
|
178
|
+
buildArgs: source.dockerBuildArgs,
|
|
179
|
+
buildSecrets: source.dockerBuildSecrets,
|
|
180
|
+
target: source.dockerBuildTarget,
|
|
181
|
+
file: source.dockerFile,
|
|
182
|
+
networkMode: source.networkMode,
|
|
183
|
+
platform: source.platform,
|
|
184
|
+
outputs: source.dockerOutputs,
|
|
185
|
+
cacheFrom: source.cacheFrom,
|
|
186
|
+
cacheTo: source.cacheTo,
|
|
187
|
+
// TODO: remove after PR is merged
|
|
188
|
+
quiet: this.options.quiet,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async isImageCached(localTagName) {
|
|
192
|
+
if (await this.docker.exists(localTagName)) {
|
|
193
|
+
this.host.emitMessage(EventType.CACHED, `Cached ${localTagName}`);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function imageExists(ecr, repositoryName, imageTag) {
|
|
200
|
+
try {
|
|
201
|
+
await ecr
|
|
202
|
+
.describeImages({ repositoryName, imageIds: [{ imageTag }] })
|
|
203
|
+
.promise();
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
if (e.code !== "ImageNotFoundException") {
|
|
208
|
+
throw e;
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Return the URI for the repository with the given name
|
|
215
|
+
*
|
|
216
|
+
* Returns undefined if the repository does not exist.
|
|
217
|
+
*/
|
|
218
|
+
async function repositoryUri(ecr, repositoryName) {
|
|
219
|
+
try {
|
|
220
|
+
const response = await ecr
|
|
221
|
+
.describeRepositories({ repositoryNames: [repositoryName] })
|
|
222
|
+
.promise();
|
|
223
|
+
return (response.repositories || [])[0]?.repositoryUri;
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
if (e.code !== "RepositoryNotFoundException") {
|
|
227
|
+
throw e;
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { AssetManifest, IManifestEntry } from "cdk-assets/lib/asset-manifest.js";
|
|
2
|
+
import { IAssetHandler, IHandlerHost, IHandlerOptions } from "../asset-handler.js";
|
|
3
|
+
export declare function makeAssetHandler(manifest: AssetManifest, asset: IManifestEntry, host: IHandlerHost, options: IHandlerOptions): IAssetHandler;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ContainerImageAssetHandler } from "./container-images.js";
|
|
2
|
+
import { FileAssetHandler } from "cdk-assets/lib/private/handlers/files.js";
|
|
3
|
+
import { DockerImageManifestEntry, FileManifestEntry, } from "cdk-assets/lib/asset-manifest.js";
|
|
4
|
+
export function makeAssetHandler(manifest, asset, host,
|
|
5
|
+
// TODO: remove after PR is merged
|
|
6
|
+
options) {
|
|
7
|
+
if (asset instanceof FileManifestEntry) {
|
|
8
|
+
// TODO: remove after PR is merged
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
return new FileAssetHandler(manifest.directory, asset, host);
|
|
11
|
+
}
|
|
12
|
+
if (asset instanceof DockerImageManifestEntry) {
|
|
13
|
+
return new ContainerImageAssetHandler(manifest.directory, asset, host,
|
|
14
|
+
// TODO: remove after PR is merged
|
|
15
|
+
options);
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`Unrecognized asset type: '${asset}'`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { AssetManifest, IManifestEntry } from "cdk-assets/lib/asset-manifest.js";
|
|
2
|
+
import { IAws } from "cdk-assets/lib/aws.js";
|
|
3
|
+
import { IPublishProgress, IPublishProgressListener } from "cdk-assets/lib/progress.js";
|
|
4
|
+
export interface AssetPublishingOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Entry point for AWS client
|
|
7
|
+
*/
|
|
8
|
+
readonly aws: IAws;
|
|
9
|
+
/**
|
|
10
|
+
* Listener for progress events
|
|
11
|
+
*
|
|
12
|
+
* @default No listener
|
|
13
|
+
*/
|
|
14
|
+
readonly progressListener?: IPublishProgressListener;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to throw at the end if there were errors
|
|
17
|
+
*
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
readonly throwOnError?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether to publish in parallel, when 'publish()' is called
|
|
23
|
+
*
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
readonly publishInParallel?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to build assets, when 'publish()' is called
|
|
29
|
+
*
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
readonly buildAssets?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether to publish assets, when 'publish()' is called
|
|
35
|
+
*
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
readonly publishAssets?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Whether to print publishing logs
|
|
41
|
+
*
|
|
42
|
+
* @default true
|
|
43
|
+
*/
|
|
44
|
+
readonly quiet?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A failure to publish an asset
|
|
48
|
+
*/
|
|
49
|
+
export interface FailedAsset {
|
|
50
|
+
/**
|
|
51
|
+
* The asset that failed to publish
|
|
52
|
+
*/
|
|
53
|
+
readonly asset: IManifestEntry;
|
|
54
|
+
/**
|
|
55
|
+
* The failure that occurred
|
|
56
|
+
*/
|
|
57
|
+
readonly error: Error;
|
|
58
|
+
}
|
|
59
|
+
export declare class AssetPublishing implements IPublishProgress {
|
|
60
|
+
private readonly manifest;
|
|
61
|
+
private readonly options;
|
|
62
|
+
/**
|
|
63
|
+
* The message for the IPublishProgress interface
|
|
64
|
+
*/
|
|
65
|
+
message: string;
|
|
66
|
+
/**
|
|
67
|
+
* The current asset for the IPublishProgress interface
|
|
68
|
+
*/
|
|
69
|
+
currentAsset?: IManifestEntry;
|
|
70
|
+
readonly failures: FailedAsset[];
|
|
71
|
+
private readonly assets;
|
|
72
|
+
private readonly totalOperations;
|
|
73
|
+
private completedOperations;
|
|
74
|
+
private aborted;
|
|
75
|
+
private readonly handlerHost;
|
|
76
|
+
private readonly publishInParallel;
|
|
77
|
+
private readonly buildAssets;
|
|
78
|
+
private readonly publishAssets;
|
|
79
|
+
private readonly handlerCache;
|
|
80
|
+
constructor(manifest: AssetManifest, options: AssetPublishingOptions);
|
|
81
|
+
/**
|
|
82
|
+
* Publish all assets from the manifest
|
|
83
|
+
*/
|
|
84
|
+
publish(): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Build a single asset from the manifest
|
|
87
|
+
*/
|
|
88
|
+
buildEntry(asset: IManifestEntry): Promise<boolean>;
|
|
89
|
+
/**
|
|
90
|
+
* Publish a single asset from the manifest
|
|
91
|
+
*/
|
|
92
|
+
publishEntry(asset: IManifestEntry): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Return whether a single asset is published
|
|
95
|
+
*/
|
|
96
|
+
isEntryPublished(asset: IManifestEntry): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* publish an asset (used by 'publish()')
|
|
99
|
+
* @param asset The asset to publish
|
|
100
|
+
* @returns false when publishing should stop
|
|
101
|
+
*/
|
|
102
|
+
private publishAsset;
|
|
103
|
+
get percentComplete(): number;
|
|
104
|
+
abort(): void;
|
|
105
|
+
get hasFailures(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Publish a progress event to the listener, if present.
|
|
108
|
+
*
|
|
109
|
+
* Returns whether an abort is requested. Helper to get rid of repetitive code in publish().
|
|
110
|
+
*/
|
|
111
|
+
private progressEvent;
|
|
112
|
+
private assetHandler;
|
|
113
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { DockerFactory } from "./private/docker.js";
|
|
2
|
+
import { makeAssetHandler } from "./private/handlers/index.js";
|
|
3
|
+
import { EventType, } from "cdk-assets/lib/progress.js";
|
|
4
|
+
export class AssetPublishing {
|
|
5
|
+
manifest;
|
|
6
|
+
options;
|
|
7
|
+
/**
|
|
8
|
+
* The message for the IPublishProgress interface
|
|
9
|
+
*/
|
|
10
|
+
message = "Starting";
|
|
11
|
+
/**
|
|
12
|
+
* The current asset for the IPublishProgress interface
|
|
13
|
+
*/
|
|
14
|
+
currentAsset;
|
|
15
|
+
failures = new Array();
|
|
16
|
+
assets;
|
|
17
|
+
totalOperations;
|
|
18
|
+
completedOperations = 0;
|
|
19
|
+
aborted = false;
|
|
20
|
+
handlerHost;
|
|
21
|
+
publishInParallel;
|
|
22
|
+
buildAssets;
|
|
23
|
+
publishAssets;
|
|
24
|
+
handlerCache = new Map();
|
|
25
|
+
constructor(manifest, options) {
|
|
26
|
+
this.manifest = manifest;
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.assets = manifest.entries;
|
|
29
|
+
this.totalOperations = this.assets.length;
|
|
30
|
+
this.publishInParallel = options.publishInParallel ?? false;
|
|
31
|
+
this.buildAssets = options.buildAssets ?? true;
|
|
32
|
+
this.publishAssets = options.publishAssets ?? true;
|
|
33
|
+
const self = this;
|
|
34
|
+
this.handlerHost = {
|
|
35
|
+
aws: this.options.aws,
|
|
36
|
+
get aborted() {
|
|
37
|
+
return self.aborted;
|
|
38
|
+
},
|
|
39
|
+
emitMessage(t, m) {
|
|
40
|
+
self.progressEvent(t, m);
|
|
41
|
+
},
|
|
42
|
+
dockerFactory: new DockerFactory(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Publish all assets from the manifest
|
|
47
|
+
*/
|
|
48
|
+
async publish() {
|
|
49
|
+
if (this.publishInParallel) {
|
|
50
|
+
await Promise.all(this.assets.map(async (asset) => this.publishAsset(asset)));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
for (const asset of this.assets) {
|
|
54
|
+
if (!(await this.publishAsset(asset))) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if ((this.options.throwOnError ?? true) && this.failures.length > 0) {
|
|
60
|
+
throw new Error(`Error publishing: ${this.failures.map((e) => e.error.message)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build a single asset from the manifest
|
|
65
|
+
*/
|
|
66
|
+
async buildEntry(asset) {
|
|
67
|
+
try {
|
|
68
|
+
if (this.progressEvent(EventType.START, `Building ${asset.id}`)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const handler = this.assetHandler(asset);
|
|
72
|
+
await handler.build();
|
|
73
|
+
if (this.aborted) {
|
|
74
|
+
throw new Error("Aborted");
|
|
75
|
+
}
|
|
76
|
+
this.completedOperations++;
|
|
77
|
+
if (this.progressEvent(EventType.SUCCESS, `Built ${asset.id}`)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
this.failures.push({ asset, error: e });
|
|
83
|
+
this.completedOperations++;
|
|
84
|
+
if (this.progressEvent(EventType.FAIL, e.message)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Publish a single asset from the manifest
|
|
92
|
+
*/
|
|
93
|
+
async publishEntry(asset) {
|
|
94
|
+
try {
|
|
95
|
+
if (this.progressEvent(EventType.START, `Publishing ${asset.id}`)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const handler = this.assetHandler(asset);
|
|
99
|
+
await handler.publish();
|
|
100
|
+
if (this.aborted) {
|
|
101
|
+
throw new Error("Aborted");
|
|
102
|
+
}
|
|
103
|
+
this.completedOperations++;
|
|
104
|
+
if (this.progressEvent(EventType.SUCCESS, `Published ${asset.id}`)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
this.failures.push({ asset, error: e });
|
|
110
|
+
this.completedOperations++;
|
|
111
|
+
if (this.progressEvent(EventType.FAIL, e.message)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Return whether a single asset is published
|
|
119
|
+
*/
|
|
120
|
+
isEntryPublished(asset) {
|
|
121
|
+
const handler = this.assetHandler(asset);
|
|
122
|
+
return handler.isPublished();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* publish an asset (used by 'publish()')
|
|
126
|
+
* @param asset The asset to publish
|
|
127
|
+
* @returns false when publishing should stop
|
|
128
|
+
*/
|
|
129
|
+
async publishAsset(asset) {
|
|
130
|
+
try {
|
|
131
|
+
if (this.progressEvent(EventType.START, `Publishing ${asset.id}`)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const handler = this.assetHandler(asset);
|
|
135
|
+
if (this.buildAssets) {
|
|
136
|
+
await handler.build();
|
|
137
|
+
}
|
|
138
|
+
if (this.publishAssets) {
|
|
139
|
+
await handler.publish();
|
|
140
|
+
}
|
|
141
|
+
if (this.aborted) {
|
|
142
|
+
throw new Error("Aborted");
|
|
143
|
+
}
|
|
144
|
+
this.completedOperations++;
|
|
145
|
+
if (this.progressEvent(EventType.SUCCESS, `Published ${asset.id}`)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
this.failures.push({ asset, error: e });
|
|
151
|
+
this.completedOperations++;
|
|
152
|
+
if (this.progressEvent(EventType.FAIL, e.message)) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
get percentComplete() {
|
|
159
|
+
if (this.totalOperations === 0) {
|
|
160
|
+
return 100;
|
|
161
|
+
}
|
|
162
|
+
return Math.floor((this.completedOperations / this.totalOperations) * 100);
|
|
163
|
+
}
|
|
164
|
+
abort() {
|
|
165
|
+
this.aborted = true;
|
|
166
|
+
}
|
|
167
|
+
get hasFailures() {
|
|
168
|
+
return this.failures.length > 0;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Publish a progress event to the listener, if present.
|
|
172
|
+
*
|
|
173
|
+
* Returns whether an abort is requested. Helper to get rid of repetitive code in publish().
|
|
174
|
+
*/
|
|
175
|
+
progressEvent(event, message) {
|
|
176
|
+
this.message = message;
|
|
177
|
+
if (this.options.progressListener) {
|
|
178
|
+
this.options.progressListener.onPublishEvent(event, this);
|
|
179
|
+
}
|
|
180
|
+
return this.aborted;
|
|
181
|
+
}
|
|
182
|
+
assetHandler(asset) {
|
|
183
|
+
const existing = this.handlerCache.get(asset);
|
|
184
|
+
if (existing) {
|
|
185
|
+
return existing;
|
|
186
|
+
}
|
|
187
|
+
const ret = makeAssetHandler(this.manifest, asset, this.handlerHost, {
|
|
188
|
+
// TODO: remove after PR is merged
|
|
189
|
+
quiet: this.options.quiet,
|
|
190
|
+
});
|
|
191
|
+
this.handlerCache.set(asset, ret);
|
|
192
|
+
return ret;
|
|
193
|
+
}
|
|
194
|
+
}
|
package/cli/commands/dev.js
CHANGED
|
@@ -62,19 +62,43 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
62
62
|
Colors.line(prefix(evt.properties.requestID), Colors.dim.bold("Invoked"), Colors.dim(useFunctions().fromID(evt.properties.functionID)?.handler));
|
|
63
63
|
});
|
|
64
64
|
bus.subscribe("worker.stdout", async (evt) => {
|
|
65
|
+
const info = useFunctions().fromID(evt.properties.functionID);
|
|
65
66
|
prefix(evt.properties.requestID);
|
|
66
67
|
const { started } = pending.get(evt.properties.requestID);
|
|
67
68
|
for (let line of evt.properties.message.split("\n")) {
|
|
69
|
+
// Remove prefix from container logs
|
|
70
|
+
if (info?.runtime === "container") {
|
|
71
|
+
// handle Node.js container logs
|
|
72
|
+
// ie. 2023-07-05T00:13:42.448Z\td7330533-2429-4871-a632-ed29a1d32246\tINFO\tfoo!
|
|
73
|
+
const parts = line.split("\t");
|
|
74
|
+
if (parts.length >= 4 &&
|
|
75
|
+
Date.parse(parts[0]) &&
|
|
76
|
+
parts[1].length === 36) {
|
|
77
|
+
line = parts.slice(3).join("\t");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
68
80
|
Colors.line(prefix(evt.properties.requestID), Colors.dim(("+" + (Date.now() - started) + "ms").padEnd(7)), Colors.dim(line));
|
|
69
81
|
}
|
|
70
82
|
});
|
|
83
|
+
bus.subscribe("function.build.started", async (evt) => {
|
|
84
|
+
const info = useFunctions().fromID(evt.properties.functionID);
|
|
85
|
+
if (!info)
|
|
86
|
+
return;
|
|
87
|
+
if (info.enableLiveDev === false)
|
|
88
|
+
return;
|
|
89
|
+
if (info.runtime !== "container")
|
|
90
|
+
return;
|
|
91
|
+
Colors.line(Colors.dim(Colors.prefix, "Building", info.handler, "container"));
|
|
92
|
+
});
|
|
71
93
|
bus.subscribe("function.build.success", async (evt) => {
|
|
72
94
|
const info = useFunctions().fromID(evt.properties.functionID);
|
|
73
95
|
if (!info)
|
|
74
96
|
return;
|
|
75
97
|
if (info.enableLiveDev === false)
|
|
76
98
|
return;
|
|
77
|
-
Colors.line(
|
|
99
|
+
Colors.line(info.runtime === "container"
|
|
100
|
+
? Colors.dim(Colors.prefix, "Built", info.handler, "container")
|
|
101
|
+
: Colors.dim(Colors.prefix, "Built", info.handler));
|
|
78
102
|
});
|
|
79
103
|
bus.subscribe("function.build.failed", async (evt) => {
|
|
80
104
|
const info = useFunctions().fromID(evt.properties.functionID);
|
package/constructs/Function.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { Architecture, Function as CDKFunction, FunctionOptions, ILayerVersion,
|
|
|
11
11
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
12
12
|
import { Size as CDKSize, Duration as CDKDuration } from "aws-cdk-lib/core";
|
|
13
13
|
declare const supportedRuntimes: {
|
|
14
|
+
container: CDKRuntime;
|
|
14
15
|
rust: CDKRuntime;
|
|
15
16
|
nodejs: CDKRuntime;
|
|
16
17
|
"nodejs4.3": CDKRuntime;
|
|
@@ -77,6 +78,9 @@ export interface FunctionProps extends Omit<FunctionOptions, "functionName" | "m
|
|
|
77
78
|
* Used to configure python function properties
|
|
78
79
|
*/
|
|
79
80
|
python?: PythonProps;
|
|
81
|
+
/**
|
|
82
|
+
* Used to configure image function properties
|
|
83
|
+
*/
|
|
80
84
|
/**
|
|
81
85
|
* Hooks to run before and after function builds
|
|
82
86
|
*/
|