cdk-local 0.129.0 → 0.130.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/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/{local-studio-HawiuYRz.js → local-studio-BlFoXpwl.js} +671 -625
- package/dist/local-studio-BlFoXpwl.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-studio-HawiuYRz.js.map +0 -1
|
@@ -6687,719 +6687,763 @@ function resolveRuntimeCodeMountPath(runtime) {
|
|
|
6687
6687
|
}
|
|
6688
6688
|
|
|
6689
6689
|
//#endregion
|
|
6690
|
-
//#region src/
|
|
6691
|
-
const execFileAsync$5 = promisify(execFile);
|
|
6690
|
+
//#region src/assets/docker-build.ts
|
|
6692
6691
|
/**
|
|
6693
|
-
*
|
|
6692
|
+
* Build a Docker image from a CDK asset source. Returns the local image
|
|
6693
|
+
* tag the caller should use for `docker tag` / `docker push` (publisher)
|
|
6694
|
+
* or `docker run` (local-invoke).
|
|
6694
6695
|
*
|
|
6695
|
-
*
|
|
6696
|
-
*
|
|
6697
|
-
* the
|
|
6698
|
-
*
|
|
6699
|
-
*
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
constructor(message) {
|
|
6703
|
-
super(message);
|
|
6704
|
-
this.name = "DockerRunnerError";
|
|
6705
|
-
Object.setPrototypeOf(this, DockerRunnerError.prototype);
|
|
6706
|
-
}
|
|
6707
|
-
};
|
|
6708
|
-
/**
|
|
6709
|
-
* Pull the image. No-op when `skipPull` is true.
|
|
6696
|
+
* Two source modes (mirrors CDK CLI):
|
|
6697
|
+
* - `executable`: run the user-supplied command, capture stdout, return
|
|
6698
|
+
* it as the local tag. The script is responsible for building AND
|
|
6699
|
+
* tagging; cdk-local just reads the tag from stdout. Used for Bazel /
|
|
6700
|
+
* custom build pipelines that produce images outside `docker build`.
|
|
6701
|
+
* - `directory`: standard `docker build <dir>` with the full BuildKit
|
|
6702
|
+
* flag set described above. Caller must pass `options.tag`.
|
|
6710
6703
|
*
|
|
6711
|
-
*
|
|
6712
|
-
* full `docker pull` progress to stdout so the user sees per-layer
|
|
6713
|
-
* downloads. In the default compact mode the call is silent (cached
|
|
6714
|
-
* images are the common case; a fresh pull still shows progress only
|
|
6715
|
-
* via `--verbose`). Errors are always surfaced: the captured stderr is
|
|
6716
|
-
* folded into the thrown `DockerRunnerError` message.
|
|
6704
|
+
* `executable` takes precedence when both fields are set (matches CDK CLI).
|
|
6717
6705
|
*/
|
|
6718
|
-
async function
|
|
6719
|
-
const
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6706
|
+
async function buildDockerImage(asset, cdkOutDir, options) {
|
|
6707
|
+
const source = asset.source;
|
|
6708
|
+
const logger = getLogger().child("docker-build");
|
|
6709
|
+
if (source.executable && source.executable.length > 0) {
|
|
6710
|
+
const [cmd, ...args] = source.executable;
|
|
6711
|
+
if (!cmd) throw options.wrapError("asset source.executable[] is empty");
|
|
6712
|
+
const cwd = source.directory ? `${cdkOutDir}/${source.directory}` : cdkOutDir;
|
|
6713
|
+
logger.debug(`Building Docker image via executable: ${source.executable.join(" ")} (cwd=${cwd})`);
|
|
6714
|
+
let result;
|
|
6727
6715
|
try {
|
|
6728
|
-
await
|
|
6729
|
-
"pull",
|
|
6730
|
-
...platformArgs,
|
|
6731
|
-
image
|
|
6732
|
-
]);
|
|
6716
|
+
result = await spawnStreaming(cmd, args, { cwd });
|
|
6733
6717
|
} catch (err) {
|
|
6734
|
-
|
|
6718
|
+
const e = err;
|
|
6719
|
+
throw options.wrapError(e.stderr || e.message || String(err));
|
|
6735
6720
|
}
|
|
6736
|
-
|
|
6721
|
+
const tag = result.stdout.trim();
|
|
6722
|
+
if (!tag) throw options.wrapError(`docker build executable produced no output (expected the local image tag on stdout): ${cmd} ${args.join(" ")}`);
|
|
6723
|
+
return tag;
|
|
6737
6724
|
}
|
|
6738
|
-
|
|
6725
|
+
if (!source.directory) throw options.wrapError(`DockerImageAssetSource must set either 'directory' or 'executable' (got: ${JSON.stringify(source)})`);
|
|
6726
|
+
if (!options.tag) throw options.wrapError("buildDockerImage(directory mode) requires options.tag");
|
|
6727
|
+
const buildArgs = buildDockerBuildCommand(source, options.tag, options.platform);
|
|
6728
|
+
const contextDir = `${cdkOutDir}/${source.directory}`;
|
|
6729
|
+
buildArgs.push(".");
|
|
6730
|
+
logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${contextDir})`);
|
|
6739
6731
|
try {
|
|
6740
|
-
await runDockerStreaming(
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
], {
|
|
6745
|
-
streamLive: false,
|
|
6746
|
-
progressLabel: `Pulling ${image}`
|
|
6732
|
+
await runDockerStreaming(buildArgs, {
|
|
6733
|
+
cwd: contextDir,
|
|
6734
|
+
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" },
|
|
6735
|
+
...options.progressLabel !== void 0 && { progressLabel: options.progressLabel }
|
|
6747
6736
|
});
|
|
6748
6737
|
} catch (err) {
|
|
6749
6738
|
const e = err;
|
|
6750
|
-
|
|
6751
|
-
const detail = e.stderr?.trim() || e.stdout?.trim() || "(no output)";
|
|
6752
|
-
throw new DockerRunnerError(`docker pull ${image} exited with code ${e.exitCode}: ${detail}`);
|
|
6739
|
+
throw options.wrapError(e.stderr || e.message || String(err));
|
|
6753
6740
|
}
|
|
6741
|
+
return options.tag;
|
|
6754
6742
|
}
|
|
6755
6743
|
/**
|
|
6756
|
-
*
|
|
6744
|
+
* Construct the `docker build` argv (without the trailing context directory).
|
|
6757
6745
|
*
|
|
6758
|
-
*
|
|
6759
|
-
*
|
|
6760
|
-
* - issuing the invoke,
|
|
6761
|
-
* - calling `removeContainer` from a `try`/`finally` so the container
|
|
6762
|
-
* is cleaned up on any error path including SIGINT.
|
|
6746
|
+
* Exported for unit-test inspection — keeps the flag-ordering assertions
|
|
6747
|
+
* independent of the spawn machinery.
|
|
6763
6748
|
*/
|
|
6764
|
-
|
|
6749
|
+
function buildDockerBuildCommand(source, tag, platformOverride) {
|
|
6765
6750
|
const args = [
|
|
6766
|
-
"
|
|
6767
|
-
"
|
|
6768
|
-
|
|
6751
|
+
"build",
|
|
6752
|
+
"--tag",
|
|
6753
|
+
tag
|
|
6769
6754
|
];
|
|
6770
|
-
if (
|
|
6771
|
-
if (
|
|
6772
|
-
if (
|
|
6773
|
-
if (
|
|
6774
|
-
|
|
6775
|
-
args.push("
|
|
6776
|
-
if (
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
if (
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
if (
|
|
6788
|
-
|
|
6789
|
-
let entryPointTail = [];
|
|
6790
|
-
if (opts.entryPoint && opts.entryPoint.length > 0) {
|
|
6791
|
-
args.push("--entrypoint", opts.entryPoint[0]);
|
|
6792
|
-
entryPointTail = opts.entryPoint.slice(1);
|
|
6793
|
-
}
|
|
6794
|
-
args.push(opts.image, ...entryPointTail, ...opts.cmd);
|
|
6795
|
-
getLogger().child("docker").debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
|
|
6796
|
-
try {
|
|
6797
|
-
const { stdout } = await execFileAsync$5(getDockerCmd(), args, {
|
|
6798
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
6799
|
-
...execEnvForSecrets(passthroughEnv)
|
|
6800
|
-
});
|
|
6801
|
-
return stdout.trim();
|
|
6802
|
-
} catch (error) {
|
|
6803
|
-
const err = error;
|
|
6804
|
-
throw new DockerRunnerError(`docker run failed: ${err.stderr?.trim() || err.message || String(error)}`);
|
|
6805
|
-
}
|
|
6755
|
+
if (source.dockerBuildArgs) for (const [k, v] of Object.entries(source.dockerBuildArgs)) args.push("--build-arg", `${k}=${v}`);
|
|
6756
|
+
if (source.dockerBuildContexts) for (const [k, v] of Object.entries(source.dockerBuildContexts)) args.push("--build-context", `${k}=${v}`);
|
|
6757
|
+
if (source.dockerBuildSecrets) for (const [k, v] of Object.entries(source.dockerBuildSecrets)) args.push("--secret", `id=${k},${v}`);
|
|
6758
|
+
if (source.dockerBuildSsh) args.push("--ssh", source.dockerBuildSsh);
|
|
6759
|
+
if (source.dockerBuildTarget) args.push("--target", source.dockerBuildTarget);
|
|
6760
|
+
if (source.dockerFile) args.push("--file", source.dockerFile);
|
|
6761
|
+
if (source.networkMode) args.push("--network", source.networkMode);
|
|
6762
|
+
const platform = platformOverride ?? source.platform;
|
|
6763
|
+
if (platform) args.push("--platform", platform);
|
|
6764
|
+
if (source.dockerOutputs) for (const output of source.dockerOutputs) args.push(`--output=${output}`);
|
|
6765
|
+
if (source.cacheFrom) for (const c of source.cacheFrom) args.push("--cache-from", cacheOptionToFlag(c));
|
|
6766
|
+
if (source.cacheTo) args.push("--cache-to", cacheOptionToFlag(source.cacheTo));
|
|
6767
|
+
if (source.cacheDisabled) args.push("--no-cache");
|
|
6768
|
+
return args;
|
|
6769
|
+
}
|
|
6770
|
+
function cacheOptionToFlag(option) {
|
|
6771
|
+
let flag = `type=${option.type}`;
|
|
6772
|
+
if (option.params) for (const [k, v] of Object.entries(option.params)) flag += `,${k}=${v}`;
|
|
6773
|
+
return flag;
|
|
6806
6774
|
}
|
|
6775
|
+
|
|
6776
|
+
//#endregion
|
|
6777
|
+
//#region src/local/ecr-puller.ts
|
|
6807
6778
|
/**
|
|
6808
|
-
*
|
|
6809
|
-
*
|
|
6779
|
+
* ECR pull fallback for `cdkl invoke` / `cdkl start-api` /
|
|
6780
|
+
* `cdkl run-task`. When the image URI resolves to an ECR repo but
|
|
6781
|
+
* doesn't match any cdk.out asset (typical when invoking a stack
|
|
6782
|
+
* deployed elsewhere or sharing a centralized registry), cdk-local
|
|
6783
|
+
* authenticates against the target registry and runs `docker pull`.
|
|
6784
|
+
*
|
|
6785
|
+
* **Cross-account / cross-region** (#455):
|
|
6786
|
+
* - Same-account, same-region: fast path. No STS hop. The default
|
|
6787
|
+
* credential chain is used directly for `ecr:GetAuthorizationToken`.
|
|
6788
|
+
* - `ecrRoleArn` is provided: `sts:AssumeRole` is issued via the
|
|
6789
|
+
* default credential chain to obtain temporary credentials for the
|
|
6790
|
+
* target account. The resulting credentials authenticate the ECR
|
|
6791
|
+
* client (regardless of region — the ECR client is built for the
|
|
6792
|
+
* URI's region, which can differ from the caller's profile region).
|
|
6793
|
+
* - Cross-account, NO `ecrRoleArn`: cdk-local falls through to the
|
|
6794
|
+
* default credential chain. This works when the caller has been
|
|
6795
|
+
* granted cross-account `ecr:GetAuthorizationToken` +
|
|
6796
|
+
* `ecr:BatchGetImage` permissions on the target repository via an
|
|
6797
|
+
* IAM policy; otherwise AWS rejects the call with `AccessDenied`
|
|
6798
|
+
* and the user is pointed at `--ecr-role-arn`.
|
|
6799
|
+
*
|
|
6800
|
+
* The `--no-pull` semantics (C3 in the design doc):
|
|
6801
|
+
* - When NOT set: `ecrLogin` + `docker pull <uri>`.
|
|
6802
|
+
* - When set: skip `docker pull`. If the image isn't in the local
|
|
6803
|
+
* cache, the subsequent `docker run` will fail; we surface a clearer
|
|
6804
|
+
* "image not in local cache" error here so the user knows to drop
|
|
6805
|
+
* `--no-pull` or pre-pull manually.
|
|
6810
6806
|
*/
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6807
|
+
/** Regex matching the `<acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` shape. */
|
|
6808
|
+
const ECR_URI_REGEX = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\/([^:]+):(.+)$/;
|
|
6809
|
+
/**
|
|
6810
|
+
* Parse an ECR image URI. Returns `undefined` for non-ECR URIs (typically:
|
|
6811
|
+
* Docker Hub, public.ecr.aws, gcr.io, ...) — those are user-managed
|
|
6812
|
+
* images we don't try to authenticate against.
|
|
6813
|
+
*/
|
|
6814
|
+
function parseEcrUri(imageUri) {
|
|
6815
|
+
const m = ECR_URI_REGEX.exec(imageUri);
|
|
6816
|
+
if (!m) return void 0;
|
|
6817
|
+
return {
|
|
6818
|
+
accountId: m[1],
|
|
6819
|
+
region: m[2],
|
|
6820
|
+
repository: m[3],
|
|
6821
|
+
tag: m[4]
|
|
6826
6822
|
};
|
|
6827
6823
|
}
|
|
6828
6824
|
/**
|
|
6829
|
-
*
|
|
6830
|
-
*
|
|
6831
|
-
*
|
|
6825
|
+
* Module-level cache for STS-issued AssumeRole credentials, keyed by
|
|
6826
|
+
* `(ecrRoleArn, callerRegion)`. Closes the reviewer's MAJOR finding: ECS
|
|
6827
|
+
* run-task with N containers under one `--ecr-role-arn` would otherwise issue
|
|
6828
|
+
* N× `AssumeRole` and N× `GetCallerIdentity` for identical credentials valid
|
|
6829
|
+
* for 3600s. The cache keeps a 5-minute safety margin against the recorded
|
|
6830
|
+
* `Expiration` so STS-side / local-clock skew never lets a stale entry through.
|
|
6831
|
+
*
|
|
6832
|
+
* Cache key is intentionally `(roleArn, region)` rather than full caller
|
|
6833
|
+
* identity — STS issues per-region session creds, and a switch of `--region`
|
|
6834
|
+
* between two `local invoke` calls in the same process must re-issue.
|
|
6835
|
+
*
|
|
6836
|
+
* NOT cleared on process exit — Node's module scope evaporates with the
|
|
6837
|
+
* process, and no inter-process sharing is desired (each `cdkl invoke`
|
|
6838
|
+
* is its own isolated runtime).
|
|
6832
6839
|
*/
|
|
6833
|
-
|
|
6834
|
-
if (!containerId) return;
|
|
6835
|
-
const logger = getLogger().child("docker");
|
|
6836
|
-
try {
|
|
6837
|
-
await execFileAsync$5(getDockerCmd(), [
|
|
6838
|
-
"rm",
|
|
6839
|
-
"-f",
|
|
6840
|
-
containerId
|
|
6841
|
-
]);
|
|
6842
|
-
logger.debug(`Removed container ${containerId}`);
|
|
6843
|
-
} catch (error) {
|
|
6844
|
-
const err = error;
|
|
6845
|
-
logger.debug(`docker rm -f ${containerId} failed: ${err.stderr || err.message || String(error)}`);
|
|
6846
|
-
}
|
|
6847
|
-
}
|
|
6840
|
+
const ASSUMED_ROLE_CACHE = /* @__PURE__ */ new Map();
|
|
6848
6841
|
/**
|
|
6849
|
-
*
|
|
6850
|
-
*
|
|
6851
|
-
*
|
|
6842
|
+
* Module-level cache for `STS:GetCallerIdentity`. The result is identity-only
|
|
6843
|
+
* (`Account`) and invariant for the lifetime of the process under one set of
|
|
6844
|
+
* default credentials. Keyed by `callerRegion` to avoid a cross-region leak
|
|
6845
|
+
* when the caller flips `AWS_REGION` mid-process (STS is global but the SDK
|
|
6846
|
+
* uses regional endpoints; the result is invariant in practice, but we key
|
|
6847
|
+
* on region for safety).
|
|
6852
6848
|
*/
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
"{{.Server.Version}}"
|
|
6860
|
-
]);
|
|
6861
|
-
} catch (error) {
|
|
6862
|
-
const err = error;
|
|
6863
|
-
if (err.code === "ENOENT") throw new DockerRunnerError(`${cmd} is not installed or not on PATH. ${getEmbedConfig().cliName} invoke needs Docker (or a compatible CLI specified via CDK_DOCKER) — install it and retry.`);
|
|
6864
|
-
throw new DockerRunnerError(`${cmd} daemon is not reachable: ${err.stderr?.trim() || err.message || String(error)}. Start Docker Desktop / the docker daemon and retry.`);
|
|
6865
|
-
}
|
|
6849
|
+
const CALLER_IDENTITY_CACHE = /* @__PURE__ */ new Map();
|
|
6850
|
+
/** 5-minute safety margin against the recorded STS expiration timestamp. */
|
|
6851
|
+
const STS_CREDENTIAL_SAFETY_MARGIN_MS = 300 * 1e3;
|
|
6852
|
+
function isCredentialFresh(creds) {
|
|
6853
|
+
if (!creds.expiration) return false;
|
|
6854
|
+
return creds.expiration.getTime() - Date.now() > STS_CREDENTIAL_SAFETY_MARGIN_MS;
|
|
6866
6855
|
}
|
|
6867
6856
|
/**
|
|
6868
|
-
*
|
|
6869
|
-
* publishing the RIE :8080 endpoint without colliding with whatever else
|
|
6870
|
-
* the user has running. The OS assigns a port via `port: 0` and we
|
|
6871
|
-
* close the probe before returning so docker can bind it next.
|
|
6857
|
+
* Pull (or verify locally cached) a container image from ECR.
|
|
6872
6858
|
*
|
|
6873
|
-
*
|
|
6874
|
-
*
|
|
6875
|
-
*
|
|
6859
|
+
* Auto-detects cross-account from `STS:GetCallerIdentity` and assumes
|
|
6860
|
+
* the supplied role when set. Returns the image URI the caller should
|
|
6861
|
+
* pass to `docker run` (same as the input — no rewriting).
|
|
6876
6862
|
*/
|
|
6877
|
-
function
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6863
|
+
async function pullEcrImage(imageUri, options) {
|
|
6864
|
+
const logger = getLogger().child("ecr-puller");
|
|
6865
|
+
const parsed = parseEcrUri(imageUri);
|
|
6866
|
+
if (!parsed) throw new LocalInvokeBuildError(`Image URI '${imageUri}' is not an ECR URI. ${getEmbedConfig().cliName} invoke v1 only authenticates against ECR for the deployed-image fallback path.`);
|
|
6867
|
+
const callerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"];
|
|
6868
|
+
if (options.skipPull) {
|
|
6869
|
+
logger.info(`Skipping ECR pull (--no-pull). Verifying ${imageUri} is in local cache...`);
|
|
6870
|
+
await verifyImageInLocalCache(imageUri);
|
|
6871
|
+
return imageUri;
|
|
6872
|
+
}
|
|
6873
|
+
const callerIdentityKey = `${options.profile ?? "_noprofile"}|${callerRegion ?? "_unset"}`;
|
|
6874
|
+
let callerAccount = CALLER_IDENTITY_CACHE.get(callerIdentityKey);
|
|
6875
|
+
if (callerAccount === void 0) {
|
|
6876
|
+
const sts = new STSClient(buildStsClientConfig({
|
|
6877
|
+
region: callerRegion,
|
|
6878
|
+
profile: options.profile
|
|
6879
|
+
}));
|
|
6880
|
+
try {
|
|
6881
|
+
const identity = await sts.send(new GetCallerIdentityCommand({}));
|
|
6882
|
+
if (!identity.Account) throw new LocalInvokeBuildError("STS GetCallerIdentity returned no Account. Verify your AWS credentials.");
|
|
6883
|
+
callerAccount = identity.Account;
|
|
6884
|
+
CALLER_IDENTITY_CACHE.set(callerIdentityKey, callerAccount);
|
|
6885
|
+
} finally {
|
|
6886
|
+
sts.destroy();
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
const crossAccount = callerAccount !== parsed.accountId;
|
|
6890
|
+
const crossRegion = callerRegion !== void 0 && callerRegion !== parsed.region;
|
|
6891
|
+
let assumed;
|
|
6892
|
+
if (options.ecrRoleArn) {
|
|
6893
|
+
const cacheKey = `${options.profile ?? "_noprofile"}|${options.ecrRoleArn}|${callerRegion ?? "_unset"}`;
|
|
6894
|
+
const cached = ASSUMED_ROLE_CACHE.get(cacheKey);
|
|
6895
|
+
if (cached && isCredentialFresh(cached)) {
|
|
6896
|
+
assumed = cached;
|
|
6897
|
+
logger.debug(`Reusing cached AssumeRole credentials for ${options.ecrRoleArn}`);
|
|
6898
|
+
} else {
|
|
6899
|
+
assumed = await assumeRoleForEcr(options.ecrRoleArn, callerRegion, options.profile, logger);
|
|
6900
|
+
ASSUMED_ROLE_CACHE.set(cacheKey, assumed);
|
|
6901
|
+
logger.info(`Assumed role ${options.ecrRoleArn} for ECR pull (account=${parsed.accountId}, region=${parsed.region})`);
|
|
6902
|
+
}
|
|
6903
|
+
} else if (crossAccount) logger.info(`Cross-account ECR pull: image account ${parsed.accountId} != caller ${callerAccount}. Using the caller's credentials; pass --ecr-role-arn <arn> if AWS rejects with AccessDenied.`);
|
|
6904
|
+
if (crossRegion) logger.info(`Cross-region ECR pull: image region ${parsed.region} != caller ${callerRegion ?? "(unset)"}. Authenticating against the image region directly.`);
|
|
6905
|
+
const ecr = new ECRClient({
|
|
6906
|
+
region: parsed.region,
|
|
6907
|
+
...assumed ? { credentials: assumed } : options.profile ? { profile: options.profile } : {}
|
|
6892
6908
|
});
|
|
6909
|
+
try {
|
|
6910
|
+
await ecrLogin(ecr, parsed.accountId, parsed.region);
|
|
6911
|
+
} finally {
|
|
6912
|
+
ecr.destroy();
|
|
6913
|
+
}
|
|
6914
|
+
logger.info(`Pulling ${imageUri}...`);
|
|
6915
|
+
try {
|
|
6916
|
+
await runDockerForeground(["pull", imageUri]);
|
|
6917
|
+
} catch (err) {
|
|
6918
|
+
throw new LocalInvokeBuildError(`docker pull ${imageUri} failed: ${err.message}`);
|
|
6919
|
+
}
|
|
6920
|
+
return imageUri;
|
|
6893
6921
|
}
|
|
6894
6922
|
/**
|
|
6895
|
-
*
|
|
6896
|
-
*
|
|
6897
|
-
*
|
|
6898
|
-
*
|
|
6899
|
-
*
|
|
6900
|
-
* still emits the inline `-e <KEY>=<value>` form into the debug log.
|
|
6923
|
+
* Assume the supplied role via the SDK default credential chain and
|
|
6924
|
+
* return the resulting temporary credentials. The STS client is built
|
|
6925
|
+
* with the caller's profile region (or unset) — STS is a global
|
|
6926
|
+
* service so the region is informational, but threading it through
|
|
6927
|
+
* mirrors the convention used by `src/utils/role-arn.ts`.
|
|
6901
6928
|
*/
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6929
|
+
async function assumeRoleForEcr(roleArn, callerRegion, profile, logger) {
|
|
6930
|
+
logger.debug(`Assuming role ${roleArn} for ECR pull...`);
|
|
6931
|
+
const sts = new STSClient(buildStsClientConfig({
|
|
6932
|
+
region: callerRegion,
|
|
6933
|
+
profile
|
|
6934
|
+
}));
|
|
6935
|
+
try {
|
|
6936
|
+
const creds = (await sts.send(new AssumeRoleCommand({
|
|
6937
|
+
RoleArn: roleArn,
|
|
6938
|
+
RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-ecr-${Date.now()}`,
|
|
6939
|
+
DurationSeconds: 3600
|
|
6940
|
+
}))).Credentials;
|
|
6941
|
+
if (!creds || !creds.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new LocalInvokeBuildError(`AssumeRole(${roleArn}) returned no usable credentials. Verify the role's trust policy allows your identity to assume it.`);
|
|
6942
|
+
return {
|
|
6943
|
+
accessKeyId: creds.AccessKeyId,
|
|
6944
|
+
secretAccessKey: creds.SecretAccessKey,
|
|
6945
|
+
sessionToken: creds.SessionToken,
|
|
6946
|
+
...creds.Expiration && { expiration: creds.Expiration }
|
|
6947
|
+
};
|
|
6948
|
+
} catch (err) {
|
|
6949
|
+
if (err instanceof LocalInvokeBuildError) throw err;
|
|
6950
|
+
throw new LocalInvokeBuildError(`Failed to assume role ${roleArn} for ECR pull: ${err instanceof Error ? err.message : String(err)}. Verify the role exists and its trust policy permits the caller's identity to assume it.`);
|
|
6951
|
+
} finally {
|
|
6952
|
+
sts.destroy();
|
|
6953
|
+
}
|
|
6954
|
+
}
|
|
6907
6955
|
/**
|
|
6908
|
-
*
|
|
6909
|
-
*
|
|
6910
|
-
*
|
|
6911
|
-
* so the secret never lands in `docker run`'s argv — invisible to
|
|
6912
|
-
* `ps aux` / `/proc/<pid>/cmdline` (other users on a shared host) and to
|
|
6913
|
-
* shell history. Non-sensitive keys keep the inline `-e KEY=VALUE` form
|
|
6914
|
-
* (fine for non-secret config, and keeps `--debug` output readable).
|
|
6915
|
-
*
|
|
6916
|
-
* Returns the `{ KEY: value }` map of routed-through keys; the caller MUST
|
|
6917
|
-
* merge it into the spawned docker process's `env` (see
|
|
6918
|
-
* {@link execEnvForSecrets}) so docker can resolve each passed-through
|
|
6919
|
-
* key. Values still appear in `docker inspect` Config.Env — that is
|
|
6920
|
-
* inherent to container env and matches production behavior.
|
|
6921
|
-
*
|
|
6922
|
-
* Unlike `--env-file`, this handles multi-line values (e.g. PEM secrets)
|
|
6923
|
-
* and needs no temp file on disk.
|
|
6956
|
+
* Authenticate the local docker daemon against the target ECR registry.
|
|
6957
|
+
* Self-contained in this module so the local-invoke path stays
|
|
6958
|
+
* independent of any full asset-publish path's larger surface area.
|
|
6924
6959
|
*/
|
|
6925
|
-
function
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6960
|
+
async function ecrLogin(client, accountId, region) {
|
|
6961
|
+
getLogger().child("ecr-puller").debug(`ECR login (account=${accountId}, region=${region})`);
|
|
6962
|
+
const authData = (await client.send(new GetAuthorizationTokenCommand({}))).authorizationData?.[0];
|
|
6963
|
+
if (!authData?.authorizationToken) throw new LocalInvokeBuildError("Failed to get ECR authorization token");
|
|
6964
|
+
const [username, password] = Buffer.from(authData.authorizationToken, "base64").toString().split(":");
|
|
6965
|
+
if (!username || password === void 0) throw new LocalInvokeBuildError("ECR authorization token has unexpected shape (missing username/password)");
|
|
6966
|
+
const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.amazonaws.com`;
|
|
6967
|
+
try {
|
|
6968
|
+
await runDockerStreaming([
|
|
6969
|
+
"login",
|
|
6970
|
+
"--username",
|
|
6971
|
+
username,
|
|
6972
|
+
"--password-stdin",
|
|
6973
|
+
endpoint
|
|
6974
|
+
], { input: password });
|
|
6975
|
+
} catch (err) {
|
|
6976
|
+
const e = err;
|
|
6977
|
+
throw new LocalInvokeBuildError(`ECR login failed: ${formatDockerLoginError(e.stderr || e.message || String(err), endpoint)}`);
|
|
6978
|
+
}
|
|
6932
6979
|
}
|
|
6933
6980
|
/**
|
|
6934
|
-
*
|
|
6935
|
-
*
|
|
6936
|
-
*
|
|
6937
|
-
* so docker inherits the normal environment PLUS the sensitive values it
|
|
6938
|
-
* must resolve, or `{}` when there is nothing to pass through (preserving
|
|
6939
|
-
* the default inherited-environment behavior).
|
|
6981
|
+
* `docker image inspect <uri>` returns non-zero when the image is not in
|
|
6982
|
+
* the local cache. Surface a clearer error than docker's raw output so
|
|
6983
|
+
* the user knows the `--no-pull` path requires a pre-cached image.
|
|
6940
6984
|
*/
|
|
6941
|
-
function
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6985
|
+
async function verifyImageInLocalCache(imageUri) {
|
|
6986
|
+
try {
|
|
6987
|
+
await runDockerStreaming([
|
|
6988
|
+
"image",
|
|
6989
|
+
"inspect",
|
|
6990
|
+
imageUri
|
|
6991
|
+
]);
|
|
6992
|
+
} catch {
|
|
6993
|
+
throw new LocalInvokeBuildError(`Image '${imageUri}' is not in the local docker cache and --no-pull was set. Either remove --no-pull (${getEmbedConfig().productName} will pull from ECR) or pre-pull the image manually with \`docker pull\`.`);
|
|
6994
|
+
}
|
|
6947
6995
|
}
|
|
6948
6996
|
/**
|
|
6949
|
-
*
|
|
6950
|
-
*
|
|
6951
|
-
* `
|
|
6952
|
-
*
|
|
6953
|
-
*
|
|
6954
|
-
* any inline-form credential that slips into a logged command.
|
|
6997
|
+
* Check whether a docker image is in the local registry. Pure boolean —
|
|
6998
|
+
* the caller decides what message to surface on miss. Reused by the
|
|
6999
|
+
* `docker-image-builder` `--no-build` path so both the ECR-pull verifier
|
|
7000
|
+
* (above) and the local-build verifier route through one `docker image
|
|
7001
|
+
* inspect` shape.
|
|
6955
7002
|
*/
|
|
6956
|
-
function
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
out.push("-e", `${key}=***`);
|
|
6967
|
-
i++;
|
|
6968
|
-
continue;
|
|
6969
|
-
}
|
|
6970
|
-
}
|
|
6971
|
-
}
|
|
6972
|
-
out.push(cur);
|
|
7003
|
+
async function isImageInLocalCache(imageRef) {
|
|
7004
|
+
try {
|
|
7005
|
+
await runDockerStreaming([
|
|
7006
|
+
"image",
|
|
7007
|
+
"inspect",
|
|
7008
|
+
imageRef
|
|
7009
|
+
]);
|
|
7010
|
+
return true;
|
|
7011
|
+
} catch {
|
|
7012
|
+
return false;
|
|
6973
7013
|
}
|
|
6974
|
-
return out;
|
|
6975
7014
|
}
|
|
6976
7015
|
|
|
6977
7016
|
//#endregion
|
|
6978
|
-
//#region src/
|
|
7017
|
+
//#region src/local/docker-image-builder.ts
|
|
6979
7018
|
/**
|
|
6980
|
-
* Build a
|
|
6981
|
-
* tag the caller should
|
|
6982
|
-
* or `docker run` (local-invoke).
|
|
6983
|
-
*
|
|
6984
|
-
* Two source modes (mirrors CDK CLI):
|
|
6985
|
-
* - `executable`: run the user-supplied command, capture stdout, return
|
|
6986
|
-
* it as the local tag. The script is responsible for building AND
|
|
6987
|
-
* tagging; cdk-local just reads the tag from stdout. Used for Bazel /
|
|
6988
|
-
* custom build pipelines that produce images outside `docker build`.
|
|
6989
|
-
* - `directory`: standard `docker build <dir>` with the full BuildKit
|
|
6990
|
-
* flag set described above. Caller must pass `options.tag`.
|
|
7019
|
+
* Build a Lambda container image from a CDK asset entry. Returns the
|
|
7020
|
+
* local image tag the caller should pass to `docker run`.
|
|
6991
7021
|
*
|
|
6992
|
-
* `
|
|
7022
|
+
* When `options.noBuild` is set, skips `docker build` entirely and
|
|
7023
|
+
* verifies the deterministic local tag is already in the docker
|
|
7024
|
+
* registry; throws `LocalInvokeBuildError` with an actionable message
|
|
7025
|
+
* when the tag is missing.
|
|
6993
7026
|
*/
|
|
6994
|
-
async function
|
|
6995
|
-
const
|
|
6996
|
-
const
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
logger.debug(`
|
|
7002
|
-
|
|
7027
|
+
async function buildContainerImage(asset, cdkOutDir, options) {
|
|
7028
|
+
const tag = computeLocalTag(asset.source);
|
|
7029
|
+
const platform = architectureToPlatform(options.architecture);
|
|
7030
|
+
const logger = getLogger().child("local-invoke-build");
|
|
7031
|
+
if (options.noBuild === true) {
|
|
7032
|
+
logger.info(`Skipping docker build (--no-build). Verifying ${tag} is in local registry...`);
|
|
7033
|
+
if (!await isImageInLocalCache(tag)) throw new LocalInvokeBuildError(`image '${tag}' not in local registry and --no-build is set; remove --no-build or run \`docker build\` manually first.`);
|
|
7034
|
+
logger.debug(`Local tag ${tag} is cached; skipping build.`);
|
|
7035
|
+
return tag;
|
|
7036
|
+
}
|
|
7037
|
+
logger.info(`Building container image (platform=${platform})...`);
|
|
7038
|
+
logger.debug(`Local tag: ${tag}`);
|
|
7039
|
+
const actualTag = await buildDockerImage(asset, cdkOutDir, {
|
|
7040
|
+
tag,
|
|
7041
|
+
platform,
|
|
7042
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
7043
|
+
progressLabel: `Building container image (platform=${platform})`
|
|
7044
|
+
});
|
|
7045
|
+
if (actualTag !== tag) {
|
|
7046
|
+
logger.debug(`Re-tagging executable-built image '${actualTag}' → '${tag}'`);
|
|
7003
7047
|
try {
|
|
7004
|
-
|
|
7048
|
+
await runDockerStreaming([
|
|
7049
|
+
"tag",
|
|
7050
|
+
actualTag,
|
|
7051
|
+
tag
|
|
7052
|
+
]);
|
|
7005
7053
|
} catch (err) {
|
|
7006
7054
|
const e = err;
|
|
7007
|
-
throw
|
|
7055
|
+
throw new LocalInvokeBuildError(`docker tag failed re-tagging '${actualTag}' → '${tag}': ${e.stderr?.trim() || e.message || String(err)}`);
|
|
7008
7056
|
}
|
|
7009
|
-
const tag = result.stdout.trim();
|
|
7010
|
-
if (!tag) throw options.wrapError(`docker build executable produced no output (expected the local image tag on stdout): ${cmd} ${args.join(" ")}`);
|
|
7011
|
-
return tag;
|
|
7012
7057
|
}
|
|
7013
|
-
|
|
7014
|
-
if (!options.tag) throw options.wrapError("buildDockerImage(directory mode) requires options.tag");
|
|
7015
|
-
const buildArgs = buildDockerBuildCommand(source, options.tag, options.platform);
|
|
7016
|
-
const contextDir = `${cdkOutDir}/${source.directory}`;
|
|
7017
|
-
buildArgs.push(".");
|
|
7018
|
-
logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${contextDir})`);
|
|
7019
|
-
try {
|
|
7020
|
-
await runDockerStreaming(buildArgs, {
|
|
7021
|
-
cwd: contextDir,
|
|
7022
|
-
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" },
|
|
7023
|
-
...options.progressLabel !== void 0 && { progressLabel: options.progressLabel }
|
|
7024
|
-
});
|
|
7025
|
-
} catch (err) {
|
|
7026
|
-
const e = err;
|
|
7027
|
-
throw options.wrapError(e.stderr || e.message || String(err));
|
|
7028
|
-
}
|
|
7029
|
-
return options.tag;
|
|
7058
|
+
return tag;
|
|
7030
7059
|
}
|
|
7031
7060
|
/**
|
|
7032
|
-
*
|
|
7061
|
+
* Translate Lambda's `Architectures` enum to a Docker `--platform` value.
|
|
7033
7062
|
*
|
|
7034
|
-
*
|
|
7035
|
-
*
|
|
7063
|
+
* Critical bug fix C2 from the design doc — without this the build /
|
|
7064
|
+
* run step uses the host's default arch, which races on M1/M2 Macs
|
|
7065
|
+
* (arm64 host) with x86_64 Lambdas. Threaded into BOTH the build (here)
|
|
7066
|
+
* and the run path (`docker-runner.runDetached`).
|
|
7036
7067
|
*/
|
|
7037
|
-
function
|
|
7038
|
-
|
|
7039
|
-
"build",
|
|
7040
|
-
"--tag",
|
|
7041
|
-
tag
|
|
7042
|
-
];
|
|
7043
|
-
if (source.dockerBuildArgs) for (const [k, v] of Object.entries(source.dockerBuildArgs)) args.push("--build-arg", `${k}=${v}`);
|
|
7044
|
-
if (source.dockerBuildContexts) for (const [k, v] of Object.entries(source.dockerBuildContexts)) args.push("--build-context", `${k}=${v}`);
|
|
7045
|
-
if (source.dockerBuildSecrets) for (const [k, v] of Object.entries(source.dockerBuildSecrets)) args.push("--secret", `id=${k},${v}`);
|
|
7046
|
-
if (source.dockerBuildSsh) args.push("--ssh", source.dockerBuildSsh);
|
|
7047
|
-
if (source.dockerBuildTarget) args.push("--target", source.dockerBuildTarget);
|
|
7048
|
-
if (source.dockerFile) args.push("--file", source.dockerFile);
|
|
7049
|
-
if (source.networkMode) args.push("--network", source.networkMode);
|
|
7050
|
-
const platform = platformOverride ?? source.platform;
|
|
7051
|
-
if (platform) args.push("--platform", platform);
|
|
7052
|
-
if (source.dockerOutputs) for (const output of source.dockerOutputs) args.push(`--output=${output}`);
|
|
7053
|
-
if (source.cacheFrom) for (const c of source.cacheFrom) args.push("--cache-from", cacheOptionToFlag(c));
|
|
7054
|
-
if (source.cacheTo) args.push("--cache-to", cacheOptionToFlag(source.cacheTo));
|
|
7055
|
-
if (source.cacheDisabled) args.push("--no-cache");
|
|
7056
|
-
return args;
|
|
7068
|
+
function architectureToPlatform(architecture) {
|
|
7069
|
+
return architecture === "arm64" ? "linux/arm64" : "linux/amd64";
|
|
7057
7070
|
}
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7071
|
+
/**
|
|
7072
|
+
* The Docker `--platform` value matching the host CPU arch.
|
|
7073
|
+
*
|
|
7074
|
+
* `process.arch` is `arm64` on Apple Silicon and `x64` on Intel/amd64
|
|
7075
|
+
* hosts; everything else is treated as amd64 for the purpose of the
|
|
7076
|
+
* emulation comparison (the only two `--platform` values cdk-local emits
|
|
7077
|
+
* are `linux/arm64` / `linux/amd64`).
|
|
7078
|
+
*/
|
|
7079
|
+
function hostPlatform() {
|
|
7080
|
+
return process.arch === "arm64" ? "linux/arm64" : "linux/amd64";
|
|
7062
7081
|
}
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
//#region src/local/ecr-puller.ts
|
|
7082
|
+
/** Process-global dedupe so a warm pool / per-request boot warns once per arch. */
|
|
7083
|
+
const warnedEmulatedPlatforms = /* @__PURE__ */ new Set();
|
|
7066
7084
|
/**
|
|
7067
|
-
*
|
|
7068
|
-
*
|
|
7069
|
-
*
|
|
7070
|
-
* deployed elsewhere or sharing a centralized registry), cdk-local
|
|
7071
|
-
* authenticates against the target registry and runs `docker pull`.
|
|
7085
|
+
* Warn once per process when a container is about to run at a `--platform`
|
|
7086
|
+
* whose CPU arch differs from the host's — i.e. it will run under CPU
|
|
7087
|
+
* emulation.
|
|
7072
7088
|
*
|
|
7073
|
-
*
|
|
7074
|
-
*
|
|
7075
|
-
*
|
|
7076
|
-
*
|
|
7077
|
-
*
|
|
7078
|
-
*
|
|
7079
|
-
*
|
|
7080
|
-
*
|
|
7081
|
-
* - Cross-account, NO `ecrRoleArn`: cdk-local falls through to the
|
|
7082
|
-
* default credential chain. This works when the caller has been
|
|
7083
|
-
* granted cross-account `ecr:GetAuthorizationToken` +
|
|
7084
|
-
* `ecr:BatchGetImage` permissions on the target repository via an
|
|
7085
|
-
* IAM policy; otherwise AWS rejects the call with `AccessDenied`
|
|
7086
|
-
* and the user is pointed at `--ecr-role-arn`.
|
|
7089
|
+
* The arch is chosen automatically from the function's declared
|
|
7090
|
+
* `Architectures`, so the user gets no signal that emulation is in play
|
|
7091
|
+
* until a container dies. Emulated containers usually run fine, but some
|
|
7092
|
+
* compiled custom-runtime binaries (notably a Swift `provided.*`
|
|
7093
|
+
* bootstrap) crash under emulation with `exec format error` or
|
|
7094
|
+
* `illegal instruction`, which is opaque without this hint. Surface the
|
|
7095
|
+
* two real fixes: enable Rosetta for x86/amd64 emulation in the Docker
|
|
7096
|
+
* engine settings, or build the function for the host arch.
|
|
7087
7097
|
*
|
|
7088
|
-
*
|
|
7089
|
-
*
|
|
7090
|
-
*
|
|
7091
|
-
* cache, the subsequent `docker run` will fail; we surface a clearer
|
|
7092
|
-
* "image not in local cache" error here so the user knows to drop
|
|
7093
|
-
* `--no-pull` or pre-pull manually.
|
|
7098
|
+
* Deduped by target platform so a warm pool / per-request boot doesn't
|
|
7099
|
+
* spam. `platform` is the resolved `--platform` value (`undefined` =>
|
|
7100
|
+
* docker picks the host arch, so there is nothing to warn about).
|
|
7094
7101
|
*/
|
|
7095
|
-
|
|
7096
|
-
|
|
7102
|
+
function warnIfEmulatedPlatform(platform, opts = {}) {
|
|
7103
|
+
if (!platform) return;
|
|
7104
|
+
const host = hostPlatform();
|
|
7105
|
+
if (platform === host) return;
|
|
7106
|
+
if (warnedEmulatedPlatforms.has(platform)) return;
|
|
7107
|
+
warnedEmulatedPlatforms.add(platform);
|
|
7108
|
+
const logger = opts.logger ?? getLogger();
|
|
7109
|
+
const what = opts.label ? `'${opts.label}' ` : "";
|
|
7110
|
+
logger.warn(`Running ${what}under ${platform} emulation on a ${host} host. Emulated containers can crash some compiled custom-runtime binaries (e.g. a Swift 'provided.*' bootstrap) with 'exec format error' or 'illegal instruction'. If you hit that, enable Rosetta for x86/amd64 emulation in your Docker engine settings, or build the function for ${host}.`);
|
|
7111
|
+
}
|
|
7097
7112
|
/**
|
|
7098
|
-
*
|
|
7099
|
-
*
|
|
7100
|
-
*
|
|
7113
|
+
* Build a stable local tag derived from the asset's build context.
|
|
7114
|
+
*
|
|
7115
|
+
* Fingerprints every field that affects the produced image so an iteration
|
|
7116
|
+
* that doesn't change those fields hits Docker's layer cache; an iteration
|
|
7117
|
+
* that DOES change them gets a fresh tag (the old tag stays around in
|
|
7118
|
+
* `docker images` but harmlessly). The fingerprint covers the full CDK
|
|
7119
|
+
* `DockerImageSource` schema so `dockerBuildSecrets` / `dockerBuildContexts`
|
|
7120
|
+
* / `cacheFrom` / etc. changes also bust the local cache as expected.
|
|
7101
7121
|
*/
|
|
7102
|
-
function
|
|
7103
|
-
const
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7122
|
+
function computeLocalTag(source) {
|
|
7123
|
+
const hash = createHash("sha256");
|
|
7124
|
+
pushField(hash, "directory", source.directory ?? "");
|
|
7125
|
+
pushField(hash, "executable", (source.executable ?? []).join(" "));
|
|
7126
|
+
pushField(hash, "dockerFile", source.dockerFile ?? "");
|
|
7127
|
+
pushField(hash, "dockerBuildTarget", source.dockerBuildTarget ?? "");
|
|
7128
|
+
pushField(hash, "networkMode", source.networkMode ?? "");
|
|
7129
|
+
pushField(hash, "platform", source.platform ?? "");
|
|
7130
|
+
pushField(hash, "dockerBuildSsh", source.dockerBuildSsh ?? "");
|
|
7131
|
+
pushField(hash, "cacheDisabled", source.cacheDisabled ? "1" : "0");
|
|
7132
|
+
pushMap(hash, "dockerBuildArgs", source.dockerBuildArgs);
|
|
7133
|
+
pushMap(hash, "dockerBuildContexts", source.dockerBuildContexts);
|
|
7134
|
+
pushMap(hash, "dockerBuildSecrets", source.dockerBuildSecrets);
|
|
7135
|
+
pushField(hash, "dockerOutputs", (source.dockerOutputs ?? []).join(""));
|
|
7136
|
+
pushField(hash, "cacheFrom", (source.cacheFrom ?? []).map((o) => JSON.stringify(o)).join(""));
|
|
7137
|
+
pushField(hash, "cacheTo", source.cacheTo ? JSON.stringify(source.cacheTo) : "");
|
|
7138
|
+
return `${getEmbedConfig().resourceNamePrefix}-invoke-${hash.digest("hex").slice(0, 16)}`;
|
|
7139
|
+
}
|
|
7140
|
+
function pushField(hash, name, value) {
|
|
7141
|
+
hash.update(name);
|
|
7142
|
+
hash.update("=");
|
|
7143
|
+
hash.update(value);
|
|
7144
|
+
hash.update("\0");
|
|
7145
|
+
}
|
|
7146
|
+
function pushMap(hash, name, value) {
|
|
7147
|
+
hash.update(name);
|
|
7148
|
+
hash.update("={");
|
|
7149
|
+
if (value) for (const [k, v] of Object.entries(value)) {
|
|
7150
|
+
hash.update(k);
|
|
7151
|
+
hash.update("=");
|
|
7152
|
+
hash.update(v);
|
|
7153
|
+
hash.update(";");
|
|
7154
|
+
}
|
|
7155
|
+
hash.update("}\0");
|
|
7111
7156
|
}
|
|
7157
|
+
|
|
7158
|
+
//#endregion
|
|
7159
|
+
//#region src/local/docker-runner.ts
|
|
7160
|
+
const execFileAsync$5 = promisify(execFile);
|
|
7112
7161
|
/**
|
|
7113
|
-
*
|
|
7114
|
-
* `(ecrRoleArn, callerRegion)`. Closes the reviewer's MAJOR finding: ECS
|
|
7115
|
-
* run-task with N containers under one `--ecr-role-arn` would otherwise issue
|
|
7116
|
-
* N× `AssumeRole` and N× `GetCallerIdentity` for identical credentials valid
|
|
7117
|
-
* for 3600s. The cache keeps a 5-minute safety margin against the recorded
|
|
7118
|
-
* `Expiration` so STS-side / local-clock skew never lets a stale entry through.
|
|
7119
|
-
*
|
|
7120
|
-
* Cache key is intentionally `(roleArn, region)` rather than full caller
|
|
7121
|
-
* identity — STS issues per-region session creds, and a switch of `--region`
|
|
7122
|
-
* between two `local invoke` calls in the same process must re-issue.
|
|
7162
|
+
* Wraps `docker pull` / `docker run` / `docker rm` for `cdkl invoke`.
|
|
7123
7163
|
*
|
|
7124
|
-
*
|
|
7125
|
-
*
|
|
7126
|
-
*
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
/**
|
|
7130
|
-
* Module-level cache for `STS:GetCallerIdentity`. The result is identity-only
|
|
7131
|
-
* (`Account`) and invariant for the lifetime of the process under one set of
|
|
7132
|
-
* default credentials. Keyed by `callerRegion` to avoid a cross-region leak
|
|
7133
|
-
* when the caller flips `AWS_REGION` mid-process (STS is global but the SDK
|
|
7134
|
-
* uses regional endpoints; the result is invariant in practice, but we key
|
|
7135
|
-
* on region for safety).
|
|
7164
|
+
* Mirrors the style of `src/assets/docker-asset-publisher.ts` (execFile for
|
|
7165
|
+
* one-shot calls, spawn for long-running ones). Kept as a separate file so
|
|
7166
|
+
* the command layer's wiring stays small; PR 5 (container Lambda) is
|
|
7167
|
+
* expected to add a second non-build call site, at which point the
|
|
7168
|
+
* common surface can be lifted into a shared helper.
|
|
7136
7169
|
*/
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
}
|
|
7170
|
+
var DockerRunnerError = class DockerRunnerError extends Error {
|
|
7171
|
+
constructor(message) {
|
|
7172
|
+
super(message);
|
|
7173
|
+
this.name = "DockerRunnerError";
|
|
7174
|
+
Object.setPrototypeOf(this, DockerRunnerError.prototype);
|
|
7175
|
+
}
|
|
7176
|
+
};
|
|
7144
7177
|
/**
|
|
7145
|
-
* Pull
|
|
7178
|
+
* Pull the image. No-op when `skipPull` is true.
|
|
7146
7179
|
*
|
|
7147
|
-
*
|
|
7148
|
-
*
|
|
7149
|
-
*
|
|
7180
|
+
* In verbose mode (`--verbose` / global log level `debug`), streams the
|
|
7181
|
+
* full `docker pull` progress to stdout so the user sees per-layer
|
|
7182
|
+
* downloads. In the default compact mode the call is silent (cached
|
|
7183
|
+
* images are the common case; a fresh pull still shows progress only
|
|
7184
|
+
* via `--verbose`). Errors are always surfaced: the captured stderr is
|
|
7185
|
+
* folded into the thrown `DockerRunnerError` message.
|
|
7150
7186
|
*/
|
|
7151
|
-
async function
|
|
7152
|
-
const logger = getLogger().child("
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
if (options.skipPull) {
|
|
7157
|
-
logger.info(`Skipping ECR pull (--no-pull). Verifying ${imageUri} is in local cache...`);
|
|
7158
|
-
await verifyImageInLocalCache(imageUri);
|
|
7159
|
-
return imageUri;
|
|
7187
|
+
async function pullImage(image, skipPull, platform) {
|
|
7188
|
+
const logger = getLogger().child("docker");
|
|
7189
|
+
if (skipPull) {
|
|
7190
|
+
logger.debug(`Skipping docker pull for ${image} (--no-pull)`);
|
|
7191
|
+
return;
|
|
7160
7192
|
}
|
|
7161
|
-
const
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
const sts = new STSClient(buildStsClientConfig({
|
|
7165
|
-
region: callerRegion,
|
|
7166
|
-
profile: options.profile
|
|
7167
|
-
}));
|
|
7193
|
+
const platformArgs = platform ? ["--platform", platform] : [];
|
|
7194
|
+
if (getLogger().getLevel() === "debug") {
|
|
7195
|
+
logger.info(`Pulling ${image}${platform ? ` (${platform})` : ""}...`);
|
|
7168
7196
|
try {
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
}
|
|
7177
|
-
const crossAccount = callerAccount !== parsed.accountId;
|
|
7178
|
-
const crossRegion = callerRegion !== void 0 && callerRegion !== parsed.region;
|
|
7179
|
-
let assumed;
|
|
7180
|
-
if (options.ecrRoleArn) {
|
|
7181
|
-
const cacheKey = `${options.profile ?? "_noprofile"}|${options.ecrRoleArn}|${callerRegion ?? "_unset"}`;
|
|
7182
|
-
const cached = ASSUMED_ROLE_CACHE.get(cacheKey);
|
|
7183
|
-
if (cached && isCredentialFresh(cached)) {
|
|
7184
|
-
assumed = cached;
|
|
7185
|
-
logger.debug(`Reusing cached AssumeRole credentials for ${options.ecrRoleArn}`);
|
|
7186
|
-
} else {
|
|
7187
|
-
assumed = await assumeRoleForEcr(options.ecrRoleArn, callerRegion, options.profile, logger);
|
|
7188
|
-
ASSUMED_ROLE_CACHE.set(cacheKey, assumed);
|
|
7189
|
-
logger.info(`Assumed role ${options.ecrRoleArn} for ECR pull (account=${parsed.accountId}, region=${parsed.region})`);
|
|
7197
|
+
await runDockerForeground([
|
|
7198
|
+
"pull",
|
|
7199
|
+
...platformArgs,
|
|
7200
|
+
image
|
|
7201
|
+
]);
|
|
7202
|
+
} catch (err) {
|
|
7203
|
+
throw new DockerRunnerError(`docker pull ${image} failed: ${err.message}`);
|
|
7190
7204
|
}
|
|
7191
|
-
|
|
7192
|
-
if (crossRegion) logger.info(`Cross-region ECR pull: image region ${parsed.region} != caller ${callerRegion ?? "(unset)"}. Authenticating against the image region directly.`);
|
|
7193
|
-
const ecr = new ECRClient({
|
|
7194
|
-
region: parsed.region,
|
|
7195
|
-
...assumed ? { credentials: assumed } : options.profile ? { profile: options.profile } : {}
|
|
7196
|
-
});
|
|
7197
|
-
try {
|
|
7198
|
-
await ecrLogin(ecr, parsed.accountId, parsed.region);
|
|
7199
|
-
} finally {
|
|
7200
|
-
ecr.destroy();
|
|
7205
|
+
return;
|
|
7201
7206
|
}
|
|
7202
|
-
logger.
|
|
7207
|
+
logger.debug(`Pulling ${image} (silent — pass --verbose to stream progress)`);
|
|
7203
7208
|
try {
|
|
7204
|
-
await
|
|
7209
|
+
await runDockerStreaming([
|
|
7210
|
+
"pull",
|
|
7211
|
+
...platformArgs,
|
|
7212
|
+
image
|
|
7213
|
+
], {
|
|
7214
|
+
streamLive: false,
|
|
7215
|
+
progressLabel: `Pulling ${image}`
|
|
7216
|
+
});
|
|
7205
7217
|
} catch (err) {
|
|
7206
|
-
|
|
7218
|
+
const e = err;
|
|
7219
|
+
if (e.exitCode === void 0 || e.exitCode === null) throw new DockerRunnerError(`docker pull ${image} failed: ${e.message ?? String(err)}`);
|
|
7220
|
+
const detail = e.stderr?.trim() || e.stdout?.trim() || "(no output)";
|
|
7221
|
+
throw new DockerRunnerError(`docker pull ${image} exited with code ${e.exitCode}: ${detail}`);
|
|
7207
7222
|
}
|
|
7208
|
-
return imageUri;
|
|
7209
7223
|
}
|
|
7210
7224
|
/**
|
|
7211
|
-
*
|
|
7212
|
-
*
|
|
7213
|
-
*
|
|
7214
|
-
*
|
|
7215
|
-
*
|
|
7225
|
+
* Run the container detached. Returns the container ID.
|
|
7226
|
+
*
|
|
7227
|
+
* The caller is responsible for:
|
|
7228
|
+
* - polling `host:port` for RIE readiness,
|
|
7229
|
+
* - issuing the invoke,
|
|
7230
|
+
* - calling `removeContainer` from a `try`/`finally` so the container
|
|
7231
|
+
* is cleaned up on any error path including SIGINT.
|
|
7216
7232
|
*/
|
|
7217
|
-
async function
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7233
|
+
async function runDetached(opts) {
|
|
7234
|
+
const args = [
|
|
7235
|
+
"run",
|
|
7236
|
+
"-d",
|
|
7237
|
+
"--rm"
|
|
7238
|
+
];
|
|
7239
|
+
if (opts.name) args.push("--name", opts.name);
|
|
7240
|
+
if (opts.network) args.push("--network", opts.network);
|
|
7241
|
+
if (opts.platform) {
|
|
7242
|
+
args.push("--platform", opts.platform);
|
|
7243
|
+
warnIfEmulatedPlatform(opts.platform, opts.name ? { label: opts.name } : {});
|
|
7244
|
+
}
|
|
7245
|
+
if (opts.extraHosts) for (const entry of opts.extraHosts) args.push("--add-host", `${entry.host}:${entry.ip}`);
|
|
7246
|
+
const host = opts.host ?? "127.0.0.1";
|
|
7247
|
+
args.push("-p", `${host}:${opts.hostPort}:${opts.containerPort ?? 8080}`);
|
|
7248
|
+
if (opts.debugPort !== void 0) args.push("-p", `${host}:${opts.debugPort}:${opts.debugPort}`);
|
|
7249
|
+
for (const mount of opts.mounts) {
|
|
7250
|
+
const ro = mount.readOnly ? ":ro" : "";
|
|
7251
|
+
args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
|
|
7252
|
+
}
|
|
7253
|
+
if (opts.extraMounts) for (const mount of opts.extraMounts) {
|
|
7254
|
+
const ro = mount.readOnly ? ":ro" : "";
|
|
7255
|
+
args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
|
|
7256
|
+
}
|
|
7257
|
+
const sensitiveKeys = opts.sensitiveEnvKeys && opts.sensitiveEnvKeys.size > 0 ? new Set([...SENSITIVE_ENV_KEYS, ...opts.sensitiveEnvKeys]) : SENSITIVE_ENV_KEYS;
|
|
7258
|
+
const passthroughEnv = appendEnvFlags(args, opts.env, sensitiveKeys);
|
|
7259
|
+
if (opts.tmpfs) args.push("--tmpfs", `${opts.tmpfs.target}:rw,size=${opts.tmpfs.sizeMb}m`);
|
|
7260
|
+
if (opts.workingDir) args.push("--workdir", opts.workingDir);
|
|
7261
|
+
let entryPointTail = [];
|
|
7262
|
+
if (opts.entryPoint && opts.entryPoint.length > 0) {
|
|
7263
|
+
args.push("--entrypoint", opts.entryPoint[0]);
|
|
7264
|
+
entryPointTail = opts.entryPoint.slice(1);
|
|
7265
|
+
}
|
|
7266
|
+
args.push(opts.image, ...entryPointTail, ...opts.cmd);
|
|
7267
|
+
getLogger().child("docker").debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
|
|
7223
7268
|
try {
|
|
7224
|
-
const
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
secretAccessKey: creds.SecretAccessKey,
|
|
7233
|
-
sessionToken: creds.SessionToken,
|
|
7234
|
-
...creds.Expiration && { expiration: creds.Expiration }
|
|
7235
|
-
};
|
|
7236
|
-
} catch (err) {
|
|
7237
|
-
if (err instanceof LocalInvokeBuildError) throw err;
|
|
7238
|
-
throw new LocalInvokeBuildError(`Failed to assume role ${roleArn} for ECR pull: ${err instanceof Error ? err.message : String(err)}. Verify the role exists and its trust policy permits the caller's identity to assume it.`);
|
|
7239
|
-
} finally {
|
|
7240
|
-
sts.destroy();
|
|
7269
|
+
const { stdout } = await execFileAsync$5(getDockerCmd(), args, {
|
|
7270
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
7271
|
+
...execEnvForSecrets(passthroughEnv)
|
|
7272
|
+
});
|
|
7273
|
+
return stdout.trim();
|
|
7274
|
+
} catch (error) {
|
|
7275
|
+
const err = error;
|
|
7276
|
+
throw new DockerRunnerError(`docker run failed: ${err.stderr?.trim() || err.message || String(error)}`);
|
|
7241
7277
|
}
|
|
7242
7278
|
}
|
|
7243
7279
|
/**
|
|
7244
|
-
*
|
|
7245
|
-
*
|
|
7246
|
-
* independent of any full asset-publish path's larger surface area.
|
|
7280
|
+
* `docker logs -f <id>` plumbed to stdout/stderr. Returns a function that
|
|
7281
|
+
* stops the stream (used by the caller in a `finally` block).
|
|
7247
7282
|
*/
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
}
|
|
7264
|
-
const e = err;
|
|
7265
|
-
throw new LocalInvokeBuildError(`ECR login failed: ${formatDockerLoginError(e.stderr || e.message || String(err), endpoint)}`);
|
|
7266
|
-
}
|
|
7283
|
+
function streamLogs(containerId) {
|
|
7284
|
+
const proc = spawn(getDockerCmd(), [
|
|
7285
|
+
"logs",
|
|
7286
|
+
"-f",
|
|
7287
|
+
containerId
|
|
7288
|
+
], { stdio: [
|
|
7289
|
+
"ignore",
|
|
7290
|
+
"pipe",
|
|
7291
|
+
"pipe"
|
|
7292
|
+
] });
|
|
7293
|
+
proc.stdout?.on("data", (chunk) => process.stdout.write(chunk));
|
|
7294
|
+
proc.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
7295
|
+
proc.on("error", () => {});
|
|
7296
|
+
return () => {
|
|
7297
|
+
if (!proc.killed) proc.kill("SIGTERM");
|
|
7298
|
+
};
|
|
7267
7299
|
}
|
|
7268
7300
|
/**
|
|
7269
|
-
* `docker
|
|
7270
|
-
*
|
|
7271
|
-
*
|
|
7301
|
+
* Best-effort `docker rm -f <id>`. Errors are swallowed (logged at debug)
|
|
7302
|
+
* because this typically runs from a `finally` and the parent has its own
|
|
7303
|
+
* error to surface.
|
|
7272
7304
|
*/
|
|
7273
|
-
async function
|
|
7305
|
+
async function removeContainer(containerId) {
|
|
7306
|
+
if (!containerId) return;
|
|
7307
|
+
const logger = getLogger().child("docker");
|
|
7274
7308
|
try {
|
|
7275
|
-
await
|
|
7276
|
-
"
|
|
7277
|
-
"
|
|
7278
|
-
|
|
7309
|
+
await execFileAsync$5(getDockerCmd(), [
|
|
7310
|
+
"rm",
|
|
7311
|
+
"-f",
|
|
7312
|
+
containerId
|
|
7279
7313
|
]);
|
|
7280
|
-
|
|
7281
|
-
|
|
7314
|
+
logger.debug(`Removed container ${containerId}`);
|
|
7315
|
+
} catch (error) {
|
|
7316
|
+
const err = error;
|
|
7317
|
+
logger.debug(`docker rm -f ${containerId} failed: ${err.stderr || err.message || String(error)}`);
|
|
7282
7318
|
}
|
|
7283
7319
|
}
|
|
7284
7320
|
/**
|
|
7285
|
-
*
|
|
7286
|
-
* the
|
|
7287
|
-
*
|
|
7288
|
-
* (above) and the local-build verifier route through one `docker image
|
|
7289
|
-
* inspect` shape.
|
|
7321
|
+
* Verify the docker daemon is reachable. Surfaces a friendlier error than
|
|
7322
|
+
* the raw `ENOENT` / "Cannot connect to the Docker daemon" the user would
|
|
7323
|
+
* otherwise see at the first run call. Called once up front.
|
|
7290
7324
|
*/
|
|
7291
|
-
async function
|
|
7325
|
+
async function ensureDockerAvailable() {
|
|
7326
|
+
const cmd = getDockerCmd();
|
|
7292
7327
|
try {
|
|
7293
|
-
await
|
|
7294
|
-
"
|
|
7295
|
-
"
|
|
7296
|
-
|
|
7328
|
+
await execFileAsync$5(cmd, [
|
|
7329
|
+
"version",
|
|
7330
|
+
"--format",
|
|
7331
|
+
"{{.Server.Version}}"
|
|
7297
7332
|
]);
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7333
|
+
} catch (error) {
|
|
7334
|
+
const err = error;
|
|
7335
|
+
if (err.code === "ENOENT") throw new DockerRunnerError(`${cmd} is not installed or not on PATH. ${getEmbedConfig().cliName} invoke needs Docker (or a compatible CLI specified via CDK_DOCKER) — install it and retry.`);
|
|
7336
|
+
throw new DockerRunnerError(`${cmd} daemon is not reachable: ${err.stderr?.trim() || err.message || String(error)}. Start Docker Desktop / the docker daemon and retry.`);
|
|
7301
7337
|
}
|
|
7302
7338
|
}
|
|
7303
|
-
|
|
7304
|
-
//#endregion
|
|
7305
|
-
//#region src/local/docker-image-builder.ts
|
|
7306
7339
|
/**
|
|
7307
|
-
*
|
|
7308
|
-
*
|
|
7340
|
+
* Allocate a free TCP port on `127.0.0.1`. Used to pick a host port for
|
|
7341
|
+
* publishing the RIE :8080 endpoint without colliding with whatever else
|
|
7342
|
+
* the user has running. The OS assigns a port via `port: 0` and we
|
|
7343
|
+
* close the probe before returning so docker can bind it next.
|
|
7309
7344
|
*
|
|
7310
|
-
*
|
|
7311
|
-
*
|
|
7312
|
-
*
|
|
7313
|
-
* when the tag is missing.
|
|
7345
|
+
* There is a tiny race window between close and `docker run -p` — in
|
|
7346
|
+
* practice it's never been observed for local invoke; if it ever
|
|
7347
|
+
* surfaces, the caller can retry with a fresh port.
|
|
7314
7348
|
*/
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
7331
|
-
progressLabel: `Building container image (platform=${platform})`
|
|
7349
|
+
function pickFreePort() {
|
|
7350
|
+
return new Promise((resolvePort, rejectPort) => {
|
|
7351
|
+
const server = createServer();
|
|
7352
|
+
server.unref();
|
|
7353
|
+
server.on("error", rejectPort);
|
|
7354
|
+
server.listen(0, "127.0.0.1", () => {
|
|
7355
|
+
const address = server.address();
|
|
7356
|
+
if (!address || typeof address === "string") {
|
|
7357
|
+
server.close();
|
|
7358
|
+
rejectPort(/* @__PURE__ */ new Error("Could not allocate a host port"));
|
|
7359
|
+
return;
|
|
7360
|
+
}
|
|
7361
|
+
const port = address.port;
|
|
7362
|
+
server.close(() => resolvePort(port));
|
|
7363
|
+
});
|
|
7332
7364
|
});
|
|
7333
|
-
if (actualTag !== tag) {
|
|
7334
|
-
logger.debug(`Re-tagging executable-built image '${actualTag}' → '${tag}'`);
|
|
7335
|
-
try {
|
|
7336
|
-
await runDockerStreaming([
|
|
7337
|
-
"tag",
|
|
7338
|
-
actualTag,
|
|
7339
|
-
tag
|
|
7340
|
-
]);
|
|
7341
|
-
} catch (err) {
|
|
7342
|
-
const e = err;
|
|
7343
|
-
throw new LocalInvokeBuildError(`docker tag failed re-tagging '${actualTag}' → '${tag}': ${e.stderr?.trim() || e.message || String(err)}`);
|
|
7344
|
-
}
|
|
7345
|
-
}
|
|
7346
|
-
return tag;
|
|
7347
7365
|
}
|
|
7348
7366
|
/**
|
|
7349
|
-
*
|
|
7350
|
-
*
|
|
7351
|
-
*
|
|
7352
|
-
*
|
|
7353
|
-
*
|
|
7354
|
-
*
|
|
7367
|
+
* Env keys whose VALUES are sensitive (AWS credentials). These are
|
|
7368
|
+
* routed through docker's value-from-process-env form by
|
|
7369
|
+
* {@link appendEnvFlags} so the value never appears in `docker run`'s
|
|
7370
|
+
* argv (`ps` / `/proc/<pid>/cmdline`), and they are also redacted by
|
|
7371
|
+
* {@link redactAwsCredentialsInArgs} as a defense for any path that
|
|
7372
|
+
* still emits the inline `-e <KEY>=<value>` form into the debug log.
|
|
7355
7373
|
*/
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7374
|
+
const SENSITIVE_ENV_KEYS = new Set([
|
|
7375
|
+
"AWS_ACCESS_KEY_ID",
|
|
7376
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
7377
|
+
"AWS_SESSION_TOKEN"
|
|
7378
|
+
]);
|
|
7359
7379
|
/**
|
|
7360
|
-
*
|
|
7380
|
+
* Append `-e` flags for `env` to `args`, routing keys in `sensitiveKeys`
|
|
7381
|
+
* through docker's value-from-process-env form (`-e KEY`, with NO
|
|
7382
|
+
* `=value`). Docker reads the value from its OWN environment at run time,
|
|
7383
|
+
* so the secret never lands in `docker run`'s argv — invisible to
|
|
7384
|
+
* `ps aux` / `/proc/<pid>/cmdline` (other users on a shared host) and to
|
|
7385
|
+
* shell history. Non-sensitive keys keep the inline `-e KEY=VALUE` form
|
|
7386
|
+
* (fine for non-secret config, and keeps `--debug` output readable).
|
|
7361
7387
|
*
|
|
7362
|
-
*
|
|
7363
|
-
*
|
|
7364
|
-
*
|
|
7365
|
-
* `docker
|
|
7366
|
-
*
|
|
7367
|
-
*
|
|
7388
|
+
* Returns the `{ KEY: value }` map of routed-through keys; the caller MUST
|
|
7389
|
+
* merge it into the spawned docker process's `env` (see
|
|
7390
|
+
* {@link execEnvForSecrets}) so docker can resolve each passed-through
|
|
7391
|
+
* key. Values still appear in `docker inspect` Config.Env — that is
|
|
7392
|
+
* inherent to container env and matches production behavior.
|
|
7393
|
+
*
|
|
7394
|
+
* Unlike `--env-file`, this handles multi-line values (e.g. PEM secrets)
|
|
7395
|
+
* and needs no temp file on disk.
|
|
7368
7396
|
*/
|
|
7369
|
-
function
|
|
7370
|
-
const
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
pushField(hash, "platform", source.platform ?? "");
|
|
7377
|
-
pushField(hash, "dockerBuildSsh", source.dockerBuildSsh ?? "");
|
|
7378
|
-
pushField(hash, "cacheDisabled", source.cacheDisabled ? "1" : "0");
|
|
7379
|
-
pushMap(hash, "dockerBuildArgs", source.dockerBuildArgs);
|
|
7380
|
-
pushMap(hash, "dockerBuildContexts", source.dockerBuildContexts);
|
|
7381
|
-
pushMap(hash, "dockerBuildSecrets", source.dockerBuildSecrets);
|
|
7382
|
-
pushField(hash, "dockerOutputs", (source.dockerOutputs ?? []).join(""));
|
|
7383
|
-
pushField(hash, "cacheFrom", (source.cacheFrom ?? []).map((o) => JSON.stringify(o)).join(""));
|
|
7384
|
-
pushField(hash, "cacheTo", source.cacheTo ? JSON.stringify(source.cacheTo) : "");
|
|
7385
|
-
return `${getEmbedConfig().resourceNamePrefix}-invoke-${hash.digest("hex").slice(0, 16)}`;
|
|
7397
|
+
function appendEnvFlags(args, env, sensitiveKeys) {
|
|
7398
|
+
const passthrough = {};
|
|
7399
|
+
for (const [k, v] of Object.entries(env)) if (sensitiveKeys.has(k)) {
|
|
7400
|
+
args.push("-e", k);
|
|
7401
|
+
passthrough[k] = v;
|
|
7402
|
+
} else args.push("-e", `${k}=${v}`);
|
|
7403
|
+
return passthrough;
|
|
7386
7404
|
}
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7405
|
+
/**
|
|
7406
|
+
* Build the `env` option for an `execFile` / `spawn` call that runs a
|
|
7407
|
+
* `docker run` whose args include passed-through sensitive keys (from
|
|
7408
|
+
* {@link appendEnvFlags}). Returns `{ env: {...process.env, ...passthrough} }`
|
|
7409
|
+
* so docker inherits the normal environment PLUS the sensitive values it
|
|
7410
|
+
* must resolve, or `{}` when there is nothing to pass through (preserving
|
|
7411
|
+
* the default inherited-environment behavior).
|
|
7412
|
+
*/
|
|
7413
|
+
function execEnvForSecrets(passthrough) {
|
|
7414
|
+
if (Object.keys(passthrough).length === 0) return {};
|
|
7415
|
+
return { env: {
|
|
7416
|
+
...process.env,
|
|
7417
|
+
...passthrough
|
|
7418
|
+
} };
|
|
7392
7419
|
}
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7420
|
+
/**
|
|
7421
|
+
* Returns a copy of `args` with any `-e <KEY>=<value>` pair whose KEY is
|
|
7422
|
+
* in {@link SENSITIVE_ENV_KEYS} replaced with `-e <KEY>=***`. The actual
|
|
7423
|
+
* `args` passed to `spawn` are never mutated — this is for log output
|
|
7424
|
+
* only. With {@link appendEnvFlags} routing credentials through the
|
|
7425
|
+
* value-less `-e KEY` form this rarely fires, but it stays as defense for
|
|
7426
|
+
* any inline-form credential that slips into a logged command.
|
|
7427
|
+
*/
|
|
7428
|
+
function redactAwsCredentialsInArgs(args) {
|
|
7429
|
+
const out = [];
|
|
7430
|
+
for (let i = 0; i < args.length; i++) {
|
|
7431
|
+
const cur = args[i];
|
|
7432
|
+
const next = args[i + 1];
|
|
7433
|
+
if (cur === "-e" && typeof next === "string") {
|
|
7434
|
+
const eqIdx = next.indexOf("=");
|
|
7435
|
+
if (eqIdx > 0) {
|
|
7436
|
+
const key = next.substring(0, eqIdx);
|
|
7437
|
+
if (SENSITIVE_ENV_KEYS.has(key)) {
|
|
7438
|
+
out.push("-e", `${key}=***`);
|
|
7439
|
+
i++;
|
|
7440
|
+
continue;
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
}
|
|
7444
|
+
out.push(cur);
|
|
7401
7445
|
}
|
|
7402
|
-
|
|
7446
|
+
return out;
|
|
7403
7447
|
}
|
|
7404
7448
|
|
|
7405
7449
|
//#endregion
|
|
@@ -21397,8 +21441,13 @@ function buildDockerRunArgs(opts) {
|
|
|
21397
21441
|
}
|
|
21398
21442
|
}
|
|
21399
21443
|
if (opts.addHostFlags && opts.addHostFlags.length > 0) for (const f of opts.addHostFlags) args.push(f);
|
|
21400
|
-
|
|
21401
|
-
|
|
21444
|
+
let resolvedPlatform;
|
|
21445
|
+
if (opts.platformOverride) resolvedPlatform = opts.platformOverride;
|
|
21446
|
+
else if (task.runtimePlatform) resolvedPlatform = task.runtimePlatform.cpuArchitecture === "ARM64" ? "linux/arm64" : "linux/amd64";
|
|
21447
|
+
if (resolvedPlatform) {
|
|
21448
|
+
args.push("--platform", resolvedPlatform);
|
|
21449
|
+
warnIfEmulatedPlatform(resolvedPlatform, { label: `${task.family}/${container.name}` });
|
|
21450
|
+
}
|
|
21402
21451
|
const ephemeralPorts = new Set(opts.ephemeralPublishContainerPorts ?? []);
|
|
21403
21452
|
if (!opts.skipHostPortPublish) for (const pm of container.portMappings) {
|
|
21404
21453
|
if (ephemeralPorts.has(pm.containerPort)) continue;
|
|
@@ -31937,10 +31986,10 @@ const STUDIO_CSS = `
|
|
|
31937
31986
|
padding: 5px 16px; font-size: 12px; cursor: pointer; margin: 0;
|
|
31938
31987
|
}
|
|
31939
31988
|
.log-clear:hover { background: #2a2a2a; color: #fff; border-color: #4a4a4a; }
|
|
31940
|
-
/* A Clear action sits
|
|
31941
|
-
|
|
31942
|
-
|
|
31943
|
-
.clear-row { display: flex; justify-content: flex-end;
|
|
31989
|
+
/* A Clear action sits on its own row, right-aligned, ABOVE the log it
|
|
31990
|
+
clears (the log streams downward) and below the action controls (the
|
|
31991
|
+
WS console's Send row). */
|
|
31992
|
+
.clear-row { display: flex; justify-content: flex-end; margin: 4px 0; }
|
|
31944
31993
|
.composer .err { color: #e0707a; margin-top: 6px; min-height: 18px; }
|
|
31945
31994
|
.section { padding: 8px 12px; border-bottom: 1px solid #222; }
|
|
31946
31995
|
.section h3 { margin: 0 0 6px; font-size: 11px; color: #888; text-transform: uppercase; }
|
|
@@ -31951,12 +32000,7 @@ const STUDIO_CSS = `
|
|
|
31951
32000
|
.endpoint:hover { text-decoration: underline; }
|
|
31952
32001
|
.ws-console .ws-status { font-size: 12px; color: #888; margin-left: 8px; font-weight: 400; }
|
|
31953
32002
|
.ws-console .ws-status.on { color: #4ec97a; }
|
|
31954
|
-
|
|
31955
|
-
pane the input does not stretch edge-to-edge and fling Send far to the
|
|
31956
|
-
right. Send then sits right after the input; the log + Clear share the
|
|
31957
|
-
same column. The cap is a no-op on a narrow pane (content uses what is
|
|
31958
|
-
available). */
|
|
31959
|
-
.ws-row { display: flex; gap: 6px; align-items: center; margin: 6px 0; max-width: 760px; }
|
|
32003
|
+
.ws-row { display: flex; gap: 6px; align-items: center; margin: 6px 0; }
|
|
31960
32004
|
.ws-row .ws-input { flex: 1; min-width: 0; background: #1a1a1a; border: 1px solid #333;
|
|
31961
32005
|
color: #ddd; border-radius: 4px; padding: 5px 7px; font: inherit; }
|
|
31962
32006
|
.ws-row .ws-input:focus { outline: none; border-color: #4ec97a; }
|
|
@@ -31964,10 +32008,7 @@ const STUDIO_CSS = `
|
|
|
31964
32008
|
padding: 5px 12px; cursor: pointer; }
|
|
31965
32009
|
.ws-row button:disabled { background: #2a2a2a; color: #666; cursor: default; }
|
|
31966
32010
|
.ws-frames { max-height: 180px; overflow: auto; background: #141414; border: 1px solid #262626;
|
|
31967
|
-
border-radius: 4px; padding: 6px 8px; margin-top: 4px; min-height: 22px;
|
|
31968
|
-
/* The serve LOGS <pre> shares the capped column so its Clear (below it,
|
|
31969
|
-
right-aligned via .clear-row) lands at the column edge, near the log. */
|
|
31970
|
-
.serve-log { max-width: 760px; }
|
|
32011
|
+
border-radius: 4px; padding: 6px 8px; margin-top: 4px; min-height: 22px; }
|
|
31971
32012
|
.searchbar { padding: 6px 10px; border-bottom: 1px solid #2a2a2a; background: #151515;
|
|
31972
32013
|
position: sticky; top: 28px; z-index: 1; }
|
|
31973
32014
|
.searchbar input {
|
|
@@ -33023,12 +33064,9 @@ const STUDIO_SCRIPT = `
|
|
|
33023
33064
|
)
|
|
33024
33065
|
);
|
|
33025
33066
|
}
|
|
33026
|
-
|
|
33027
|
-
|
|
33028
|
-
//
|
|
33029
|
-
// lines, so let the user empty the panel (display-only — the server-side
|
|
33030
|
-
// store / history is untouched). Below the log + right-aligned so it lands
|
|
33031
|
-
// near the content it clears, not in the section header above.
|
|
33067
|
+
// Clear sits ABOVE the log, right-aligned (issue #338): hammering a serve
|
|
33068
|
+
// piles up log lines, so let the user empty the panel (display-only — the
|
|
33069
|
+
// server-side store / history is untouched).
|
|
33032
33070
|
const logClearRow = el('div', 'clear-row');
|
|
33033
33071
|
const logClear = el('button', 'log-clear', 'Clear');
|
|
33034
33072
|
logClear.onclick = function () {
|
|
@@ -33039,6 +33077,8 @@ const STUDIO_SCRIPT = `
|
|
|
33039
33077
|
};
|
|
33040
33078
|
logClearRow.appendChild(logClear);
|
|
33041
33079
|
logSec.appendChild(logClearRow);
|
|
33080
|
+
const logPre = el('pre', null, logs.length ? logs.join('\\n') : '(none)');
|
|
33081
|
+
logSec.appendChild(logPre);
|
|
33042
33082
|
ws.appendChild(logSec);
|
|
33043
33083
|
// Register the live LOGS <pre> so streamed log events update it surgically
|
|
33044
33084
|
// (issue #334) instead of re-rendering the whole serve workspace.
|
|
@@ -33488,22 +33528,28 @@ const STUDIO_SCRIPT = `
|
|
|
33488
33528
|
sendBtn.disabled = !on;
|
|
33489
33529
|
sendBtn.onclick = wsSend;
|
|
33490
33530
|
|
|
33531
|
+
// Input fills the row width; Send is wrapped onto its OWN row below so on
|
|
33532
|
+
// a wide pane it sits at the left (near the input) instead of being flung
|
|
33533
|
+
// to the far right edge.
|
|
33491
33534
|
const row = el('div', 'ws-row');
|
|
33492
33535
|
row.appendChild(connectBtn);
|
|
33493
33536
|
row.appendChild(input);
|
|
33494
|
-
row.appendChild(sendBtn);
|
|
33495
33537
|
sec.appendChild(row);
|
|
33496
33538
|
|
|
33497
|
-
const
|
|
33498
|
-
|
|
33539
|
+
const sendRow = el('div', 'ws-row');
|
|
33540
|
+
sendRow.appendChild(sendBtn);
|
|
33541
|
+
sec.appendChild(sendRow);
|
|
33499
33542
|
|
|
33500
|
-
// Clear sits
|
|
33501
|
-
// aligned
|
|
33543
|
+
// Clear sits directly below the Send button (and above the log it clears),
|
|
33544
|
+
// right-aligned.
|
|
33502
33545
|
const clearRow = el('div', 'clear-row');
|
|
33503
33546
|
const clearBtn = el('button', 'log-clear ws-clear', 'Clear');
|
|
33504
33547
|
clearBtn.onclick = wsClear;
|
|
33505
33548
|
clearRow.appendChild(clearBtn);
|
|
33506
33549
|
sec.appendChild(clearRow);
|
|
33550
|
+
|
|
33551
|
+
const frames = el('pre', 'ws-frames', wsFrames.join('\\n'));
|
|
33552
|
+
sec.appendChild(frames);
|
|
33507
33553
|
return sec;
|
|
33508
33554
|
}
|
|
33509
33555
|
|
|
@@ -36507,4 +36553,4 @@ function addStudioSpecificOptions(cmd) {
|
|
|
36507
36553
|
|
|
36508
36554
|
//#endregion
|
|
36509
36555
|
export { CLOUDFRONT_DISTRIBUTION_TYPE as $, computeRequestIdentityHash as $n, availableWebSocketApiIdentifiers as $r, getContainerNetworkIp as $t, addStartCloudFrontSpecificOptions as A, resolveApiTargetSubset as An, buildContainerImage as Ar, addImageOverrideOptions as At, classifyS3Error as B, groupRoutesByServer as Bn, createLocalStateProvider as Br, enforceImageOverrideOrphans as Bt, addListSpecificOptions as C, toCmdArgv as Cn, buildMgmtEndpointEnvUrl as Cr, createLocalStartServiceCommand as Ct, createLocalStartAgentCoreCommand as D, addStartApiSpecificOptions as Dn, buildDisconnectEvent as Dr, MAX_TASKS_SUBNET_RANGE_CAP as Dt, addStartAgentCoreSpecificOptions as E, createLocalInvokeCommand as En, buildConnectEvent as Er, createLocalRunTaskCommand as Et, idFromArn as F, materializeLayerFromArn as Fn, substituteAgainstState as Fr, resolveEcsAssumeRoleOption as Ft, serveFromStaticOrigin as G, defaultCredentialsLoader as Gn, resolveCfnStackName as Gr, describePinnedImageUri as Gt, matchBehavior as H, startApiServer as Hn, rejectExplicitCfnStackWithMultipleStacks as Hr, parseImageOverrideFlags as Ht, resolveKvsModulesForDistribution as I, resolveEnvVars$1 as In, substituteAgainstStateAsync as Ir, resolveSharedSidecarCredentials as It, applyEdgeResponseResult as J, createJwksCache as Jn, resolveSsmParameters as Jr, buildCloudMapIndex as Jt, serveLambdaUrlOrigin as K, buildCognitoJwksUrl as Kn, CfnLocalStateProvider as Kr, isLocalCdkAssetImage as Kt, createDeployedKvsDataSource as L, availableApiIdentifiers as Ln, substituteEnvVarsFromState as Lr, runEcsServiceEmulator as Lt, parseKvsFileOverrides as M, createFileWatcher as Mn, resolveRuntimeFileExtension as Mr, ecsClusterOption as Mt, parseOriginOverrides as N, attachStageContext as Nn, resolveRuntimeImage as Nr, parseMaxTasks as Nt, startAgentCoreWsBridge as O, createLocalStartApiCommand as On, buildMessageEvent as Or, addCommonEcsServiceOptions as Ot, resolveCloudFrontTarget as P, buildStageMap as Pn, EcsTaskResolutionError as Pr, parseRestartPolicy as Pt, httpHeadersToEdge as Q, buildMethodArn as Qn, listTargets as Qr, setShadowReadyTimeoutMs as Qt, resolveDeployedKvsArnByName as R, filterRoutesByApiIdentifier as Rn, substituteEnvVarsFromStateAsync as Rr, ImageOverrideError as Rt, StudioEventBus as S, resolveProfileCredentials as Si, renderCodeDockerfile as Sn, ConnectionRegistry as Sr, addStartServiceSpecificOptions as St, formatTargetListing as T, addInvokeSpecificOptions as Tn, parseConnectionsPath as Tr, addRunTaskSpecificOptions as Tt, startCloudFrontServer as U, resolveSelectionExpression as Un, resolveCfnFallbackRegion as Ur, resolveImageOverrides as Ut, createS3OriginReader as V, readMtlsMaterialsFromDisk as Vn, isCfnFlagPresent as Vr, mergeForService as Vt, resolveErrorResponseCandidates as W, resolveServiceIntegrationParameters as Wn, resolveCfnRegion as Wr, runImageOverrideBuilds as Wt, buildEdgeResponseEvent as X, verifyJwtAuthorizer as Xn, resolveSingleTarget as Xr, DEFAULT_SHADOW_READY_TIMEOUT_MS as Xt, buildEdgeRequestEvent as Y, verifyCognitoJwt as Yn, resolveWatchConfig as Yr, CloudMapRegistry as Yt, edgeHeadersToHttp as Z, verifyJwtViaDiscovery as Zn, countTargets as Zr, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as Zt, filterStudioTargetGroups as _, formatStateRemedy as _i, waitForAgentCorePing as _n, tryParseStatus as _r, createLocalStartAlbCommand as _t, createLocalStudioCommand as a, discoverRoutes as ai, A2A_CONTAINER_PORT as an, buildCorsConfigByApiId as ar, pickLambdaEdgeFunctionLogicalId as at, renderStudioHtml as b, LocalInvokeBuildError as bi, buildAgentCoreCodeImage as bn, probeHostGatewaySupport as br, isApplicationLoadBalancer as bt, startStudioProxy as c, AGENTCORE_A2A_PROTOCOL as ci, MCP_CONTAINER_PORT as cn, matchPreflight as cr, compileCloudFrontFunction as ct, createStudioDispatcher as d, AGENTCORE_MCP_PROTOCOL as di, mcpInvokeOnce as dn, applyAuthorizerOverlay as dr, stripCloudFrontImport as dt, discoverWebSocketApis as ei, attachContainerLogStreamer as en, evaluateCachedLambdaPolicy as er, describeS3OriginDomain as et, filterStudioCustomResources as f, AGENTCORE_RUNTIME_TYPE as fi, parseSseForJsonRpc as fn, buildHttpApiV2Event as fr, createCloudFrontModule as ft, annotatePinnedEcsTargets as g, derivePseudoParametersFromRegion as gi, invokeAgentCore as gn, selectIntegrationResponse as gr, albStrategy as gt, annotateEcsTaskPinnedTargets as h, resolveAgentCoreTarget as hi, AGENTCORE_SESSION_ID_HEADER as hn, pickResponseTemplate as hr, addAlbSpecificOptions as ht, coerceStopRequest as i, webSocketApiMatchesIdentifier as ii, invokeAgentCoreWs as in, applyCorsResponseHeaders as ir, pickKvsLogicalIdFromArn as it, createLocalStartCloudFrontCommand as j, createAuthorizerCache as jn, resolveRuntimeCodeMountPath as jr, buildEcsImageResolutionContext$1 as jt, LocalStartCloudFrontError as k, createWatchPredicates as kn, architectureToPlatform as kr, addEcsAssumeRoleOptions as kt, relayServeRequest as l, AGENTCORE_AGUI_PROTOCOL as li, MCP_PATH as ln, matchRoute as lr, runViewerRequest as lt, annotateAlbPinnedBackingServices as m, pickAgentCoreCandidateStack as mi, signAgentCoreInvocation as mn, evaluateResponseParameters as mr, createUnboundCloudFrontModule as mt, coerceRunRequest as n, filterWebSocketApisByIdentifiers as ni, createLocalInvokeAgentCoreCommand as nn, invokeTokenAuthorizer as nr, isCloudFrontDistribution as nt, resolveServeBaseUrl as o, pickRefLogicalId as oi, A2A_PATH as on, buildCorsConfigFromCloudFrontChain as or, pickTargetFunctionLogicalId as ot, isCustomResourceLambdaTarget as p, AgentCoreResolutionError as pi, AGENTCORE_SIGV4_SERVICE as pn, buildRestV1Event as pr, createLocalFileKvsDataSource as pt, applyEdgeRequestResult as q, buildJwksUrlFromIssuer as qn, collectSsmParameterRefs as qr, listPinnedTargets as qt, coerceServeRequest as r, parseSelectionExpressionPath as ri, bridgeAgentCoreWs as rn, attachAuthorizers as rr, pickFunctionUrlLogicalIdFromOrigin as rt, createStudioServeManager as s, resolveLambdaArnIntrinsic as si, a2aInvokeOnce as sn, isFunctionUrlOacFronted as sr, resolveCloudFrontDistribution as st, addStudioSpecificOptions as t, discoverWebSocketApisOrThrow as ti, addInvokeAgentCoreSpecificOptions as tn, invokeRequestAuthorizer as tr, extractKvsAssociations as tt, reinvoke as u, AGENTCORE_HTTP_PROTOCOL as ui, MCP_PROTOCOL_VERSION as un, translateLambdaResponse as ur, runViewerResponse as ut, startStudioServer as v, substituteImagePlaceholders as vi, downloadAndExtractS3Bundle as vn, VtlEvaluationError as vr, parseLbPortOverrides as vt, createLocalListCommand as w, classifySourceChange as wn, handleConnectionsRequest as wr, serviceStrategy as wt, createStudioStore as x, buildStsClientConfig as xi, computeCodeImageTag as xn, bufferToBody as xr, resolveAlbFrontDoor as xt, toStudioTargetGroups as y, tryResolveImageFnJoin as yi, SUPPORTED_CODE_RUNTIMES as yn, HOST_GATEWAY_MIN_VERSION as yr, resolveAlbTarget as yt, resolveDeployedOriginBucket as z, filterRoutesByApiIdentifiers as zn, LocalStateSourceError as zr, buildImageOverrideTag as zt };
|
|
36510
|
-
//# sourceMappingURL=local-studio-
|
|
36556
|
+
//# sourceMappingURL=local-studio-BlFoXpwl.js.map
|