@go-to-k/cdkd 0.82.1 → 0.83.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 +393 -106
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.83.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.82.1.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -70040,6 +70040,76 @@ import { dirname as dirname7 } from "node:path";
|
|
|
70040
70040
|
import * as path2 from "node:path";
|
|
70041
70041
|
import { Command as Command16, Option as Option9 } from "commander";
|
|
70042
70042
|
|
|
70043
|
+
// src/cli/commands/local-state-loader.ts
|
|
70044
|
+
init_aws_clients();
|
|
70045
|
+
async function loadStateForStack(stackName, synthRegion, opts) {
|
|
70046
|
+
const logger = getLogger();
|
|
70047
|
+
const prefix = opts.logPrefix ?? "--from-state";
|
|
70048
|
+
const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
|
|
70049
|
+
let stateBucket;
|
|
70050
|
+
try {
|
|
70051
|
+
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
|
|
70052
|
+
} catch (err) {
|
|
70053
|
+
logger.warn(
|
|
70054
|
+
`${prefix}: could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Falling back.`
|
|
70055
|
+
);
|
|
70056
|
+
return void 0;
|
|
70057
|
+
}
|
|
70058
|
+
const awsClients = new AwsClients({
|
|
70059
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
70060
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
70061
|
+
});
|
|
70062
|
+
setAwsClients(awsClients);
|
|
70063
|
+
try {
|
|
70064
|
+
const stateConfig = { bucket: stateBucket, prefix: opts.statePrefix };
|
|
70065
|
+
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
70066
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
70067
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
70068
|
+
});
|
|
70069
|
+
await stateBackend.verifyBucketExists();
|
|
70070
|
+
const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
|
|
70071
|
+
if (refs.length === 0) {
|
|
70072
|
+
logger.warn(
|
|
70073
|
+
`${prefix}: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back.`
|
|
70074
|
+
);
|
|
70075
|
+
return void 0;
|
|
70076
|
+
}
|
|
70077
|
+
let targetRegion;
|
|
70078
|
+
if (opts.stackRegion) {
|
|
70079
|
+
const found = refs.find((r) => r.region === opts.stackRegion);
|
|
70080
|
+
if (!found) {
|
|
70081
|
+
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
70082
|
+
logger.warn(
|
|
70083
|
+
`${prefix}: stack '${stackName}' has no state in region '${opts.stackRegion}' (available: ${seen}). Falling back.`
|
|
70084
|
+
);
|
|
70085
|
+
return void 0;
|
|
70086
|
+
}
|
|
70087
|
+
targetRegion = opts.stackRegion;
|
|
70088
|
+
} else if (synthRegion && refs.some((r) => r.region === synthRegion)) {
|
|
70089
|
+
targetRegion = synthRegion;
|
|
70090
|
+
} else if (refs.length === 1) {
|
|
70091
|
+
targetRegion = refs[0].region ?? synthRegion ?? region;
|
|
70092
|
+
} else {
|
|
70093
|
+
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
70094
|
+
logger.warn(
|
|
70095
|
+
`${prefix}: stack '${stackName}' has state in multiple regions (${seen}). Re-run with --stack-region <region>. Falling back.`
|
|
70096
|
+
);
|
|
70097
|
+
return void 0;
|
|
70098
|
+
}
|
|
70099
|
+
const stateData = await stateBackend.getState(stackName, targetRegion);
|
|
70100
|
+
if (!stateData) {
|
|
70101
|
+
logger.warn(
|
|
70102
|
+
`${prefix}: state record for '${stackName}' (${targetRegion}) returned empty. Falling back.`
|
|
70103
|
+
);
|
|
70104
|
+
return void 0;
|
|
70105
|
+
}
|
|
70106
|
+
logger.debug(`${prefix}: loaded state for ${stackName} (${targetRegion})`);
|
|
70107
|
+
return { state: stateData.state, region: targetRegion };
|
|
70108
|
+
} finally {
|
|
70109
|
+
awsClients.destroy();
|
|
70110
|
+
}
|
|
70111
|
+
}
|
|
70112
|
+
|
|
70043
70113
|
// src/local/lambda-resolver.ts
|
|
70044
70114
|
import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
|
|
70045
70115
|
import { dirname, isAbsolute, resolve as resolve4 } from "node:path";
|
|
@@ -71217,9 +71287,6 @@ function extractHashFromImageUri(imageUri) {
|
|
|
71217
71287
|
return match?.[1];
|
|
71218
71288
|
}
|
|
71219
71289
|
|
|
71220
|
-
// src/cli/commands/local-invoke.ts
|
|
71221
|
-
init_aws_clients();
|
|
71222
|
-
|
|
71223
71290
|
// src/utils/single-flight.ts
|
|
71224
71291
|
function singleFlight(fn, onError) {
|
|
71225
71292
|
let promise;
|
|
@@ -76646,6 +76713,83 @@ var EcsTaskResolutionError = class _EcsTaskResolutionError extends Error {
|
|
|
76646
76713
|
Object.setPrototypeOf(this, _EcsTaskResolutionError.prototype);
|
|
76647
76714
|
}
|
|
76648
76715
|
};
|
|
76716
|
+
function derivePartitionAndUrlSuffix(region) {
|
|
76717
|
+
if (region.startsWith("cn-"))
|
|
76718
|
+
return { partition: "aws-cn", urlSuffix: "amazonaws.com.cn" };
|
|
76719
|
+
if (region.startsWith("us-gov-"))
|
|
76720
|
+
return { partition: "aws-us-gov", urlSuffix: "amazonaws.com" };
|
|
76721
|
+
if (region.startsWith("us-iso-"))
|
|
76722
|
+
return { partition: "aws-iso", urlSuffix: "c2s.ic.gov" };
|
|
76723
|
+
if (region.startsWith("us-isob-"))
|
|
76724
|
+
return { partition: "aws-iso-b", urlSuffix: "sc2s.sgov.gov" };
|
|
76725
|
+
return { partition: "aws", urlSuffix: "amazonaws.com" };
|
|
76726
|
+
}
|
|
76727
|
+
function detectEcsImageResolutionNeeds(stack) {
|
|
76728
|
+
const resources = stack.template.Resources ?? {};
|
|
76729
|
+
let needsPseudoParameters = false;
|
|
76730
|
+
let needsStateResources = false;
|
|
76731
|
+
for (const res of Object.values(resources)) {
|
|
76732
|
+
if (res.Type !== "AWS::ECS::TaskDefinition")
|
|
76733
|
+
continue;
|
|
76734
|
+
const props = res.Properties ?? {};
|
|
76735
|
+
const containers = Array.isArray(props["ContainerDefinitions"]) ? props["ContainerDefinitions"] : [];
|
|
76736
|
+
for (const c of containers) {
|
|
76737
|
+
if (!c || typeof c !== "object")
|
|
76738
|
+
continue;
|
|
76739
|
+
const image = c["Image"];
|
|
76740
|
+
const need = inspectImageForSubstitutions(image, resources);
|
|
76741
|
+
if (need.pseudo)
|
|
76742
|
+
needsPseudoParameters = true;
|
|
76743
|
+
if (need.state)
|
|
76744
|
+
needsStateResources = true;
|
|
76745
|
+
if (needsPseudoParameters && needsStateResources)
|
|
76746
|
+
break;
|
|
76747
|
+
}
|
|
76748
|
+
if (needsPseudoParameters && needsStateResources)
|
|
76749
|
+
break;
|
|
76750
|
+
}
|
|
76751
|
+
return { needsPseudoParameters, needsStateResources };
|
|
76752
|
+
}
|
|
76753
|
+
function inspectImageForSubstitutions(image, resources) {
|
|
76754
|
+
if (!image || typeof image !== "object")
|
|
76755
|
+
return { pseudo: false, state: false };
|
|
76756
|
+
const obj = image;
|
|
76757
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
76758
|
+
if (getAtt !== void 0) {
|
|
76759
|
+
let lid;
|
|
76760
|
+
if (Array.isArray(getAtt) && typeof getAtt[0] === "string")
|
|
76761
|
+
lid = getAtt[0];
|
|
76762
|
+
else if (typeof getAtt === "string")
|
|
76763
|
+
lid = getAtt.split(".")[0];
|
|
76764
|
+
if (lid && resources[lid]?.Type === "AWS::ECR::Repository") {
|
|
76765
|
+
return { pseudo: false, state: true };
|
|
76766
|
+
}
|
|
76767
|
+
}
|
|
76768
|
+
let sub;
|
|
76769
|
+
const subRaw = obj["Fn::Sub"];
|
|
76770
|
+
if (typeof subRaw === "string")
|
|
76771
|
+
sub = subRaw;
|
|
76772
|
+
else if (Array.isArray(subRaw) && typeof subRaw[0] === "string")
|
|
76773
|
+
sub = subRaw[0];
|
|
76774
|
+
if (!sub)
|
|
76775
|
+
return { pseudo: false, state: false };
|
|
76776
|
+
let pseudo = false;
|
|
76777
|
+
let state = false;
|
|
76778
|
+
const placeholderRegex = /\$\{([^}]+)\}/g;
|
|
76779
|
+
let m;
|
|
76780
|
+
while ((m = placeholderRegex.exec(sub)) !== null) {
|
|
76781
|
+
const key = m[1];
|
|
76782
|
+
if (key.startsWith("AWS::")) {
|
|
76783
|
+
pseudo = true;
|
|
76784
|
+
continue;
|
|
76785
|
+
}
|
|
76786
|
+
const dot = key.indexOf(".");
|
|
76787
|
+
const lid = dot === -1 ? key : key.slice(0, dot);
|
|
76788
|
+
if (resources[lid]?.Type === "AWS::ECR::Repository")
|
|
76789
|
+
state = true;
|
|
76790
|
+
}
|
|
76791
|
+
return { pseudo, state };
|
|
76792
|
+
}
|
|
76649
76793
|
function parseEcsTarget(target) {
|
|
76650
76794
|
if (typeof target !== "string" || target.length === 0) {
|
|
76651
76795
|
throw new EcsTaskResolutionError(
|
|
@@ -76667,7 +76811,7 @@ function parseEcsTarget(target) {
|
|
|
76667
76811
|
}
|
|
76668
76812
|
return { stackPattern: null, pathOrId: target, isPath: false };
|
|
76669
76813
|
}
|
|
76670
|
-
function resolveEcsTaskTarget(target, stacks) {
|
|
76814
|
+
function resolveEcsTaskTarget(target, stacks, context) {
|
|
76671
76815
|
if (stacks.length === 0) {
|
|
76672
76816
|
throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
76673
76817
|
}
|
|
@@ -76710,7 +76854,7 @@ function resolveEcsTaskTarget(target, stacks) {
|
|
|
76710
76854
|
`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`
|
|
76711
76855
|
);
|
|
76712
76856
|
}
|
|
76713
|
-
return extractTaskDefinitionProperties(stack, logicalId, resource);
|
|
76857
|
+
return extractTaskDefinitionProperties(stack, logicalId, resource, context);
|
|
76714
76858
|
}
|
|
76715
76859
|
function pickStack2(parsed, stacks) {
|
|
76716
76860
|
if (parsed.stackPattern === null) {
|
|
@@ -76733,7 +76877,7 @@ function pickStack2(parsed, stacks) {
|
|
|
76733
76877
|
}
|
|
76734
76878
|
return matched[0];
|
|
76735
76879
|
}
|
|
76736
|
-
function extractTaskDefinitionProperties(stack, logicalId, resource) {
|
|
76880
|
+
function extractTaskDefinitionProperties(stack, logicalId, resource, context) {
|
|
76737
76881
|
const props = resource.Properties ?? {};
|
|
76738
76882
|
const warnings = [];
|
|
76739
76883
|
const family = pickString(props["Family"]) ?? logicalId;
|
|
@@ -76760,7 +76904,7 @@ function extractTaskDefinitionProperties(stack, logicalId, resource) {
|
|
|
76760
76904
|
throw new EcsTaskResolutionError(`Task definition '${logicalId}' has no ContainerDefinitions.`);
|
|
76761
76905
|
}
|
|
76762
76906
|
const containers = rawContainers.map(
|
|
76763
|
-
(c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack)
|
|
76907
|
+
(c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack, context)
|
|
76764
76908
|
);
|
|
76765
76909
|
const rawVolumes = props["Volumes"];
|
|
76766
76910
|
const volumes = Array.isArray(rawVolumes) ? rawVolumes.map((v, idx) => parseVolume(v, idx, logicalId)) : [];
|
|
@@ -76806,7 +76950,7 @@ function parseRuntimePlatform(value) {
|
|
|
76806
76950
|
return void 0;
|
|
76807
76951
|
return { cpuArchitecture: cpu, operatingSystemFamily: os };
|
|
76808
76952
|
}
|
|
76809
|
-
function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
|
|
76953
|
+
function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, context) {
|
|
76810
76954
|
if (!raw || typeof raw !== "object") {
|
|
76811
76955
|
throw new EcsTaskResolutionError(
|
|
76812
76956
|
`Task '${taskLogicalId}' ContainerDefinitions[${idx}] is not an object.`
|
|
@@ -76819,7 +76963,7 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
|
|
|
76819
76963
|
`Task '${taskLogicalId}' ContainerDefinitions[${idx}] has no Name.`
|
|
76820
76964
|
);
|
|
76821
76965
|
}
|
|
76822
|
-
const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack);
|
|
76966
|
+
const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack, context);
|
|
76823
76967
|
const command = pickStringArray2(c["Command"]);
|
|
76824
76968
|
const entryPoint = pickStringArray2(c["EntryPoint"]);
|
|
76825
76969
|
const workingDirectory = pickString(c["WorkingDirectory"]);
|
|
@@ -76965,7 +77109,11 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
|
|
|
76965
77109
|
out.readonlyRootFilesystem = readonlyRootFilesystem;
|
|
76966
77110
|
return out;
|
|
76967
77111
|
}
|
|
76968
|
-
function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack) {
|
|
77112
|
+
function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack, context) {
|
|
77113
|
+
const getAttImage = tryResolveImageGetAtt(raw, resources, context);
|
|
77114
|
+
if (getAttImage) {
|
|
77115
|
+
return classifyResolvedImage(getAttImage);
|
|
77116
|
+
}
|
|
76969
77117
|
const flat = extractImageString(raw);
|
|
76970
77118
|
if (!flat) {
|
|
76971
77119
|
throw new EcsTaskResolutionError(
|
|
@@ -76979,23 +77127,123 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
|
|
|
76979
77127
|
out.assetHash = hashMatch[1];
|
|
76980
77128
|
return out;
|
|
76981
77129
|
}
|
|
76982
|
-
const
|
|
76983
|
-
if (
|
|
76984
|
-
|
|
76985
|
-
|
|
76986
|
-
|
|
77130
|
+
const substituted = substituteImagePlaceholders(flat, resources, context);
|
|
77131
|
+
if (substituted.includes("${")) {
|
|
77132
|
+
const unresolvedRepoRef = findUnresolvedEcrRepositoryRef(substituted, resources);
|
|
77133
|
+
if (unresolvedRepoRef) {
|
|
77134
|
+
throw new EcsTaskResolutionError(
|
|
77135
|
+
`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.`
|
|
77136
|
+
);
|
|
77137
|
+
}
|
|
77138
|
+
if (substituted.includes("AWS::")) {
|
|
77139
|
+
throw new EcsTaskResolutionError(
|
|
77140
|
+
`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.`
|
|
77141
|
+
);
|
|
77142
|
+
}
|
|
76987
77143
|
throw new EcsTaskResolutionError(
|
|
76988
|
-
`Container '${containerName}' in task '${taskLogicalId}' has an Image
|
|
77144
|
+
`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
77145
|
);
|
|
76990
77146
|
}
|
|
76991
|
-
|
|
76992
|
-
|
|
76993
|
-
|
|
76994
|
-
|
|
76995
|
-
|
|
77147
|
+
const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
|
|
77148
|
+
if (ecrMatch) {
|
|
77149
|
+
return { kind: "ecr", uri: substituted, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77150
|
+
}
|
|
77151
|
+
return { kind: "public", uri: substituted };
|
|
77152
|
+
}
|
|
77153
|
+
function findUnresolvedEcrRepositoryRef(substituted, resources) {
|
|
77154
|
+
const placeholderRegex = /\$\{([^}]+)\}/g;
|
|
77155
|
+
let m;
|
|
77156
|
+
while ((m = placeholderRegex.exec(substituted)) !== null) {
|
|
77157
|
+
const key = m[1];
|
|
77158
|
+
if (key.startsWith("AWS::"))
|
|
77159
|
+
continue;
|
|
77160
|
+
const dot = key.indexOf(".");
|
|
77161
|
+
const lid = dot === -1 ? key : key.slice(0, dot);
|
|
77162
|
+
if (resources[lid]?.Type === "AWS::ECR::Repository")
|
|
77163
|
+
return lid;
|
|
77164
|
+
}
|
|
77165
|
+
return void 0;
|
|
77166
|
+
}
|
|
77167
|
+
function classifyResolvedImage(uri) {
|
|
77168
|
+
if (uri.includes("cdk-hnb659fds-container-assets-")) {
|
|
77169
|
+
const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);
|
|
77170
|
+
const out = { kind: "cdk-asset" };
|
|
77171
|
+
if (hashMatch)
|
|
77172
|
+
out.assetHash = hashMatch[1];
|
|
77173
|
+
return out;
|
|
77174
|
+
}
|
|
77175
|
+
const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
|
|
77176
|
+
if (ecrMatch) {
|
|
77177
|
+
return { kind: "ecr", uri, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77178
|
+
}
|
|
77179
|
+
return { kind: "public", uri };
|
|
77180
|
+
}
|
|
77181
|
+
function substituteImagePlaceholders(flat, resources, context) {
|
|
77182
|
+
if (!flat.includes("${"))
|
|
77183
|
+
return flat;
|
|
77184
|
+
return flat.replace(/\$\{([^}]+)\}/g, (full, key) => {
|
|
77185
|
+
if (context?.pseudoParameters) {
|
|
77186
|
+
if (key === "AWS::AccountId" && context.pseudoParameters.accountId) {
|
|
77187
|
+
return context.pseudoParameters.accountId;
|
|
77188
|
+
}
|
|
77189
|
+
if (key === "AWS::Region" && context.pseudoParameters.region) {
|
|
77190
|
+
return context.pseudoParameters.region;
|
|
77191
|
+
}
|
|
77192
|
+
if (key === "AWS::Partition" && context.pseudoParameters.partition) {
|
|
77193
|
+
return context.pseudoParameters.partition;
|
|
77194
|
+
}
|
|
77195
|
+
if (key === "AWS::URLSuffix" && context.pseudoParameters.urlSuffix) {
|
|
77196
|
+
return context.pseudoParameters.urlSuffix;
|
|
77197
|
+
}
|
|
77198
|
+
}
|
|
77199
|
+
if (context?.stateResources) {
|
|
77200
|
+
const dot = key.indexOf(".");
|
|
77201
|
+
const logicalId = dot === -1 ? key : key.slice(0, dot);
|
|
77202
|
+
const refResource = resources[logicalId];
|
|
77203
|
+
const stateEntry = context.stateResources[logicalId];
|
|
77204
|
+
if (refResource?.Type === "AWS::ECR::Repository" && stateEntry) {
|
|
77205
|
+
if (dot === -1) {
|
|
77206
|
+
return stateEntry.physicalId;
|
|
77207
|
+
}
|
|
77208
|
+
const attr = key.slice(dot + 1);
|
|
77209
|
+
const cached = stateEntry.attributes?.[attr];
|
|
77210
|
+
if (typeof cached === "string")
|
|
77211
|
+
return cached;
|
|
77212
|
+
}
|
|
77213
|
+
}
|
|
77214
|
+
return full;
|
|
77215
|
+
});
|
|
77216
|
+
}
|
|
77217
|
+
function tryResolveImageGetAtt(raw, resources, context) {
|
|
77218
|
+
if (!raw || typeof raw !== "object")
|
|
77219
|
+
return void 0;
|
|
77220
|
+
const obj = raw;
|
|
77221
|
+
const arg = obj["Fn::GetAtt"];
|
|
77222
|
+
if (arg === void 0)
|
|
77223
|
+
return void 0;
|
|
77224
|
+
let logicalId;
|
|
77225
|
+
let attr;
|
|
77226
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
|
|
77227
|
+
logicalId = arg[0];
|
|
77228
|
+
attr = arg[1];
|
|
77229
|
+
} else if (typeof arg === "string") {
|
|
77230
|
+
const dot = arg.indexOf(".");
|
|
77231
|
+
if (dot > 0 && dot < arg.length - 1) {
|
|
77232
|
+
logicalId = arg.slice(0, dot);
|
|
77233
|
+
attr = arg.slice(dot + 1);
|
|
76996
77234
|
}
|
|
76997
77235
|
}
|
|
76998
|
-
|
|
77236
|
+
if (!logicalId || !attr)
|
|
77237
|
+
return void 0;
|
|
77238
|
+
const refResource = resources[logicalId];
|
|
77239
|
+
if (refResource?.Type !== "AWS::ECR::Repository")
|
|
77240
|
+
return void 0;
|
|
77241
|
+
if (attr !== "RepositoryUri" && attr !== "Arn")
|
|
77242
|
+
return void 0;
|
|
77243
|
+
const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];
|
|
77244
|
+
if (typeof cached === "string" && cached.length > 0)
|
|
77245
|
+
return cached;
|
|
77246
|
+
return void 0;
|
|
76999
77247
|
}
|
|
77000
77248
|
function extractImageString(value) {
|
|
77001
77249
|
if (typeof value === "string" && value.length > 0)
|
|
@@ -77074,6 +77322,7 @@ function parseVolume(raw, idx, taskLogicalId) {
|
|
|
77074
77322
|
}
|
|
77075
77323
|
return { name, kind: "host" };
|
|
77076
77324
|
}
|
|
77325
|
+
var TASK_ROLE_ACCOUNT_PLACEHOLDER = "${AWS::AccountId}";
|
|
77077
77326
|
function resolveRoleArn(value, resources) {
|
|
77078
77327
|
if (value === void 0 || value === null)
|
|
77079
77328
|
return void 0;
|
|
@@ -77082,24 +77331,23 @@ function resolveRoleArn(value, resources) {
|
|
|
77082
77331
|
if (typeof value !== "object")
|
|
77083
77332
|
return void 0;
|
|
77084
77333
|
const obj = value;
|
|
77334
|
+
let refLogicalId;
|
|
77085
77335
|
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) {
|
|
77336
|
+
refLogicalId = obj["Ref"];
|
|
77337
|
+
} else if ("Fn::GetAtt" in obj) {
|
|
77093
77338
|
const arg = obj["Fn::GetAtt"];
|
|
77094
77339
|
if (Array.isArray(arg) && typeof arg[0] === "string") {
|
|
77095
|
-
const
|
|
77096
|
-
|
|
77097
|
-
|
|
77098
|
-
return void 0;
|
|
77099
|
-
}
|
|
77340
|
+
const attr = typeof arg[1] === "string" ? arg[1] : "";
|
|
77341
|
+
if (attr === "" || attr === "Arn")
|
|
77342
|
+
refLogicalId = arg[0];
|
|
77100
77343
|
}
|
|
77101
77344
|
}
|
|
77102
|
-
|
|
77345
|
+
if (refLogicalId === void 0)
|
|
77346
|
+
return void 0;
|
|
77347
|
+
const role = resources[refLogicalId];
|
|
77348
|
+
if (role?.Type !== "AWS::IAM::Role")
|
|
77349
|
+
return void 0;
|
|
77350
|
+
return `arn:aws:iam::${TASK_ROLE_ACCOUNT_PLACEHOLDER}:role/${refLogicalId}`;
|
|
77103
77351
|
}
|
|
77104
77352
|
function pickString(value) {
|
|
77105
77353
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -77936,7 +78184,8 @@ async function localRunTaskCommand(target, options) {
|
|
|
77936
78184
|
...Object.keys(context).length > 0 && { context }
|
|
77937
78185
|
};
|
|
77938
78186
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
77939
|
-
const
|
|
78187
|
+
const imageContext = await buildEcsImageResolutionContext(target, stacks, options);
|
|
78188
|
+
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
77940
78189
|
logger.info(
|
|
77941
78190
|
`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`
|
|
77942
78191
|
);
|
|
@@ -77955,10 +78204,10 @@ async function localRunTaskCommand(target, options) {
|
|
|
77955
78204
|
if (options.assumeTaskRole === true) {
|
|
77956
78205
|
if (!task.taskRoleArn) {
|
|
77957
78206
|
throw new Error(
|
|
77958
|
-
`--assume-task-role passed without an ARN but the task definition
|
|
78207
|
+
`--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
78208
|
);
|
|
77960
78209
|
}
|
|
77961
|
-
resolvedRoleArn = task.taskRoleArn;
|
|
78210
|
+
resolvedRoleArn = await resolvePlaceholderAccount(task.taskRoleArn, options.region);
|
|
77962
78211
|
assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region);
|
|
77963
78212
|
} else if (typeof options.assumeTaskRole === "string") {
|
|
77964
78213
|
resolvedRoleArn = options.assumeTaskRole;
|
|
@@ -78006,6 +78255,24 @@ async function localRunTaskCommand(target, options) {
|
|
|
78006
78255
|
await cleanup();
|
|
78007
78256
|
}
|
|
78008
78257
|
}
|
|
78258
|
+
async function resolvePlaceholderAccount(arn, region) {
|
|
78259
|
+
if (!arn.includes(TASK_ROLE_ACCOUNT_PLACEHOLDER))
|
|
78260
|
+
return arn;
|
|
78261
|
+
const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
|
|
78262
|
+
const sts = new STSClient11({ ...region && { region } });
|
|
78263
|
+
try {
|
|
78264
|
+
const identity = await sts.send(new GetCallerIdentityCommand12({}));
|
|
78265
|
+
const account = identity.Account;
|
|
78266
|
+
if (!account) {
|
|
78267
|
+
throw new Error(
|
|
78268
|
+
`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`
|
|
78269
|
+
);
|
|
78270
|
+
}
|
|
78271
|
+
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
78272
|
+
} finally {
|
|
78273
|
+
sts.destroy();
|
|
78274
|
+
}
|
|
78275
|
+
}
|
|
78009
78276
|
async function assumeTaskRole(roleArn, region) {
|
|
78010
78277
|
const { STSClient: STSClient11, AssumeRoleCommand: AssumeRoleCommand2 } = await import("@aws-sdk/client-sts");
|
|
78011
78278
|
const sts = new STSClient11({ ...region && { region } });
|
|
@@ -78030,6 +78297,80 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
78030
78297
|
sts.destroy();
|
|
78031
78298
|
}
|
|
78032
78299
|
}
|
|
78300
|
+
async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
78301
|
+
const logger = getLogger();
|
|
78302
|
+
const parsed = parseEcsTarget(target);
|
|
78303
|
+
const candidate = pickCandidateStack(parsed.stackPattern, stacks);
|
|
78304
|
+
if (!candidate)
|
|
78305
|
+
return void 0;
|
|
78306
|
+
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
78307
|
+
if (!needs.needsPseudoParameters && !needs.needsStateResources)
|
|
78308
|
+
return void 0;
|
|
78309
|
+
const ctx = {};
|
|
78310
|
+
if (needs.needsPseudoParameters) {
|
|
78311
|
+
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
78312
|
+
if (!region) {
|
|
78313
|
+
logger.warn(
|
|
78314
|
+
"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."
|
|
78315
|
+
);
|
|
78316
|
+
}
|
|
78317
|
+
let accountId;
|
|
78318
|
+
try {
|
|
78319
|
+
accountId = await resolveCallerAccountId(region);
|
|
78320
|
+
} catch (err) {
|
|
78321
|
+
logger.warn(
|
|
78322
|
+
`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.`
|
|
78323
|
+
);
|
|
78324
|
+
}
|
|
78325
|
+
const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
|
|
78326
|
+
ctx.pseudoParameters = {
|
|
78327
|
+
...accountId !== void 0 && { accountId },
|
|
78328
|
+
...region !== void 0 && { region },
|
|
78329
|
+
...partitionAndSuffix && {
|
|
78330
|
+
partition: partitionAndSuffix.partition,
|
|
78331
|
+
urlSuffix: partitionAndSuffix.urlSuffix
|
|
78332
|
+
}
|
|
78333
|
+
};
|
|
78334
|
+
}
|
|
78335
|
+
if (options.fromState && needs.needsStateResources) {
|
|
78336
|
+
const loaded = await loadStateForStack(candidate.stackName, candidate.region, {
|
|
78337
|
+
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
78338
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
78339
|
+
statePrefix: options.statePrefix,
|
|
78340
|
+
...options.region !== void 0 && { region: options.region },
|
|
78341
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
78342
|
+
});
|
|
78343
|
+
if (loaded) {
|
|
78344
|
+
ctx.stateResources = loaded.state.resources;
|
|
78345
|
+
}
|
|
78346
|
+
} else if (!options.fromState && needs.needsStateResources) {
|
|
78347
|
+
logger.warn(
|
|
78348
|
+
"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."
|
|
78349
|
+
);
|
|
78350
|
+
}
|
|
78351
|
+
return ctx;
|
|
78352
|
+
}
|
|
78353
|
+
function pickCandidateStack(stackPattern, stacks) {
|
|
78354
|
+
if (stackPattern === null) {
|
|
78355
|
+
if (stacks.length === 1)
|
|
78356
|
+
return stacks[0];
|
|
78357
|
+
return void 0;
|
|
78358
|
+
}
|
|
78359
|
+
const matched = matchStacks(stacks, [stackPattern]);
|
|
78360
|
+
if (matched.length === 1)
|
|
78361
|
+
return matched[0];
|
|
78362
|
+
return void 0;
|
|
78363
|
+
}
|
|
78364
|
+
async function resolveCallerAccountId(region) {
|
|
78365
|
+
const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
|
|
78366
|
+
const sts = new STSClient11({ ...region && { region } });
|
|
78367
|
+
try {
|
|
78368
|
+
const identity = await sts.send(new GetCallerIdentityCommand12({}));
|
|
78369
|
+
return identity.Account;
|
|
78370
|
+
} finally {
|
|
78371
|
+
sts.destroy();
|
|
78372
|
+
}
|
|
78373
|
+
}
|
|
78033
78374
|
function readEnvOverridesFile2(filePath) {
|
|
78034
78375
|
if (!filePath)
|
|
78035
78376
|
return void 0;
|
|
@@ -78097,8 +78438,20 @@ function createLocalRunTaskCommand() {
|
|
|
78097
78438
|
"--detach",
|
|
78098
78439
|
"Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle."
|
|
78099
78440
|
).default(false)
|
|
78441
|
+
).addOption(
|
|
78442
|
+
new Option8(
|
|
78443
|
+
"--from-state",
|
|
78444
|
+
"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."
|
|
78445
|
+
).default(false)
|
|
78446
|
+
).addOption(
|
|
78447
|
+
new Option8(
|
|
78448
|
+
"--stack-region <region>",
|
|
78449
|
+
"Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions)."
|
|
78450
|
+
)
|
|
78100
78451
|
).action(withErrorHandling(localRunTaskCommand));
|
|
78101
|
-
[...commonOptions, ...appOptions, ...contextOptions].forEach(
|
|
78452
|
+
[...commonOptions, ...appOptions, ...contextOptions, ...stateOptions].forEach(
|
|
78453
|
+
(opt) => cmd.addOption(opt)
|
|
78454
|
+
);
|
|
78102
78455
|
cmd.addOption(deprecatedRegionOption);
|
|
78103
78456
|
return cmd;
|
|
78104
78457
|
}
|
|
@@ -78507,72 +78860,6 @@ function materializeInlineCode2(handler, source, fileExtension) {
|
|
|
78507
78860
|
writeFileSync6(filePath, source, "utf-8");
|
|
78508
78861
|
return dir;
|
|
78509
78862
|
}
|
|
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
78863
|
function suggestAssumeRoleFromState(state, logicalId) {
|
|
78577
78864
|
const logger = getLogger();
|
|
78578
78865
|
const lambda = state.resources[logicalId];
|
|
@@ -78688,7 +78975,7 @@ function reorderArgs(argv) {
|
|
|
78688
78975
|
}
|
|
78689
78976
|
async function main() {
|
|
78690
78977
|
const program = new Command17();
|
|
78691
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
78978
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.83.0");
|
|
78692
78979
|
program.addCommand(createBootstrapCommand());
|
|
78693
78980
|
program.addCommand(createSynthCommand());
|
|
78694
78981
|
program.addCommand(createListCommand());
|