@go-to-k/cdkd 0.283.12 → 0.283.14
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/{asg-provider-BKR0cF_X.js → asg-provider-CgnPBnOS.js} +2 -2
- package/dist/{asg-provider-BKR0cF_X.js.map → asg-provider-CgnPBnOS.js.map} +1 -1
- package/dist/cli.js +485 -79
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-DHOpGcMS.js → deploy-engine-B8gQpv93.js} +3 -3
- package/dist/{deploy-engine-DHOpGcMS.js.map → deploy-engine-B8gQpv93.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as coerceCfnBoolean, $t as resolveStateBucketWithDefault, A as cyan, An as MissingCdkCliError, At as AssetModeResolver, B as findSilentDropProperties, Bt as getDockerCmd, C as makeCanonicalizePropertiesFn, Ct as stringifyValue, D as renderStatefulReason, Dn as LocalMigrateError, Dt as loadPublishableAssetManifest, E as isStatefulRecreateTargetSync, En as LocalInvokeBuildError$1, Et as createAssetRedirectResolver, F as IAMRoleProvider, Fn as ResourceUpdateNotSupportedError, Ft as validateAssetBucketName, G as IntrinsicFunctionResolver, Gn as isRetryableTransientError, Gt as Synthesizer, H as slowCcOperationTimeoutMs, Hn as normalizeAwsError, Ht as runDockerStreaming, I as collectInlinePolicyNamesManagedBySiblings, In as StackHasActiveImportsError, It as validateContainerRepoName, J as refStateLookupFromResource, Jt as getLegacyStateBucketName, K as cfnRefValueFromPhysicalId, Kn as isThrottlingError, Kt as synthesisStatusMessage, L as clearOnUpdateRemoval, Ln as StackTerminationProtectionError, Lt as buildDenyExternalAccessPolicy, M as green, Mn as PartialFailureError, Mt as ensureAssetStorage, N as red, Nn as ProvisioningError, Nt as getBootstrapMarkerKey, O as formatResourceLine, On as LocalStartServiceError, Ot as rewriteTemplateAssetReferences, P as yellow, Pn as ResourceTimeoutError, Pt as parseBootstrapMarker, Q as assertRegionMatch, Qt as resolveSkipPrefix, R as ProviderRegistry, Rt as buildDockerImage, S as unsupportedFinalSnapshotError, Sn as CdkdError, St as AssetPublisher, T as MULTI_REGION_RECREATE_BLOCKED_TYPES, Tn as DeployCancelledError, Tt as buildAssetRedirectMap, U as disableInstanceApiTermination, Un as withErrorHandling, Ut as AssetManifestLoader, V as CloudControlProvider, Vt as runDockerForeground, W as isTerminationProtectionPropagationError, Wn as isMarkedNonRetryable, Wt as getDockerImageBySourceHash, X as normalizeAwsTagsToCfn, Xt as resolveAutoAssetStorage, Y as WAFv2WebACLProvider, Yt as resolveApp, Z as resolveExplicitPhysicalId, Zt as resolveCaptureObservedState, _ as buildFinalSnapshotIdentifier, _n as AwsClients, _t as TemplateParser, a as DeploymentEventsStore, an as CFN_TEMPLATE_URL_LIMIT, at as requireConfigObject, b as isFinalSnapshotError, bn as setAwsClients, bt as rebuildClientForBucketRegion, c as replayFailedOperations, cn as uploadCfnTemplate, ct as s3BucketDomainName, d as deleteSkipReason, dn as canonicalizeRegion, dt as s3BucketWebsiteUrl, en as resolveStateBucketWithDefaultAndSource, et as configBooleanRefusal, f as withResourceDeadline, fn as derivePartitionAndUrlSuffix, ft as applyRoleArnIfSet, g as PRE_DELETE_SNAPSHOT_TYPES, gn as resolveBucketRegion, gt as DagBuilder, h as ATOMIC_FINAL_SNAPSHOT_TYPES, ht as withRetry, i as DeploymentEventsReader, in as CFN_TEMPLATE_BODY_LIMIT, it as requireConfigArray, j as gray, jn as NestedStackChildDirectDestroyError, jt as BOOTSTRAP_MARKER_PREFIX, k as bold, kt as escapeRegExp, l as replayRollback, ln as expectedOwnerParam, lt as s3BucketDualStackDomainName, m as computeImplicitDeleteEdges, mn as processStackMessages, mt as describeTypeWithThrottleRetry, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as stateBucketExistenceConfirmed, nt as readConfigString, o as planFailedOps, on as MIGRATE_TMP_PREFIX, ot as requireConfigString, p as IMPLICIT_DELETE_DEPENDENCIES, pn as AssemblyReader, pt as DiffCalculator, q as getAccountInfo, qn as markNonRetryable, qt as getDefaultStateBucketName, r as DeployEngine, rn as warnDeprecatedNoPrefixCliFlag, rt as replayWarn, s as planRollback, sn as findLargeInlineResources, st as s3BucketArn, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resolveUseCdkBootstrapAssets, tt as configStringRefusal, u as UNSPECIFIED_SKIP_REASON, un as PARTITION_TABLE, ut as s3BucketRegionalDomainName, v as ccRoutedFinalSnapshotError, vn as getAwsClients, vt as LockManager, w as extractDeploymentEventError, wt as WorkGraph, x as refusesFinalSnapshot, xt as shouldRetainResource, y as createPreDeleteFinalSnapshot, yn as resetAwsClients, yt as S3StateBackend, z as findActionableSilentDrops, zt as formatDockerLoginError } from "./deploy-engine-
|
|
2
|
+
import { $ as coerceCfnBoolean, $t as resolveStateBucketWithDefault, A as cyan, An as MissingCdkCliError, At as AssetModeResolver, B as findSilentDropProperties, Bt as getDockerCmd, C as makeCanonicalizePropertiesFn, Ct as stringifyValue, D as renderStatefulReason, Dn as LocalMigrateError, Dt as loadPublishableAssetManifest, E as isStatefulRecreateTargetSync, En as LocalInvokeBuildError$1, Et as createAssetRedirectResolver, F as IAMRoleProvider, Fn as ResourceUpdateNotSupportedError, Ft as validateAssetBucketName, G as IntrinsicFunctionResolver, Gn as isRetryableTransientError, Gt as Synthesizer, H as slowCcOperationTimeoutMs, Hn as normalizeAwsError, Ht as runDockerStreaming, I as collectInlinePolicyNamesManagedBySiblings, In as StackHasActiveImportsError, It as validateContainerRepoName, J as refStateLookupFromResource, Jt as getLegacyStateBucketName, K as cfnRefValueFromPhysicalId, Kn as isThrottlingError, Kt as synthesisStatusMessage, L as clearOnUpdateRemoval, Ln as StackTerminationProtectionError, Lt as buildDenyExternalAccessPolicy, M as green, Mn as PartialFailureError, Mt as ensureAssetStorage, N as red, Nn as ProvisioningError, Nt as getBootstrapMarkerKey, O as formatResourceLine, On as LocalStartServiceError, Ot as rewriteTemplateAssetReferences, P as yellow, Pn as ResourceTimeoutError, Pt as parseBootstrapMarker, Q as assertRegionMatch, Qt as resolveSkipPrefix, R as ProviderRegistry, Rt as buildDockerImage, S as unsupportedFinalSnapshotError, Sn as CdkdError, St as AssetPublisher, T as MULTI_REGION_RECREATE_BLOCKED_TYPES, Tn as DeployCancelledError, Tt as buildAssetRedirectMap, U as disableInstanceApiTermination, Un as withErrorHandling, Ut as AssetManifestLoader, V as CloudControlProvider, Vt as runDockerForeground, W as isTerminationProtectionPropagationError, Wn as isMarkedNonRetryable, Wt as getDockerImageBySourceHash, X as normalizeAwsTagsToCfn, Xt as resolveAutoAssetStorage, Y as WAFv2WebACLProvider, Yt as resolveApp, Z as resolveExplicitPhysicalId, Zt as resolveCaptureObservedState, _ as buildFinalSnapshotIdentifier, _n as AwsClients, _t as TemplateParser, a as DeploymentEventsStore, an as CFN_TEMPLATE_URL_LIMIT, at as requireConfigObject, b as isFinalSnapshotError, bn as setAwsClients, bt as rebuildClientForBucketRegion, c as replayFailedOperations, cn as uploadCfnTemplate, ct as s3BucketDomainName, d as deleteSkipReason, dn as canonicalizeRegion, dt as s3BucketWebsiteUrl, en as resolveStateBucketWithDefaultAndSource, et as configBooleanRefusal, f as withResourceDeadline, fn as derivePartitionAndUrlSuffix, ft as applyRoleArnIfSet, g as PRE_DELETE_SNAPSHOT_TYPES, gn as resolveBucketRegion, gt as DagBuilder, h as ATOMIC_FINAL_SNAPSHOT_TYPES, ht as withRetry, i as DeploymentEventsReader, in as CFN_TEMPLATE_BODY_LIMIT, it as requireConfigArray, j as gray, jn as NestedStackChildDirectDestroyError, jt as BOOTSTRAP_MARKER_PREFIX, k as bold, kt as escapeRegExp, l as replayRollback, ln as expectedOwnerParam, lt as s3BucketDualStackDomainName, m as computeImplicitDeleteEdges, mn as processStackMessages, mt as describeTypeWithThrottleRetry, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as stateBucketExistenceConfirmed, nt as readConfigString, o as planFailedOps, on as MIGRATE_TMP_PREFIX, ot as requireConfigString, p as IMPLICIT_DELETE_DEPENDENCIES, pn as AssemblyReader, pt as DiffCalculator, q as getAccountInfo, qn as markNonRetryable, qt as getDefaultStateBucketName, r as DeployEngine, rn as warnDeprecatedNoPrefixCliFlag, rt as replayWarn, s as planRollback, sn as findLargeInlineResources, st as s3BucketArn, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resolveUseCdkBootstrapAssets, tt as configStringRefusal, u as UNSPECIFIED_SKIP_REASON, un as PARTITION_TABLE, ut as s3BucketRegionalDomainName, v as ccRoutedFinalSnapshotError, vn as getAwsClients, vt as LockManager, w as extractDeploymentEventError, wt as WorkGraph, x as refusesFinalSnapshot, xt as shouldRetainResource, y as createPreDeleteFinalSnapshot, yn as resetAwsClients, yt as S3StateBackend, z as findActionableSilentDrops, zt as formatDockerLoginError } from "./deploy-engine-B8gQpv93.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-CgnPBnOS.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, 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";
|
|
@@ -20534,6 +20534,128 @@ var SSMParameterProvider = class {
|
|
|
20534
20534
|
return out;
|
|
20535
20535
|
}
|
|
20536
20536
|
/**
|
|
20537
|
+
* Build the `Arn` attribute for an SSM parameter (issue #1824).
|
|
20538
|
+
*
|
|
20539
|
+
* WHY THIS EXISTS. An output / cross-resource `Fn::GetAtt` reads the CACHED
|
|
20540
|
+
* `resource.attributes[<CFnName>]` in
|
|
20541
|
+
* `IntrinsicFunctionResolver.constructAttribute`, which never calls a
|
|
20542
|
+
* provider's `getAttribute` — and for an `*Arn` name the resolver's shape
|
|
20543
|
+
* guard HARD-FAILS rather than degrading when the cached value is missing and
|
|
20544
|
+
* the physicalId is name-shaped (an SSM physicalId IS the parameter name). So
|
|
20545
|
+
* `new cdk.CfnOutput(this, 'ParamArn', { value: param.attrArn })` failed the
|
|
20546
|
+
* deploy until the ARN was cached under its CFn name.
|
|
20547
|
+
*
|
|
20548
|
+
* SOURCE DECISION — CONSTRUCTED, not read back. `PutParameter` returns only
|
|
20549
|
+
* `Version` / `Tier`, so there were exactly two candidate sources, and the
|
|
20550
|
+
* rejected one is recorded here so the call stays challengeable:
|
|
20551
|
+
*
|
|
20552
|
+
* - `GetParameter`'s `Parameter.ARN` is authoritative, and cdkd already
|
|
20553
|
+
* issues that call in `readCurrentState` / `import`, so it needs no new IAM
|
|
20554
|
+
* permission. It was REJECTED on COST: it would be a brand-new round trip
|
|
20555
|
+
* on EVERY parameter create AND every update, and SSM parameters are among
|
|
20556
|
+
* the most numerous resources in real CDK stacks — a 50-parameter stack
|
|
20557
|
+
* would pay 50 extra reads for a value that is a pure function of data
|
|
20558
|
+
* cdkd already holds, against a project whose whole premise is removing
|
|
20559
|
+
* round trips.
|
|
20560
|
+
* - CONSTRUCTION is deterministic. The parameter ARN is the documented
|
|
20561
|
+
* `arn:{partition}:ssm:{region}:{account}:parameter/{name}`, with the
|
|
20562
|
+
* name's leading `/` folded into the separator — the same formula
|
|
20563
|
+
* `aws-cdk-lib`'s `StringParameter.parameterArn` uses — so nothing has to
|
|
20564
|
+
* be discovered from AWS. It is also the established idiom in this repo for
|
|
20565
|
+
* "the create response carries no ARN": `AppSyncProvider.buildAppSyncArn`
|
|
20566
|
+
* and the four `CloudControlProvider` enrichment sites (KMS key, ECR
|
|
20567
|
+
* repository x2, Kinesis stream) all construct through the same
|
|
20568
|
+
* `getAccountInfo` + `derivePartitionAndUrlSuffix` + `fabricated`-refusal
|
|
20569
|
+
* pair this method reuses.
|
|
20570
|
+
*
|
|
20571
|
+
* The PARTITION is DERIVED, never hardcoded `arn:aws:` — that hardcoding is an
|
|
20572
|
+
* active bug class here (#1794 / #1815), and on a `cn-` / `us-gov-` deploy it
|
|
20573
|
+
* would record an ARN naming a partition the parameter is not in.
|
|
20574
|
+
*
|
|
20575
|
+
* The `fabricated`-account guard (#1730 / #1746) is honored by REFUSING rather
|
|
20576
|
+
* than by substituting: `getAccountInfo` catches its own STS failure and
|
|
20577
|
+
* answers the placeholder `123456789012`, which carries no wildcard and is
|
|
20578
|
+
* therefore invisible to `isPlaceholderArn`, so a fabricated id must yield NO
|
|
20579
|
+
* attribute instead of a plausible-looking wrong one. Returning `undefined`
|
|
20580
|
+
* degrades to exactly the pre-fix behavior — the resolver's shape guard fails
|
|
20581
|
+
* LOUDLY on the name-shaped physicalId — never to a silently wrong value.
|
|
20582
|
+
*
|
|
20583
|
+
* DEGRADATION ON THE UPDATE PATH is a DELIBERATE trade-off, not one this
|
|
20584
|
+
* design avoids. An update result's `attributes` REPLACE the state record's
|
|
20585
|
+
* rather than merging into it (`deploy-engine.ts`:
|
|
20586
|
+
* `result.attributes ?? (wasReplaced ? undefined : currentResource.attributes)`),
|
|
20587
|
+
* so whenever this method degrades during an `update` the returned map carries
|
|
20588
|
+
* no `Arn` and the create-time one is DROPPED. Construction does not prevent
|
|
20589
|
+
* that — it only swaps WHICH dependency can fail, STS plus the client's region
|
|
20590
|
+
* resolver instead of SSM — so read the source decision above as buying one
|
|
20591
|
+
* fewer round trip and nothing else.
|
|
20592
|
+
*
|
|
20593
|
+
* The alternative is to return NO `attributes` at all on the degraded path, so
|
|
20594
|
+
* the engine's `??` carries the previous map forward and the `Arn` survives.
|
|
20595
|
+
* That is REJECTED: the same map carries `Type` / `Value`, which are the values
|
|
20596
|
+
* THIS update just sent. Carrying the previous map forward would answer
|
|
20597
|
+
* `Fn::GetAtt [Param, Value]` with the SUPERSEDED value — silently, and to a
|
|
20598
|
+
* question cdkd can answer correctly — and would hand `cdkd drift` a baseline
|
|
20599
|
+
* AWS does not hold. Dropping the `Arn` instead degrades to the resolver's LOUD
|
|
20600
|
+
* `*Arn` shape-guard failure, which is exactly the pre-fix behavior, and the
|
|
20601
|
+
* resource's next UPDATE re-records it. A loud missing value beats a quiet
|
|
20602
|
+
* wrong one — the same reasoning the `fabricated`-account arm above applies.
|
|
20603
|
+
*
|
|
20604
|
+
* Two honest bounds on that "next update re-records it". `getAccountInfo`
|
|
20605
|
+
* caches a SUCCESSFUL identity for the process, so within one deploy only a run
|
|
20606
|
+
* whose FIRST `GetCallerIdentity` fails degrades at all — but the 10s
|
|
20607
|
+
* fabricated-answer TTL then covers every parameter created in that window
|
|
20608
|
+
* rather than a random subset. And a plain re-deploy does NOT heal it: a
|
|
20609
|
+
* resource whose resolved properties equal its state record is skipped with no
|
|
20610
|
+
* provider call, so healing needs a real property change (issue
|
|
20611
|
+
* [#1852](https://github.com/go-to-k/cdkd/issues/1852), which also covers the
|
|
20612
|
+
* resolver message that currently misattributes this to a missing enrichment).
|
|
20613
|
+
*
|
|
20614
|
+
* NEVER THROWS, and that is enforced here rather than asserted at the call
|
|
20615
|
+
* sites. `create` calls this AFTER `PutParameter` has already committed a
|
|
20616
|
+
* parameter, so a throw would surface as a failed create over a missing
|
|
20617
|
+
* ATTRIBUTE — leaving an orphan that makes the next deploy hit
|
|
20618
|
+
* `ParameterAlreadyExists` (the issue #376 class), and the cleanup that exists
|
|
20619
|
+
* for that is deliberately out of reach by then. `getAccountInfo` catches its
|
|
20620
|
+
* own STS failure, but `config.region()` is a resolver that can reject, so the
|
|
20621
|
+
* whole body is wrapped: an unexpected failure degrades to "no Arn recorded"
|
|
20622
|
+
* exactly as the fabricated-account arm does.
|
|
20623
|
+
*/
|
|
20624
|
+
async buildParameterArn(name) {
|
|
20625
|
+
try {
|
|
20626
|
+
return await this.buildParameterArnUnguarded(name);
|
|
20627
|
+
} catch (error) {
|
|
20628
|
+
this.logger.warn(`Could not build the Arn attribute for SSM parameter ${name}: ${error instanceof Error ? error.message : String(error)}. The parameter itself is unaffected; the Arn is NOT recorded, so an Fn::GetAtt on it will fail until a later deploy records it.`);
|
|
20629
|
+
return;
|
|
20630
|
+
}
|
|
20631
|
+
}
|
|
20632
|
+
/**
|
|
20633
|
+
* {@link buildParameterArn}'s body. Only that method may call it.
|
|
20634
|
+
*
|
|
20635
|
+
* `name` is always a parameter NAME, never an ARN, and that is an INVARIANT
|
|
20636
|
+
* rather than an assumption this method has to defend (issue #1824 review
|
|
20637
|
+
* round 3). Both callers pass a value SSM has just accepted as a
|
|
20638
|
+
* `PutParameter` `Name`, and that field forbids an ARN outright — "You can't
|
|
20639
|
+
* enter the Amazon Resource Name (ARN) for a parameter, only the parameter
|
|
20640
|
+
* name itself", plus a name charset of `a-zA-Z0-9_.-` and `/` that excludes
|
|
20641
|
+
* `:`. So an ARN-shaped id fails at `PutParameter` several statements ABOVE
|
|
20642
|
+
* this method on both paths, and a guard here could only ever fire against a
|
|
20643
|
+
* mock. Round 2 added exactly such a guard, believing an ARN could arrive from
|
|
20644
|
+
* `cdkd import --resource Param=arn:...`; it can, but the defect is that the
|
|
20645
|
+
* import RECORDED it, so the fix belongs at the import boundary — where
|
|
20646
|
+
* `import()` now refuses it — and not here. Do not re-add it.
|
|
20647
|
+
*/
|
|
20648
|
+
async buildParameterArnUnguarded(name) {
|
|
20649
|
+
const accountInfo = await getAccountInfo(await this.ssmClient.config.region());
|
|
20650
|
+
if (accountInfo.fabricated) {
|
|
20651
|
+
this.logger.warn(`Cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so the Arn attribute for SSM parameter ${name} would be fabricated and is NOT recorded. An Fn::GetAtt on it will fail until a later deploy resolves the account.`);
|
|
20652
|
+
return;
|
|
20653
|
+
}
|
|
20654
|
+
const { partition } = derivePartitionAndUrlSuffix(accountInfo.region);
|
|
20655
|
+
const qualifiedName = name.startsWith("/") ? name.slice(1) : name;
|
|
20656
|
+
return `arn:${partition}:ssm:${canonicalizeRegion(accountInfo.region)}:${accountInfo.accountId}:parameter/${qualifiedName}`;
|
|
20657
|
+
}
|
|
20658
|
+
/**
|
|
20537
20659
|
* Create an SSM parameter
|
|
20538
20660
|
*/
|
|
20539
20661
|
async create(logicalId, resourceType, properties) {
|
|
@@ -20575,11 +20697,13 @@ var SSMParameterProvider = class {
|
|
|
20575
20697
|
throw innerError;
|
|
20576
20698
|
}
|
|
20577
20699
|
this.logger.debug(`Successfully created SSM parameter ${logicalId}: ${name}`);
|
|
20700
|
+
const arn = await this.buildParameterArn(name);
|
|
20578
20701
|
return {
|
|
20579
20702
|
physicalId: name,
|
|
20580
20703
|
attributes: {
|
|
20581
20704
|
Type: type,
|
|
20582
|
-
Value: value
|
|
20705
|
+
Value: value,
|
|
20706
|
+
...arn !== void 0 && { Arn: arn }
|
|
20583
20707
|
}
|
|
20584
20708
|
};
|
|
20585
20709
|
} catch (error) {
|
|
@@ -20626,12 +20750,14 @@ var SSMParameterProvider = class {
|
|
|
20626
20750
|
this.logger.debug(`Updated tags for SSM parameter ${physicalId}`);
|
|
20627
20751
|
}
|
|
20628
20752
|
this.logger.debug(`Successfully updated SSM parameter ${logicalId}`);
|
|
20753
|
+
const arn = await this.buildParameterArn(physicalId);
|
|
20629
20754
|
return {
|
|
20630
20755
|
physicalId,
|
|
20631
20756
|
wasReplaced: false,
|
|
20632
20757
|
attributes: {
|
|
20633
20758
|
Type: type,
|
|
20634
|
-
Value: value
|
|
20759
|
+
Value: value,
|
|
20760
|
+
...arn !== void 0 && { Arn: arn }
|
|
20635
20761
|
}
|
|
20636
20762
|
};
|
|
20637
20763
|
} catch (error) {
|
|
@@ -20742,6 +20868,57 @@ var SSMParameterProvider = class {
|
|
|
20742
20868
|
return result;
|
|
20743
20869
|
}
|
|
20744
20870
|
/**
|
|
20871
|
+
* Refuse a physical id SSM's WRITE APIs could never accept (issue #1824
|
|
20872
|
+
* review round 3).
|
|
20873
|
+
*
|
|
20874
|
+
* An SSM parameter's physicalId must always be the parameter NAME, because
|
|
20875
|
+
* that is the only thing the write APIs take. `PutParameterRequest.Name`:
|
|
20876
|
+
* "You can't enter the Amazon Resource Name (ARN) for a parameter, only the
|
|
20877
|
+
* parameter name itself", with a name charset of `a-zA-Z0-9_.-` plus `/` as
|
|
20878
|
+
* the hierarchy separator; `DeleteParameterRequest.Name` repeats the ARN
|
|
20879
|
+
* prohibition verbatim. A COLON therefore cannot appear in any writable name,
|
|
20880
|
+
* which is the single predicate used here — it covers both shapes
|
|
20881
|
+
* `GetParameter` additionally accepts and this method has to stop:
|
|
20882
|
+
*
|
|
20883
|
+
* - an ARN (`arn:aws:ssm:us-east-1:111122223333:parameter/foo`), and
|
|
20884
|
+
* - a version / label SELECTOR (`GetParameterRequest.Name`: "To query by
|
|
20885
|
+
* parameter label, use `"Name": "name:label"`. To query by parameter
|
|
20886
|
+
* version, use `"Name": "name:version"`").
|
|
20887
|
+
*
|
|
20888
|
+
* Both sail through this provider's existence check, so before this guard the
|
|
20889
|
+
* value was RECORDED as the physicalId and the damage landed later and
|
|
20890
|
+
* elsewhere: `cdkd deploy` failing at `PutParameter({Name: physicalId})` with
|
|
20891
|
+
* a `ValidationException` after a `Value` change, and `cdkd destroy` failing
|
|
20892
|
+
* identically at `DeleteParameter`. This is where the bad value ENTERS
|
|
20893
|
+
* (`--resource` / `Properties.Name`), so it is where it is stopped.
|
|
20894
|
+
*
|
|
20895
|
+
* REFUSED, NOT NORMALIZED to the name, and the ARN grammar is the reason. The
|
|
20896
|
+
* mapping back is genuinely ambiguous rather than merely awkward:
|
|
20897
|
+
*
|
|
20898
|
+
* - The leading `/` is NOT recoverable. `arn:…:parameter/foo` is the ARN of
|
|
20899
|
+
* BOTH the simple name `foo` and the one-level hierarchical name `/foo`,
|
|
20900
|
+
* since the name's leading slash IS the separator — `aws-cdk-lib`'s
|
|
20901
|
+
* `arnForParameterName` renders the two to the identical string, and that
|
|
20902
|
+
* is precisely why CDK needs an explicit `simpleName` flag whenever the
|
|
20903
|
+
* name is a token. Guessing wrong writes a physicalId naming a DIFFERENT
|
|
20904
|
+
* parameter, i.e. converts a loud failure into a silent wrong-resource
|
|
20905
|
+
* write.
|
|
20906
|
+
* - A parameter SHARED from another account has no name form at all
|
|
20907
|
+
* ("For parameters shared with you from another account, you must use the
|
|
20908
|
+
* full ARN"), so for that shape there is nothing to normalize TO.
|
|
20909
|
+
*
|
|
20910
|
+
* A refusal is a loud, fixable error, and the message names the exact command
|
|
20911
|
+
* that prints the name to pass instead.
|
|
20912
|
+
*/
|
|
20913
|
+
refuseUnwritableParameterId(input, explicit) {
|
|
20914
|
+
if (!explicit.includes(":")) return;
|
|
20915
|
+
const isArn = explicit.startsWith("arn:");
|
|
20916
|
+
const shape = isArn ? "an ARN" : "a version / label selector";
|
|
20917
|
+
const remedy = input.knownPhysicalId === explicit ? `pass the parameter NAME instead: --resource ${input.logicalId}=<parameterName>` : `set Properties.Name to the parameter NAME rather than ${shape}`;
|
|
20918
|
+
const whyNotDerived = isArn ? " cdkd deliberately does NOT derive the name from the ARN: the leading \"/\" is not recoverable (arn:...:parameter/foo is the ARN of BOTH the simple name \"foo\" and the hierarchical name \"/foo\"), and a parameter shared from another account has no name form at all — so a derived name could silently address a DIFFERENT parameter." : "";
|
|
20919
|
+
throw new ProvisioningError(`Cannot adopt SSM parameter ${input.logicalId} from ${shape} ('${explicit}'): cdkd records a parameter's NAME as its physical id, because SSM's write APIs accept only the name (PutParameter and DeleteParameter both reject an ARN, and a name cannot contain ':'), so the next cdkd deploy and cdkd destroy would fail with a ValidationException. ${remedy}.${whyNotDerived} Read the name AWS holds with: aws ssm get-parameter --name '${explicit}' --query Parameter.Name --output text`, input.resourceType, input.logicalId);
|
|
20920
|
+
}
|
|
20921
|
+
/**
|
|
20745
20922
|
* Adopt an existing SSM parameter into cdkd state.
|
|
20746
20923
|
*
|
|
20747
20924
|
* SSM physical IDs ARE the parameter names (`/foo/bar`). The CDK template
|
|
@@ -20749,19 +20926,25 @@ var SSMParameterProvider = class {
|
|
|
20749
20926
|
* covers most cases.
|
|
20750
20927
|
*
|
|
20751
20928
|
* Lookup order:
|
|
20752
|
-
* 1. `--resource` override or `Properties.Name` →
|
|
20929
|
+
* 1. `--resource` override or `Properties.Name` → refuse it outright when it
|
|
20930
|
+
* is not a writable NAME (see {@link refuseUnwritableParameterId} — note
|
|
20931
|
+
* `GetParameter` accepts an ARN and a `name:version` selector while every
|
|
20932
|
+
* write API rejects both), else verify via `GetParameter`.
|
|
20753
20933
|
*/
|
|
20754
20934
|
async import(input) {
|
|
20755
20935
|
const explicit = resolveExplicitPhysicalId(input, "Name");
|
|
20756
|
-
if (explicit)
|
|
20757
|
-
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20763
|
-
|
|
20764
|
-
|
|
20936
|
+
if (explicit) {
|
|
20937
|
+
this.refuseUnwritableParameterId(input, explicit);
|
|
20938
|
+
try {
|
|
20939
|
+
const arn = (await this.ssmClient.send(new GetParameterCommand({ Name: explicit }))).Parameter?.ARN;
|
|
20940
|
+
return {
|
|
20941
|
+
physicalId: explicit,
|
|
20942
|
+
attributes: { ...arn !== void 0 && { Arn: arn } }
|
|
20943
|
+
};
|
|
20944
|
+
} catch (err) {
|
|
20945
|
+
if (err instanceof ParameterNotFound) return null;
|
|
20946
|
+
throw err;
|
|
20947
|
+
}
|
|
20765
20948
|
}
|
|
20766
20949
|
return null;
|
|
20767
20950
|
}
|
|
@@ -33740,16 +33923,20 @@ var RDSProvider = class {
|
|
|
33740
33923
|
});
|
|
33741
33924
|
try {
|
|
33742
33925
|
const tags = this.buildTags(properties);
|
|
33743
|
-
await this.getClient().send(new CreateDBSubnetGroupCommand({
|
|
33926
|
+
const created = await this.getClient().send(new CreateDBSubnetGroupCommand({
|
|
33744
33927
|
DBSubnetGroupName: dbSubnetGroupName,
|
|
33745
33928
|
DBSubnetGroupDescription: properties["DBSubnetGroupDescription"] || `Subnet group for ${logicalId}`,
|
|
33746
33929
|
SubnetIds: properties["SubnetIds"],
|
|
33747
33930
|
...tags.length > 0 && { Tags: tags }
|
|
33748
33931
|
}));
|
|
33749
33932
|
this.logger.debug(`Successfully created DBSubnetGroup ${logicalId}: ${dbSubnetGroupName}`);
|
|
33933
|
+
const dbSubnetGroupArn = created.DBSubnetGroup?.DBSubnetGroupArn;
|
|
33750
33934
|
return {
|
|
33751
33935
|
physicalId: dbSubnetGroupName,
|
|
33752
|
-
attributes: {
|
|
33936
|
+
attributes: {
|
|
33937
|
+
DBSubnetGroupName: dbSubnetGroupName,
|
|
33938
|
+
...dbSubnetGroupArn !== void 0 && { DBSubnetGroupArn: dbSubnetGroupArn }
|
|
33939
|
+
}
|
|
33753
33940
|
};
|
|
33754
33941
|
} catch (error) {
|
|
33755
33942
|
const cause = error instanceof Error ? error : void 0;
|
|
@@ -33773,7 +33960,10 @@ var RDSProvider = class {
|
|
|
33773
33960
|
return {
|
|
33774
33961
|
physicalId,
|
|
33775
33962
|
wasReplaced: false,
|
|
33776
|
-
attributes: {
|
|
33963
|
+
attributes: {
|
|
33964
|
+
DBSubnetGroupName: physicalId,
|
|
33965
|
+
...arn !== void 0 && { DBSubnetGroupArn: arn }
|
|
33966
|
+
}
|
|
33777
33967
|
};
|
|
33778
33968
|
} catch (error) {
|
|
33779
33969
|
const cause = error instanceof Error ? error : void 0;
|
|
@@ -34379,10 +34569,10 @@ var RDSProvider = class {
|
|
|
34379
34569
|
async importDBSubnetGroup(input) {
|
|
34380
34570
|
const explicit = resolveExplicitPhysicalId(input, "DBSubnetGroupName");
|
|
34381
34571
|
if (explicit) try {
|
|
34382
|
-
await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: explicit }));
|
|
34572
|
+
const arn = (await this.getClient().send(new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: explicit }))).DBSubnetGroups?.[0]?.DBSubnetGroupArn;
|
|
34383
34573
|
return {
|
|
34384
34574
|
physicalId: explicit,
|
|
34385
|
-
attributes: {}
|
|
34575
|
+
attributes: { ...arn !== void 0 && { DBSubnetGroupArn: arn } }
|
|
34386
34576
|
};
|
|
34387
34577
|
} catch (err) {
|
|
34388
34578
|
if (err.name === "DBSubnetGroupNotFoundFault") return null;
|
|
@@ -68668,12 +68858,21 @@ function indexGrandchildTemplatePaths(childTemplate, childTemplatePath) {
|
|
|
68668
68858
|
* `--stack-region`, bucket resolution failure) logs at warn and returns
|
|
68669
68859
|
* `undefined`. Auth failures and other genuine errors propagate.
|
|
68670
68860
|
*
|
|
68671
|
-
*
|
|
68861
|
+
* No lock acquisition and no `state.json` write path here. That is NOT the
|
|
68862
|
+
* same as "read-only against the bucket", and the difference was mis-stated
|
|
68863
|
+
* until issue #1836: {@link buildCrossStackResolver}'s exports-index lookup
|
|
68864
|
+
* goes through {@link ExportIndexStore}, whose `load` REBUILDS the index from
|
|
68865
|
+
* the per-stack state files on a miss and PUTs the rebuilt object
|
|
68866
|
+
* (`cdkd/_index/{region}/exports.json`). So an `Fn::ImportValue` lookup from a
|
|
68867
|
+
* `cdkd local` command can write that one derived key. Keeping the index KEY
|
|
68868
|
+
* the same spelling the state records carry is what keeps that write CORRECT
|
|
68869
|
+
* rather than an empty index — see the note on `consumerRegion` below.
|
|
68672
68870
|
*/
|
|
68673
68871
|
async function loadStateForStack(stackName, synthRegion, opts) {
|
|
68674
68872
|
const logger = getLogger();
|
|
68675
68873
|
const prefix = opts.logPrefix ?? "--from-state";
|
|
68676
|
-
const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
|
|
68874
|
+
const region = canonicalizeRegion(opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1");
|
|
68875
|
+
const clientRegion = canonicalizeRegion(opts.region) || void 0;
|
|
68677
68876
|
let stateBucket;
|
|
68678
68877
|
try {
|
|
68679
68878
|
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
|
|
@@ -68682,7 +68881,7 @@ async function loadStateForStack(stackName, synthRegion, opts) {
|
|
|
68682
68881
|
return;
|
|
68683
68882
|
}
|
|
68684
68883
|
const awsClients = new AwsClients({
|
|
68685
|
-
...
|
|
68884
|
+
...clientRegion !== void 0 && { region: clientRegion },
|
|
68686
68885
|
...opts.profile !== void 0 && { profile: opts.profile }
|
|
68687
68886
|
});
|
|
68688
68887
|
setAwsClients(awsClients);
|
|
@@ -68692,7 +68891,7 @@ async function loadStateForStack(stackName, synthRegion, opts) {
|
|
|
68692
68891
|
prefix: opts.statePrefix
|
|
68693
68892
|
};
|
|
68694
68893
|
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
68695
|
-
...
|
|
68894
|
+
...clientRegion !== void 0 && { region: clientRegion },
|
|
68696
68895
|
...opts.profile !== void 0 && { profile: opts.profile }
|
|
68697
68896
|
});
|
|
68698
68897
|
await stateBackend.verifyBucketExists();
|
|
@@ -68701,15 +68900,58 @@ async function loadStateForStack(stackName, synthRegion, opts) {
|
|
|
68701
68900
|
logger.warn(`${prefix}: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back.`);
|
|
68702
68901
|
return;
|
|
68703
68902
|
}
|
|
68903
|
+
/**
|
|
68904
|
+
* Issue #1836: EXACT match first, case-insensitive second.
|
|
68905
|
+
*
|
|
68906
|
+
* `S3StateBackend.listStacks` dedupes on the EXACT `{stack}\0{region}` pair,
|
|
68907
|
+
* so `cdkd/MyStack/US-EAST-1/state.json` and
|
|
68908
|
+
* `cdkd/MyStack/us-east-1/state.json` are two DISTINCT refs — and
|
|
68909
|
+
* ListObjectsV2 returns them in ASCII order, i.e. the upper-cased one FIRST.
|
|
68910
|
+
* A fold-only `find` therefore hands `--stack-region us-east-1` the OTHER
|
|
68911
|
+
* record, silently reading state the user did not name; the pre-fold `===`
|
|
68912
|
+
* got that case right. So the fold is the RECOVERY for a case mismatch, never
|
|
68913
|
+
* an override of a spelling that exists verbatim.
|
|
68914
|
+
*
|
|
68915
|
+
* `candidate` is therefore the spelling the USER supplied, not the folded
|
|
68916
|
+
* one (round 3 of the #1836 review): the fold at each command's handler
|
|
68917
|
+
* entry and at the `--from-state` factory is what an SDK client needs, but
|
|
68918
|
+
* feeding it to this compare made the exact-match rule unreachable from any
|
|
68919
|
+
* real CLI invocation — every candidate was already canonical, so `exact`
|
|
68920
|
+
* could only ever match an already-canonical record and `--stack-region
|
|
68921
|
+
* US-EAST-1` read the `us-east-1` one while REPORTING it as the exact
|
|
68922
|
+
* spelling. `opts.rawStackRegion` carries the unfolded value here; the
|
|
68923
|
+
* recovery arm folds both sides itself, so nothing upstream has to.
|
|
68924
|
+
*
|
|
68925
|
+
* When more than one ref is canonical-equal the choice is announced, because
|
|
68926
|
+
* either answer reads some record the user did not spell out in full and the
|
|
68927
|
+
* silence is what made the original miss hard to diagnose. The wording
|
|
68928
|
+
* states which of the two rules decided it — and does so from the same
|
|
68929
|
+
* `exact` binding the choice was made from, so it cannot claim an exact
|
|
68930
|
+
* match the compare did not make.
|
|
68931
|
+
*/
|
|
68932
|
+
const findByRegion = (candidate, source) => {
|
|
68933
|
+
const exact = refs.find((r) => r.region === candidate);
|
|
68934
|
+
const folded = refs.filter((r) => canonicalizeRegion(r.region) === canonicalizeRegion(candidate));
|
|
68935
|
+
const chosen = exact ?? folded[0];
|
|
68936
|
+
if (folded.length > 1 && chosen) {
|
|
68937
|
+
const how = exact ? `matches ${source} '${candidate}' exactly` : `no record spells ${source} '${candidate}' exactly, so this is a case-insensitive recovery`;
|
|
68938
|
+
logger.warn(`${prefix}: stack '${stackName}' has state under ${folded.length} case-variant spellings of ${source} '${candidate}' (${folded.map((r) => r.region ?? "(legacy)").join(", ")}). Reading '${chosen.region ?? "(legacy)"}' — ${how}.`);
|
|
68939
|
+
}
|
|
68940
|
+
return chosen;
|
|
68941
|
+
};
|
|
68942
|
+
let synthMatch;
|
|
68943
|
+
if (!opts.stackRegion && synthRegion) synthMatch = findByRegion(synthRegion, "the synth-derived stack region");
|
|
68704
68944
|
let targetRegion;
|
|
68705
68945
|
if (opts.stackRegion) {
|
|
68706
|
-
|
|
68946
|
+
const requestedRegion = opts.rawStackRegion ?? opts.stackRegion;
|
|
68947
|
+
const found = findByRegion(requestedRegion, "--stack-region");
|
|
68948
|
+
if (!found) {
|
|
68707
68949
|
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
68708
|
-
logger.warn(`${prefix}: stack '${stackName}' has no state in region '${
|
|
68950
|
+
logger.warn(`${prefix}: stack '${stackName}' has no state in region '${requestedRegion}' (available: ${seen}). Falling back.`);
|
|
68709
68951
|
return;
|
|
68710
68952
|
}
|
|
68711
|
-
targetRegion =
|
|
68712
|
-
} else if (synthRegion &&
|
|
68953
|
+
targetRegion = found.region ?? requestedRegion;
|
|
68954
|
+
} else if (synthRegion && synthMatch) targetRegion = synthMatch.region ?? synthRegion;
|
|
68713
68955
|
else if (refs.length === 1) targetRegion = refs[0].region ?? synthRegion ?? region;
|
|
68714
68956
|
else {
|
|
68715
68957
|
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
@@ -68758,7 +69000,8 @@ async function loadStateForStack(stackName, synthRegion, opts) {
|
|
|
68758
69000
|
async function loadBootstrapContainerRepo(synthRegion, opts) {
|
|
68759
69001
|
const logger = getLogger();
|
|
68760
69002
|
const prefix = opts.logPrefix ?? "--from-state";
|
|
68761
|
-
const
|
|
69003
|
+
const rawRegion = opts.region ?? opts.rawStackRegion ?? opts.stackRegion ?? synthRegion ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? "us-east-1";
|
|
69004
|
+
const region = canonicalizeRegion(rawRegion);
|
|
68762
69005
|
let stateBucket;
|
|
68763
69006
|
try {
|
|
68764
69007
|
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
|
|
@@ -68766,8 +69009,9 @@ async function loadBootstrapContainerRepo(synthRegion, opts) {
|
|
|
68766
69009
|
logger.debug(`${prefix}: could not resolve state bucket for the bootstrap-marker read: ${err instanceof Error ? err.message : String(err)}. Falling back to conventional asset-repo names.`);
|
|
68767
69010
|
return;
|
|
68768
69011
|
}
|
|
69012
|
+
const clientRegion = canonicalizeRegion(opts.region) || void 0;
|
|
68769
69013
|
const awsClients = new AwsClients({
|
|
68770
|
-
...
|
|
69014
|
+
...clientRegion !== void 0 && { region: clientRegion },
|
|
68771
69015
|
...opts.profile !== void 0 && { profile: opts.profile }
|
|
68772
69016
|
});
|
|
68773
69017
|
setAwsClients(awsClients);
|
|
@@ -68777,16 +69021,25 @@ async function loadBootstrapContainerRepo(synthRegion, opts) {
|
|
|
68777
69021
|
prefix: opts.statePrefix
|
|
68778
69022
|
};
|
|
68779
69023
|
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
68780
|
-
...
|
|
69024
|
+
...clientRegion !== void 0 && { region: clientRegion },
|
|
68781
69025
|
...opts.profile !== void 0 && { profile: opts.profile }
|
|
68782
69026
|
});
|
|
68783
69027
|
const markerKey = getBootstrapMarkerKey(region);
|
|
68784
|
-
|
|
69028
|
+
let resolvedKey = markerKey;
|
|
69029
|
+
let body = await stateBackend.getRawObject(markerKey);
|
|
69030
|
+
if (body === null && rawRegion !== region) {
|
|
69031
|
+
const rawKey = getBootstrapMarkerKey(rawRegion);
|
|
69032
|
+
body = await stateBackend.getRawObject(rawKey);
|
|
69033
|
+
if (body !== null) {
|
|
69034
|
+
resolvedKey = rawKey;
|
|
69035
|
+
logger.debug(`${prefix}: bootstrap marker found at the un-folded key '${rawKey}' (no '${markerKey}') — an upper-cased region was used at 'cdkd bootstrap' time.`);
|
|
69036
|
+
}
|
|
69037
|
+
}
|
|
68785
69038
|
if (body === null) {
|
|
68786
|
-
logger.debug(`${prefix}: no bootstrap marker at '${markerKey}' in bucket '${stateBucket}' — assuming conventional asset-repo names.`);
|
|
69039
|
+
logger.debug(`${prefix}: no bootstrap marker at '${markerKey}'${rawRegion !== region ? ` (nor '${getBootstrapMarkerKey(rawRegion)}')` : ""} in bucket '${stateBucket}' — assuming conventional asset-repo names.`);
|
|
68787
69040
|
return;
|
|
68788
69041
|
}
|
|
68789
|
-
const marker = parseBootstrapMarker(body,
|
|
69042
|
+
const marker = parseBootstrapMarker(body, resolvedKey);
|
|
68790
69043
|
logger.debug(`${prefix}: bootstrap marker for ${region} names container repo '${marker.containerRepo}'.`);
|
|
68791
69044
|
return marker.containerRepo;
|
|
68792
69045
|
} catch (err) {
|
|
@@ -68825,13 +69078,14 @@ async function buildCrossStackResolver(consumerRegion, opts) {
|
|
|
68825
69078
|
const prefix = opts.logPrefix ?? "--from-state";
|
|
68826
69079
|
let stateBucket;
|
|
68827
69080
|
try {
|
|
68828
|
-
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, consumerRegion);
|
|
69081
|
+
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, canonicalizeRegion(consumerRegion));
|
|
68829
69082
|
} catch (err) {
|
|
68830
69083
|
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.`);
|
|
68831
69084
|
return;
|
|
68832
69085
|
}
|
|
69086
|
+
const clientRegion = canonicalizeRegion(opts.region) || void 0;
|
|
68833
69087
|
const awsClients = new AwsClients({
|
|
68834
|
-
...
|
|
69088
|
+
...clientRegion !== void 0 && { region: clientRegion },
|
|
68835
69089
|
...opts.profile !== void 0 && { profile: opts.profile }
|
|
68836
69090
|
});
|
|
68837
69091
|
const stateConfig = {
|
|
@@ -68839,7 +69093,7 @@ async function buildCrossStackResolver(consumerRegion, opts) {
|
|
|
68839
69093
|
prefix: opts.statePrefix
|
|
68840
69094
|
};
|
|
68841
69095
|
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
68842
|
-
...
|
|
69096
|
+
...clientRegion !== void 0 && { region: clientRegion },
|
|
68843
69097
|
...opts.profile !== void 0 && { profile: opts.profile }
|
|
68844
69098
|
});
|
|
68845
69099
|
try {
|
|
@@ -68871,9 +69125,10 @@ async function buildCrossStackResolver(consumerRegion, opts) {
|
|
|
68871
69125
|
logger.debug(`${prefix}: failed to list stacks during Fn::ImportValue fallback for '${exportName}': ${err instanceof Error ? err.message : String(err)}`);
|
|
68872
69126
|
return;
|
|
68873
69127
|
}
|
|
69128
|
+
const canonicalConsumerRegion = canonicalizeRegion(consumerRegion);
|
|
68874
69129
|
for (const ref of refs) {
|
|
68875
69130
|
const region = ref.region ?? consumerRegion;
|
|
68876
|
-
if (region !==
|
|
69131
|
+
if (canonicalizeRegion(region) !== canonicalConsumerRegion) continue;
|
|
68877
69132
|
try {
|
|
68878
69133
|
const got = await stateBackend.getState(ref.stackName, region);
|
|
68879
69134
|
if (!got || !got.state.outputs) continue;
|
|
@@ -68890,14 +69145,31 @@ async function buildCrossStackResolver(consumerRegion, opts) {
|
|
|
68890
69145
|
}
|
|
68891
69146
|
},
|
|
68892
69147
|
async resolveGetStackOutput(producerStack, producerRegion, outputName) {
|
|
68893
|
-
|
|
68894
|
-
const got = await stateBackend.getState(producerStack, producerRegion);
|
|
69148
|
+
const readOutput = (got) => {
|
|
68895
69149
|
if (!got || !got.state.outputs) return void 0;
|
|
68896
69150
|
if (!(outputName in got.state.outputs)) return void 0;
|
|
68897
69151
|
const value = got.state.outputs[outputName];
|
|
68898
69152
|
if (typeof value === "string") return value;
|
|
68899
69153
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
68900
69154
|
return JSON.stringify(value);
|
|
69155
|
+
};
|
|
69156
|
+
try {
|
|
69157
|
+
const got = await stateBackend.getState(producerStack, producerRegion);
|
|
69158
|
+
if (got) return readOutput(got);
|
|
69159
|
+
const canonicalProducerRegion = canonicalizeRegion(producerRegion);
|
|
69160
|
+
const refs = await stateBackend.listStacks();
|
|
69161
|
+
for (const ref of refs) {
|
|
69162
|
+
if (ref.stackName !== producerStack) continue;
|
|
69163
|
+
const region = ref.region ?? producerRegion;
|
|
69164
|
+
if (region === producerRegion) continue;
|
|
69165
|
+
if (canonicalizeRegion(region) !== canonicalProducerRegion) continue;
|
|
69166
|
+
const recovered = readOutput(await stateBackend.getState(producerStack, region));
|
|
69167
|
+
if (recovered !== void 0) {
|
|
69168
|
+
logger.debug(`${prefix}: Fn::GetStackOutput '${producerStack}.${outputName}' resolved from the case-variant state record '${region}' (no record spells '${producerRegion}' exactly).`);
|
|
69169
|
+
return recovered;
|
|
69170
|
+
}
|
|
69171
|
+
}
|
|
69172
|
+
return;
|
|
68901
69173
|
} catch (err) {
|
|
68902
69174
|
logger.debug(`${prefix}: state read failed for Fn::GetStackOutput '${producerStack}.${outputName}' (${producerRegion}): ${err instanceof Error ? err.message : String(err)}`);
|
|
68903
69175
|
return;
|
|
@@ -68937,6 +69209,7 @@ var S3LocalStateProvider = class {
|
|
|
68937
69209
|
const loaded = await loadStateForStack(stackName, synthRegion, {
|
|
68938
69210
|
statePrefix: this.opts.statePrefix,
|
|
68939
69211
|
...this.opts.stackRegion !== void 0 && { stackRegion: this.opts.stackRegion },
|
|
69212
|
+
...this.opts.rawStackRegion !== void 0 && { rawStackRegion: this.opts.rawStackRegion },
|
|
68940
69213
|
...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
|
|
68941
69214
|
...this.opts.region !== void 0 && { region: this.opts.region },
|
|
68942
69215
|
...this.opts.profile !== void 0 && { profile: this.opts.profile }
|
|
@@ -69003,12 +69276,16 @@ var S3LocalStateProvider = class {
|
|
|
69003
69276
|
*/
|
|
69004
69277
|
const fromStateFactory = (options) => {
|
|
69005
69278
|
const opts = options;
|
|
69279
|
+
const clientRegion = canonicalizeRegion(opts.region) || void 0;
|
|
69280
|
+
const stackRegion = canonicalizeRegion(opts.stackRegion) || void 0;
|
|
69281
|
+
const rawStackRegion = opts.rawStackRegion || opts.stackRegion || void 0;
|
|
69006
69282
|
return new S3LocalStateProvider({
|
|
69007
69283
|
statePrefix: opts.statePrefix,
|
|
69008
69284
|
...opts.stateBucket !== void 0 && { stateBucket: opts.stateBucket },
|
|
69009
|
-
...
|
|
69285
|
+
...clientRegion !== void 0 && { region: clientRegion },
|
|
69010
69286
|
...opts.profile !== void 0 && { profile: opts.profile },
|
|
69011
|
-
...
|
|
69287
|
+
...stackRegion !== void 0 && { stackRegion },
|
|
69288
|
+
...rawStackRegion !== void 0 && { rawStackRegion }
|
|
69012
69289
|
});
|
|
69013
69290
|
};
|
|
69014
69291
|
/**
|
|
@@ -72190,6 +72467,10 @@ async function localStartApiCommand(target, options) {
|
|
|
72190
72467
|
const logger = getLogger();
|
|
72191
72468
|
if (options.verbose) logger.setLevel("debug");
|
|
72192
72469
|
if (options.region !== void 0) options.region = canonicalizeRegion(options.region);
|
|
72470
|
+
if (options.stackRegion !== void 0) {
|
|
72471
|
+
options.rawStackRegion = options.stackRegion;
|
|
72472
|
+
options.stackRegion = canonicalizeRegion(options.stackRegion);
|
|
72473
|
+
}
|
|
72193
72474
|
let apiFilter = target;
|
|
72194
72475
|
if (options.api !== void 0) {
|
|
72195
72476
|
if (target !== void 0) throw new Error(`Cannot specify both positional target ('${target}') and --api flag ('${options.api}'). Use one or the other. The positional form is preferred — '--api' is a deprecated alias.`);
|
|
@@ -74532,6 +74813,10 @@ async function localRunTaskCommand(target, options) {
|
|
|
74532
74813
|
if (options.verbose) logger.setLevel("debug");
|
|
74533
74814
|
warnIfDeprecatedRegion(options);
|
|
74534
74815
|
if (options.region !== void 0) options.region = canonicalizeRegion(options.region);
|
|
74816
|
+
if (options.stackRegion !== void 0) {
|
|
74817
|
+
options.rawStackRegion = options.stackRegion;
|
|
74818
|
+
options.stackRegion = canonicalizeRegion(options.stackRegion);
|
|
74819
|
+
}
|
|
74535
74820
|
const state = createEcsRunState();
|
|
74536
74821
|
let sigintHandler;
|
|
74537
74822
|
let sigintCount = 0;
|
|
@@ -74576,12 +74861,12 @@ async function localRunTaskCommand(target, options) {
|
|
|
74576
74861
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
74577
74862
|
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
74578
74863
|
stateProvider = createLocalStateProvider$1(options, candidate?.stackName ?? "", candidate?.region);
|
|
74579
|
-
const imageContext = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
|
|
74864
|
+
const { context: imageContext, stateRecordRegion } = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
|
|
74580
74865
|
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
74581
74866
|
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
74582
74867
|
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
|
|
74583
74868
|
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
74584
|
-
const consumerRegion = options
|
|
74869
|
+
const consumerRegion = resolveEcsConsumerRegion(stateRecordRegion, options, task.stack.region);
|
|
74585
74870
|
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
74586
74871
|
if (resolver) await applyCrossStackResolverToTask(task, {
|
|
74587
74872
|
resources: imageContext?.stateResources ?? {},
|
|
@@ -74691,9 +74976,48 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
74691
74976
|
}
|
|
74692
74977
|
}
|
|
74693
74978
|
/**
|
|
74979
|
+
* Region the cross-stack resolver is scoped to for `cdkd local run-task`
|
|
74980
|
+
* (issue #1836).
|
|
74981
|
+
*
|
|
74982
|
+
* This value does THREE things (round 3 said TWO and was wrong — corrected in
|
|
74983
|
+
* round 4), and the first is why the state record's own spelling outranks the env
|
|
74984
|
+
* chain:
|
|
74985
|
+
*
|
|
74986
|
+
* 1. it becomes the exports-index KEY `cdkd/_index/{region}/exports.json`;
|
|
74987
|
+
* 2. it is the same-region filter of the index-miss per-stack scan; and
|
|
74988
|
+
* 3. via `SubstitutionContext.consumerRegion`, it is cdk-local's DEFAULT producer
|
|
74989
|
+
* region for an `Fn::GetStackOutput` that carries no explicit `Region` — i.e.
|
|
74990
|
+
* it becomes the `cdkd/{producer}/{region}/state.json` key that intrinsic
|
|
74991
|
+
* reads.
|
|
74992
|
+
*
|
|
74993
|
+
* All three land in `buildCrossStackResolver` (`local-state-loader.ts`), whose
|
|
74994
|
+
* `resolveImport` scan (2) and — since round 4 — `resolveGetStackOutput` (3) fold
|
|
74995
|
+
* BOTH sides for the comparison / recovery, while (1) is deliberately raw.
|
|
74996
|
+
*
|
|
74997
|
+
* `local invoke` / `local invoke-agentcore` both pass `loaded.region` there; this
|
|
74998
|
+
* command passed a FOLDED env-chain value instead, so the two commands keyed one
|
|
74999
|
+
* index differently — and a key the state records do not spell makes
|
|
75000
|
+
* `ExportIndexStore`'s rebuild filter match zero refs and PUT an EMPTY index,
|
|
75001
|
+
* permanently degrading every `Fn::ImportValue` to the O(N) scan. Keyed by a
|
|
75002
|
+
* record's spelling the filter always matches.
|
|
75003
|
+
*
|
|
75004
|
+
* The env-chain arm remains for the case where no state record was loaded at all
|
|
75005
|
+
* (nothing in the task needed one), and it stays FOLDED for the reason the rest
|
|
75006
|
+
* of this file folds: both env-var links escape the handler-entry `--region`
|
|
75007
|
+
* fold, and a raw upper-cased spelling reaches an S3 key and an SDK client.
|
|
75008
|
+
*
|
|
75009
|
+
* @internal exported for unit tests.
|
|
75010
|
+
*/
|
|
75011
|
+
function resolveEcsConsumerRegion(stateRecordRegion, options, synthRegion) {
|
|
75012
|
+
if (stateRecordRegion) return stateRecordRegion;
|
|
75013
|
+
return canonicalizeRegion((options.region || process.env["AWS_REGION"]) ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1");
|
|
75014
|
+
}
|
|
75015
|
+
/**
|
|
74694
75016
|
* Build the substitution context the ECS task resolver consumes (issue
|
|
74695
|
-
* #264)
|
|
74696
|
-
*
|
|
75017
|
+
* #264), plus the region of the state record it loaded (if any — issue #1836;
|
|
75018
|
+
* see {@link resolveEcsConsumerRegion} for what reads it). `context` is
|
|
75019
|
+
* `undefined` when no container's `Image` field needs substitution — the
|
|
75020
|
+
* resolver behaves as before in that case.
|
|
74697
75021
|
*
|
|
74698
75022
|
* Tier 1 (pseudo parameters) fires `sts:GetCallerIdentity` once for
|
|
74699
75023
|
* `${AWS::AccountId}`; region / partition / URL suffix come from the CLI
|
|
@@ -74712,10 +75036,11 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
74712
75036
|
*/
|
|
74713
75037
|
async function buildEcsImageResolutionContext$1(candidate, stateProvider, options) {
|
|
74714
75038
|
const logger = getLogger();
|
|
74715
|
-
if (!candidate) return
|
|
75039
|
+
if (!candidate) return {};
|
|
74716
75040
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
74717
|
-
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution && !needs.needsAssetRepoMarker) return;
|
|
75041
|
+
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution && !needs.needsAssetRepoMarker) return {};
|
|
74718
75042
|
const ctx = {};
|
|
75043
|
+
let stateRecordRegion;
|
|
74719
75044
|
const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
|
|
74720
75045
|
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
74721
75046
|
const region = canonicalizeRegion(options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region);
|
|
@@ -74739,13 +75064,17 @@ async function buildEcsImageResolutionContext$1(candidate, stateProvider, option
|
|
|
74739
75064
|
const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
|
|
74740
75065
|
if (stateProvider && wantsState) {
|
|
74741
75066
|
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
74742
|
-
if (loaded)
|
|
75067
|
+
if (loaded) {
|
|
75068
|
+
ctx.stateResources = loaded.resources;
|
|
75069
|
+
stateRecordRegion = loaded.region;
|
|
75070
|
+
}
|
|
74743
75071
|
} else if (!stateProvider && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute the deployed repository URI. Otherwise the resolver will surface its existing error.");
|
|
74744
75072
|
else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute them against deployed state. Without a state source these entries are dropped (per-key warnings will follow).");
|
|
74745
75073
|
if (needs.needsAssetRepoMarker && options.fromState) {
|
|
74746
75074
|
const containerRepo = await loadBootstrapContainerRepo(candidate.region, {
|
|
74747
75075
|
statePrefix: options.statePrefix,
|
|
74748
75076
|
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
75077
|
+
...options.rawStackRegion !== void 0 && { rawStackRegion: options.rawStackRegion },
|
|
74749
75078
|
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
74750
75079
|
...options.region !== void 0 && { region: options.region },
|
|
74751
75080
|
...options.profile !== void 0 && { profile: options.profile },
|
|
@@ -74753,7 +75082,10 @@ async function buildEcsImageResolutionContext$1(candidate, stateProvider, option
|
|
|
74753
75082
|
});
|
|
74754
75083
|
if (containerRepo !== void 0) ctx.cdkAssetContainerRepo = containerRepo;
|
|
74755
75084
|
}
|
|
74756
|
-
return
|
|
75085
|
+
return {
|
|
75086
|
+
context: ctx,
|
|
75087
|
+
...stateRecordRegion !== void 0 && { stateRecordRegion }
|
|
75088
|
+
};
|
|
74757
75089
|
}
|
|
74758
75090
|
function pickCandidateStack(stackPattern, stacks) {
|
|
74759
75091
|
if (stackPattern === null) {
|
|
@@ -75186,6 +75518,10 @@ async function localInvokeAgentCoreCommand(target, options) {
|
|
|
75186
75518
|
if (options.verbose) logger.setLevel("debug");
|
|
75187
75519
|
warnIfDeprecatedRegion(options);
|
|
75188
75520
|
if (options.region !== void 0) options.region = canonicalizeRegion(options.region);
|
|
75521
|
+
if (options.stackRegion !== void 0) {
|
|
75522
|
+
options.rawStackRegion = options.stackRegion;
|
|
75523
|
+
options.stackRegion = canonicalizeRegion(options.stackRegion);
|
|
75524
|
+
}
|
|
75189
75525
|
let containerId;
|
|
75190
75526
|
let stopLogs;
|
|
75191
75527
|
let sigintHandler;
|
|
@@ -76076,16 +76412,37 @@ function emitA2aResult(result) {
|
|
|
76076
76412
|
function platformToArchitecture(platform) {
|
|
76077
76413
|
return platform === "linux/amd64" ? "x86_64" : "arm64";
|
|
76078
76414
|
}
|
|
76415
|
+
/**
|
|
76416
|
+
* Copy the developer's AWS credentials + region into the container env for the
|
|
76417
|
+
* DEFAULT (no `--assume-role`) path.
|
|
76418
|
+
*
|
|
76419
|
+
* Issue [#1836](https://github.com/go-to-k/cdkd/issues/1836): the two REGION
|
|
76420
|
+
* entries are folded through `canonicalizeRegion` on the way in; the credentials
|
|
76421
|
+
* are copied verbatim (an AKID / session token is case-SENSITIVE, so lower-casing
|
|
76422
|
+
* the whole pass-through list would corrupt every one of them). The value lands
|
|
76423
|
+
* in the container's own `AWS_REGION`, i.e. in every SDK client the agent builds,
|
|
76424
|
+
* and SDK endpoint resolution is case-SENSITIVE (`CN-NORTH-1` resolves the
|
|
76425
|
+
* COMMERCIAL partition).
|
|
76426
|
+
*
|
|
76427
|
+
* Folding only inside `applyAgentCoreCredentialEnv`'s assume-role arm — which is
|
|
76428
|
+
* where issue #1814 stopped, and what round 3 of the #1836 review found still
|
|
76429
|
+
* open here after the same gap was closed in `local-invoke.ts` — made ONE command
|
|
76430
|
+
* yield two different container regions for the same shell: `cn-north-1` with
|
|
76431
|
+
* `--assume-role`, `CN-NORTH-1` without.
|
|
76432
|
+
*/
|
|
76079
76433
|
function forwardAwsEnv$1(env) {
|
|
76080
|
-
|
|
76434
|
+
const passThrough = [
|
|
76081
76435
|
"AWS_ACCESS_KEY_ID",
|
|
76082
76436
|
"AWS_SECRET_ACCESS_KEY",
|
|
76083
76437
|
"AWS_SESSION_TOKEN",
|
|
76084
76438
|
"AWS_REGION",
|
|
76085
76439
|
"AWS_DEFAULT_REGION"
|
|
76086
|
-
]
|
|
76440
|
+
];
|
|
76441
|
+
const regionKeys = /* @__PURE__ */ new Set(["AWS_REGION", "AWS_DEFAULT_REGION"]);
|
|
76442
|
+
for (const key of passThrough) {
|
|
76087
76443
|
const value = process.env[key];
|
|
76088
|
-
if (value
|
|
76444
|
+
if (value === void 0) continue;
|
|
76445
|
+
env[key] = regionKeys.has(key) ? canonicalizeRegion(value) : value;
|
|
76089
76446
|
}
|
|
76090
76447
|
}
|
|
76091
76448
|
async function assumeAgentCoreExecutionRole(roleArn, region) {
|
|
@@ -76388,6 +76745,10 @@ async function localInvokeCommand(target, options) {
|
|
|
76388
76745
|
if (options.verbose) logger.setLevel("debug");
|
|
76389
76746
|
warnIfDeprecatedRegion(options);
|
|
76390
76747
|
if (options.region !== void 0) options.region = canonicalizeRegion(options.region);
|
|
76748
|
+
if (options.stackRegion !== void 0) {
|
|
76749
|
+
options.rawStackRegion = options.stackRegion;
|
|
76750
|
+
options.stackRegion = canonicalizeRegion(options.stackRegion);
|
|
76751
|
+
}
|
|
76391
76752
|
let imagePlan;
|
|
76392
76753
|
let containerId;
|
|
76393
76754
|
let stopLogs;
|
|
@@ -76541,29 +76902,12 @@ async function localInvokeCommand(target, options) {
|
|
|
76541
76902
|
AWS_LAMBDA_LOG_STREAM_NAME: "local",
|
|
76542
76903
|
...envResult.resolved
|
|
76543
76904
|
};
|
|
76544
|
-
|
|
76545
|
-
|
|
76546
|
-
|
|
76547
|
-
|
|
76548
|
-
|
|
76549
|
-
|
|
76550
|
-
dockerEnv["AWS_SECRET_ACCESS_KEY"] = creds.secretAccessKey;
|
|
76551
|
-
dockerEnv["AWS_SESSION_TOKEN"] = creds.sessionToken;
|
|
76552
|
-
if (stsRegion) dockerEnv["AWS_REGION"] = stsRegion;
|
|
76553
|
-
assumeSucceeded = true;
|
|
76554
|
-
} catch (err) {
|
|
76555
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
76556
|
-
logger.warn(`--assume-role: STS AssumeRole(${resolvedAssumeRoleArn}) failed: ${reason}. Falling back to the developer's shell credentials.`);
|
|
76557
|
-
}
|
|
76558
|
-
}
|
|
76559
|
-
if (!assumeSucceeded) {
|
|
76560
|
-
forwardAwsEnv(dockerEnv);
|
|
76561
|
-
applyProfileCredentialsOverlay(dockerEnv, profileCredentials, false);
|
|
76562
|
-
if (profileCredsFile) {
|
|
76563
|
-
dockerEnv["AWS_SHARED_CREDENTIALS_FILE"] = profileCredsFile.containerPath;
|
|
76564
|
-
dockerEnv["AWS_PROFILE"] = profileCredsFile.profileName;
|
|
76565
|
-
}
|
|
76566
|
-
}
|
|
76905
|
+
await applyLambdaCredentialEnv(dockerEnv, {
|
|
76906
|
+
...resolvedAssumeRoleArn && { assumeRoleArn: resolvedAssumeRoleArn },
|
|
76907
|
+
...options.region !== void 0 && { region: options.region },
|
|
76908
|
+
...profileCredentials && { profileCredentials },
|
|
76909
|
+
...profileCredsFile && { profileCredsFile }
|
|
76910
|
+
});
|
|
76567
76911
|
let debugPort;
|
|
76568
76912
|
if (options.debugPort) {
|
|
76569
76913
|
debugPort = Number(options.debugPort);
|
|
@@ -76963,6 +77307,55 @@ async function readStdin() {
|
|
|
76963
77307
|
return Buffer.concat(chunks).toString("utf-8");
|
|
76964
77308
|
}
|
|
76965
77309
|
/**
|
|
77310
|
+
* Inject AWS credentials into the container env. Precedence:
|
|
77311
|
+
* 1. `--assume-role` → STS-issued temp creds for the resolved ARN (on
|
|
77312
|
+
* STS failure, warn + fall through to dev creds). Swapping the
|
|
77313
|
+
* developer's credentials for the function's deployed execution role
|
|
77314
|
+
* degrades rather than hard-errors: this is a developer-loop tool, not
|
|
77315
|
+
* a security boundary, and the most common cause is the role's
|
|
77316
|
+
* `AssumeRolePolicy` not trusting the developer's IAM principal (a
|
|
77317
|
+
* config gap, not a cdkd bug).
|
|
77318
|
+
* 2. dev shell creds (`forwardAwsEnv` — which folds the region case for the
|
|
77319
|
+
* same reason arm 1 does) + `--profile` overlay
|
|
77320
|
+
* ({@link applyProfileCredentialsOverlay}) + the bind-mounted
|
|
77321
|
+
* credentials-file env so handler `fromIni({ profile })` resolves.
|
|
77322
|
+
* Applied in that order: the overlay intentionally wins over whatever
|
|
77323
|
+
* `forwardAwsEnv` copied, and the credentials-file env is additive.
|
|
77324
|
+
*
|
|
77325
|
+
* Extracted from `localInvokeCommand`'s body by issue
|
|
77326
|
+
* [#1836](https://github.com/go-to-k/cdkd/issues/1836) and shaped to mirror
|
|
77327
|
+
* `applyAgentCoreCredentialEnv` in `local-invoke-agentcore.ts` (the sibling
|
|
77328
|
+
* issue [#1814](https://github.com/go-to-k/cdkd/issues/1814) fixed), so a unit
|
|
77329
|
+
* test can lock the STS-client region binding with a mocked STS instead of
|
|
77330
|
+
* driving the full synth + docker pipeline.
|
|
77331
|
+
*/
|
|
77332
|
+
async function applyLambdaCredentialEnv(dockerEnv, args) {
|
|
77333
|
+
const logger = getLogger();
|
|
77334
|
+
let assumeSucceeded = false;
|
|
77335
|
+
if (args.assumeRoleArn) {
|
|
77336
|
+
const stsRegion = canonicalizeRegion(args.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"]);
|
|
77337
|
+
try {
|
|
77338
|
+
const creds = await assumeLambdaExecutionRole(args.assumeRoleArn, stsRegion);
|
|
77339
|
+
dockerEnv["AWS_ACCESS_KEY_ID"] = creds.accessKeyId;
|
|
77340
|
+
dockerEnv["AWS_SECRET_ACCESS_KEY"] = creds.secretAccessKey;
|
|
77341
|
+
dockerEnv["AWS_SESSION_TOKEN"] = creds.sessionToken;
|
|
77342
|
+
if (stsRegion) dockerEnv["AWS_REGION"] = stsRegion;
|
|
77343
|
+
assumeSucceeded = true;
|
|
77344
|
+
} catch (err) {
|
|
77345
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
77346
|
+
logger.warn(`--assume-role: STS AssumeRole(${args.assumeRoleArn}) failed: ${reason}. Falling back to the developer's shell credentials.`);
|
|
77347
|
+
}
|
|
77348
|
+
}
|
|
77349
|
+
if (!assumeSucceeded) {
|
|
77350
|
+
forwardAwsEnv(dockerEnv);
|
|
77351
|
+
applyProfileCredentialsOverlay(dockerEnv, args.profileCredentials, false);
|
|
77352
|
+
if (args.profileCredsFile) {
|
|
77353
|
+
dockerEnv["AWS_SHARED_CREDENTIALS_FILE"] = args.profileCredsFile.containerPath;
|
|
77354
|
+
dockerEnv["AWS_PROFILE"] = args.profileCredsFile.profileName;
|
|
77355
|
+
}
|
|
77356
|
+
}
|
|
77357
|
+
}
|
|
77358
|
+
/**
|
|
76966
77359
|
* Assume the Lambda execution role and return temporary credentials.
|
|
76967
77360
|
*
|
|
76968
77361
|
* Closes the "developer has admin creds, the deployed function has narrow
|
|
@@ -77002,17 +77395,30 @@ async function assumeLambdaExecutionRole(roleArn, region) {
|
|
|
77002
77395
|
*
|
|
77003
77396
|
* Region is inherited from `AWS_REGION` / `AWS_DEFAULT_REGION` so
|
|
77004
77397
|
* `aws.config.region` inside the handler works without extra setup.
|
|
77398
|
+
*
|
|
77399
|
+
* Issue [#1836](https://github.com/go-to-k/cdkd/issues/1836): the two REGION
|
|
77400
|
+
* entries are folded through `canonicalizeRegion` on the way in, the credentials
|
|
77401
|
+
* are copied verbatim. This is the DEFAULT path — no `--assume-role` — and it
|
|
77402
|
+
* carries the same exposure the assume-role branch had: the value lands in the
|
|
77403
|
+
* container's own `AWS_REGION`, i.e. in every SDK client the handler builds, and
|
|
77404
|
+
* SDK endpoint resolution is case-SENSITIVE (`CN-NORTH-1` resolves the
|
|
77405
|
+
* COMMERCIAL partition). Folding only inside `applyLambdaCredentialEnv`'s
|
|
77406
|
+
* assume-role arm made ONE command yield two different container regions for the
|
|
77407
|
+
* same shell — `cn-north-1` with `--assume-role`, `CN-NORTH-1` without.
|
|
77005
77408
|
*/
|
|
77006
77409
|
function forwardAwsEnv(env) {
|
|
77007
|
-
|
|
77410
|
+
const passThrough = [
|
|
77008
77411
|
"AWS_ACCESS_KEY_ID",
|
|
77009
77412
|
"AWS_SECRET_ACCESS_KEY",
|
|
77010
77413
|
"AWS_SESSION_TOKEN",
|
|
77011
77414
|
"AWS_REGION",
|
|
77012
77415
|
"AWS_DEFAULT_REGION"
|
|
77013
|
-
]
|
|
77416
|
+
];
|
|
77417
|
+
const regionKeys = /* @__PURE__ */ new Set(["AWS_REGION", "AWS_DEFAULT_REGION"]);
|
|
77418
|
+
for (const key of passThrough) {
|
|
77014
77419
|
const value = process.env[key];
|
|
77015
|
-
if (value
|
|
77420
|
+
if (value === void 0) continue;
|
|
77421
|
+
env[key] = regionKeys.has(key) ? canonicalizeRegion(value) : value;
|
|
77016
77422
|
}
|
|
77017
77423
|
}
|
|
77018
77424
|
/**
|
|
@@ -78258,7 +78664,7 @@ function createMigrateCommand() {
|
|
|
78258
78664
|
*/
|
|
78259
78665
|
function buildProgram() {
|
|
78260
78666
|
const program = new Command();
|
|
78261
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.
|
|
78667
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.14");
|
|
78262
78668
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
78263
78669
|
const { profile } = actionCommand.optsWithGlobals();
|
|
78264
78670
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|