@go-to-k/cdkd 0.91.2 → 0.91.3
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/README.md +1 -1
- package/dist/cli.js +324 -39
- package/dist/cli.js.map +2 -2
- package/dist/go-to-k-cdkd-0.91.3.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.91.2.tgz +0 -0
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- **Rollback on failure**: When a deploy errors mid-stack, cdkd rolls back the resources it just created so the stack state stays consistent (CloudFormation parity — but cdkd does this without round-tripping through CFn). Pass `cdkd deploy --no-rollback` to skip rollback and keep the partial state for Terraform-style inspection / repair. See [Rollback behavior](#rollback-behavior).
|
|
25
25
|
- **`--no-wait` for async resources**: Skip the multi-minute wait on CloudFront / RDS / ElastiCache / NAT Gateway and return as soon as the create call returns (CloudFormation always blocks)
|
|
26
26
|
- **VPC route DependsOn relaxation (on by default)**: Drop CDK-injected defensive `DependsOn` edges from VPC Lambdas onto private-subnet routes so `CloudFront::Distribution` and `Lambda::Url` start their ~3-min propagation in parallel with NAT Gateway stabilization (~50% faster on VPC + Lambda + CloudFront stacks). Pass `--no-aggressive-vpc-parallel` to opt out.
|
|
27
|
-
- **Local execution without deploying** (`cdkd local invoke` / `cdkd local start-api` / `cdkd local run-task`): run any Lambda — stand up every API Gateway route as a local HTTP server — or start every container in an `AWS::ECS::TaskDefinition` on a per-task docker network with the AWS-published metadata-endpoints sidecar. SAM-compatible mental model but reuses cdkd's synthesis / asset / route-discovery (no `template.yaml` round-trip). All AWS Lambda runtimes (Node.js / Python / Ruby / Java / .NET / `provided.*`) and one server per discovered API (HTTP API v2 / REST v1 / Function URL) with their own port / authorizers / CORS configs. `local run-task` is Phase 1 (single task, DependsOn ordering, IAM task-role via AssumeRole) — ECS Services / ALB routing / Service Connect are Phase 2 / Phase 3 follow-ups.
|
|
27
|
+
- **Local execution without deploying** (`cdkd local invoke` / `cdkd local start-api` / `cdkd local run-task`): run any Lambda — stand up every API Gateway route as a local HTTP server — or start every container in an `AWS::ECS::TaskDefinition` on a per-task docker network with the AWS-published metadata-endpoints sidecar. SAM-compatible mental model but reuses cdkd's synthesis / asset / route-discovery (no `template.yaml` round-trip). All AWS Lambda runtimes (Node.js / Python / Ruby / Java / .NET / `provided.*`) and one server per discovered API (HTTP API v2 / REST v1 / Function URL) with their own port / authorizers / CORS configs. `local run-task` is Phase 1 (single task, DependsOn ordering, IAM task-role via AssumeRole) — ECS Services / ALB routing / Service Connect are Phase 2 / Phase 3 follow-ups. `cdkd local run-task --from-state` substitutes intrinsic-valued container `Environment[].Value` (`Ref` / `Fn::GetAtt` / `Fn::Sub` / `Fn::Join`) and `Secrets[].ValueFrom` against the deployed cdkd state — `table.tableName` / `ecs.Secret.fromSecretsManager(secret)` / `ecs.Secret.fromSsmParameter(param)` Just Work locally instead of silently dropping.
|
|
28
28
|
|
|
29
29
|
> **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd. If you encounter an unsupported resource type, deployment will fail with a clear error message.
|
|
30
30
|
|
package/dist/cli.js
CHANGED
|
@@ -47837,22 +47837,32 @@ var ECSProvider = class {
|
|
|
47837
47837
|
essential: def["Essential"],
|
|
47838
47838
|
command: def["Command"],
|
|
47839
47839
|
entryPoint: def["EntryPoint"],
|
|
47840
|
-
environment:
|
|
47841
|
-
|
|
47842
|
-
|
|
47840
|
+
environment: this.convertEnvironment(
|
|
47841
|
+
def["Environment"]
|
|
47842
|
+
),
|
|
47843
|
+
environmentFiles: this.convertEnvironmentFiles(
|
|
47844
|
+
def["EnvironmentFiles"]
|
|
47845
|
+
),
|
|
47846
|
+
secrets: this.convertSecrets(def["Secrets"]),
|
|
47843
47847
|
portMappings: this.convertPortMappings(
|
|
47844
47848
|
def["PortMappings"]
|
|
47845
47849
|
),
|
|
47846
|
-
mountPoints:
|
|
47847
|
-
|
|
47848
|
-
|
|
47850
|
+
mountPoints: this.convertMountPoints(
|
|
47851
|
+
def["MountPoints"]
|
|
47852
|
+
),
|
|
47853
|
+
volumesFrom: this.convertVolumesFrom(
|
|
47854
|
+
def["VolumesFrom"]
|
|
47855
|
+
),
|
|
47856
|
+
dependsOn: this.convertDependsOn(
|
|
47857
|
+
def["DependsOn"]
|
|
47858
|
+
),
|
|
47849
47859
|
links: def["Links"],
|
|
47850
47860
|
workingDirectory: def["WorkingDirectory"],
|
|
47851
47861
|
disableNetworking: def["DisableNetworking"],
|
|
47852
47862
|
privileged: def["Privileged"],
|
|
47853
47863
|
readonlyRootFilesystem: def["ReadonlyRootFilesystem"],
|
|
47854
47864
|
user: def["User"],
|
|
47855
|
-
ulimits: def["Ulimits"],
|
|
47865
|
+
ulimits: this.convertUlimits(def["Ulimits"]),
|
|
47856
47866
|
logConfiguration: this.convertLogConfiguration(
|
|
47857
47867
|
def["LogConfiguration"]
|
|
47858
47868
|
),
|
|
@@ -47881,6 +47891,103 @@ var ECSProvider = class {
|
|
|
47881
47891
|
name: m["Name"]
|
|
47882
47892
|
}));
|
|
47883
47893
|
}
|
|
47894
|
+
/**
|
|
47895
|
+
* Convert CFn Environment (KeyValuePair) to ECS SDK format.
|
|
47896
|
+
* CFn template emits `{Name, Value}` (PascalCase); ECS SDK requires
|
|
47897
|
+
* `{name, value}` (camelCase). Pre-fix the cast `as KeyValuePair[]`
|
|
47898
|
+
* silently dropped both fields and AWS rejected RegisterTaskDefinition
|
|
47899
|
+
* with a generic null/empty validation error.
|
|
47900
|
+
*/
|
|
47901
|
+
convertEnvironment(entries) {
|
|
47902
|
+
if (!entries)
|
|
47903
|
+
return void 0;
|
|
47904
|
+
return entries.map((e) => ({
|
|
47905
|
+
name: e["Name"],
|
|
47906
|
+
value: e["Value"]
|
|
47907
|
+
}));
|
|
47908
|
+
}
|
|
47909
|
+
/**
|
|
47910
|
+
* Convert CFn EnvironmentFiles (S3-backed env-var files) to ECS SDK format.
|
|
47911
|
+
* CFn: `{Type, Value}` → SDK: `{type, value}`.
|
|
47912
|
+
*/
|
|
47913
|
+
convertEnvironmentFiles(entries) {
|
|
47914
|
+
if (!entries)
|
|
47915
|
+
return void 0;
|
|
47916
|
+
return entries.map((e) => ({
|
|
47917
|
+
type: e["Type"],
|
|
47918
|
+
value: e["Value"]
|
|
47919
|
+
}));
|
|
47920
|
+
}
|
|
47921
|
+
/**
|
|
47922
|
+
* Convert CFn Secrets to ECS SDK format.
|
|
47923
|
+
* CFn: `{Name, ValueFrom}` → SDK: `{name, valueFrom}`.
|
|
47924
|
+
* Same PascalCase→camelCase trap as convertEnvironment — discovered
|
|
47925
|
+
* end-to-end via the local-run-task-from-state integ on 2026-05-12
|
|
47926
|
+
* (issue #291 fixture).
|
|
47927
|
+
*/
|
|
47928
|
+
convertSecrets(entries) {
|
|
47929
|
+
if (!entries)
|
|
47930
|
+
return void 0;
|
|
47931
|
+
return entries.map((e) => ({
|
|
47932
|
+
name: e["Name"],
|
|
47933
|
+
valueFrom: e["ValueFrom"]
|
|
47934
|
+
}));
|
|
47935
|
+
}
|
|
47936
|
+
/**
|
|
47937
|
+
* Convert CFn MountPoints to ECS SDK format.
|
|
47938
|
+
* CFn: `{SourceVolume, ContainerPath, ReadOnly}` → SDK: `{sourceVolume,
|
|
47939
|
+
* containerPath, readOnly}`.
|
|
47940
|
+
*/
|
|
47941
|
+
convertMountPoints(entries) {
|
|
47942
|
+
if (!entries)
|
|
47943
|
+
return void 0;
|
|
47944
|
+
return entries.map((e) => ({
|
|
47945
|
+
sourceVolume: e["SourceVolume"],
|
|
47946
|
+
containerPath: e["ContainerPath"],
|
|
47947
|
+
readOnly: e["ReadOnly"]
|
|
47948
|
+
}));
|
|
47949
|
+
}
|
|
47950
|
+
/**
|
|
47951
|
+
* Convert CFn VolumesFrom to ECS SDK format.
|
|
47952
|
+
* CFn: `{SourceContainer, ReadOnly}` → SDK: `{sourceContainer, readOnly}`.
|
|
47953
|
+
*/
|
|
47954
|
+
convertVolumesFrom(entries) {
|
|
47955
|
+
if (!entries)
|
|
47956
|
+
return void 0;
|
|
47957
|
+
return entries.map((e) => ({
|
|
47958
|
+
sourceContainer: e["SourceContainer"],
|
|
47959
|
+
readOnly: e["ReadOnly"]
|
|
47960
|
+
}));
|
|
47961
|
+
}
|
|
47962
|
+
/**
|
|
47963
|
+
* Convert CFn DependsOn to ECS SDK format.
|
|
47964
|
+
* CFn: `{ContainerName, Condition}` → SDK: `{containerName, condition}`.
|
|
47965
|
+
* The pre-existing local-run-task-multi-container integ was relying
|
|
47966
|
+
* on ECS SDK being lenient about the dependsOn key casing on input,
|
|
47967
|
+
* but per the SDK type definition the input is camelCase so this
|
|
47968
|
+
* converter brings the actual wire shape in line with the contract.
|
|
47969
|
+
*/
|
|
47970
|
+
convertDependsOn(entries) {
|
|
47971
|
+
if (!entries)
|
|
47972
|
+
return void 0;
|
|
47973
|
+
return entries.map((e) => ({
|
|
47974
|
+
containerName: e["ContainerName"],
|
|
47975
|
+
condition: e["Condition"]
|
|
47976
|
+
}));
|
|
47977
|
+
}
|
|
47978
|
+
/**
|
|
47979
|
+
* Convert CFn Ulimits to ECS SDK format.
|
|
47980
|
+
* CFn: `{Name, SoftLimit, HardLimit}` → SDK: `{name, softLimit, hardLimit}`.
|
|
47981
|
+
*/
|
|
47982
|
+
convertUlimits(entries) {
|
|
47983
|
+
if (!entries)
|
|
47984
|
+
return void 0;
|
|
47985
|
+
return entries.map((e) => ({
|
|
47986
|
+
name: e["Name"],
|
|
47987
|
+
softLimit: e["SoftLimit"],
|
|
47988
|
+
hardLimit: e["HardLimit"]
|
|
47989
|
+
}));
|
|
47990
|
+
}
|
|
47884
47991
|
/**
|
|
47885
47992
|
* Convert CFn LogConfiguration to ECS SDK format
|
|
47886
47993
|
*/
|
|
@@ -70791,7 +70898,8 @@ function isLiteralEnvValue(value) {
|
|
|
70791
70898
|
}
|
|
70792
70899
|
|
|
70793
70900
|
// src/local/state-resolver.ts
|
|
70794
|
-
function substituteAgainstState(value,
|
|
70901
|
+
function substituteAgainstState(value, contextOrResources) {
|
|
70902
|
+
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
70795
70903
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
70796
70904
|
return { kind: "literal", value };
|
|
70797
70905
|
}
|
|
@@ -70812,24 +70920,40 @@ function substituteAgainstState(value, resources) {
|
|
|
70812
70920
|
const intrinsic = keys[0];
|
|
70813
70921
|
const arg = obj[intrinsic];
|
|
70814
70922
|
if (intrinsic === "Ref") {
|
|
70815
|
-
return resolveRef(arg,
|
|
70923
|
+
return resolveRef(arg, context);
|
|
70816
70924
|
}
|
|
70817
70925
|
if (intrinsic === "Fn::GetAtt") {
|
|
70818
|
-
return resolveGetAtt(arg,
|
|
70926
|
+
return resolveGetAtt(arg, context);
|
|
70819
70927
|
}
|
|
70820
70928
|
if (intrinsic === "Fn::Sub") {
|
|
70821
|
-
return resolveSub(arg,
|
|
70929
|
+
return resolveSub(arg, context);
|
|
70930
|
+
}
|
|
70931
|
+
if (intrinsic === "Fn::Join") {
|
|
70932
|
+
return resolveJoin(arg, context);
|
|
70822
70933
|
}
|
|
70823
70934
|
return {
|
|
70824
70935
|
kind: "unresolved",
|
|
70825
|
-
reason: `unsupported intrinsic '${intrinsic}' (
|
|
70936
|
+
reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join)`
|
|
70826
70937
|
};
|
|
70827
70938
|
}
|
|
70828
|
-
function
|
|
70939
|
+
function isContext(v) {
|
|
70940
|
+
if (typeof v !== "object" || v === null)
|
|
70941
|
+
return false;
|
|
70942
|
+
const r = v["resources"];
|
|
70943
|
+
if (r === void 0)
|
|
70944
|
+
return false;
|
|
70945
|
+
if (typeof r !== "object" || r === null)
|
|
70946
|
+
return false;
|
|
70947
|
+
return !("physicalId" in r);
|
|
70948
|
+
}
|
|
70949
|
+
function resolveRef(arg, context) {
|
|
70829
70950
|
if (typeof arg !== "string" || arg.length === 0) {
|
|
70830
70951
|
return { kind: "unresolved", reason: `Ref expects a non-empty logical ID, got ${typeof arg}` };
|
|
70831
70952
|
}
|
|
70832
|
-
|
|
70953
|
+
if (arg.startsWith("AWS::")) {
|
|
70954
|
+
return resolvePseudoParameter(arg, context.pseudoParameters);
|
|
70955
|
+
}
|
|
70956
|
+
const resource = context.resources[arg];
|
|
70833
70957
|
if (!resource) {
|
|
70834
70958
|
return {
|
|
70835
70959
|
kind: "unresolved",
|
|
@@ -70838,7 +70962,39 @@ function resolveRef(arg, resources) {
|
|
|
70838
70962
|
}
|
|
70839
70963
|
return { kind: "literal", value: resource.physicalId };
|
|
70840
70964
|
}
|
|
70841
|
-
function
|
|
70965
|
+
function resolvePseudoParameter(name, pseudo) {
|
|
70966
|
+
if (!pseudo) {
|
|
70967
|
+
return {
|
|
70968
|
+
kind: "unresolved",
|
|
70969
|
+
reason: `Ref '${name}': pseudo parameter not supplied (need --from-state context)`
|
|
70970
|
+
};
|
|
70971
|
+
}
|
|
70972
|
+
switch (name) {
|
|
70973
|
+
case "AWS::AccountId":
|
|
70974
|
+
if (pseudo.accountId !== void 0)
|
|
70975
|
+
return { kind: "literal", value: pseudo.accountId };
|
|
70976
|
+
break;
|
|
70977
|
+
case "AWS::Region":
|
|
70978
|
+
if (pseudo.region !== void 0)
|
|
70979
|
+
return { kind: "literal", value: pseudo.region };
|
|
70980
|
+
break;
|
|
70981
|
+
case "AWS::Partition":
|
|
70982
|
+
if (pseudo.partition !== void 0)
|
|
70983
|
+
return { kind: "literal", value: pseudo.partition };
|
|
70984
|
+
break;
|
|
70985
|
+
case "AWS::URLSuffix":
|
|
70986
|
+
if (pseudo.urlSuffix !== void 0)
|
|
70987
|
+
return { kind: "literal", value: pseudo.urlSuffix };
|
|
70988
|
+
break;
|
|
70989
|
+
default:
|
|
70990
|
+
return {
|
|
70991
|
+
kind: "unresolved",
|
|
70992
|
+
reason: `Ref '${name}': pseudo parameter not supported (supported: AWS::AccountId, AWS::Region, AWS::Partition, AWS::URLSuffix)`
|
|
70993
|
+
};
|
|
70994
|
+
}
|
|
70995
|
+
return { kind: "unresolved", reason: `Ref '${name}': pseudo parameter value not resolved` };
|
|
70996
|
+
}
|
|
70997
|
+
function resolveGetAtt(arg, context) {
|
|
70842
70998
|
let logicalId;
|
|
70843
70999
|
let attr;
|
|
70844
71000
|
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string") {
|
|
@@ -70846,7 +71002,7 @@ function resolveGetAtt(arg, resources) {
|
|
|
70846
71002
|
if (typeof arg[1] !== "string") {
|
|
70847
71003
|
return {
|
|
70848
71004
|
kind: "unresolved",
|
|
70849
|
-
reason: `Fn::GetAtt's second arg must be a string attribute name, got ${typeof arg[1]} (nested intrinsics in attribute names are not supported
|
|
71005
|
+
reason: `Fn::GetAtt's second arg must be a string attribute name, got ${typeof arg[1]} (nested intrinsics in attribute names are not supported)`
|
|
70850
71006
|
};
|
|
70851
71007
|
}
|
|
70852
71008
|
attr = arg[1];
|
|
@@ -70866,7 +71022,7 @@ function resolveGetAtt(arg, resources) {
|
|
|
70866
71022
|
reason: `Fn::GetAtt expects [LogicalId, Attribute] or 'LogicalId.Attribute', got ${Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg}`
|
|
70867
71023
|
};
|
|
70868
71024
|
}
|
|
70869
|
-
const resource = resources[logicalId];
|
|
71025
|
+
const resource = context.resources[logicalId];
|
|
70870
71026
|
if (!resource) {
|
|
70871
71027
|
return {
|
|
70872
71028
|
kind: "unresolved",
|
|
@@ -70885,7 +71041,7 @@ function resolveGetAtt(arg, resources) {
|
|
|
70885
71041
|
}
|
|
70886
71042
|
return { kind: "literal", value: JSON.stringify(cached) };
|
|
70887
71043
|
}
|
|
70888
|
-
function resolveSub(arg,
|
|
71044
|
+
function resolveSub(arg, context) {
|
|
70889
71045
|
let template;
|
|
70890
71046
|
let bindings = {};
|
|
70891
71047
|
if (typeof arg === "string") {
|
|
@@ -70910,7 +71066,18 @@ function resolveSub(arg, resources) {
|
|
|
70910
71066
|
if (resolutions.has(placeholder))
|
|
70911
71067
|
continue;
|
|
70912
71068
|
if (placeholder in bindings) {
|
|
70913
|
-
const sub = substituteAgainstState(bindings[placeholder],
|
|
71069
|
+
const sub = substituteAgainstState(bindings[placeholder], context);
|
|
71070
|
+
if (sub.kind !== "literal") {
|
|
71071
|
+
return {
|
|
71072
|
+
kind: "unresolved",
|
|
71073
|
+
reason: `Fn::Sub placeholder '\${${placeholder}}': ${sub.reason}`
|
|
71074
|
+
};
|
|
71075
|
+
}
|
|
71076
|
+
resolutions.set(placeholder, String(sub.value));
|
|
71077
|
+
continue;
|
|
71078
|
+
}
|
|
71079
|
+
if (placeholder.startsWith("AWS::")) {
|
|
71080
|
+
const sub = resolvePseudoParameter(placeholder, context.pseudoParameters);
|
|
70914
71081
|
if (sub.kind !== "literal") {
|
|
70915
71082
|
return {
|
|
70916
71083
|
kind: "unresolved",
|
|
@@ -70922,7 +71089,7 @@ function resolveSub(arg, resources) {
|
|
|
70922
71089
|
}
|
|
70923
71090
|
const dot = placeholder.indexOf(".");
|
|
70924
71091
|
if (dot === -1) {
|
|
70925
|
-
const sub = resolveRef(placeholder,
|
|
71092
|
+
const sub = resolveRef(placeholder, context);
|
|
70926
71093
|
if (sub.kind !== "literal") {
|
|
70927
71094
|
return {
|
|
70928
71095
|
kind: "unresolved",
|
|
@@ -70931,7 +71098,7 @@ function resolveSub(arg, resources) {
|
|
|
70931
71098
|
}
|
|
70932
71099
|
resolutions.set(placeholder, String(sub.value));
|
|
70933
71100
|
} else {
|
|
70934
|
-
const sub = resolveGetAtt(placeholder,
|
|
71101
|
+
const sub = resolveGetAtt(placeholder, context);
|
|
70935
71102
|
if (sub.kind !== "literal") {
|
|
70936
71103
|
return {
|
|
70937
71104
|
kind: "unresolved",
|
|
@@ -70946,17 +71113,45 @@ function resolveSub(arg, resources) {
|
|
|
70946
71113
|
});
|
|
70947
71114
|
return { kind: "literal", value: out };
|
|
70948
71115
|
}
|
|
70949
|
-
function
|
|
71116
|
+
function resolveJoin(arg, context) {
|
|
71117
|
+
if (!Array.isArray(arg) || arg.length !== 2 || !Array.isArray(arg[1])) {
|
|
71118
|
+
return {
|
|
71119
|
+
kind: "unresolved",
|
|
71120
|
+
reason: `Fn::Join expects [delimiter, [elements]], got ${Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg}`
|
|
71121
|
+
};
|
|
71122
|
+
}
|
|
71123
|
+
const [delimiterRaw, elements] = arg;
|
|
71124
|
+
if (typeof delimiterRaw !== "string") {
|
|
71125
|
+
return {
|
|
71126
|
+
kind: "unresolved",
|
|
71127
|
+
reason: `Fn::Join delimiter must be a string, got ${typeof delimiterRaw}`
|
|
71128
|
+
};
|
|
71129
|
+
}
|
|
71130
|
+
const parts = [];
|
|
71131
|
+
for (let i = 0; i < elements.length; i += 1) {
|
|
71132
|
+
const sub = substituteAgainstState(elements[i], context);
|
|
71133
|
+
if (sub.kind !== "literal") {
|
|
71134
|
+
return {
|
|
71135
|
+
kind: "unresolved",
|
|
71136
|
+
reason: `Fn::Join element [${i}]: ${sub.reason}`
|
|
71137
|
+
};
|
|
71138
|
+
}
|
|
71139
|
+
parts.push(String(sub.value));
|
|
71140
|
+
}
|
|
71141
|
+
return { kind: "literal", value: parts.join(delimiterRaw) };
|
|
71142
|
+
}
|
|
71143
|
+
function substituteEnvVarsFromState(templateEnv, contextOrResources) {
|
|
70950
71144
|
const env = {};
|
|
70951
71145
|
const audit = { resolvedKeys: [], unresolved: [] };
|
|
70952
71146
|
if (!templateEnv)
|
|
70953
71147
|
return { env, audit };
|
|
71148
|
+
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
70954
71149
|
for (const [key, value] of Object.entries(templateEnv)) {
|
|
70955
71150
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
70956
71151
|
env[key] = value;
|
|
70957
71152
|
continue;
|
|
70958
71153
|
}
|
|
70959
|
-
const result = substituteAgainstState(value,
|
|
71154
|
+
const result = substituteAgainstState(value, context);
|
|
70960
71155
|
if (result.kind === "literal") {
|
|
70961
71156
|
env[key] = result.value;
|
|
70962
71157
|
audit.resolvedKeys.push(key);
|
|
@@ -77063,6 +77258,7 @@ function detectEcsImageResolutionNeeds(stack) {
|
|
|
77063
77258
|
const resources = stack.template.Resources ?? {};
|
|
77064
77259
|
let needsPseudoParameters = false;
|
|
77065
77260
|
let needsStateResources = false;
|
|
77261
|
+
let needsEnvOrSecretSubstitution = false;
|
|
77066
77262
|
for (const res of Object.values(resources)) {
|
|
77067
77263
|
if (res.Type !== "AWS::ECS::TaskDefinition")
|
|
77068
77264
|
continue;
|
|
@@ -77071,19 +77267,42 @@ function detectEcsImageResolutionNeeds(stack) {
|
|
|
77071
77267
|
for (const c of containers) {
|
|
77072
77268
|
if (!c || typeof c !== "object")
|
|
77073
77269
|
continue;
|
|
77074
|
-
const
|
|
77270
|
+
const co = c;
|
|
77271
|
+
const image = co["Image"];
|
|
77075
77272
|
const need = inspectImageForSubstitutions(image, resources);
|
|
77076
77273
|
if (need.pseudo)
|
|
77077
77274
|
needsPseudoParameters = true;
|
|
77078
77275
|
if (need.state)
|
|
77079
77276
|
needsStateResources = true;
|
|
77080
|
-
if (
|
|
77081
|
-
|
|
77277
|
+
if (containerHasIntrinsicEnvOrSecret(co))
|
|
77278
|
+
needsEnvOrSecretSubstitution = true;
|
|
77279
|
+
}
|
|
77280
|
+
}
|
|
77281
|
+
return { needsPseudoParameters, needsStateResources, needsEnvOrSecretSubstitution };
|
|
77282
|
+
}
|
|
77283
|
+
function containerHasIntrinsicEnvOrSecret(c) {
|
|
77284
|
+
const env = c["Environment"];
|
|
77285
|
+
if (Array.isArray(env)) {
|
|
77286
|
+
for (const entry of env) {
|
|
77287
|
+
if (!entry || typeof entry !== "object")
|
|
77288
|
+
continue;
|
|
77289
|
+
const v = entry["Value"];
|
|
77290
|
+
if (v !== void 0 && typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
|
|
77291
|
+
return true;
|
|
77292
|
+
}
|
|
77293
|
+
}
|
|
77294
|
+
}
|
|
77295
|
+
const secrets = c["Secrets"];
|
|
77296
|
+
if (Array.isArray(secrets)) {
|
|
77297
|
+
for (const entry of secrets) {
|
|
77298
|
+
if (!entry || typeof entry !== "object")
|
|
77299
|
+
continue;
|
|
77300
|
+
const v = entry["ValueFrom"];
|
|
77301
|
+
if (v !== void 0 && typeof v !== "string")
|
|
77302
|
+
return true;
|
|
77082
77303
|
}
|
|
77083
|
-
if (needsPseudoParameters && needsStateResources)
|
|
77084
|
-
break;
|
|
77085
77304
|
}
|
|
77086
|
-
return
|
|
77305
|
+
return false;
|
|
77087
77306
|
}
|
|
77088
77307
|
function inspectImageForSubstitutions(image, resources) {
|
|
77089
77308
|
if (!image || typeof image !== "object")
|
|
@@ -77284,6 +77503,11 @@ function extractTaskDefinitionProperties(stack, logicalId, resource, context) {
|
|
|
77284
77503
|
const containers = rawContainers.map(
|
|
77285
77504
|
(c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack, context)
|
|
77286
77505
|
);
|
|
77506
|
+
for (const ctr of containers) {
|
|
77507
|
+
for (const w of ctr.warnings) {
|
|
77508
|
+
warnings.push(`Container '${ctr.name}': ${w}`);
|
|
77509
|
+
}
|
|
77510
|
+
}
|
|
77287
77511
|
const rawVolumes = props["Volumes"];
|
|
77288
77512
|
const volumes = Array.isArray(rawVolumes) ? rawVolumes.map((v, idx) => parseVolume(v, idx, logicalId)) : [];
|
|
77289
77513
|
const containerNames = new Set(containers.map((c) => c.name));
|
|
@@ -77345,7 +77569,9 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77345
77569
|
const command = pickStringArray2(c["Command"]);
|
|
77346
77570
|
const entryPoint = pickStringArray2(c["EntryPoint"]);
|
|
77347
77571
|
const workingDirectory = pickString(c["WorkingDirectory"]);
|
|
77572
|
+
const subContext = buildSubstitutionContextFromImageContext(context);
|
|
77348
77573
|
const environment = {};
|
|
77574
|
+
const droppedEnvKeys = [];
|
|
77349
77575
|
if (Array.isArray(c["Environment"])) {
|
|
77350
77576
|
for (const entry of c["Environment"]) {
|
|
77351
77577
|
if (!entry || typeof entry !== "object")
|
|
@@ -77357,19 +77583,54 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77357
77583
|
continue;
|
|
77358
77584
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
77359
77585
|
environment[key] = String(value);
|
|
77586
|
+
continue;
|
|
77587
|
+
}
|
|
77588
|
+
if (subContext) {
|
|
77589
|
+
const sub = substituteAgainstState(value, subContext);
|
|
77590
|
+
if (sub.kind === "literal") {
|
|
77591
|
+
environment[key] = String(sub.value);
|
|
77592
|
+
continue;
|
|
77593
|
+
}
|
|
77594
|
+
droppedEnvKeys.push({ key, reason: sub.reason });
|
|
77595
|
+
} else {
|
|
77596
|
+
droppedEnvKeys.push({
|
|
77597
|
+
key,
|
|
77598
|
+
reason: "intrinsic-valued; pass --from-state to substitute against deployed state"
|
|
77599
|
+
});
|
|
77360
77600
|
}
|
|
77361
77601
|
}
|
|
77362
77602
|
}
|
|
77363
77603
|
const secrets = [];
|
|
77604
|
+
const droppedSecretKeys = [];
|
|
77364
77605
|
if (Array.isArray(c["Secrets"])) {
|
|
77365
77606
|
for (const entry of c["Secrets"]) {
|
|
77366
77607
|
if (!entry || typeof entry !== "object")
|
|
77367
77608
|
continue;
|
|
77368
77609
|
const e = entry;
|
|
77369
77610
|
const sName = pickString(e["Name"]);
|
|
77370
|
-
const
|
|
77371
|
-
if (sName
|
|
77372
|
-
|
|
77611
|
+
const valueFromRaw = e["ValueFrom"];
|
|
77612
|
+
if (!sName)
|
|
77613
|
+
continue;
|
|
77614
|
+
if (typeof valueFromRaw === "string" && valueFromRaw.length > 0) {
|
|
77615
|
+
secrets.push({ name: sName, valueFrom: valueFromRaw });
|
|
77616
|
+
continue;
|
|
77617
|
+
}
|
|
77618
|
+
if (subContext) {
|
|
77619
|
+
const sub = substituteAgainstState(valueFromRaw, subContext);
|
|
77620
|
+
if (sub.kind === "literal" && typeof sub.value === "string" && sub.value.length > 0) {
|
|
77621
|
+
secrets.push({ name: sName, valueFrom: sub.value });
|
|
77622
|
+
continue;
|
|
77623
|
+
}
|
|
77624
|
+
droppedSecretKeys.push({
|
|
77625
|
+
key: sName,
|
|
77626
|
+
reason: sub.kind === "literal" ? "resolved to non-string / empty value" : sub.reason
|
|
77627
|
+
});
|
|
77628
|
+
} else {
|
|
77629
|
+
droppedSecretKeys.push({
|
|
77630
|
+
key: sName,
|
|
77631
|
+
reason: "intrinsic-valued ValueFrom; pass --from-state to resolve the deployed ARN"
|
|
77632
|
+
});
|
|
77633
|
+
}
|
|
77373
77634
|
}
|
|
77374
77635
|
}
|
|
77375
77636
|
const portMappings = [];
|
|
@@ -77459,6 +77720,13 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77459
77720
|
ulimits.push({ name: uName, softLimit: soft, hardLimit: hard });
|
|
77460
77721
|
}
|
|
77461
77722
|
}
|
|
77723
|
+
const warnings = [];
|
|
77724
|
+
for (const d of droppedEnvKeys) {
|
|
77725
|
+
warnings.push(`Environment '${d.key}' dropped: ${d.reason}`);
|
|
77726
|
+
}
|
|
77727
|
+
for (const d of droppedSecretKeys) {
|
|
77728
|
+
warnings.push(`Secret '${d.key}' dropped: ${d.reason}`);
|
|
77729
|
+
}
|
|
77462
77730
|
const out = {
|
|
77463
77731
|
name,
|
|
77464
77732
|
image,
|
|
@@ -77469,7 +77737,8 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77469
77737
|
dependsOn,
|
|
77470
77738
|
links,
|
|
77471
77739
|
essential,
|
|
77472
|
-
ulimits
|
|
77740
|
+
ulimits,
|
|
77741
|
+
warnings
|
|
77473
77742
|
};
|
|
77474
77743
|
if (command !== void 0)
|
|
77475
77744
|
out.command = command;
|
|
@@ -77487,6 +77756,15 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77487
77756
|
out.readonlyRootFilesystem = readonlyRootFilesystem;
|
|
77488
77757
|
return out;
|
|
77489
77758
|
}
|
|
77759
|
+
function buildSubstitutionContextFromImageContext(context) {
|
|
77760
|
+
if (!context?.stateResources)
|
|
77761
|
+
return void 0;
|
|
77762
|
+
const subContext = { resources: context.stateResources };
|
|
77763
|
+
if (context.pseudoParameters) {
|
|
77764
|
+
subContext.pseudoParameters = { ...context.pseudoParameters };
|
|
77765
|
+
}
|
|
77766
|
+
return subContext;
|
|
77767
|
+
}
|
|
77490
77768
|
function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack, context) {
|
|
77491
77769
|
const getAttImage = tryResolveImageGetAtt(raw, resources, context);
|
|
77492
77770
|
if (getAttImage) {
|
|
@@ -78681,14 +78959,16 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78681
78959
|
if (!candidate)
|
|
78682
78960
|
return void 0;
|
|
78683
78961
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
78684
|
-
if (!needs.needsPseudoParameters && !needs.needsStateResources)
|
|
78962
|
+
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) {
|
|
78685
78963
|
return void 0;
|
|
78964
|
+
}
|
|
78686
78965
|
const ctx = {};
|
|
78687
|
-
|
|
78966
|
+
const wantsPseudoForEnvOrSecret = options.fromState && needs.needsEnvOrSecretSubstitution;
|
|
78967
|
+
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
78688
78968
|
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
78689
78969
|
if (!region) {
|
|
78690
78970
|
logger.warn(
|
|
78691
|
-
"
|
|
78971
|
+
"Resolver references ${AWS::Region} but cdkd could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack."
|
|
78692
78972
|
);
|
|
78693
78973
|
}
|
|
78694
78974
|
let accountId;
|
|
@@ -78696,7 +78976,7 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78696
78976
|
accountId = await resolveCallerAccountId(region);
|
|
78697
78977
|
} catch (err) {
|
|
78698
78978
|
logger.warn(
|
|
78699
|
-
`
|
|
78979
|
+
`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`
|
|
78700
78980
|
);
|
|
78701
78981
|
}
|
|
78702
78982
|
const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
|
|
@@ -78709,7 +78989,8 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78709
78989
|
}
|
|
78710
78990
|
};
|
|
78711
78991
|
}
|
|
78712
|
-
|
|
78992
|
+
const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
|
|
78993
|
+
if (options.fromState && wantsState) {
|
|
78713
78994
|
const loaded = await loadStateForStack(candidate.stackName, candidate.region, {
|
|
78714
78995
|
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
78715
78996
|
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
@@ -78724,6 +79005,10 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78724
79005
|
logger.warn(
|
|
78725
79006
|
"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."
|
|
78726
79007
|
);
|
|
79008
|
+
} else if (!options.fromState && needs.needsEnvOrSecretSubstitution) {
|
|
79009
|
+
logger.warn(
|
|
79010
|
+
"Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass --from-state to substitute them against the deployed cdkd state. Without --from-state these entries are dropped (per-key warnings will follow)."
|
|
79011
|
+
);
|
|
78727
79012
|
}
|
|
78728
79013
|
return ctx;
|
|
78729
79014
|
}
|
|
@@ -80308,7 +80593,7 @@ function reorderArgs(argv) {
|
|
|
80308
80593
|
}
|
|
80309
80594
|
async function main() {
|
|
80310
80595
|
const program = new Command18();
|
|
80311
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.91.
|
|
80596
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.91.3");
|
|
80312
80597
|
program.addCommand(createBootstrapCommand());
|
|
80313
80598
|
program.addCommand(createSynthCommand());
|
|
80314
80599
|
program.addCommand(createListCommand());
|