@go-to-k/cdkd 0.121.0 → 0.123.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/README.md +7 -3
- package/dist/cli.js +505 -67
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-B2RZT3ai.js → deploy-engine-DWpeb9wT.js} +339 -20
- package/dist/deploy-engine-DWpeb9wT.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-B2RZT3ai.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-CuHRHcyW.js";
|
|
3
|
-
import {
|
|
3
|
+
import { A as AssetPublisher, B as getLegacyStateBucketName, C as applyRoleArnIfSet, Ct as generateResourceNameWithFallback, D as LockManager, E as TemplateParser, F as getDockerCmd, G as resolveStateBucketWithDefaultAndSource, H as resolveCaptureObservedState, I as runDockerForeground, K as warnDeprecatedNoPrefixCliFlag, L as runDockerStreaming, M as WorkGraph, N as buildDockerImage, O as S3StateBackend, P as formatDockerLoginError, R as Synthesizer, S as IntrinsicFunctionResolver, St as generateResourceName, T as DagBuilder, Tt as withStackName, U as resolveSkipPrefix, V as resolveApp, W as resolveStateBucketWithDefault, Y as resolveBucketRegion, Z as CdkdError, _ as normalizeAwsTagsToCfn, a as withRetry, at as ResourceUpdateNotSupportedError, b as CloudControlProvider, bt as PATTERN_B_NAME_PROPERTIES, c as cyan, ct as StackTerminationProtectionError, d as red, et as LocalInvokeBuildError, f as yellow, g as matchesCdkPath, gt as getLogger, h as CDK_PATH_TAG, i as withResourceDeadline, it as ResourceTimeoutError, j as stringifyValue, k as shouldRetainResource, l as gray, m as collectInlinePolicyNamesManagedBySiblings, mt as withErrorHandling, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as PartialFailureError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as RouteDiscoveryError, p as IAMRoleProvider, pt as normalizeAwsError, q as AssemblyReader, r as DeployEngine, rt as ProvisioningError, s as bold, st as StackHasActiveImportsError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as green, v as resolveExplicitPhysicalId, vt as runStackBuffered, w as DiffCalculator, wt as withSkipPrefix, x as assertRegionMatch, xt as PATTERN_B_RESOURCE_TYPES, y as ProviderRegistry, yt as getLiveRenderer, z as getDefaultStateBucketName } from "./deploy-engine-DWpeb9wT.js";
|
|
4
4
|
import { createHash, createHmac, createPublicKey, createVerify, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
5
5
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListBucketsCommand, ListDirectoryBucketsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
6
6
|
import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachUserPolicyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachUserPolicyCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListInstanceProfilesCommand, ListUserPoliciesCommand, ListUserTagsCommand, ListUsersCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagUserCommand, UntagUserCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
|
|
@@ -437,63 +437,6 @@ function effectiveAssumeRoleArn(logicalId, opt) {
|
|
|
437
437
|
*/
|
|
438
438
|
const destroyOptions = [new Option("-f, --force", "Do not ask for confirmation before destroying the stacks").default(false), new Option("--remove-protection", "Bypass deletion protection on protected resources by flipping the per-resource protection flag off in-place before delete. Covers stack-level terminationProtection (CDK property) and resource-level protection on AWS::Logs::LogGroup, AWS::RDS::DBInstance, AWS::RDS::DBCluster, AWS::DocDB::DBCluster, AWS::Neptune::DBCluster, AWS::Neptune::DBInstance, AWS::DynamoDB::Table, AWS::EC2::Instance, AWS::Cognito::UserPool, AWS::AutoScaling::AutoScalingGroup, and AWS::ElasticLoadBalancingV2::LoadBalancer.").default(false)];
|
|
439
439
|
|
|
440
|
-
//#endregion
|
|
441
|
-
//#region src/utils/role-arn.ts
|
|
442
|
-
/**
|
|
443
|
-
* Resolve the role-arn argument (CLI flag or `CDKD_ROLE_ARN` env var) and,
|
|
444
|
-
* when set, assume the role and write the resulting temporary credentials
|
|
445
|
-
* into `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`
|
|
446
|
-
* for the rest of the process.
|
|
447
|
-
*
|
|
448
|
-
* **Why env vars, not threaded credentials.** cdkd constructs ~13
|
|
449
|
-
* independent `AwsClients` instances across deploy / destroy / state /
|
|
450
|
-
* import / etc. paths (each with its own region, sometimes — e.g. the
|
|
451
|
-
* state-bucket client lives in a different region from the provisioning
|
|
452
|
-
* clients). Threading a `credentials` object through every site is high
|
|
453
|
-
* churn for an opt-in flag. AWS SDK v3 reads the standard `AWS_*` env
|
|
454
|
-
* vars at the top of its default credentials chain, so writing into them
|
|
455
|
-
* once at the command's entry makes every later `new XxxClient()` pick
|
|
456
|
-
* up the assumed-role credentials automatically without touching the
|
|
457
|
-
* client construction sites.
|
|
458
|
-
*
|
|
459
|
-
* **Why cdkd needs admin-equivalent on the assumed role.** Unlike `cdk
|
|
460
|
-
* deploy`, cdkd does NOT route through CloudFormation. There is no
|
|
461
|
-
* cfn-exec-role to delegate to. Every IAM / EC2 / Lambda / etc. API
|
|
462
|
-
* call is issued from the cdkd process directly. The role you pass to
|
|
463
|
-
* `--role-arn` (or set in `CDKD_ROLE_ARN`) MUST therefore have
|
|
464
|
-
* admin-equivalent permissions on the resources being deployed; CDK
|
|
465
|
-
* CLI's `cdk-hnb659fds-deploy-role-*` is NOT sufficient — that role
|
|
466
|
-
* only carries CFn + asset-publish permissions.
|
|
467
|
-
*
|
|
468
|
-
* Default session duration is 1 hour. For longer-running deploys, the
|
|
469
|
-
* caller should re-issue the cdkd command (the in-flight credentials
|
|
470
|
-
* stay valid until expiry, but a re-run is the simplest recovery for
|
|
471
|
-
* the rare case where a deploy outlives them).
|
|
472
|
-
*/
|
|
473
|
-
async function applyRoleArnIfSet(opts) {
|
|
474
|
-
const roleArn = opts.roleArn || process.env["CDKD_ROLE_ARN"];
|
|
475
|
-
if (!roleArn) return;
|
|
476
|
-
const logger = getLogger().child("role-arn");
|
|
477
|
-
logger.debug(`Assuming role ${roleArn}...`);
|
|
478
|
-
const sts = new STSClient({ ...opts.region && { region: opts.region } });
|
|
479
|
-
try {
|
|
480
|
-
const response = await sts.send(new AssumeRoleCommand({
|
|
481
|
-
RoleArn: roleArn,
|
|
482
|
-
RoleSessionName: `cdkd-${Date.now()}`,
|
|
483
|
-
DurationSeconds: 3600
|
|
484
|
-
}));
|
|
485
|
-
if (!response.Credentials) throw new Error(`AssumeRole returned no credentials for role ${roleArn}`);
|
|
486
|
-
const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = response.Credentials;
|
|
487
|
-
if (!AccessKeyId || !SecretAccessKey || !SessionToken) throw new Error(`AssumeRole response missing credentials fields for role ${roleArn}`);
|
|
488
|
-
process.env["AWS_ACCESS_KEY_ID"] = AccessKeyId;
|
|
489
|
-
process.env["AWS_SECRET_ACCESS_KEY"] = SecretAccessKey;
|
|
490
|
-
process.env["AWS_SESSION_TOKEN"] = SessionToken;
|
|
491
|
-
logger.info(`Assumed role ${roleArn} (session expires ${Expiration?.toISOString() ?? "unknown"})`);
|
|
492
|
-
} finally {
|
|
493
|
-
sts.destroy();
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
440
|
//#endregion
|
|
498
441
|
//#region src/cli/commands/bootstrap.ts
|
|
499
442
|
/**
|
|
@@ -35768,6 +35711,119 @@ async function loadStateForStack(stackName, synthRegion, opts) {
|
|
|
35768
35711
|
resetAwsClients();
|
|
35769
35712
|
}
|
|
35770
35713
|
}
|
|
35714
|
+
/**
|
|
35715
|
+
* Build a {@link CrossStackResolver} that walks cdkd's S3 state to look
|
|
35716
|
+
* up `Fn::ImportValue` / `Fn::GetStackOutput` references the same way
|
|
35717
|
+
* `cdkd deploy`'s `IntrinsicFunctionResolver` does. Returns `undefined`
|
|
35718
|
+
* when the state bucket cannot be resolved (warn + fall back; matches
|
|
35719
|
+
* `loadStateForStack`'s policy).
|
|
35720
|
+
*
|
|
35721
|
+
* The returned `dispose` closes the AWS clients owned by the resolver
|
|
35722
|
+
* when the caller is done — callers MUST call it (typically in a
|
|
35723
|
+
* `try / finally`) so the per-request S3 client isn't leaked across the
|
|
35724
|
+
* CLI's lifetime.
|
|
35725
|
+
*
|
|
35726
|
+
* Why a separate AwsClients instance from `loadStateForStack`: the
|
|
35727
|
+
* existing helper destroys its clients in a `finally` immediately after
|
|
35728
|
+
* loading the consumer stack's state. The cross-stack resolver lives
|
|
35729
|
+
* longer — every env-var that references a cross-stack output triggers a
|
|
35730
|
+
* new state read. Owning a fresh `AwsClients` here gives the resolver
|
|
35731
|
+
* an independent lifetime managed by the caller.
|
|
35732
|
+
*
|
|
35733
|
+
* Same-account / same-region only in v1 (the resolver's `producerRegion`
|
|
35734
|
+
* arg is honored, but only for state lookups within the same cdkd state
|
|
35735
|
+
* bucket). Cross-region `Fn::ImportValue` is tracked under #451;
|
|
35736
|
+
* cross-account `Fn::GetStackOutput.RoleArn` is tracked under #449.
|
|
35737
|
+
*/
|
|
35738
|
+
async function buildCrossStackResolver(consumerRegion, opts) {
|
|
35739
|
+
const logger = getLogger();
|
|
35740
|
+
const prefix = opts.logPrefix ?? "--from-state";
|
|
35741
|
+
let stateBucket;
|
|
35742
|
+
try {
|
|
35743
|
+
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, consumerRegion);
|
|
35744
|
+
} catch (err) {
|
|
35745
|
+
logger.warn(`${prefix}: cross-stack resolver could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Fn::ImportValue / Fn::GetStackOutput env entries will warn-and-drop.`);
|
|
35746
|
+
return;
|
|
35747
|
+
}
|
|
35748
|
+
const awsClients = new AwsClients({
|
|
35749
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
35750
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
35751
|
+
});
|
|
35752
|
+
const stateConfig = {
|
|
35753
|
+
bucket: stateBucket,
|
|
35754
|
+
prefix: opts.statePrefix
|
|
35755
|
+
};
|
|
35756
|
+
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
35757
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
35758
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
35759
|
+
});
|
|
35760
|
+
try {
|
|
35761
|
+
await stateBackend.verifyBucketExists();
|
|
35762
|
+
} catch (err) {
|
|
35763
|
+
awsClients.destroy();
|
|
35764
|
+
logger.warn(`${prefix}: cross-stack resolver could not access state bucket '${stateBucket}': ${err instanceof Error ? err.message : String(err)}. Fn::ImportValue / Fn::GetStackOutput env entries will warn-and-drop.`);
|
|
35765
|
+
return;
|
|
35766
|
+
}
|
|
35767
|
+
const exportIndex = new ExportIndexStore(awsClients.s3, stateBucket, opts.statePrefix, consumerRegion, stateBackend);
|
|
35768
|
+
return {
|
|
35769
|
+
resolver: {
|
|
35770
|
+
async resolveImport(exportName) {
|
|
35771
|
+
try {
|
|
35772
|
+
const entry = await exportIndex.lookup(exportName);
|
|
35773
|
+
if (entry) {
|
|
35774
|
+
const value = entry.value;
|
|
35775
|
+
if (typeof value === "string") return value;
|
|
35776
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
35777
|
+
return JSON.stringify(value);
|
|
35778
|
+
}
|
|
35779
|
+
} catch (err) {
|
|
35780
|
+
logger.debug(`${prefix}: exports index lookup failed for '${exportName}': ${err instanceof Error ? err.message : String(err)}; falling back to per-stack state scan`);
|
|
35781
|
+
}
|
|
35782
|
+
let refs;
|
|
35783
|
+
try {
|
|
35784
|
+
refs = await stateBackend.listStacks();
|
|
35785
|
+
} catch (err) {
|
|
35786
|
+
logger.debug(`${prefix}: failed to list stacks during Fn::ImportValue fallback for '${exportName}': ${err instanceof Error ? err.message : String(err)}`);
|
|
35787
|
+
return;
|
|
35788
|
+
}
|
|
35789
|
+
for (const ref of refs) {
|
|
35790
|
+
const region = ref.region ?? consumerRegion;
|
|
35791
|
+
if (region !== consumerRegion) continue;
|
|
35792
|
+
try {
|
|
35793
|
+
const got = await stateBackend.getState(ref.stackName, region);
|
|
35794
|
+
if (!got || !got.state.outputs) continue;
|
|
35795
|
+
if (exportName in got.state.outputs) {
|
|
35796
|
+
const value = got.state.outputs[exportName];
|
|
35797
|
+
if (typeof value === "string") return value;
|
|
35798
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
35799
|
+
return JSON.stringify(value);
|
|
35800
|
+
}
|
|
35801
|
+
} catch (err) {
|
|
35802
|
+
logger.debug(`${prefix}: state read failed for ${ref.stackName} (${region}) during Fn::ImportValue fallback: ${err instanceof Error ? err.message : String(err)}`);
|
|
35803
|
+
continue;
|
|
35804
|
+
}
|
|
35805
|
+
}
|
|
35806
|
+
},
|
|
35807
|
+
async resolveGetStackOutput(producerStack, producerRegion, outputName) {
|
|
35808
|
+
try {
|
|
35809
|
+
const got = await stateBackend.getState(producerStack, producerRegion);
|
|
35810
|
+
if (!got || !got.state.outputs) return void 0;
|
|
35811
|
+
if (!(outputName in got.state.outputs)) return void 0;
|
|
35812
|
+
const value = got.state.outputs[outputName];
|
|
35813
|
+
if (typeof value === "string") return value;
|
|
35814
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
35815
|
+
return JSON.stringify(value);
|
|
35816
|
+
} catch (err) {
|
|
35817
|
+
logger.debug(`${prefix}: state read failed for Fn::GetStackOutput '${producerStack}.${outputName}' (${producerRegion}): ${err instanceof Error ? err.message : String(err)}`);
|
|
35818
|
+
return;
|
|
35819
|
+
}
|
|
35820
|
+
}
|
|
35821
|
+
},
|
|
35822
|
+
dispose: () => {
|
|
35823
|
+
awsClients.destroy();
|
|
35824
|
+
}
|
|
35825
|
+
};
|
|
35826
|
+
}
|
|
35771
35827
|
|
|
35772
35828
|
//#endregion
|
|
35773
35829
|
//#region src/local/intrinsic-image.ts
|
|
@@ -36733,6 +36789,159 @@ function resolveJoin(arg, context) {
|
|
|
36733
36789
|
};
|
|
36734
36790
|
}
|
|
36735
36791
|
/**
|
|
36792
|
+
* Async sibling of {@link substituteAgainstState}. Same semantics for every
|
|
36793
|
+
* intrinsic the sync path supports; additionally consults the
|
|
36794
|
+
* `crossStackResolver` (when supplied on the context) for `Fn::ImportValue`
|
|
36795
|
+
* and `Fn::GetStackOutput`.
|
|
36796
|
+
*
|
|
36797
|
+
* Callers that don't need cross-stack support should keep using the sync
|
|
36798
|
+
* helper. Code paths that wire `--from-state` env / secret substitution
|
|
36799
|
+
* (e.g. `cdkd local invoke --from-state`, `cdkd local run-task --from-state`)
|
|
36800
|
+
* route through this async version so a single env-var referencing a
|
|
36801
|
+
* cross-stack output is no longer warn-and-dropped.
|
|
36802
|
+
*/
|
|
36803
|
+
async function substituteAgainstStateAsync(value, contextOrResources) {
|
|
36804
|
+
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
36805
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return {
|
|
36806
|
+
kind: "literal",
|
|
36807
|
+
value
|
|
36808
|
+
};
|
|
36809
|
+
if (value === null || typeof value !== "object") return {
|
|
36810
|
+
kind: "unresolved",
|
|
36811
|
+
reason: `unsupported value type: ${value === null ? "null" : typeof value}`
|
|
36812
|
+
};
|
|
36813
|
+
const obj = value;
|
|
36814
|
+
const keys = Object.keys(obj);
|
|
36815
|
+
if (keys.length !== 1) return {
|
|
36816
|
+
kind: "unresolved",
|
|
36817
|
+
reason: `expected an intrinsic with one key, got ${keys.length} keys`
|
|
36818
|
+
};
|
|
36819
|
+
const intrinsic = keys[0];
|
|
36820
|
+
const arg = obj[intrinsic];
|
|
36821
|
+
if (intrinsic === "Ref" || intrinsic === "Fn::GetAtt" || intrinsic === "Fn::Sub" || intrinsic === "Fn::Join") return substituteAgainstState(value, context);
|
|
36822
|
+
if (intrinsic === "Fn::ImportValue") return resolveImportValueAsync(arg, context);
|
|
36823
|
+
if (intrinsic === "Fn::GetStackOutput") return resolveGetStackOutputAsync(arg, context);
|
|
36824
|
+
return {
|
|
36825
|
+
kind: "unresolved",
|
|
36826
|
+
reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join, Fn::ImportValue, Fn::GetStackOutput)`
|
|
36827
|
+
};
|
|
36828
|
+
}
|
|
36829
|
+
/**
|
|
36830
|
+
* `Fn::ImportValue: <exportName>` — the argument may itself be an
|
|
36831
|
+
* intrinsic that resolves to a string (e.g.
|
|
36832
|
+
* `{Fn::ImportValue: {Fn::Sub: 'MyStack-${AWS::Region}-Bucket'}}` — the
|
|
36833
|
+
* inner `Fn::Sub` is resolved against `pseudoParameters` first, then the
|
|
36834
|
+
* resulting string is looked up via the cross-stack resolver).
|
|
36835
|
+
*/
|
|
36836
|
+
async function resolveImportValueAsync(arg, context) {
|
|
36837
|
+
const inner = substituteAgainstState(arg, context);
|
|
36838
|
+
if (inner.kind !== "literal") return {
|
|
36839
|
+
kind: "unresolved",
|
|
36840
|
+
reason: `Fn::ImportValue argument: ${inner.reason}`
|
|
36841
|
+
};
|
|
36842
|
+
if (typeof inner.value !== "string" || inner.value.length === 0) return {
|
|
36843
|
+
kind: "unresolved",
|
|
36844
|
+
reason: `Fn::ImportValue argument must resolve to a non-empty string, got ${typeof inner.value}`
|
|
36845
|
+
};
|
|
36846
|
+
const exportName = inner.value;
|
|
36847
|
+
if (!context.crossStackResolver) return {
|
|
36848
|
+
kind: "unresolved",
|
|
36849
|
+
reason: `Fn::ImportValue '${exportName}': no cross-stack resolver supplied (pass --from-state and ensure the producer stack was deployed via cdkd deploy)`
|
|
36850
|
+
};
|
|
36851
|
+
let resolved;
|
|
36852
|
+
try {
|
|
36853
|
+
resolved = await context.crossStackResolver.resolveImport(exportName);
|
|
36854
|
+
} catch (err) {
|
|
36855
|
+
return {
|
|
36856
|
+
kind: "unresolved",
|
|
36857
|
+
reason: `Fn::ImportValue '${exportName}': lookup failed: ${err instanceof Error ? err.message : String(err)}`
|
|
36858
|
+
};
|
|
36859
|
+
}
|
|
36860
|
+
if (resolved === void 0) return {
|
|
36861
|
+
kind: "unresolved",
|
|
36862
|
+
reason: `Fn::ImportValue '${exportName}': export not found in any cdkd-managed stack in this region`
|
|
36863
|
+
};
|
|
36864
|
+
return {
|
|
36865
|
+
kind: "literal",
|
|
36866
|
+
value: resolved
|
|
36867
|
+
};
|
|
36868
|
+
}
|
|
36869
|
+
/**
|
|
36870
|
+
* `Fn::GetStackOutput: { StackName, OutputName, Region? }`. Same shape as
|
|
36871
|
+
* the deploy-engine resolver. `RoleArn` (cross-account) is intentionally
|
|
36872
|
+
* NOT supported in this path — the user-visible error message points at
|
|
36873
|
+
* the followup issue tracking it.
|
|
36874
|
+
*/
|
|
36875
|
+
async function resolveGetStackOutputAsync(arg, context) {
|
|
36876
|
+
if (!arg || typeof arg !== "object" || Array.isArray(arg)) return {
|
|
36877
|
+
kind: "unresolved",
|
|
36878
|
+
reason: `Fn::GetStackOutput argument must be an object with StackName / OutputName / Region, got ${arg === null ? "null" : Array.isArray(arg) ? "array" : typeof arg}`
|
|
36879
|
+
};
|
|
36880
|
+
const args = arg;
|
|
36881
|
+
const stackNameSub = substituteAgainstState(args["StackName"], context);
|
|
36882
|
+
if (stackNameSub.kind !== "literal") return {
|
|
36883
|
+
kind: "unresolved",
|
|
36884
|
+
reason: `Fn::GetStackOutput.StackName: ${stackNameSub.reason}`
|
|
36885
|
+
};
|
|
36886
|
+
if (typeof stackNameSub.value !== "string" || stackNameSub.value.length === 0) return {
|
|
36887
|
+
kind: "unresolved",
|
|
36888
|
+
reason: `Fn::GetStackOutput.StackName must resolve to a non-empty string, got ${typeof stackNameSub.value}`
|
|
36889
|
+
};
|
|
36890
|
+
const stackName = stackNameSub.value;
|
|
36891
|
+
const outputNameSub = substituteAgainstState(args["OutputName"], context);
|
|
36892
|
+
if (outputNameSub.kind !== "literal") return {
|
|
36893
|
+
kind: "unresolved",
|
|
36894
|
+
reason: `Fn::GetStackOutput.OutputName: ${outputNameSub.reason}`
|
|
36895
|
+
};
|
|
36896
|
+
if (typeof outputNameSub.value !== "string" || outputNameSub.value.length === 0) return {
|
|
36897
|
+
kind: "unresolved",
|
|
36898
|
+
reason: `Fn::GetStackOutput.OutputName must resolve to a non-empty string, got ${typeof outputNameSub.value}`
|
|
36899
|
+
};
|
|
36900
|
+
const outputName = outputNameSub.value;
|
|
36901
|
+
let region;
|
|
36902
|
+
if (args["Region"] !== void 0 && args["Region"] !== null) {
|
|
36903
|
+
const regionSub = substituteAgainstState(args["Region"], context);
|
|
36904
|
+
if (regionSub.kind !== "literal") return {
|
|
36905
|
+
kind: "unresolved",
|
|
36906
|
+
reason: `Fn::GetStackOutput.Region: ${regionSub.reason}`
|
|
36907
|
+
};
|
|
36908
|
+
if (typeof regionSub.value !== "string" || regionSub.value.length === 0) return {
|
|
36909
|
+
kind: "unresolved",
|
|
36910
|
+
reason: `Fn::GetStackOutput.Region must resolve to a non-empty string, got ${typeof regionSub.value}`
|
|
36911
|
+
};
|
|
36912
|
+
region = regionSub.value;
|
|
36913
|
+
} else region = context.consumerRegion ?? context.pseudoParameters?.region;
|
|
36914
|
+
if (!region) return {
|
|
36915
|
+
kind: "unresolved",
|
|
36916
|
+
reason: `Fn::GetStackOutput '${stackName}.${outputName}': no Region supplied and consumer region is unknown (set --region, AWS_REGION, or env.region on the CDK stack)`
|
|
36917
|
+
};
|
|
36918
|
+
if (args["RoleArn"] !== void 0 && args["RoleArn"] !== null) return {
|
|
36919
|
+
kind: "unresolved",
|
|
36920
|
+
reason: `Fn::GetStackOutput '${stackName}.${outputName}': RoleArn (cross-account) is not yet supported by --from-state — tracked under issue #449`
|
|
36921
|
+
};
|
|
36922
|
+
if (!context.crossStackResolver) return {
|
|
36923
|
+
kind: "unresolved",
|
|
36924
|
+
reason: `Fn::GetStackOutput '${stackName}.${outputName}': no cross-stack resolver supplied (pass --from-state and ensure the producer stack was deployed via cdkd deploy)`
|
|
36925
|
+
};
|
|
36926
|
+
let resolved;
|
|
36927
|
+
try {
|
|
36928
|
+
resolved = await context.crossStackResolver.resolveGetStackOutput(stackName, region, outputName);
|
|
36929
|
+
} catch (err) {
|
|
36930
|
+
return {
|
|
36931
|
+
kind: "unresolved",
|
|
36932
|
+
reason: `Fn::GetStackOutput '${stackName}.${outputName}' (${region}): lookup failed: ${err instanceof Error ? err.message : String(err)}`
|
|
36933
|
+
};
|
|
36934
|
+
}
|
|
36935
|
+
if (resolved === void 0) return {
|
|
36936
|
+
kind: "unresolved",
|
|
36937
|
+
reason: `Fn::GetStackOutput '${stackName}.${outputName}' (${region}): output not found in producer stack state`
|
|
36938
|
+
};
|
|
36939
|
+
return {
|
|
36940
|
+
kind: "literal",
|
|
36941
|
+
value: resolved
|
|
36942
|
+
};
|
|
36943
|
+
}
|
|
36944
|
+
/**
|
|
36736
36945
|
* Build a pre-substituted env map from the template entry by feeding each
|
|
36737
36946
|
* intrinsic value through `substituteAgainstState`. Literal entries pass
|
|
36738
36947
|
* through untouched (the env-resolver handles them).
|
|
@@ -36773,6 +36982,50 @@ function substituteEnvVarsFromState(templateEnv, contextOrResources) {
|
|
|
36773
36982
|
audit
|
|
36774
36983
|
};
|
|
36775
36984
|
}
|
|
36985
|
+
/**
|
|
36986
|
+
* Async sibling of {@link substituteEnvVarsFromState}. Routes every
|
|
36987
|
+
* intrinsic-valued entry through {@link substituteAgainstStateAsync} so
|
|
36988
|
+
* `Fn::ImportValue` / `Fn::GetStackOutput` resolve via the context's
|
|
36989
|
+
* `crossStackResolver` (when supplied). Mirrors the sync version in every
|
|
36990
|
+
* other respect: literal entries pass through unchanged, unresolved
|
|
36991
|
+
* entries are dropped with a per-key audit reason, and the env-resolver
|
|
36992
|
+
* sees the same "no template value" shape so the warn-and-drop path
|
|
36993
|
+
* fires consistently.
|
|
36994
|
+
*
|
|
36995
|
+
* Closes issue #454 — `cdkd local invoke --from-state` and
|
|
36996
|
+
* `cdkd local run-task --from-state` can now resolve cross-stack output
|
|
36997
|
+
* references in env vars / secrets instead of warn-and-dropping them.
|
|
36998
|
+
*/
|
|
36999
|
+
async function substituteEnvVarsFromStateAsync(templateEnv, contextOrResources) {
|
|
37000
|
+
const env = {};
|
|
37001
|
+
const audit = {
|
|
37002
|
+
resolvedKeys: [],
|
|
37003
|
+
unresolved: []
|
|
37004
|
+
};
|
|
37005
|
+
if (!templateEnv) return {
|
|
37006
|
+
env,
|
|
37007
|
+
audit
|
|
37008
|
+
};
|
|
37009
|
+
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
37010
|
+
for (const [key, value] of Object.entries(templateEnv)) {
|
|
37011
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
37012
|
+
env[key] = value;
|
|
37013
|
+
continue;
|
|
37014
|
+
}
|
|
37015
|
+
const result = await substituteAgainstStateAsync(value, context);
|
|
37016
|
+
if (result.kind === "literal") {
|
|
37017
|
+
env[key] = result.value;
|
|
37018
|
+
audit.resolvedKeys.push(key);
|
|
37019
|
+
} else audit.unresolved.push({
|
|
37020
|
+
key,
|
|
37021
|
+
reason: result.reason
|
|
37022
|
+
});
|
|
37023
|
+
}
|
|
37024
|
+
return {
|
|
37025
|
+
env,
|
|
37026
|
+
audit
|
|
37027
|
+
};
|
|
37028
|
+
}
|
|
36776
37029
|
|
|
36777
37030
|
//#endregion
|
|
36778
37031
|
//#region src/local/ecs-task-resolver.ts
|
|
@@ -36816,6 +37069,7 @@ function detectEcsImageResolutionNeeds(stack) {
|
|
|
36816
37069
|
let needsPseudoParameters = false;
|
|
36817
37070
|
let needsStateResources = false;
|
|
36818
37071
|
let needsEnvOrSecretSubstitution = false;
|
|
37072
|
+
let needsCrossStackResolver = false;
|
|
36819
37073
|
for (const res of Object.values(resources)) {
|
|
36820
37074
|
if (res.Type !== "AWS::ECS::TaskDefinition") continue;
|
|
36821
37075
|
const props = res.Properties ?? {};
|
|
@@ -36828,6 +37082,10 @@ function detectEcsImageResolutionNeeds(stack) {
|
|
|
36828
37082
|
if (need.pseudo) needsPseudoParameters = true;
|
|
36829
37083
|
if (need.state) needsStateResources = true;
|
|
36830
37084
|
if (containerHasIntrinsicEnvOrSecret(co)) needsEnvOrSecretSubstitution = true;
|
|
37085
|
+
if (containerHasCrossStackEnvOrSecret(co)) {
|
|
37086
|
+
needsEnvOrSecretSubstitution = true;
|
|
37087
|
+
needsCrossStackResolver = true;
|
|
37088
|
+
}
|
|
36831
37089
|
}
|
|
36832
37090
|
const rawVolumes = props["Volumes"];
|
|
36833
37091
|
if (Array.isArray(rawVolumes)) {
|
|
@@ -36841,10 +37099,37 @@ function detectEcsImageResolutionNeeds(stack) {
|
|
|
36841
37099
|
return {
|
|
36842
37100
|
needsPseudoParameters,
|
|
36843
37101
|
needsStateResources,
|
|
36844
|
-
needsEnvOrSecretSubstitution
|
|
37102
|
+
needsEnvOrSecretSubstitution,
|
|
37103
|
+
needsCrossStackResolver
|
|
36845
37104
|
};
|
|
36846
37105
|
}
|
|
36847
37106
|
/**
|
|
37107
|
+
* Returns true when any `Environment[].Value` or `Secrets[].ValueFrom`
|
|
37108
|
+
* is a top-level `Fn::ImportValue` / `Fn::GetStackOutput` intrinsic.
|
|
37109
|
+
* Issue #454 — gates cross-stack resolver construction so literal +
|
|
37110
|
+
* same-stack-intrinsic env / secret maps don't pay the extra cost.
|
|
37111
|
+
*/
|
|
37112
|
+
function containerHasCrossStackEnvOrSecret(c) {
|
|
37113
|
+
const env = c["Environment"];
|
|
37114
|
+
if (Array.isArray(env)) for (const entry of env) {
|
|
37115
|
+
if (!entry || typeof entry !== "object") continue;
|
|
37116
|
+
const v = entry["Value"];
|
|
37117
|
+
if (isCrossStackIntrinsic(v)) return true;
|
|
37118
|
+
}
|
|
37119
|
+
const secrets = c["Secrets"];
|
|
37120
|
+
if (Array.isArray(secrets)) for (const entry of secrets) {
|
|
37121
|
+
if (!entry || typeof entry !== "object") continue;
|
|
37122
|
+
const v = entry["ValueFrom"];
|
|
37123
|
+
if (isCrossStackIntrinsic(v)) return true;
|
|
37124
|
+
}
|
|
37125
|
+
return false;
|
|
37126
|
+
}
|
|
37127
|
+
function isCrossStackIntrinsic(value) {
|
|
37128
|
+
if (!value || typeof value !== "object") return false;
|
|
37129
|
+
const obj = value;
|
|
37130
|
+
return "Fn::ImportValue" in obj || "Fn::GetStackOutput" in obj;
|
|
37131
|
+
}
|
|
37132
|
+
/**
|
|
36848
37133
|
* Detect whether a Volume entry has an intrinsic-valued `Host.SourcePath`.
|
|
36849
37134
|
* Used by `detectEcsImageResolutionNeeds` (Gap 6 of #286) to trigger
|
|
36850
37135
|
* state-load + pseudo-parameter resolution under `--from-state` when the
|
|
@@ -37589,6 +37874,87 @@ function checkVolumeHostPath(hostPath) {
|
|
|
37589
37874
|
return false;
|
|
37590
37875
|
}
|
|
37591
37876
|
}
|
|
37877
|
+
/**
|
|
37878
|
+
* Async post-pass that walks the resolved task's container Environment +
|
|
37879
|
+
* Secrets entries from the raw template (preserved in `task.resource`)
|
|
37880
|
+
* and re-attempts substitution via {@link substituteAgainstStateAsync}
|
|
37881
|
+
* against the supplied context. The sync `parseContainerDefinition`
|
|
37882
|
+
* pass already substituted everything the legacy resolver handles; this
|
|
37883
|
+
* pass picks up the additional shapes the async resolver supports —
|
|
37884
|
+
* specifically `Fn::ImportValue` / `Fn::GetStackOutput` (issue #454).
|
|
37885
|
+
*
|
|
37886
|
+
* Resolved entries are patched onto the container's `environment` /
|
|
37887
|
+
* `secrets` map AND the corresponding `warnings` entries are filtered
|
|
37888
|
+
* out so the CLI doesn't print a stale per-container warn for an entry
|
|
37889
|
+
* the post-pass successfully resolved. Entries that STILL can't resolve
|
|
37890
|
+
* (e.g. producer stack not deployed) keep their original warning so the
|
|
37891
|
+
* CLI's UX matches the sync path.
|
|
37892
|
+
*
|
|
37893
|
+
* Pure-functional on the task structure outside of in-place mutation of
|
|
37894
|
+
* the container's `environment` / `secrets` / `warnings` arrays. The
|
|
37895
|
+
* task is expected to be the same `ResolvedEcsTask` instance returned
|
|
37896
|
+
* by `resolveEcsTaskTarget` — the runner downstream reads from these
|
|
37897
|
+
* fields directly.
|
|
37898
|
+
*/
|
|
37899
|
+
async function applyCrossStackResolverToTask(task, context) {
|
|
37900
|
+
if (!context.crossStackResolver) return;
|
|
37901
|
+
const rawContainers = (task.resource.Properties ?? {})["ContainerDefinitions"];
|
|
37902
|
+
if (!Array.isArray(rawContainers)) return;
|
|
37903
|
+
for (let idx = 0; idx < task.containers.length; idx += 1) {
|
|
37904
|
+
const container = task.containers[idx];
|
|
37905
|
+
const raw = rawContainers[idx];
|
|
37906
|
+
if (!raw || typeof raw !== "object") continue;
|
|
37907
|
+
const c = raw;
|
|
37908
|
+
const containerName = pickString(c["Name"]) ?? container.name;
|
|
37909
|
+
const resolvedEnvKeys = /* @__PURE__ */ new Set();
|
|
37910
|
+
const resolvedSecretNames = /* @__PURE__ */ new Set();
|
|
37911
|
+
if (Array.isArray(c["Environment"])) for (const entry of c["Environment"]) {
|
|
37912
|
+
if (!entry || typeof entry !== "object") continue;
|
|
37913
|
+
const e = entry;
|
|
37914
|
+
const key = pickString(e["Name"]);
|
|
37915
|
+
const value = e["Value"];
|
|
37916
|
+
if (!key) continue;
|
|
37917
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") continue;
|
|
37918
|
+
if (key in container.environment) continue;
|
|
37919
|
+
if (!isCrossStackIntrinsic(value)) continue;
|
|
37920
|
+
const sub = await substituteAgainstStateAsync(value, context);
|
|
37921
|
+
if (sub.kind === "literal") {
|
|
37922
|
+
container.environment[key] = String(sub.value);
|
|
37923
|
+
resolvedEnvKeys.add(key);
|
|
37924
|
+
}
|
|
37925
|
+
}
|
|
37926
|
+
if (Array.isArray(c["Secrets"])) for (const entry of c["Secrets"]) {
|
|
37927
|
+
if (!entry || typeof entry !== "object") continue;
|
|
37928
|
+
const e = entry;
|
|
37929
|
+
const sName = pickString(e["Name"]);
|
|
37930
|
+
const valueFromRaw = e["ValueFrom"];
|
|
37931
|
+
if (!sName) continue;
|
|
37932
|
+
if (typeof valueFromRaw === "string" && valueFromRaw.length > 0) continue;
|
|
37933
|
+
if (container.secrets.some((s) => s.name === sName)) continue;
|
|
37934
|
+
if (!isCrossStackIntrinsic(valueFromRaw)) continue;
|
|
37935
|
+
const sub = await substituteAgainstStateAsync(valueFromRaw, context);
|
|
37936
|
+
if (sub.kind === "literal" && typeof sub.value === "string" && sub.value.length > 0) {
|
|
37937
|
+
container.secrets.push({
|
|
37938
|
+
name: sName,
|
|
37939
|
+
valueFrom: sub.value
|
|
37940
|
+
});
|
|
37941
|
+
resolvedSecretNames.add(sName);
|
|
37942
|
+
}
|
|
37943
|
+
}
|
|
37944
|
+
if (resolvedEnvKeys.size > 0 || resolvedSecretNames.size > 0) {
|
|
37945
|
+
container.warnings = container.warnings.filter((w) => {
|
|
37946
|
+
for (const k of resolvedEnvKeys) if (w.startsWith(`Environment '${k}' dropped:`)) return false;
|
|
37947
|
+
for (const k of resolvedSecretNames) if (w.startsWith(`Secret '${k}' dropped:`)) return false;
|
|
37948
|
+
return true;
|
|
37949
|
+
});
|
|
37950
|
+
task.warnings = task.warnings.filter((w) => {
|
|
37951
|
+
for (const k of resolvedEnvKeys) if (w.startsWith(`Container '${containerName}': Environment '${k}' dropped:`)) return false;
|
|
37952
|
+
for (const k of resolvedSecretNames) if (w.startsWith(`Container '${containerName}': Secret '${k}' dropped:`)) return false;
|
|
37953
|
+
return true;
|
|
37954
|
+
});
|
|
37955
|
+
}
|
|
37956
|
+
}
|
|
37957
|
+
}
|
|
37592
37958
|
|
|
37593
37959
|
//#endregion
|
|
37594
37960
|
//#region src/local/runtime-image.ts
|
|
@@ -44605,8 +44971,34 @@ async function localRunTaskCommand(target, options) {
|
|
|
44605
44971
|
...Object.keys(context).length > 0 && { context }
|
|
44606
44972
|
};
|
|
44607
44973
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
44608
|
-
const
|
|
44974
|
+
const imageContext = await buildEcsImageResolutionContext(target, stacks, options);
|
|
44975
|
+
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
44609
44976
|
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
44977
|
+
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
|
|
44978
|
+
let taskCrossStackDispose;
|
|
44979
|
+
if (options.fromState && taskNeeds.needsCrossStackResolver) {
|
|
44980
|
+
const consumerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? task.stack.region ?? "us-east-1";
|
|
44981
|
+
const built = await buildCrossStackResolver(consumerRegion, {
|
|
44982
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
44983
|
+
statePrefix: options.statePrefix,
|
|
44984
|
+
...options.region !== void 0 && { region: options.region },
|
|
44985
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
44986
|
+
});
|
|
44987
|
+
if (built) {
|
|
44988
|
+
taskCrossStackDispose = built.dispose;
|
|
44989
|
+
try {
|
|
44990
|
+
await applyCrossStackResolverToTask(task, {
|
|
44991
|
+
resources: imageContext?.stateResources ?? {},
|
|
44992
|
+
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
44993
|
+
consumerRegion,
|
|
44994
|
+
crossStackResolver: built.resolver
|
|
44995
|
+
});
|
|
44996
|
+
} finally {
|
|
44997
|
+
taskCrossStackDispose();
|
|
44998
|
+
taskCrossStackDispose = void 0;
|
|
44999
|
+
}
|
|
45000
|
+
}
|
|
45001
|
+
} else if (!options.fromState && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass --from-state to substitute them against deployed cdkd state.");
|
|
44610
45002
|
sigintHandler = () => {
|
|
44611
45003
|
sigintCount += 1;
|
|
44612
45004
|
if (sigintCount >= 2) {
|
|
@@ -44894,6 +45286,7 @@ async function localInvokeCommand(target, options) {
|
|
|
44894
45286
|
let stateAudit;
|
|
44895
45287
|
let templateEnv = getTemplateEnv(lambda.resource);
|
|
44896
45288
|
let stateForRoleHint;
|
|
45289
|
+
let crossStackDispose;
|
|
44897
45290
|
if (options.fromState) {
|
|
44898
45291
|
const loaded = await loadStateForStack(lambda.stack.stackName, lambda.stack.region, {
|
|
44899
45292
|
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
@@ -44904,16 +45297,38 @@ async function localInvokeCommand(target, options) {
|
|
|
44904
45297
|
});
|
|
44905
45298
|
if (loaded) {
|
|
44906
45299
|
stateForRoleHint = loaded.state;
|
|
44907
|
-
const subContext = {
|
|
45300
|
+
const subContext = {
|
|
45301
|
+
resources: loaded.state.resources,
|
|
45302
|
+
consumerRegion: loaded.region
|
|
45303
|
+
};
|
|
44908
45304
|
if (envHasIntrinsicValue(templateEnv)) {
|
|
44909
45305
|
const pseudo = await resolvePseudoParametersForInvoke(lambda.stack.region, options);
|
|
44910
45306
|
if (pseudo) subContext.pseudoParameters = pseudo;
|
|
44911
45307
|
}
|
|
44912
|
-
|
|
44913
|
-
|
|
44914
|
-
|
|
44915
|
-
|
|
44916
|
-
|
|
45308
|
+
if (envHasCrossStackIntrinsic(templateEnv)) {
|
|
45309
|
+
const built = await buildCrossStackResolver(loaded.region, {
|
|
45310
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
45311
|
+
statePrefix: options.statePrefix,
|
|
45312
|
+
...options.region !== void 0 && { region: options.region },
|
|
45313
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
45314
|
+
});
|
|
45315
|
+
if (built) {
|
|
45316
|
+
subContext.crossStackResolver = built.resolver;
|
|
45317
|
+
crossStackDispose = built.dispose;
|
|
45318
|
+
}
|
|
45319
|
+
}
|
|
45320
|
+
try {
|
|
45321
|
+
const { env, audit } = await substituteEnvVarsFromStateAsync(templateEnv, subContext);
|
|
45322
|
+
templateEnv = env;
|
|
45323
|
+
stateAudit = audit;
|
|
45324
|
+
for (const key of audit.resolvedKeys) logger.debug(`--from-state: substituted env var ${key} from cdkd state`);
|
|
45325
|
+
for (const { key, reason } of audit.unresolved) logger.warn(`--from-state: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`);
|
|
45326
|
+
} finally {
|
|
45327
|
+
if (crossStackDispose) {
|
|
45328
|
+
crossStackDispose();
|
|
45329
|
+
crossStackDispose = void 0;
|
|
45330
|
+
}
|
|
45331
|
+
}
|
|
44917
45332
|
}
|
|
44918
45333
|
}
|
|
44919
45334
|
const overrides = readEnvOverridesFile(options.envVars);
|
|
@@ -45186,6 +45601,29 @@ function envHasIntrinsicValue(templateEnv) {
|
|
|
45186
45601
|
return false;
|
|
45187
45602
|
}
|
|
45188
45603
|
/**
|
|
45604
|
+
* Returns true when any value in the function's template env map carries
|
|
45605
|
+
* a top-level `Fn::ImportValue` / `Fn::GetStackOutput` intrinsic. Used to
|
|
45606
|
+
* gate the cross-stack resolver construction inside the `--from-state`
|
|
45607
|
+
* flow: literal + same-stack-intrinsic env maps shouldn't pay for the
|
|
45608
|
+
* extra S3 client / index-load cost (issue #454).
|
|
45609
|
+
*
|
|
45610
|
+
* Detection is one level deep — same heuristic CDK 2.x uses for these
|
|
45611
|
+
* intrinsics in practice. Nested cross-stack intrinsics (e.g. an
|
|
45612
|
+
* `Fn::ImportValue` buried inside a `Fn::Join`) are not detected here;
|
|
45613
|
+
* those won't resolve in v1 anyway because the async resolver path
|
|
45614
|
+
* defers to the sync helper for `Fn::Join` / `Fn::Sub` bodies (see
|
|
45615
|
+
* `substituteAgainstStateAsync` docstring).
|
|
45616
|
+
*/
|
|
45617
|
+
function envHasCrossStackIntrinsic(templateEnv) {
|
|
45618
|
+
if (!templateEnv) return false;
|
|
45619
|
+
for (const v of Object.values(templateEnv)) {
|
|
45620
|
+
if (!v || typeof v !== "object") continue;
|
|
45621
|
+
const obj = v;
|
|
45622
|
+
if ("Fn::ImportValue" in obj || "Fn::GetStackOutput" in obj) return true;
|
|
45623
|
+
}
|
|
45624
|
+
return false;
|
|
45625
|
+
}
|
|
45626
|
+
/**
|
|
45189
45627
|
* Build the AWS pseudo-parameter bag for `--from-state` env-var
|
|
45190
45628
|
* substitution. Issues a single `sts:GetCallerIdentity` for the account
|
|
45191
45629
|
* id and derives `partition` / `urlSuffix` from the resolved region. Any
|
|
@@ -46717,7 +47155,7 @@ function reorderArgs(argv) {
|
|
|
46717
47155
|
*/
|
|
46718
47156
|
async function main() {
|
|
46719
47157
|
const program = new Command();
|
|
46720
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
47158
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.123.0");
|
|
46721
47159
|
program.addCommand(createBootstrapCommand());
|
|
46722
47160
|
program.addCommand(createSynthCommand());
|
|
46723
47161
|
program.addCommand(createListCommand());
|