@go-to-k/cdkd 0.82.1 → 0.83.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.
- package/dist/cli.js +426 -106
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.83.1.tgz +0 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.82.1.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -21471,6 +21471,39 @@ var TemplateParser = class {
|
|
|
21471
21471
|
}
|
|
21472
21472
|
return;
|
|
21473
21473
|
}
|
|
21474
|
+
if ("Fn::Sub" in obj) {
|
|
21475
|
+
const subValue = obj["Fn::Sub"];
|
|
21476
|
+
let body;
|
|
21477
|
+
let mapKeys;
|
|
21478
|
+
if (typeof subValue === "string") {
|
|
21479
|
+
body = subValue;
|
|
21480
|
+
} else if (Array.isArray(subValue) && subValue.length >= 1 && typeof subValue[0] === "string") {
|
|
21481
|
+
body = subValue[0];
|
|
21482
|
+
const variables = subValue[1];
|
|
21483
|
+
if (variables && typeof variables === "object" && !Array.isArray(variables)) {
|
|
21484
|
+
const varMap = variables;
|
|
21485
|
+
mapKeys = new Set(Object.keys(varMap));
|
|
21486
|
+
Object.values(varMap).forEach((v) => this.extractRefsFromValue(v, dependencies));
|
|
21487
|
+
}
|
|
21488
|
+
}
|
|
21489
|
+
if (body !== void 0) {
|
|
21490
|
+
for (const match of body.matchAll(/\$\{([^}]+)\}/g)) {
|
|
21491
|
+
const placeholder = match[1];
|
|
21492
|
+
if (!placeholder)
|
|
21493
|
+
continue;
|
|
21494
|
+
const dot = placeholder.indexOf(".");
|
|
21495
|
+
const name = dot >= 0 ? placeholder.slice(0, dot) : placeholder;
|
|
21496
|
+
if (!name)
|
|
21497
|
+
continue;
|
|
21498
|
+
if (name.startsWith("AWS::"))
|
|
21499
|
+
continue;
|
|
21500
|
+
if (mapKeys?.has(name))
|
|
21501
|
+
continue;
|
|
21502
|
+
dependencies.add(name);
|
|
21503
|
+
}
|
|
21504
|
+
}
|
|
21505
|
+
return;
|
|
21506
|
+
}
|
|
21474
21507
|
Object.values(obj).forEach((v) => this.extractRefsFromValue(v, dependencies));
|
|
21475
21508
|
}
|
|
21476
21509
|
/**
|
|
@@ -70040,6 +70073,76 @@ import { dirname as dirname7 } from "node:path";
|
|
|
70040
70073
|
import * as path2 from "node:path";
|
|
70041
70074
|
import { Command as Command16, Option as Option9 } from "commander";
|
|
70042
70075
|
|
|
70076
|
+
// src/cli/commands/local-state-loader.ts
|
|
70077
|
+
init_aws_clients();
|
|
70078
|
+
async function loadStateForStack(stackName, synthRegion, opts) {
|
|
70079
|
+
const logger = getLogger();
|
|
70080
|
+
const prefix = opts.logPrefix ?? "--from-state";
|
|
70081
|
+
const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
|
|
70082
|
+
let stateBucket;
|
|
70083
|
+
try {
|
|
70084
|
+
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
|
|
70085
|
+
} catch (err) {
|
|
70086
|
+
logger.warn(
|
|
70087
|
+
`${prefix}: could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Falling back.`
|
|
70088
|
+
);
|
|
70089
|
+
return void 0;
|
|
70090
|
+
}
|
|
70091
|
+
const awsClients = new AwsClients({
|
|
70092
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
70093
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
70094
|
+
});
|
|
70095
|
+
setAwsClients(awsClients);
|
|
70096
|
+
try {
|
|
70097
|
+
const stateConfig = { bucket: stateBucket, prefix: opts.statePrefix };
|
|
70098
|
+
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
70099
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
70100
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
70101
|
+
});
|
|
70102
|
+
await stateBackend.verifyBucketExists();
|
|
70103
|
+
const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
|
|
70104
|
+
if (refs.length === 0) {
|
|
70105
|
+
logger.warn(
|
|
70106
|
+
`${prefix}: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back.`
|
|
70107
|
+
);
|
|
70108
|
+
return void 0;
|
|
70109
|
+
}
|
|
70110
|
+
let targetRegion;
|
|
70111
|
+
if (opts.stackRegion) {
|
|
70112
|
+
const found = refs.find((r) => r.region === opts.stackRegion);
|
|
70113
|
+
if (!found) {
|
|
70114
|
+
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
70115
|
+
logger.warn(
|
|
70116
|
+
`${prefix}: stack '${stackName}' has no state in region '${opts.stackRegion}' (available: ${seen}). Falling back.`
|
|
70117
|
+
);
|
|
70118
|
+
return void 0;
|
|
70119
|
+
}
|
|
70120
|
+
targetRegion = opts.stackRegion;
|
|
70121
|
+
} else if (synthRegion && refs.some((r) => r.region === synthRegion)) {
|
|
70122
|
+
targetRegion = synthRegion;
|
|
70123
|
+
} else if (refs.length === 1) {
|
|
70124
|
+
targetRegion = refs[0].region ?? synthRegion ?? region;
|
|
70125
|
+
} else {
|
|
70126
|
+
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
70127
|
+
logger.warn(
|
|
70128
|
+
`${prefix}: stack '${stackName}' has state in multiple regions (${seen}). Re-run with --stack-region <region>. Falling back.`
|
|
70129
|
+
);
|
|
70130
|
+
return void 0;
|
|
70131
|
+
}
|
|
70132
|
+
const stateData = await stateBackend.getState(stackName, targetRegion);
|
|
70133
|
+
if (!stateData) {
|
|
70134
|
+
logger.warn(
|
|
70135
|
+
`${prefix}: state record for '${stackName}' (${targetRegion}) returned empty. Falling back.`
|
|
70136
|
+
);
|
|
70137
|
+
return void 0;
|
|
70138
|
+
}
|
|
70139
|
+
logger.debug(`${prefix}: loaded state for ${stackName} (${targetRegion})`);
|
|
70140
|
+
return { state: stateData.state, region: targetRegion };
|
|
70141
|
+
} finally {
|
|
70142
|
+
awsClients.destroy();
|
|
70143
|
+
}
|
|
70144
|
+
}
|
|
70145
|
+
|
|
70043
70146
|
// src/local/lambda-resolver.ts
|
|
70044
70147
|
import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
|
|
70045
70148
|
import { dirname, isAbsolute, resolve as resolve4 } from "node:path";
|
|
@@ -71217,9 +71320,6 @@ function extractHashFromImageUri(imageUri) {
|
|
|
71217
71320
|
return match?.[1];
|
|
71218
71321
|
}
|
|
71219
71322
|
|
|
71220
|
-
// src/cli/commands/local-invoke.ts
|
|
71221
|
-
init_aws_clients();
|
|
71222
|
-
|
|
71223
71323
|
// src/utils/single-flight.ts
|
|
71224
71324
|
function singleFlight(fn, onError) {
|
|
71225
71325
|
let promise;
|
|
@@ -76646,6 +76746,83 @@ var EcsTaskResolutionError = class _EcsTaskResolutionError extends Error {
|
|
|
76646
76746
|
Object.setPrototypeOf(this, _EcsTaskResolutionError.prototype);
|
|
76647
76747
|
}
|
|
76648
76748
|
};
|
|
76749
|
+
function derivePartitionAndUrlSuffix(region) {
|
|
76750
|
+
if (region.startsWith("cn-"))
|
|
76751
|
+
return { partition: "aws-cn", urlSuffix: "amazonaws.com.cn" };
|
|
76752
|
+
if (region.startsWith("us-gov-"))
|
|
76753
|
+
return { partition: "aws-us-gov", urlSuffix: "amazonaws.com" };
|
|
76754
|
+
if (region.startsWith("us-iso-"))
|
|
76755
|
+
return { partition: "aws-iso", urlSuffix: "c2s.ic.gov" };
|
|
76756
|
+
if (region.startsWith("us-isob-"))
|
|
76757
|
+
return { partition: "aws-iso-b", urlSuffix: "sc2s.sgov.gov" };
|
|
76758
|
+
return { partition: "aws", urlSuffix: "amazonaws.com" };
|
|
76759
|
+
}
|
|
76760
|
+
function detectEcsImageResolutionNeeds(stack) {
|
|
76761
|
+
const resources = stack.template.Resources ?? {};
|
|
76762
|
+
let needsPseudoParameters = false;
|
|
76763
|
+
let needsStateResources = false;
|
|
76764
|
+
for (const res of Object.values(resources)) {
|
|
76765
|
+
if (res.Type !== "AWS::ECS::TaskDefinition")
|
|
76766
|
+
continue;
|
|
76767
|
+
const props = res.Properties ?? {};
|
|
76768
|
+
const containers = Array.isArray(props["ContainerDefinitions"]) ? props["ContainerDefinitions"] : [];
|
|
76769
|
+
for (const c of containers) {
|
|
76770
|
+
if (!c || typeof c !== "object")
|
|
76771
|
+
continue;
|
|
76772
|
+
const image = c["Image"];
|
|
76773
|
+
const need = inspectImageForSubstitutions(image, resources);
|
|
76774
|
+
if (need.pseudo)
|
|
76775
|
+
needsPseudoParameters = true;
|
|
76776
|
+
if (need.state)
|
|
76777
|
+
needsStateResources = true;
|
|
76778
|
+
if (needsPseudoParameters && needsStateResources)
|
|
76779
|
+
break;
|
|
76780
|
+
}
|
|
76781
|
+
if (needsPseudoParameters && needsStateResources)
|
|
76782
|
+
break;
|
|
76783
|
+
}
|
|
76784
|
+
return { needsPseudoParameters, needsStateResources };
|
|
76785
|
+
}
|
|
76786
|
+
function inspectImageForSubstitutions(image, resources) {
|
|
76787
|
+
if (!image || typeof image !== "object")
|
|
76788
|
+
return { pseudo: false, state: false };
|
|
76789
|
+
const obj = image;
|
|
76790
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
76791
|
+
if (getAtt !== void 0) {
|
|
76792
|
+
let lid;
|
|
76793
|
+
if (Array.isArray(getAtt) && typeof getAtt[0] === "string")
|
|
76794
|
+
lid = getAtt[0];
|
|
76795
|
+
else if (typeof getAtt === "string")
|
|
76796
|
+
lid = getAtt.split(".")[0];
|
|
76797
|
+
if (lid && resources[lid]?.Type === "AWS::ECR::Repository") {
|
|
76798
|
+
return { pseudo: false, state: true };
|
|
76799
|
+
}
|
|
76800
|
+
}
|
|
76801
|
+
let sub;
|
|
76802
|
+
const subRaw = obj["Fn::Sub"];
|
|
76803
|
+
if (typeof subRaw === "string")
|
|
76804
|
+
sub = subRaw;
|
|
76805
|
+
else if (Array.isArray(subRaw) && typeof subRaw[0] === "string")
|
|
76806
|
+
sub = subRaw[0];
|
|
76807
|
+
if (!sub)
|
|
76808
|
+
return { pseudo: false, state: false };
|
|
76809
|
+
let pseudo = false;
|
|
76810
|
+
let state = false;
|
|
76811
|
+
const placeholderRegex = /\$\{([^}]+)\}/g;
|
|
76812
|
+
let m;
|
|
76813
|
+
while ((m = placeholderRegex.exec(sub)) !== null) {
|
|
76814
|
+
const key = m[1];
|
|
76815
|
+
if (key.startsWith("AWS::")) {
|
|
76816
|
+
pseudo = true;
|
|
76817
|
+
continue;
|
|
76818
|
+
}
|
|
76819
|
+
const dot = key.indexOf(".");
|
|
76820
|
+
const lid = dot === -1 ? key : key.slice(0, dot);
|
|
76821
|
+
if (resources[lid]?.Type === "AWS::ECR::Repository")
|
|
76822
|
+
state = true;
|
|
76823
|
+
}
|
|
76824
|
+
return { pseudo, state };
|
|
76825
|
+
}
|
|
76649
76826
|
function parseEcsTarget(target) {
|
|
76650
76827
|
if (typeof target !== "string" || target.length === 0) {
|
|
76651
76828
|
throw new EcsTaskResolutionError(
|
|
@@ -76667,7 +76844,7 @@ function parseEcsTarget(target) {
|
|
|
76667
76844
|
}
|
|
76668
76845
|
return { stackPattern: null, pathOrId: target, isPath: false };
|
|
76669
76846
|
}
|
|
76670
|
-
function resolveEcsTaskTarget(target, stacks) {
|
|
76847
|
+
function resolveEcsTaskTarget(target, stacks, context) {
|
|
76671
76848
|
if (stacks.length === 0) {
|
|
76672
76849
|
throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
76673
76850
|
}
|
|
@@ -76710,7 +76887,7 @@ function resolveEcsTaskTarget(target, stacks) {
|
|
|
76710
76887
|
`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`
|
|
76711
76888
|
);
|
|
76712
76889
|
}
|
|
76713
|
-
return extractTaskDefinitionProperties(stack, logicalId, resource);
|
|
76890
|
+
return extractTaskDefinitionProperties(stack, logicalId, resource, context);
|
|
76714
76891
|
}
|
|
76715
76892
|
function pickStack2(parsed, stacks) {
|
|
76716
76893
|
if (parsed.stackPattern === null) {
|
|
@@ -76733,7 +76910,7 @@ function pickStack2(parsed, stacks) {
|
|
|
76733
76910
|
}
|
|
76734
76911
|
return matched[0];
|
|
76735
76912
|
}
|
|
76736
|
-
function extractTaskDefinitionProperties(stack, logicalId, resource) {
|
|
76913
|
+
function extractTaskDefinitionProperties(stack, logicalId, resource, context) {
|
|
76737
76914
|
const props = resource.Properties ?? {};
|
|
76738
76915
|
const warnings = [];
|
|
76739
76916
|
const family = pickString(props["Family"]) ?? logicalId;
|
|
@@ -76760,7 +76937,7 @@ function extractTaskDefinitionProperties(stack, logicalId, resource) {
|
|
|
76760
76937
|
throw new EcsTaskResolutionError(`Task definition '${logicalId}' has no ContainerDefinitions.`);
|
|
76761
76938
|
}
|
|
76762
76939
|
const containers = rawContainers.map(
|
|
76763
|
-
(c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack)
|
|
76940
|
+
(c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack, context)
|
|
76764
76941
|
);
|
|
76765
76942
|
const rawVolumes = props["Volumes"];
|
|
76766
76943
|
const volumes = Array.isArray(rawVolumes) ? rawVolumes.map((v, idx) => parseVolume(v, idx, logicalId)) : [];
|
|
@@ -76806,7 +76983,7 @@ function parseRuntimePlatform(value) {
|
|
|
76806
76983
|
return void 0;
|
|
76807
76984
|
return { cpuArchitecture: cpu, operatingSystemFamily: os };
|
|
76808
76985
|
}
|
|
76809
|
-
function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
|
|
76986
|
+
function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, context) {
|
|
76810
76987
|
if (!raw || typeof raw !== "object") {
|
|
76811
76988
|
throw new EcsTaskResolutionError(
|
|
76812
76989
|
`Task '${taskLogicalId}' ContainerDefinitions[${idx}] is not an object.`
|
|
@@ -76819,7 +76996,7 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
|
|
|
76819
76996
|
`Task '${taskLogicalId}' ContainerDefinitions[${idx}] has no Name.`
|
|
76820
76997
|
);
|
|
76821
76998
|
}
|
|
76822
|
-
const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack);
|
|
76999
|
+
const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack, context);
|
|
76823
77000
|
const command = pickStringArray2(c["Command"]);
|
|
76824
77001
|
const entryPoint = pickStringArray2(c["EntryPoint"]);
|
|
76825
77002
|
const workingDirectory = pickString(c["WorkingDirectory"]);
|
|
@@ -76965,7 +77142,11 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
|
|
|
76965
77142
|
out.readonlyRootFilesystem = readonlyRootFilesystem;
|
|
76966
77143
|
return out;
|
|
76967
77144
|
}
|
|
76968
|
-
function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack) {
|
|
77145
|
+
function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack, context) {
|
|
77146
|
+
const getAttImage = tryResolveImageGetAtt(raw, resources, context);
|
|
77147
|
+
if (getAttImage) {
|
|
77148
|
+
return classifyResolvedImage(getAttImage);
|
|
77149
|
+
}
|
|
76969
77150
|
const flat = extractImageString(raw);
|
|
76970
77151
|
if (!flat) {
|
|
76971
77152
|
throw new EcsTaskResolutionError(
|
|
@@ -76979,23 +77160,123 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
|
|
|
76979
77160
|
out.assetHash = hashMatch[1];
|
|
76980
77161
|
return out;
|
|
76981
77162
|
}
|
|
76982
|
-
const
|
|
76983
|
-
if (
|
|
76984
|
-
|
|
76985
|
-
|
|
76986
|
-
|
|
77163
|
+
const substituted = substituteImagePlaceholders(flat, resources, context);
|
|
77164
|
+
if (substituted.includes("${")) {
|
|
77165
|
+
const unresolvedRepoRef = findUnresolvedEcrRepositoryRef(substituted, resources);
|
|
77166
|
+
if (unresolvedRepoRef) {
|
|
77167
|
+
throw new EcsTaskResolutionError(
|
|
77168
|
+
`Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${unresolvedRepoRef}'. cdkd local run-task v1 cannot resolve the repository URI without state \u2014 pass --from-state (the stack must have been deployed via cdkd deploy), build via ContainerImage.fromAsset, or pin a public image.`
|
|
77169
|
+
);
|
|
77170
|
+
}
|
|
77171
|
+
if (substituted.includes("AWS::")) {
|
|
77172
|
+
throw new EcsTaskResolutionError(
|
|
77173
|
+
`Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). cdkd could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`
|
|
77174
|
+
);
|
|
77175
|
+
}
|
|
76987
77176
|
throw new EcsTaskResolutionError(
|
|
76988
|
-
`Container '${containerName}' in task '${taskLogicalId}' has an Image
|
|
77177
|
+
`Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). cdkd local run-task v1 only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`
|
|
76989
77178
|
);
|
|
76990
77179
|
}
|
|
76991
|
-
|
|
76992
|
-
|
|
76993
|
-
|
|
76994
|
-
|
|
76995
|
-
|
|
77180
|
+
const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
|
|
77181
|
+
if (ecrMatch) {
|
|
77182
|
+
return { kind: "ecr", uri: substituted, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77183
|
+
}
|
|
77184
|
+
return { kind: "public", uri: substituted };
|
|
77185
|
+
}
|
|
77186
|
+
function findUnresolvedEcrRepositoryRef(substituted, resources) {
|
|
77187
|
+
const placeholderRegex = /\$\{([^}]+)\}/g;
|
|
77188
|
+
let m;
|
|
77189
|
+
while ((m = placeholderRegex.exec(substituted)) !== null) {
|
|
77190
|
+
const key = m[1];
|
|
77191
|
+
if (key.startsWith("AWS::"))
|
|
77192
|
+
continue;
|
|
77193
|
+
const dot = key.indexOf(".");
|
|
77194
|
+
const lid = dot === -1 ? key : key.slice(0, dot);
|
|
77195
|
+
if (resources[lid]?.Type === "AWS::ECR::Repository")
|
|
77196
|
+
return lid;
|
|
77197
|
+
}
|
|
77198
|
+
return void 0;
|
|
77199
|
+
}
|
|
77200
|
+
function classifyResolvedImage(uri) {
|
|
77201
|
+
if (uri.includes("cdk-hnb659fds-container-assets-")) {
|
|
77202
|
+
const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);
|
|
77203
|
+
const out = { kind: "cdk-asset" };
|
|
77204
|
+
if (hashMatch)
|
|
77205
|
+
out.assetHash = hashMatch[1];
|
|
77206
|
+
return out;
|
|
77207
|
+
}
|
|
77208
|
+
const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
|
|
77209
|
+
if (ecrMatch) {
|
|
77210
|
+
return { kind: "ecr", uri, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77211
|
+
}
|
|
77212
|
+
return { kind: "public", uri };
|
|
77213
|
+
}
|
|
77214
|
+
function substituteImagePlaceholders(flat, resources, context) {
|
|
77215
|
+
if (!flat.includes("${"))
|
|
77216
|
+
return flat;
|
|
77217
|
+
return flat.replace(/\$\{([^}]+)\}/g, (full, key) => {
|
|
77218
|
+
if (context?.pseudoParameters) {
|
|
77219
|
+
if (key === "AWS::AccountId" && context.pseudoParameters.accountId) {
|
|
77220
|
+
return context.pseudoParameters.accountId;
|
|
77221
|
+
}
|
|
77222
|
+
if (key === "AWS::Region" && context.pseudoParameters.region) {
|
|
77223
|
+
return context.pseudoParameters.region;
|
|
77224
|
+
}
|
|
77225
|
+
if (key === "AWS::Partition" && context.pseudoParameters.partition) {
|
|
77226
|
+
return context.pseudoParameters.partition;
|
|
77227
|
+
}
|
|
77228
|
+
if (key === "AWS::URLSuffix" && context.pseudoParameters.urlSuffix) {
|
|
77229
|
+
return context.pseudoParameters.urlSuffix;
|
|
77230
|
+
}
|
|
77231
|
+
}
|
|
77232
|
+
if (context?.stateResources) {
|
|
77233
|
+
const dot = key.indexOf(".");
|
|
77234
|
+
const logicalId = dot === -1 ? key : key.slice(0, dot);
|
|
77235
|
+
const refResource = resources[logicalId];
|
|
77236
|
+
const stateEntry = context.stateResources[logicalId];
|
|
77237
|
+
if (refResource?.Type === "AWS::ECR::Repository" && stateEntry) {
|
|
77238
|
+
if (dot === -1) {
|
|
77239
|
+
return stateEntry.physicalId;
|
|
77240
|
+
}
|
|
77241
|
+
const attr = key.slice(dot + 1);
|
|
77242
|
+
const cached = stateEntry.attributes?.[attr];
|
|
77243
|
+
if (typeof cached === "string")
|
|
77244
|
+
return cached;
|
|
77245
|
+
}
|
|
77246
|
+
}
|
|
77247
|
+
return full;
|
|
77248
|
+
});
|
|
77249
|
+
}
|
|
77250
|
+
function tryResolveImageGetAtt(raw, resources, context) {
|
|
77251
|
+
if (!raw || typeof raw !== "object")
|
|
77252
|
+
return void 0;
|
|
77253
|
+
const obj = raw;
|
|
77254
|
+
const arg = obj["Fn::GetAtt"];
|
|
77255
|
+
if (arg === void 0)
|
|
77256
|
+
return void 0;
|
|
77257
|
+
let logicalId;
|
|
77258
|
+
let attr;
|
|
77259
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
|
|
77260
|
+
logicalId = arg[0];
|
|
77261
|
+
attr = arg[1];
|
|
77262
|
+
} else if (typeof arg === "string") {
|
|
77263
|
+
const dot = arg.indexOf(".");
|
|
77264
|
+
if (dot > 0 && dot < arg.length - 1) {
|
|
77265
|
+
logicalId = arg.slice(0, dot);
|
|
77266
|
+
attr = arg.slice(dot + 1);
|
|
76996
77267
|
}
|
|
76997
77268
|
}
|
|
76998
|
-
|
|
77269
|
+
if (!logicalId || !attr)
|
|
77270
|
+
return void 0;
|
|
77271
|
+
const refResource = resources[logicalId];
|
|
77272
|
+
if (refResource?.Type !== "AWS::ECR::Repository")
|
|
77273
|
+
return void 0;
|
|
77274
|
+
if (attr !== "RepositoryUri" && attr !== "Arn")
|
|
77275
|
+
return void 0;
|
|
77276
|
+
const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];
|
|
77277
|
+
if (typeof cached === "string" && cached.length > 0)
|
|
77278
|
+
return cached;
|
|
77279
|
+
return void 0;
|
|
76999
77280
|
}
|
|
77000
77281
|
function extractImageString(value) {
|
|
77001
77282
|
if (typeof value === "string" && value.length > 0)
|
|
@@ -77074,6 +77355,7 @@ function parseVolume(raw, idx, taskLogicalId) {
|
|
|
77074
77355
|
}
|
|
77075
77356
|
return { name, kind: "host" };
|
|
77076
77357
|
}
|
|
77358
|
+
var TASK_ROLE_ACCOUNT_PLACEHOLDER = "${AWS::AccountId}";
|
|
77077
77359
|
function resolveRoleArn(value, resources) {
|
|
77078
77360
|
if (value === void 0 || value === null)
|
|
77079
77361
|
return void 0;
|
|
@@ -77082,24 +77364,23 @@ function resolveRoleArn(value, resources) {
|
|
|
77082
77364
|
if (typeof value !== "object")
|
|
77083
77365
|
return void 0;
|
|
77084
77366
|
const obj = value;
|
|
77367
|
+
let refLogicalId;
|
|
77085
77368
|
if ("Ref" in obj && typeof obj["Ref"] === "string") {
|
|
77086
|
-
|
|
77087
|
-
|
|
77088
|
-
if (role?.Type === "AWS::IAM::Role") {
|
|
77089
|
-
return void 0;
|
|
77090
|
-
}
|
|
77091
|
-
}
|
|
77092
|
-
if ("Fn::GetAtt" in obj) {
|
|
77369
|
+
refLogicalId = obj["Ref"];
|
|
77370
|
+
} else if ("Fn::GetAtt" in obj) {
|
|
77093
77371
|
const arg = obj["Fn::GetAtt"];
|
|
77094
77372
|
if (Array.isArray(arg) && typeof arg[0] === "string") {
|
|
77095
|
-
const
|
|
77096
|
-
|
|
77097
|
-
|
|
77098
|
-
return void 0;
|
|
77099
|
-
}
|
|
77373
|
+
const attr = typeof arg[1] === "string" ? arg[1] : "";
|
|
77374
|
+
if (attr === "" || attr === "Arn")
|
|
77375
|
+
refLogicalId = arg[0];
|
|
77100
77376
|
}
|
|
77101
77377
|
}
|
|
77102
|
-
|
|
77378
|
+
if (refLogicalId === void 0)
|
|
77379
|
+
return void 0;
|
|
77380
|
+
const role = resources[refLogicalId];
|
|
77381
|
+
if (role?.Type !== "AWS::IAM::Role")
|
|
77382
|
+
return void 0;
|
|
77383
|
+
return `arn:aws:iam::${TASK_ROLE_ACCOUNT_PLACEHOLDER}:role/${refLogicalId}`;
|
|
77103
77384
|
}
|
|
77104
77385
|
function pickString(value) {
|
|
77105
77386
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -77936,7 +78217,8 @@ async function localRunTaskCommand(target, options) {
|
|
|
77936
78217
|
...Object.keys(context).length > 0 && { context }
|
|
77937
78218
|
};
|
|
77938
78219
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
77939
|
-
const
|
|
78220
|
+
const imageContext = await buildEcsImageResolutionContext(target, stacks, options);
|
|
78221
|
+
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
77940
78222
|
logger.info(
|
|
77941
78223
|
`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`
|
|
77942
78224
|
);
|
|
@@ -77955,10 +78237,10 @@ async function localRunTaskCommand(target, options) {
|
|
|
77955
78237
|
if (options.assumeTaskRole === true) {
|
|
77956
78238
|
if (!task.taskRoleArn) {
|
|
77957
78239
|
throw new Error(
|
|
77958
|
-
`--assume-task-role passed without an ARN but the task definition
|
|
78240
|
+
`--assume-task-role passed without an ARN but the task definition has no resolvable TaskRoleArn. Either the task definition does not set TaskRoleArn, or it points at a resource cdkd cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-task-role <arn>`
|
|
77959
78241
|
);
|
|
77960
78242
|
}
|
|
77961
|
-
resolvedRoleArn = task.taskRoleArn;
|
|
78243
|
+
resolvedRoleArn = await resolvePlaceholderAccount(task.taskRoleArn, options.region);
|
|
77962
78244
|
assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region);
|
|
77963
78245
|
} else if (typeof options.assumeTaskRole === "string") {
|
|
77964
78246
|
resolvedRoleArn = options.assumeTaskRole;
|
|
@@ -78006,6 +78288,24 @@ async function localRunTaskCommand(target, options) {
|
|
|
78006
78288
|
await cleanup();
|
|
78007
78289
|
}
|
|
78008
78290
|
}
|
|
78291
|
+
async function resolvePlaceholderAccount(arn, region) {
|
|
78292
|
+
if (!arn.includes(TASK_ROLE_ACCOUNT_PLACEHOLDER))
|
|
78293
|
+
return arn;
|
|
78294
|
+
const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
|
|
78295
|
+
const sts = new STSClient11({ ...region && { region } });
|
|
78296
|
+
try {
|
|
78297
|
+
const identity = await sts.send(new GetCallerIdentityCommand12({}));
|
|
78298
|
+
const account = identity.Account;
|
|
78299
|
+
if (!account) {
|
|
78300
|
+
throw new Error(
|
|
78301
|
+
`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`
|
|
78302
|
+
);
|
|
78303
|
+
}
|
|
78304
|
+
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
78305
|
+
} finally {
|
|
78306
|
+
sts.destroy();
|
|
78307
|
+
}
|
|
78308
|
+
}
|
|
78009
78309
|
async function assumeTaskRole(roleArn, region) {
|
|
78010
78310
|
const { STSClient: STSClient11, AssumeRoleCommand: AssumeRoleCommand2 } = await import("@aws-sdk/client-sts");
|
|
78011
78311
|
const sts = new STSClient11({ ...region && { region } });
|
|
@@ -78030,6 +78330,80 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
78030
78330
|
sts.destroy();
|
|
78031
78331
|
}
|
|
78032
78332
|
}
|
|
78333
|
+
async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
78334
|
+
const logger = getLogger();
|
|
78335
|
+
const parsed = parseEcsTarget(target);
|
|
78336
|
+
const candidate = pickCandidateStack(parsed.stackPattern, stacks);
|
|
78337
|
+
if (!candidate)
|
|
78338
|
+
return void 0;
|
|
78339
|
+
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
78340
|
+
if (!needs.needsPseudoParameters && !needs.needsStateResources)
|
|
78341
|
+
return void 0;
|
|
78342
|
+
const ctx = {};
|
|
78343
|
+
if (needs.needsPseudoParameters) {
|
|
78344
|
+
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
78345
|
+
if (!region) {
|
|
78346
|
+
logger.warn(
|
|
78347
|
+
"Container Image references ${AWS::Region} but cdkd could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack."
|
|
78348
|
+
);
|
|
78349
|
+
}
|
|
78350
|
+
let accountId;
|
|
78351
|
+
try {
|
|
78352
|
+
accountId = await resolveCallerAccountId(region);
|
|
78353
|
+
} catch (err) {
|
|
78354
|
+
logger.warn(
|
|
78355
|
+
`Container Image references \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; the resolver will surface its existing error.`
|
|
78356
|
+
);
|
|
78357
|
+
}
|
|
78358
|
+
const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
|
|
78359
|
+
ctx.pseudoParameters = {
|
|
78360
|
+
...accountId !== void 0 && { accountId },
|
|
78361
|
+
...region !== void 0 && { region },
|
|
78362
|
+
...partitionAndSuffix && {
|
|
78363
|
+
partition: partitionAndSuffix.partition,
|
|
78364
|
+
urlSuffix: partitionAndSuffix.urlSuffix
|
|
78365
|
+
}
|
|
78366
|
+
};
|
|
78367
|
+
}
|
|
78368
|
+
if (options.fromState && needs.needsStateResources) {
|
|
78369
|
+
const loaded = await loadStateForStack(candidate.stackName, candidate.region, {
|
|
78370
|
+
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
78371
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
78372
|
+
statePrefix: options.statePrefix,
|
|
78373
|
+
...options.region !== void 0 && { region: options.region },
|
|
78374
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
78375
|
+
});
|
|
78376
|
+
if (loaded) {
|
|
78377
|
+
ctx.stateResources = loaded.state.resources;
|
|
78378
|
+
}
|
|
78379
|
+
} else if (!options.fromState && needs.needsStateResources) {
|
|
78380
|
+
logger.warn(
|
|
78381
|
+
"Container Image references a same-stack AWS::ECR::Repository. Pass --from-state to substitute the deployed repository URI (requires the stack to have been deployed via cdkd deploy). Otherwise the resolver will surface its existing error."
|
|
78382
|
+
);
|
|
78383
|
+
}
|
|
78384
|
+
return ctx;
|
|
78385
|
+
}
|
|
78386
|
+
function pickCandidateStack(stackPattern, stacks) {
|
|
78387
|
+
if (stackPattern === null) {
|
|
78388
|
+
if (stacks.length === 1)
|
|
78389
|
+
return stacks[0];
|
|
78390
|
+
return void 0;
|
|
78391
|
+
}
|
|
78392
|
+
const matched = matchStacks(stacks, [stackPattern]);
|
|
78393
|
+
if (matched.length === 1)
|
|
78394
|
+
return matched[0];
|
|
78395
|
+
return void 0;
|
|
78396
|
+
}
|
|
78397
|
+
async function resolveCallerAccountId(region) {
|
|
78398
|
+
const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
|
|
78399
|
+
const sts = new STSClient11({ ...region && { region } });
|
|
78400
|
+
try {
|
|
78401
|
+
const identity = await sts.send(new GetCallerIdentityCommand12({}));
|
|
78402
|
+
return identity.Account;
|
|
78403
|
+
} finally {
|
|
78404
|
+
sts.destroy();
|
|
78405
|
+
}
|
|
78406
|
+
}
|
|
78033
78407
|
function readEnvOverridesFile2(filePath) {
|
|
78034
78408
|
if (!filePath)
|
|
78035
78409
|
return void 0;
|
|
@@ -78097,8 +78471,20 @@ function createLocalRunTaskCommand() {
|
|
|
78097
78471
|
"--detach",
|
|
78098
78472
|
"Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle."
|
|
78099
78473
|
).default(false)
|
|
78474
|
+
).addOption(
|
|
78475
|
+
new Option8(
|
|
78476
|
+
"--from-state",
|
|
78477
|
+
"Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt references to same-stack AWS::ECR::Repository resources with the deployed URI. Off by default \u2014 only the AWS pseudo-parameter tier (${AWS::AccountId} / ${AWS::Region}) is resolved without this flag."
|
|
78478
|
+
).default(false)
|
|
78479
|
+
).addOption(
|
|
78480
|
+
new Option8(
|
|
78481
|
+
"--stack-region <region>",
|
|
78482
|
+
"Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions)."
|
|
78483
|
+
)
|
|
78100
78484
|
).action(withErrorHandling(localRunTaskCommand));
|
|
78101
|
-
[...commonOptions, ...appOptions, ...contextOptions].forEach(
|
|
78485
|
+
[...commonOptions, ...appOptions, ...contextOptions, ...stateOptions].forEach(
|
|
78486
|
+
(opt) => cmd.addOption(opt)
|
|
78487
|
+
);
|
|
78102
78488
|
cmd.addOption(deprecatedRegionOption);
|
|
78103
78489
|
return cmd;
|
|
78104
78490
|
}
|
|
@@ -78507,72 +78893,6 @@ function materializeInlineCode2(handler, source, fileExtension) {
|
|
|
78507
78893
|
writeFileSync6(filePath, source, "utf-8");
|
|
78508
78894
|
return dir;
|
|
78509
78895
|
}
|
|
78510
|
-
async function loadStateForStack(stackName, synthRegion, opts) {
|
|
78511
|
-
const logger = getLogger();
|
|
78512
|
-
const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
|
|
78513
|
-
let stateBucket;
|
|
78514
|
-
try {
|
|
78515
|
-
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
|
|
78516
|
-
} catch (err) {
|
|
78517
|
-
logger.warn(
|
|
78518
|
-
`--from-state: could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Falling back to PR 1 warn-and-drop semantics.`
|
|
78519
|
-
);
|
|
78520
|
-
return void 0;
|
|
78521
|
-
}
|
|
78522
|
-
const awsClients = new AwsClients({
|
|
78523
|
-
...opts.region !== void 0 && { region: opts.region },
|
|
78524
|
-
...opts.profile !== void 0 && { profile: opts.profile }
|
|
78525
|
-
});
|
|
78526
|
-
setAwsClients(awsClients);
|
|
78527
|
-
try {
|
|
78528
|
-
const stateConfig = { bucket: stateBucket, prefix: opts.statePrefix };
|
|
78529
|
-
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
78530
|
-
...opts.region !== void 0 && { region: opts.region },
|
|
78531
|
-
...opts.profile !== void 0 && { profile: opts.profile }
|
|
78532
|
-
});
|
|
78533
|
-
await stateBackend.verifyBucketExists();
|
|
78534
|
-
const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
|
|
78535
|
-
if (refs.length === 0) {
|
|
78536
|
-
logger.warn(
|
|
78537
|
-
`--from-state: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back to PR 1 warn-and-drop semantics.`
|
|
78538
|
-
);
|
|
78539
|
-
return void 0;
|
|
78540
|
-
}
|
|
78541
|
-
let targetRegion;
|
|
78542
|
-
if (opts.stackRegion) {
|
|
78543
|
-
const found = refs.find((r) => r.region === opts.stackRegion);
|
|
78544
|
-
if (!found) {
|
|
78545
|
-
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
78546
|
-
logger.warn(
|
|
78547
|
-
`--from-state: stack '${stackName}' has no state in region '${opts.stackRegion}' (available: ${seen}). Falling back.`
|
|
78548
|
-
);
|
|
78549
|
-
return void 0;
|
|
78550
|
-
}
|
|
78551
|
-
targetRegion = opts.stackRegion;
|
|
78552
|
-
} else if (synthRegion && refs.some((r) => r.region === synthRegion)) {
|
|
78553
|
-
targetRegion = synthRegion;
|
|
78554
|
-
} else if (refs.length === 1) {
|
|
78555
|
-
targetRegion = refs[0].region ?? synthRegion ?? region;
|
|
78556
|
-
} else {
|
|
78557
|
-
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
78558
|
-
logger.warn(
|
|
78559
|
-
`--from-state: stack '${stackName}' has state in multiple regions (${seen}). Re-run with --stack-region <region>. Falling back.`
|
|
78560
|
-
);
|
|
78561
|
-
return void 0;
|
|
78562
|
-
}
|
|
78563
|
-
const stateData = await stateBackend.getState(stackName, targetRegion);
|
|
78564
|
-
if (!stateData) {
|
|
78565
|
-
logger.warn(
|
|
78566
|
-
`--from-state: state record for '${stackName}' (${targetRegion}) returned empty. Falling back.`
|
|
78567
|
-
);
|
|
78568
|
-
return void 0;
|
|
78569
|
-
}
|
|
78570
|
-
logger.debug(`--from-state: loaded state for ${stackName} (${targetRegion})`);
|
|
78571
|
-
return { state: stateData.state, region: targetRegion };
|
|
78572
|
-
} finally {
|
|
78573
|
-
awsClients.destroy();
|
|
78574
|
-
}
|
|
78575
|
-
}
|
|
78576
78896
|
function suggestAssumeRoleFromState(state, logicalId) {
|
|
78577
78897
|
const logger = getLogger();
|
|
78578
78898
|
const lambda = state.resources[logicalId];
|
|
@@ -78688,7 +79008,7 @@ function reorderArgs(argv) {
|
|
|
78688
79008
|
}
|
|
78689
79009
|
async function main() {
|
|
78690
79010
|
const program = new Command17();
|
|
78691
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
79011
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.83.1");
|
|
78692
79012
|
program.addCommand(createBootstrapCommand());
|
|
78693
79013
|
program.addCommand(createSynthCommand());
|
|
78694
79014
|
program.addCommand(createListCommand());
|