sst 2.23.15 → 2.24.1

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.
@@ -1,237 +0,0 @@
1
- import * as fs from "fs";
2
- import * as os from "os";
3
- import * as path from "path";
4
- import { cdkCredentialsConfig, obtainEcrCredentials, } from "cdk-assets/lib/private/docker-credentials.js";
5
- import { shell, } from "cdk-assets/lib/private/shell.js";
6
- import { createCriticalSection } from "cdk-assets/lib/private/util.js";
7
- var InspectImageErrorCode;
8
- (function (InspectImageErrorCode) {
9
- InspectImageErrorCode[InspectImageErrorCode["Docker"] = 1] = "Docker";
10
- InspectImageErrorCode[InspectImageErrorCode["Podman"] = 125] = "Podman";
11
- })(InspectImageErrorCode || (InspectImageErrorCode = {}));
12
- export class Docker {
13
- logger;
14
- configDir = undefined;
15
- constructor(logger) {
16
- this.logger = logger;
17
- }
18
- /**
19
- * Whether an image with the given tag exists
20
- */
21
- async exists(tag) {
22
- try {
23
- await this.execute(["inspect", tag], { quiet: true });
24
- return true;
25
- }
26
- catch (e) {
27
- const error = e;
28
- /**
29
- * The only error we expect to be thrown will have this property and value.
30
- * If it doesn't, it's unrecognized so re-throw it.
31
- */
32
- if (error.code !== "PROCESS_FAILED") {
33
- throw error;
34
- }
35
- /**
36
- * If we know the shell command above returned an error, check to see
37
- * if the exit code is one we know to actually mean that the image doesn't
38
- * exist.
39
- */
40
- switch (error.exitCode) {
41
- case InspectImageErrorCode.Docker:
42
- case InspectImageErrorCode.Podman:
43
- // Docker and Podman will return this exit code when an image doesn't exist, return false
44
- // context: https://github.com/aws/aws-cdk/issues/16209
45
- return false;
46
- default:
47
- // This is an error but it's not an exit code we recognize, throw.
48
- throw error;
49
- }
50
- }
51
- }
52
- async build(options) {
53
- const buildCommand = [
54
- "build",
55
- ...flatten(Object.entries(options.buildArgs || {}).map(([k, v]) => [
56
- "--build-arg",
57
- `${k}=${v}`,
58
- ])),
59
- ...flatten(Object.entries(options.buildSecrets || {}).map(([k, v]) => [
60
- "--secret",
61
- `id=${k},${v}`,
62
- ])),
63
- "--tag",
64
- options.tag,
65
- ...(options.target ? ["--target", options.target] : []),
66
- ...(options.file ? ["--file", options.file] : []),
67
- ...(options.networkMode ? ["--network", options.networkMode] : []),
68
- ...(options.platform ? ["--platform", options.platform] : []),
69
- ...(options.outputs
70
- ? options.outputs.map((output) => [`--output=${output}`])
71
- : []),
72
- ...(options.cacheFrom
73
- ? [
74
- ...options.cacheFrom
75
- .map((cacheFrom) => [
76
- "--cache-from",
77
- this.cacheOptionToFlag(cacheFrom),
78
- ])
79
- .flat(),
80
- ]
81
- : []),
82
- ...(options.cacheTo
83
- ? ["--cache-to", this.cacheOptionToFlag(options.cacheTo)]
84
- : []),
85
- ".",
86
- ];
87
- await this.execute(buildCommand, {
88
- cwd: options.directory,
89
- // TODO: remove after PR is merged
90
- quiet: options.quiet,
91
- });
92
- }
93
- /**
94
- * Get credentials from ECR and run docker login
95
- */
96
- async login(ecr) {
97
- const credentials = await obtainEcrCredentials(ecr);
98
- // Use --password-stdin otherwise docker will complain. Loudly.
99
- await this.execute([
100
- "login",
101
- "--username",
102
- credentials.username,
103
- "--password-stdin",
104
- credentials.endpoint,
105
- ], {
106
- input: credentials.password,
107
- // Need to quiet otherwise Docker will complain
108
- // 'WARNING! Your password will be stored unencrypted'
109
- // doesn't really matter since it's a token.
110
- quiet: true,
111
- });
112
- }
113
- async tag(sourceTag, targetTag) {
114
- await this.execute(["tag", sourceTag, targetTag]);
115
- }
116
- // TODO: remove after PR is merged
117
- async push(options) {
118
- await this.execute(["push", options.tag], { quiet: options.quiet });
119
- }
120
- /**
121
- * If a CDK Docker Credentials file exists, creates a new Docker config directory.
122
- * Sets up `docker-credential-cdk-assets` to be the credential helper for each domain in the CDK config.
123
- * All future commands (e.g., `build`, `push`) will use this config.
124
- *
125
- * See https://docs.docker.com/engine/reference/commandline/login/#credential-helpers for more details on cred helpers.
126
- *
127
- * @returns true if CDK config was found and configured, false otherwise
128
- */
129
- configureCdkCredentials() {
130
- const config = cdkCredentialsConfig();
131
- if (!config) {
132
- return false;
133
- }
134
- this.configDir = fs.mkdtempSync(path.join(os.tmpdir(), "cdkDockerConfig"));
135
- const domains = Object.keys(config.domainCredentials);
136
- const credHelpers = domains.reduce((map, domain) => {
137
- map[domain] = "cdk-assets"; // Use docker-credential-cdk-assets for this domain
138
- return map;
139
- }, {});
140
- fs.writeFileSync(path.join(this.configDir, "config.json"), JSON.stringify({ credHelpers }), { encoding: "utf-8" });
141
- return true;
142
- }
143
- /**
144
- * Removes any configured Docker config directory.
145
- * All future commands (e.g., `build`, `push`) will use the default config.
146
- *
147
- * This is useful after calling `configureCdkCredentials` to reset to default credentials.
148
- */
149
- resetAuthPlugins() {
150
- this.configDir = undefined;
151
- }
152
- async execute(args, options = {}) {
153
- const configArgs = this.configDir ? ["--config", this.configDir] : [];
154
- // TODO: remove after PR is merged
155
- //const pathToCdkAssets = path.resolve(__dirname, "..", "..", "bin");
156
- const pathToCdkAssets = "";
157
- try {
158
- await shell([getDockerCmd(), ...configArgs, ...args], {
159
- logger: this.logger,
160
- ...options,
161
- env: {
162
- ...process.env,
163
- ...options.env,
164
- PATH: `${pathToCdkAssets}${path.delimiter}${options.env?.PATH ?? process.env.PATH}`,
165
- },
166
- });
167
- }
168
- catch (e) {
169
- if (e.code === "ENOENT") {
170
- throw new Error("Unable to execute 'docker' in order to build a container asset. Please install 'docker' and try again.");
171
- }
172
- throw e;
173
- }
174
- }
175
- cacheOptionToFlag(option) {
176
- let flag = `type=${option.type}`;
177
- if (option.params) {
178
- flag +=
179
- "," +
180
- Object.entries(option.params)
181
- .map(([k, v]) => `${k}=${v}`)
182
- .join(",");
183
- }
184
- return flag;
185
- }
186
- }
187
- /**
188
- * Helps get appropriately configured Docker instances during the container
189
- * image publishing process.
190
- */
191
- export class DockerFactory {
192
- enterLoggedInDestinationsCriticalSection = createCriticalSection();
193
- loggedInDestinations = new Set();
194
- /**
195
- * Gets a Docker instance for building images.
196
- */
197
- async forBuild(options) {
198
- const docker = new Docker(options.logger);
199
- // Default behavior is to login before build so that the Dockerfile can reference images in the ECR repo
200
- // However, if we're in a pipelines environment (for example),
201
- // we may have alternative credentials to the default ones to use for the build itself.
202
- // If the special config file is present, delay the login to the default credentials until the push.
203
- // If the config file is present, we will configure and use those credentials for the build.
204
- let cdkDockerCredentialsConfigured = docker.configureCdkCredentials();
205
- if (!cdkDockerCredentialsConfigured) {
206
- await this.loginOncePerDestination(docker, options);
207
- }
208
- return docker;
209
- }
210
- /**
211
- * Gets a Docker instance for pushing images to ECR.
212
- */
213
- async forEcrPush(options) {
214
- const docker = new Docker(options.logger);
215
- await this.loginOncePerDestination(docker, options);
216
- return docker;
217
- }
218
- async loginOncePerDestination(docker, options) {
219
- // Changes: 012345678910.dkr.ecr.us-west-2.amazonaws.com/tagging-test
220
- // To this: 012345678910.dkr.ecr.us-west-2.amazonaws.com
221
- const repositoryDomain = options.repoUri.split("/")[0];
222
- // Ensure one-at-a-time access to loggedInDestinations.
223
- await this.enterLoggedInDestinationsCriticalSection(async () => {
224
- if (this.loggedInDestinations.has(repositoryDomain)) {
225
- return;
226
- }
227
- await docker.login(options.ecr);
228
- this.loggedInDestinations.add(repositoryDomain);
229
- });
230
- }
231
- }
232
- function getDockerCmd() {
233
- return process.env.CDK_DOCKER ?? "docker";
234
- }
235
- function flatten(x) {
236
- return Array.prototype.concat([], ...x);
237
- }
@@ -1,22 +0,0 @@
1
- import { DockerImageManifestEntry } from "cdk-assets/lib/asset-manifest.js";
2
- import { IAssetHandler, IHandlerHost, IHandlerOptions } from "../asset-handler.js";
3
- export declare class ContainerImageAssetHandler implements IAssetHandler {
4
- private readonly workDir;
5
- private readonly asset;
6
- private readonly host;
7
- private readonly options;
8
- private init?;
9
- constructor(workDir: string, asset: DockerImageManifestEntry, host: IHandlerHost, options: IHandlerOptions);
10
- build(): Promise<void>;
11
- isPublished(): Promise<boolean>;
12
- publish(): Promise<void>;
13
- private initOnce;
14
- /**
15
- * Check whether the image already exists in the ECR repo
16
- *
17
- * Use the fields from the destination to do the actual check. The imageUri
18
- * should correspond to that, but is only used to print Docker image location
19
- * for user benefit (the format is slightly different).
20
- */
21
- private destinationAlreadyExists;
22
- }
@@ -1,231 +0,0 @@
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
- }
@@ -1,3 +0,0 @@
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;
@@ -1,18 +0,0 @@
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
- }
@@ -1,113 +0,0 @@
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
- }