@go-to-k/cdkd 0.281.20 → 0.281.22
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-Cv9YCLsx.js → asg-provider-pvAc9MXZ.js} +2 -2
- package/dist/{asg-provider-Cv9YCLsx.js.map → asg-provider-pvAc9MXZ.js.map} +1 -1
- package/dist/cli.js +213 -54
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-C3-CvvrB.js → deploy-engine-DQd1PUUv.js} +53 -52
- package/dist/deploy-engine-DQd1PUUv.js.map +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-C3-CvvrB.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as configStringRefusal, $t as
|
|
2
|
+
import { $ as configStringRefusal, $t as MIGRATE_TMP_PREFIX, A as green, At as validateContainerRepoName, B as slowCcOperationTimeoutMs, Bt as getDefaultStateBucketName, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as PartialFailureError, Ct as rewriteTemplateAssetReferences, D as bold, Dn as StackHasActiveImportsError, Dt as getBootstrapMarkerKey, E as formatResourceLine, En as ResourceUpdateNotSupportedError, Et as ensureAssetStorage, F as clearOnUpdateRemoval, Ft as runDockerStreaming, G as getAccountInfo, Gt as resolveSkipPrefix, H as isTerminationProtectionPropagationError, Ht as resolveApp, I as ProviderRegistry, It as AssetManifestLoader, J as normalizeAwsTagsToCfn, Jt as resolveUseCdkBootstrapAssets, K as refStateLookupFromResource, Kt as resolveStateBucketWithDefault, L as findActionableSilentDrops, Lt as getDockerImageBySourceHash, M as yellow, Mt as formatDockerLoginError, N as IAMRoleProvider, Nn as normalizeAwsError, Nt as getDockerCmd, O as cyan, On as StackTerminationProtectionError, Ot as parseBootstrapMarker, P as collectInlinePolicyNamesManagedBySiblings, Pn as withErrorHandling, Pt as runDockerForeground, Q as configBooleanRefusal, Qt as CFN_TEMPLATE_URL_LIMIT, R as findSilentDropProperties, Rt as Synthesizer, S as extractDeploymentEventError, Sn as NestedStackChildDirectDestroyError, St as loadPublishableAssetManifest, T as renderStatefulReason, Tn as ResourceTimeoutError, Tt as BOOTSTRAP_MARKER_PREFIX, U as IntrinsicFunctionResolver, Ut as resolveAutoAssetStorage, V as disableInstanceApiTermination, Vt as getLegacyStateBucketName, W as cfnRefValueFromPhysicalId, Wt as resolveCaptureObservedState, X as assertRegionMatch, Xt as warnDeprecatedNoPrefixCliFlag, Y as resolveExplicitPhysicalId, Yt as stateBucketExistenceConfirmed, Z as coerceCfnBoolean, Zt as CFN_TEMPLATE_BODY_LIMIT, _ as createPreDeleteFinalSnapshot, _n as LocalInvokeBuildError$1, _t as AssetPublisher, a as DeploymentEventsStore, an as processStackMessages, at as applyRoleArnIfSet, b as unsupportedFinalSnapshotError, bt as buildAssetRedirectMap, c as replayFailedOperations, cn as AwsClients, ct as withRetry, d as IMPLICIT_DELETE_DEPENDENCIES, dn as setAwsClients, dt as DagBuilder, en as findLargeInlineResources, et as readConfigString, f as computeImplicitDeleteEdges, ft as TemplateParser, g as ccRoutedFinalSnapshotError, gn as DeployCancelledError, gt as shouldRetainResource, h as buildFinalSnapshotIdentifier, ht as rebuildClientForBucketRegion, i as DeploymentEventsReader, in as AssemblyReader, it as requireConfigString, j as red, jt as buildDockerImage, k as gray, kt as validateAssetBucketName, l as replayRollback, ln as getAwsClients, lt as isRetryableTransientError, m as PRE_DELETE_SNAPSHOT_TYPES, mt as S3StateBackend, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as expectedOwnerParam, nt as requireConfigArray, o as planFailedOps, ot as DiffCalculator, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as CdkdError, pt as LockManager, q as WAFv2WebACLProvider, qt as resolveStateBucketWithDefaultAndSource, r as DeployEngine, rn as derivePartitionAndUrlSuffix, rt as requireConfigObject, s as planRollback, sn as resolveBucketRegion, st as describeTypeWithThrottleRetry, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as uploadCfnTemplate, tt as replayWarn, u as withResourceDeadline, un as resetAwsClients, ut as isThrottlingError, v as isFinalSnapshotError, vn as LocalMigrateError, vt as stringifyValue, w as isStatefulRecreateTargetSync, wn as ProvisioningError, wt as AssetModeResolver, x as makeCanonicalizePropertiesFn, xn as MissingCdkCliError, xt as createAssetRedirectResolver, y as refusesFinalSnapshot, yn as LocalStartServiceError, yt as WorkGraph, z as CloudControlProvider, zt as synthesisStatusMessage } from "./deploy-engine-DQd1PUUv.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-pvAc9MXZ.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";
|
|
@@ -13320,6 +13320,51 @@ const TABLE_ACTIVE_WAIT_ATTEMPTS = 60;
|
|
|
13320
13320
|
* genuinely wedged capacity edit still fails in a minute rather than ten.
|
|
13321
13321
|
*/
|
|
13322
13322
|
const BILLING_FLIP_ACTIVE_WAIT_ATTEMPTS = 600;
|
|
13323
|
+
/**
|
|
13324
|
+
* Is this `WarmThroughput` VALUE one cdkd will actually put on the wire
|
|
13325
|
+
* (issue #1760)?
|
|
13326
|
+
*
|
|
13327
|
+
* The single definition of the send rule. `create()` and `update()` call it
|
|
13328
|
+
* where they used to inline `if (properties['WarmThroughput'])`, and the
|
|
13329
|
+
* bag-level {@link declaresWarmThroughput} below calls it too — so the drift
|
|
13330
|
+
* side cannot answer "the template declared one" for a value the write side
|
|
13331
|
+
* silently skips. That divergence is not hypothetical: with the two sites
|
|
13332
|
+
* spelled independently, respelling either as `!== undefined` leaves every
|
|
13333
|
+
* test green while the pair starts disagreeing on `WarmThroughput: null`, and
|
|
13334
|
+
* the disagreement re-creates the exact phantom drift this issue removed —
|
|
13335
|
+
* cdkd sends nothing, AWS still computes 12000/4000, the readback emits it as
|
|
13336
|
+
* though the template had asked, and `--revert` can never clear it because the
|
|
13337
|
+
* write gate skips the falsy value. Keep this the ONLY spelling of the rule.
|
|
13338
|
+
*
|
|
13339
|
+
* Deliberately TRUTHINESS, because that is what the wire has always done.
|
|
13340
|
+
*/
|
|
13341
|
+
function isSendableWarmThroughput(value) {
|
|
13342
|
+
return Boolean(value);
|
|
13343
|
+
}
|
|
13344
|
+
/**
|
|
13345
|
+
* Does this desired-property bag DECLARE a table-level `WarmThroughput`?
|
|
13346
|
+
*
|
|
13347
|
+
* The drift-side question ("did cdkd send one for this resource"), answered by
|
|
13348
|
+
* {@link isSendableWarmThroughput} so it cannot drift from the write path.
|
|
13349
|
+
*
|
|
13350
|
+
* An ABSENT or EMPTY bag answers TRUE: the caller supplied nothing to decide
|
|
13351
|
+
* with, so both drift consumers keep the pre-#1760 behavior (emit it, compare
|
|
13352
|
+
* it) rather than dropping a key on the strength of a bag that was never
|
|
13353
|
+
* passed. That fallback is exactly why the write sites take the VALUE-level
|
|
13354
|
+
* predicate instead of this one — handing `create()` an absent bag must send
|
|
13355
|
+
* nothing, not send `undefined`.
|
|
13356
|
+
*
|
|
13357
|
+
* Note the empty-bag arm carries the same residual as the declared arm
|
|
13358
|
+
* (issue #1768): a `properties: {}` record keeps the AWS-computed value in its
|
|
13359
|
+
* snapshot, so a `--revert` on such a resource can re-send it. Left as-is
|
|
13360
|
+
* rather than dropped, because a bag that was never populated is not evidence
|
|
13361
|
+
* the template declared nothing — and a wrong DROP here is unrecoverable
|
|
13362
|
+
* phantom drift, while the residual is a loud, per-resource revert failure.
|
|
13363
|
+
*/
|
|
13364
|
+
function declaresWarmThroughput(properties) {
|
|
13365
|
+
if (properties === void 0 || Object.keys(properties).length === 0) return true;
|
|
13366
|
+
return isSendableWarmThroughput(properties["WarmThroughput"]);
|
|
13367
|
+
}
|
|
13323
13368
|
var DynamoDBTableProvider = class {
|
|
13324
13369
|
dynamoDBClient;
|
|
13325
13370
|
logger = getLogger().child("DynamoDBTableProvider");
|
|
@@ -13376,7 +13421,7 @@ var DynamoDBTableProvider = class {
|
|
|
13376
13421
|
};
|
|
13377
13422
|
}
|
|
13378
13423
|
if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = properties["OnDemandThroughput"];
|
|
13379
|
-
if (properties["WarmThroughput"]) createParams.WarmThroughput = properties["WarmThroughput"];
|
|
13424
|
+
if (isSendableWarmThroughput(properties["WarmThroughput"])) createParams.WarmThroughput = properties["WarmThroughput"];
|
|
13380
13425
|
if (properties["StreamSpecification"]) createParams.StreamSpecification = {
|
|
13381
13426
|
StreamEnabled: true,
|
|
13382
13427
|
StreamViewType: properties["StreamSpecification"]["StreamViewType"]
|
|
@@ -13539,7 +13584,7 @@ var DynamoDBTableProvider = class {
|
|
|
13539
13584
|
}
|
|
13540
13585
|
}
|
|
13541
13586
|
if (JSON.stringify(properties["WarmThroughput"]) !== JSON.stringify(previousProperties["WarmThroughput"])) {
|
|
13542
|
-
if (properties["WarmThroughput"]) {
|
|
13587
|
+
if (isSendableWarmThroughput(properties["WarmThroughput"])) {
|
|
13543
13588
|
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
13544
13589
|
TableName: physicalId,
|
|
13545
13590
|
WarmThroughput: properties["WarmThroughput"]
|
|
@@ -14131,6 +14176,75 @@ var DynamoDBTableProvider = class {
|
|
|
14131
14176
|
}
|
|
14132
14177
|
}
|
|
14133
14178
|
/**
|
|
14179
|
+
* `AttributeDefinitions` is a SET, keyed by `AttributeName`: CFn accepts the
|
|
14180
|
+
* members in any order and `DescribeTable` returns them in an order matching
|
|
14181
|
+
* neither the request nor the template. Measured live (us-east-1,
|
|
14182
|
+
* 2026-08-13): a `CreateTable` whose input declared
|
|
14183
|
+
* `[{pk,S}, {gsipk,S}]` read back as `[{gsipk,S}, {pk,S}]` on the create
|
|
14184
|
+
* response AND on every later `DescribeTable`. The comparator compares
|
|
14185
|
+
* arrays positionally, so without this declaration the reorder is permanent
|
|
14186
|
+
* phantom drift on a table nobody touched — reachable whenever the drift
|
|
14187
|
+
* baseline is the template `properties` rather than `observedProperties`
|
|
14188
|
+
* (after a reverse-replacement rollback, which strips `observedProperties`,
|
|
14189
|
+
* or on a resource deployed before observed-capture existed; on the ordinary
|
|
14190
|
+
* path both sides come from the same readback and already agree, which is
|
|
14191
|
+
* why routine runs never surfaced it). Issue #1760, the
|
|
14192
|
+
* `AWS::DynamoDB::Table` twin of issue #1742's `AWS::DynamoDB::GlobalTable`
|
|
14193
|
+
* finding.
|
|
14194
|
+
*
|
|
14195
|
+
* `KeySchema` is deliberately NOT declared, at the table level or the index
|
|
14196
|
+
* level: it is order-SIGNIFICANT (HASH before RANGE), so sorting it would
|
|
14197
|
+
* HIDE a real key change rather than remove a phantom one.
|
|
14198
|
+
*/
|
|
14199
|
+
getDriftUnorderedPaths(resourceType) {
|
|
14200
|
+
if (resourceType !== "AWS::DynamoDB::Table") return [];
|
|
14201
|
+
return ["AttributeDefinitions"];
|
|
14202
|
+
}
|
|
14203
|
+
/**
|
|
14204
|
+
* `WarmThroughput` is reported by AWS for EVERY table, whether or not the
|
|
14205
|
+
* template ever asked for one. Measured live (us-east-1, 2026-08-13) on a
|
|
14206
|
+
* `CreateTable` whose input declared NO `WarmThroughput`:
|
|
14207
|
+
*
|
|
14208
|
+
* ```
|
|
14209
|
+
* "WarmThroughput": { "ReadUnitsPerSecond": 12000,
|
|
14210
|
+
* "WriteUnitsPerSecond": 4000, "Status": "ACTIVE" }
|
|
14211
|
+
* ```
|
|
14212
|
+
*
|
|
14213
|
+
* so `readCurrentState`'s emit-when-present guard could never do what its
|
|
14214
|
+
* comment claimed ("only on tables that set warm throughput"). That value is
|
|
14215
|
+
* AWS-computed and AWS-owned — it only ever increases as the table's traffic
|
|
14216
|
+
* grows, and it cannot be lowered — so once it is frozen into an
|
|
14217
|
+
* `observedProperties` baseline, a later AWS-side increase surfaces as drift
|
|
14218
|
+
* on a property the user never declared and `--revert` would issue a
|
|
14219
|
+
* decrease AWS rejects. `readCurrentState` therefore emits it only when the
|
|
14220
|
+
* DESIRED bag declares it (the fix issue #1742 PROPOSES for the per-index
|
|
14221
|
+
* sibling — still open and unlanded at the time of writing, so this is the
|
|
14222
|
+
* first site to carry the shape), never as a blanket drop:
|
|
14223
|
+
* `AWS::DynamoDB::Table` accepts an explicit `WarmThroughput` and a real
|
|
14224
|
+
* change to one must stay visible. Known residual on that declared arm
|
|
14225
|
+
* (issue #1768): AWS may GROW a declared value, which is reported as drift —
|
|
14226
|
+
* correctly, cdkd holds a value AWS no longer has — but `--revert` then
|
|
14227
|
+
* issues a decrease AWS rejects, surfacing as a per-resource revert failure
|
|
14228
|
+
* rather than as a silent wrong answer.
|
|
14229
|
+
*
|
|
14230
|
+
* This declaration is the OTHER half: a state record written by an earlier
|
|
14231
|
+
* binary already carries the computed value in `observedProperties`, so the
|
|
14232
|
+
* gate alone would flip every such table to a one-sided `WarmThroughput`
|
|
14233
|
+
* drift (baseline has it, the AWS side no longer emits it) until the next
|
|
14234
|
+
* deploy refreshed the capture. Ignoring the path on BOTH sides when the
|
|
14235
|
+
* template declares nothing covers the transition and the steady state
|
|
14236
|
+
* alike; a table that DOES declare `WarmThroughput` is compared normally.
|
|
14237
|
+
* Same per-resource scoping seam as `AWS::ApiGatewayV2::Integration`'s
|
|
14238
|
+
* `TlsConfig` (issue #1602): an absent / empty bag falls back to the
|
|
14239
|
+
* type-level answer of COMPARING, since hiding a real drift is the worse
|
|
14240
|
+
* failure.
|
|
14241
|
+
*/
|
|
14242
|
+
getDriftUnknownPaths(resourceType, properties) {
|
|
14243
|
+
if (resourceType !== "AWS::DynamoDB::Table") return [];
|
|
14244
|
+
if (declaresWarmThroughput(properties)) return [];
|
|
14245
|
+
return ["WarmThroughput"];
|
|
14246
|
+
}
|
|
14247
|
+
/**
|
|
14134
14248
|
* Read the AWS-current DynamoDB table configuration in CFn-property shape.
|
|
14135
14249
|
*
|
|
14136
14250
|
* `DescribeTable` returns every field cdkd manages in one call. AWS uses
|
|
@@ -14142,8 +14256,18 @@ var DynamoDBTableProvider = class {
|
|
|
14142
14256
|
* - StreamSpecification's CFn shape includes only `StreamViewType`; the
|
|
14143
14257
|
* API response carries `StreamEnabled` too. We surface both since the
|
|
14144
14258
|
* drift comparator only descends into keys present in state.
|
|
14145
|
-
* - GSI / LSI in the API response include `IndexStatus`, `ItemCount
|
|
14146
|
-
* sizing fields
|
|
14259
|
+
* - GSI / LSI in the API response include `IndexStatus`, `ItemCount`,
|
|
14260
|
+
* `IndexArn` and sizing fields cdkd never sets, and they are forwarded
|
|
14261
|
+
* VERBATIM. This used to claim "the comparator filters them" — it does
|
|
14262
|
+
* NOT, and issue #1767 quotes that sentence as the bug: the filter is
|
|
14263
|
+
* `calculateResourceDrift`'s state-keys-only walk, which only reaches an
|
|
14264
|
+
* absent TOP-LEVEL key. These are members of an array under a key the
|
|
14265
|
+
* baseline DOES carry, and arrays compare positionally, so every
|
|
14266
|
+
* AWS-managed member participates — `ItemCount` MOVES as data lands, so
|
|
14267
|
+
* an in-use table with an index drifts against its own
|
|
14268
|
+
* `observedProperties` on the ordinary path. Fixing it needs a full
|
|
14269
|
+
* reverse-mapper plus the same migration companion this issue's
|
|
14270
|
+
* `WarmThroughput` gate needed, hence a separate issue.
|
|
14147
14271
|
*
|
|
14148
14272
|
* Returns `undefined` when the table is gone (`ResourceNotFoundException`).
|
|
14149
14273
|
*
|
|
@@ -14153,8 +14277,12 @@ var DynamoDBTableProvider = class {
|
|
|
14153
14277
|
* drift, and the result key is omitted entirely when AWS reports no user
|
|
14154
14278
|
* tags (matches `create()`'s behavior of only sending Tags when the
|
|
14155
14279
|
* template carries them).
|
|
14280
|
+
*
|
|
14281
|
+
* `properties` is the DESIRED bag (the state-recorded template intent every
|
|
14282
|
+
* caller passes) and is consulted for exactly one field, `WarmThroughput` —
|
|
14283
|
+
* see {@link getDriftUnknownPaths} for the measurement and the reasoning.
|
|
14156
14284
|
*/
|
|
14157
|
-
async readCurrentState(physicalId, _logicalId, _resourceType) {
|
|
14285
|
+
async readCurrentState(physicalId, _logicalId, _resourceType, properties) {
|
|
14158
14286
|
try {
|
|
14159
14287
|
const table = (await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: physicalId }))).Table;
|
|
14160
14288
|
if (!table) return void 0;
|
|
@@ -14173,7 +14301,7 @@ var DynamoDBTableProvider = class {
|
|
|
14173
14301
|
if (table.OnDemandThroughput.MaxWriteRequestUnits !== void 0) odt["MaxWriteRequestUnits"] = table.OnDemandThroughput.MaxWriteRequestUnits;
|
|
14174
14302
|
if (Object.keys(odt).length > 0) result["OnDemandThroughput"] = odt;
|
|
14175
14303
|
}
|
|
14176
|
-
if (table.WarmThroughput) {
|
|
14304
|
+
if (table.WarmThroughput && declaresWarmThroughput(properties)) {
|
|
14177
14305
|
const wt = {};
|
|
14178
14306
|
if (table.WarmThroughput.ReadUnitsPerSecond !== void 0) wt["ReadUnitsPerSecond"] = table.WarmThroughput.ReadUnitsPerSecond;
|
|
14179
14307
|
if (table.WarmThroughput.WriteUnitsPerSecond !== void 0) wt["WriteUnitsPerSecond"] = table.WarmThroughput.WriteUnitsPerSecond;
|
|
@@ -65699,6 +65827,61 @@ function notFoundError$1(target, stack, resources) {
|
|
|
65699
65827
|
return new LocalInvokeResolutionError(msg.trimEnd());
|
|
65700
65828
|
}
|
|
65701
65829
|
|
|
65830
|
+
//#endregion
|
|
65831
|
+
//#region src/utils/ecr-uri.ts
|
|
65832
|
+
/**
|
|
65833
|
+
* Matching the HOST half of an ECR image URI:
|
|
65834
|
+
* `<acct>.dkr.ecr.<region>.<urlSuffix>/`.
|
|
65835
|
+
*
|
|
65836
|
+
* The suffix is CAPTURED rather than spelled out (issue #1758): the previous
|
|
65837
|
+
* pattern hardcoded `amazonaws.com` with an optional `.cn` tail, so a `us-iso*`
|
|
65838
|
+
* registry (`c2s.ic.gov` / `sc2s.sgov.gov`) never matched and the caller
|
|
65839
|
+
* silently classified a real ECR image as a user-managed one — skipping the
|
|
65840
|
+
* `docker login` it needs.
|
|
65841
|
+
*/
|
|
65842
|
+
const ECR_URI_HOST_REGEX = /^(\d{12})\.dkr\.ecr\.([^.]+)\.([^/]+)\//;
|
|
65843
|
+
/**
|
|
65844
|
+
* The account + region of an ECR image URI, or `undefined` when the URI is not
|
|
65845
|
+
* an ECR registry host for the region it names.
|
|
65846
|
+
*
|
|
65847
|
+
* Lives in `src/utils/` rather than beside its first caller because it has TWO
|
|
65848
|
+
* consumers in different layers — `src/local/ecr-puller.ts` (which needs the
|
|
65849
|
+
* `:tag` tail too) and `src/local/ecs-task-resolver.ts` (which classifies an
|
|
65850
|
+
* image that may carry a digest or no tag at all). Before issue #1758 they each
|
|
65851
|
+
* carried their own copy of the hardcoded commercial pattern, and fixing only
|
|
65852
|
+
* the pull path left `cdkd local run-task` broken outside the commercial
|
|
65853
|
+
* partition; ONE definition is what stops them drifting apart again.
|
|
65854
|
+
*
|
|
65855
|
+
* The module is deliberately free of AWS SDK imports, which is what lets
|
|
65856
|
+
* `ecs-task-resolver.ts` consume it without breaking the invariant that module
|
|
65857
|
+
* documents about itself (it resolves secrets through a separate module for
|
|
65858
|
+
* exactly that reason).
|
|
65859
|
+
*/
|
|
65860
|
+
function parseEcrRegistryHost(imageUri) {
|
|
65861
|
+
const m = ECR_URI_HOST_REGEX.exec(imageUri);
|
|
65862
|
+
if (!m) return void 0;
|
|
65863
|
+
const region = m[2];
|
|
65864
|
+
if (m[3] !== derivePartitionAndUrlSuffix(region).urlSuffix) return void 0;
|
|
65865
|
+
return {
|
|
65866
|
+
accountId: m[1],
|
|
65867
|
+
region
|
|
65868
|
+
};
|
|
65869
|
+
}
|
|
65870
|
+
/**
|
|
65871
|
+
* True when the URI has the ECR HOST SHAPE but its suffix does not belong to
|
|
65872
|
+
* the region it names — i.e. exactly the case {@link parseEcrRegistryHost}
|
|
65873
|
+
* rejects for a reason the caller may want to report.
|
|
65874
|
+
*
|
|
65875
|
+
* The two rejections are worth telling apart at a call site that degrades
|
|
65876
|
+
* silently: a genuinely public image and a registry in a partition
|
|
65877
|
+
* `derivePartitionAndUrlSuffix` does not know yet (issue #1764) both come back
|
|
65878
|
+
* `undefined`, and only the second is a cdkd gap rather than a user choice.
|
|
65879
|
+
*/
|
|
65880
|
+
function looksLikeEcrHostWithForeignSuffix(imageUri) {
|
|
65881
|
+
const m = ECR_URI_HOST_REGEX.exec(imageUri);
|
|
65882
|
+
return m !== null && m[3] !== derivePartitionAndUrlSuffix(m[2]).urlSuffix;
|
|
65883
|
+
}
|
|
65884
|
+
|
|
65702
65885
|
//#endregion
|
|
65703
65886
|
//#region src/local/ecs-task-resolver.ts
|
|
65704
65887
|
function detectEcsImageResolutionNeeds(stack) {
|
|
@@ -66318,13 +66501,14 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
|
|
|
66318
66501
|
if (substituted.includes("AWS::")) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). cdkd could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`);
|
|
66319
66502
|
throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). cdkd local run-task v1 only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`);
|
|
66320
66503
|
}
|
|
66321
|
-
const
|
|
66322
|
-
if (
|
|
66504
|
+
const ecrHost = parseEcrRegistryHost(substituted);
|
|
66505
|
+
if (ecrHost) return {
|
|
66323
66506
|
kind: "ecr",
|
|
66324
66507
|
uri: substituted,
|
|
66325
|
-
account:
|
|
66326
|
-
region:
|
|
66508
|
+
account: ecrHost.accountId,
|
|
66509
|
+
region: ecrHost.region
|
|
66327
66510
|
};
|
|
66511
|
+
if (looksLikeEcrHostWithForeignSuffix(substituted)) getLogger().child("ecs-task-resolver").debug(`Image '${substituted}' has an ECR host shape but a URL suffix that does not belong to its region's partition; treating it as a public image (no docker login).`);
|
|
66328
66512
|
return {
|
|
66329
66513
|
kind: "public",
|
|
66330
66514
|
uri: substituted
|
|
@@ -66361,12 +66545,12 @@ function classifyResolvedImage(uri, cdkAssetContainerRepo) {
|
|
|
66361
66545
|
if (hashMatch) out.assetHash = hashMatch[1];
|
|
66362
66546
|
return out;
|
|
66363
66547
|
}
|
|
66364
|
-
const
|
|
66365
|
-
if (
|
|
66548
|
+
const ecrHost = parseEcrRegistryHost(uri);
|
|
66549
|
+
if (ecrHost) return {
|
|
66366
66550
|
kind: "ecr",
|
|
66367
66551
|
uri,
|
|
66368
|
-
account:
|
|
66369
|
-
region:
|
|
66552
|
+
account: ecrHost.accountId,
|
|
66553
|
+
region: ecrHost.region
|
|
66370
66554
|
};
|
|
66371
66555
|
return {
|
|
66372
66556
|
kind: "public",
|
|
@@ -66952,50 +67136,25 @@ async function buildContainerImage$1(...args) {
|
|
|
66952
67136
|
|
|
66953
67137
|
//#endregion
|
|
66954
67138
|
//#region src/local/ecr-puller.ts
|
|
66955
|
-
/**
|
|
66956
|
-
|
|
66957
|
-
|
|
66958
|
-
|
|
66959
|
-
* deployed elsewhere or sharing a centralized registry), cdkd
|
|
66960
|
-
* authenticates against the target registry and runs `docker pull`.
|
|
66961
|
-
*
|
|
66962
|
-
* **Cross-account / cross-region** (#455):
|
|
66963
|
-
* - Same-account, same-region: fast path. No STS hop. The default
|
|
66964
|
-
* credential chain is used directly for `ecr:GetAuthorizationToken`.
|
|
66965
|
-
* - `ecrRoleArn` is provided: `sts:AssumeRole` is issued via the
|
|
66966
|
-
* default credential chain to obtain temporary credentials for the
|
|
66967
|
-
* target account. The resulting credentials authenticate the ECR
|
|
66968
|
-
* client (regardless of region — the ECR client is built for the
|
|
66969
|
-
* URI's region, which can differ from the caller's profile region).
|
|
66970
|
-
* - Cross-account, NO `ecrRoleArn`: cdkd falls through to the
|
|
66971
|
-
* default credential chain. This works when the caller has been
|
|
66972
|
-
* granted cross-account `ecr:GetAuthorizationToken` +
|
|
66973
|
-
* `ecr:BatchGetImage` permissions on the target repository via an
|
|
66974
|
-
* IAM policy; otherwise AWS rejects the call with `AccessDenied`
|
|
66975
|
-
* and the user is pointed at `--ecr-role-arn`.
|
|
66976
|
-
*
|
|
66977
|
-
* The `--no-pull` semantics (C3 in the design doc):
|
|
66978
|
-
* - When NOT set: `ecrLogin` + `docker pull <uri>`.
|
|
66979
|
-
* - When set: skip `docker pull`. If the image isn't in the local
|
|
66980
|
-
* cache, the subsequent `docker run` will fail; we surface a clearer
|
|
66981
|
-
* "image not in local cache" error here so the user knows to drop
|
|
66982
|
-
* `--no-pull` or pre-pull manually.
|
|
66983
|
-
*/
|
|
66984
|
-
/** Regex matching the `<acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` shape. */
|
|
66985
|
-
const ECR_URI_REGEX = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\/([^:]+):(.+)$/;
|
|
67139
|
+
/** The URL suffix an ECR registry host uses in `region`'s partition (issue #1758). */
|
|
67140
|
+
function ecrUrlSuffix(region) {
|
|
67141
|
+
return derivePartitionAndUrlSuffix(region).urlSuffix;
|
|
67142
|
+
}
|
|
66986
67143
|
/**
|
|
66987
67144
|
* Parse an ECR image URI. Returns `undefined` for non-ECR URIs (typically:
|
|
66988
67145
|
* Docker Hub, public.ecr.aws, gcr.io, ...) — those are user-managed
|
|
66989
67146
|
* images we don't try to authenticate against.
|
|
66990
67147
|
*/
|
|
66991
67148
|
function parseEcrUri(imageUri) {
|
|
66992
|
-
const
|
|
67149
|
+
const host = parseEcrRegistryHost(imageUri);
|
|
67150
|
+
if (!host) return void 0;
|
|
67151
|
+
const m = /^([^:]+):(.+)$/.exec(imageUri.slice(imageUri.indexOf("/") + 1));
|
|
66993
67152
|
if (!m) return void 0;
|
|
66994
67153
|
return {
|
|
66995
|
-
accountId:
|
|
66996
|
-
region:
|
|
66997
|
-
repository: m[
|
|
66998
|
-
tag: m[
|
|
67154
|
+
accountId: host.accountId,
|
|
67155
|
+
region: host.region,
|
|
67156
|
+
repository: m[1],
|
|
67157
|
+
tag: m[2]
|
|
66999
67158
|
};
|
|
67000
67159
|
}
|
|
67001
67160
|
/**
|
|
@@ -67134,7 +67293,7 @@ async function ecrLogin(client, accountId, region) {
|
|
|
67134
67293
|
if (!authData?.authorizationToken) throw new LocalInvokeBuildError$1("Failed to get ECR authorization token");
|
|
67135
67294
|
const [username, password] = Buffer.from(authData.authorizationToken, "base64").toString().split(":");
|
|
67136
67295
|
if (!username || password === void 0) throw new LocalInvokeBuildError$1("ECR authorization token has unexpected shape (missing username/password)");
|
|
67137
|
-
const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}
|
|
67296
|
+
const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.${ecrUrlSuffix(region)}`;
|
|
67138
67297
|
try {
|
|
67139
67298
|
await runDockerStreaming([
|
|
67140
67299
|
"login",
|
|
@@ -74346,7 +74505,7 @@ function createMigrateCommand() {
|
|
|
74346
74505
|
*/
|
|
74347
74506
|
function buildProgram() {
|
|
74348
74507
|
const program = new Command();
|
|
74349
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.
|
|
74508
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.22");
|
|
74350
74509
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
74351
74510
|
const { profile } = actionCommand.optsWithGlobals();
|
|
74352
74511
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|