@go-to-k/cdkd 0.290.1 → 0.290.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{asg-provider-e05iqtU_.js → asg-provider-RLnpBIKj.js} +20 -15
- package/dist/asg-provider-RLnpBIKj.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-aYUEGRRl.js → deploy-engine-0Wg4MwP2.js} +693 -103
- package/dist/deploy-engine-0Wg4MwP2.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{logger-DTEUzYP_.js → logger-th1myfgh.js} +63 -2
- package/dist/logger-th1myfgh.js.map +1 -0
- package/dist/{program-CnTfNj2k.js → program-CoRRfEe_.js} +397 -117
- package/dist/{program-CnTfNj2k.js.map → program-CoRRfEe_.js.map} +1 -1
- package/dist/{version-DTli1mA9.js → version-BTao4oxo.js} +2 -2
- package/dist/{version-DTli1mA9.js.map → version-BTao4oxo.js.map} +1 -1
- package/package.json +1 -1
- package/dist/asg-provider-e05iqtU_.js.map +0 -1
- package/dist/deploy-engine-aYUEGRRl.js.map +0 -1
- package/dist/logger-DTEUzYP_.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { _ as withSkipPrefix, b as
|
|
1
|
+
import { $ as secretSafeKeyDisplay, $n as parseBootstrapMarker, $t as DiffCalculator, A as planOrphanAdoption, An as formatLockExpiry, Ar as resolveUseCdkBootstrapAssets, At as WAFv2WebACLProvider, B as formatResourceLine, Bn as AssetPublisher, Br as PARTITION_TABLE, Bt as replayWarn, C as createPreDeleteFinalSnapshot, Ci as isThrottlingError, Cn as scrubResourceRecord, Cr as getLegacyStateBucketName, Ct as cfnRefValueFromPhysicalId, D as makeCanonicalizePropertiesFn, Di as retryClassificationText, Dn as buildForceUnlockCommand, Dr as resolveSkipPrefix, Dt as parameterTypeMayLoseSecretIdentity, E as unsupportedFinalSnapshotError, Ei as markRedactedCause, En as rebuildClientForBucketRegion, Er as resolveCaptureObservedState, Et as isUnboundTemplateParameter, F as WARM_THROUGHPUT_MEMBERS, Fn as importableOutputKeys, Fr as MIGRATE_TMP_PREFIX, Ft as coerceCfnInteger, G as red, Gn as loadPublishableAssetManifest, Gt as producerRegionsFromState, H as cyan, Hn as WorkGraph, Hr as derivePartitionAndUrlSuffix, Ht as requireConfigObject, I as coerceWarmThroughput$1, In as importableOutputs, Ir as findLargeInlineResources, It as configBooleanRefusal, J as collectPublishedOutputNames, Jn as BOOTSTRAP_MARKER_PREFIX, Jr as getAwsClients, Jt as s3BucketDomainName, K as yellow, Kn as rewriteTemplateAssetReferences, Kr as resolveBucketRegion, Kt as withSharedDrainBudget, L as isWarmThroughputDecrease, Ln as orphansAfterRollback, Lr as uploadCfnTemplate, Lt as configIntegerRefusal, M as isStatefulRecreateTargetForReplace, Mn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Mr as warnDeprecatedNoPrefixCliFlag, Mt as resolveExplicitPhysicalId, N as isStatefulRecreateTargetSync, Nn as DEFAULT_STATE_PREFIX, Nr as CFN_TEMPLATE_BODY_LIMIT, Nt as assertRegionMatch, O as extractDeploymentEventError, On as buildLockContentionMessage, Or as resolveStateBucketWithDefault, Ot as refStateLookupFromResource, P as renderStatefulReason, Pn as exportNamesCarriedFrom, Pr as CFN_TEMPLATE_URL_LIMIT, Pt as coerceCfnBoolean, Q as secretBearingStateKeyWarning, Qn as isCrossRegionRedirect, Qr as CdkdError, Qt as applyRoleArnIfSet, R as toCfnInteger, Rn as orphansCarriedFrom, Rr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Rt as configStringRefusal, S as ccRoutedFinalSnapshotError, Si as isRetryableTransientError, Sn as redactSecretsForState, Sr as getDefaultStateBucketName, St as carriesDynamicReference, T as refusesFinalSnapshot, Ti as markNonRetryable, Tn as S3StateBackend, Tr as resolveAutoAssetStorage, Tt as getAccountInfo, U as gray, Un as buildAssetRedirectMap, Ur as AssemblyReader, Ut as requireConfigString, V as bold, Vn as stringifyValue, Vr as canonicalizeRegion, Vt as requireConfigArray, W as green, Wn as createAssetRedirectResolver, Wr as processStackMessages, Wt as classifyReplaySecretRegion, X as isExportAliasCollision, Xn as ensureAssetStorage, Xr as setAwsClients, Xt as s3BucketRegionalDomainName, Y as exportAliasCollisionScrubWarning, Yn as assertAssetBucketRegion, Yr as resetAwsClients, Yt as s3BucketDualStackDomainName, Z as secretBearing, Zn as getBootstrapMarkerKey, Zt as s3BucketWebsiteUrl, _ as isSecretBearingReferenceString, _n as markSameGenerationBag, _r as stripControlChars, _t as deleteIndeterminateGuards, a as DeploymentEventsStore, ai as LocalInvokeBuildError$1, an as DagBuilder, ar as buildDockerImage, at as wouldReturnToSdkProvider, b as PRE_DELETE_SNAPSHOT_TYPES, bi as withErrorHandling, bn as recordMaskOnlyValue, br as Synthesizer, bt as isTerminationProtectionPropagationError, c as replayFailedOperations, ci as NestedStackChildDirectDestroyError, cn as STATE_SOURCED_CROSS_GENERATION_RULES, cr as describeDockerFailure, ct as maskerOrIdentity, d as updatePartialReason, di as ResourceTimeoutError, dn as carriesSecretMask, dr as getDockerCmd, dt as interruptWatchListenerCount, ei as CrossAccountSecretRefusalError, en as INTRINSIC_KEYS, er as readBootstrapMarkerBody, et as getCurrentResourceSecrets, f as withResourceDeadline, fi as ResourceUpdateNotSupportedError, fn as createSecretMasker, fr as partitionSensitiveEnv, ft as isInterruptedWaitError, g as bagHoldsSecretExpression, gn as isSingleDynamicReferenceToken, gr as escapeRegExp, gt as UNSPECIFIED_SKIP_REASON, h as computeImplicitDeleteEdges, hn as identityKeyFor, hr as runDockerStreaming, ht as slowCcOperationTimeoutMs, i as DeploymentEventsReader, ii as IntrinsicResolutionRefusalError, in as withRetry, ir as describeAwsFailure, it as ProviderRegistry, j as MULTI_REGION_RECREATE_BLOCKED_TYPES, jn as shellQuote, jr as stateBucketExistenceConfirmed, jt as normalizeAwsTagsToCfn, k as makeSiblingClaimReader, kn as forceQuitRecoveryClause, kr as resolveStateBucketWithDefaultAndSource, kt as isSettledInstanceState, l as replayRollback, li as PartialFailureError, ln as STATE_SOURCED_READBACK_RULES, lr as dockerSpawnEnvWithSensitive, lt as beginCommandInterruptScope, m as IMPLICIT_DELETE_DEPENDENCIES, mi as StackTerminationProtectionError, mn as errorCauseChain, mr as runDockerForeground, mt as CloudControlProvider, n as DEFAULT_RESOURCE_WARN_AFTER_MS, ni as DeployCancelledError, nn as findSilentDropProperties, nr as validateContainerRepoName, nt as collectInlinePolicyNamesManagedBySiblings, o as planFailedOps, oi as LocalStartServiceError, on as TemplateParser, or as describeDockerCapturedOutput, ot as createMaskedRetryLogger, p as maskingRetryLogger, pi as StackHasActiveImportsError, pn as dynamicReferenceTokens, pr as redactDockerArgvValues, pt as startInterruptWatch, q as collectDeclaredOutputNames, qn as AssetModeResolver, qr as AwsClients, qt as s3BucketArn, r as DeployEngine, ri as DynamicReferenceRegionAmbiguousError, rn as describeTypeWithThrottleRetry, rr as buildDenyExternalAccessPolicy, rt as clearOnUpdateRemoval, s as planRollback, sn as STATE_SOURCED_BASELINE_RULES, sr as describeDockerExecFailure, st as maskDeep, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as findActionableSilentDrops, tr as validateAssetBucketName, tt as IAMRoleProvider, u as updatePartialMessage, ui as ProvisioningError, un as TEMPLATE_SOURCED_RULES, ur as formatDockerLoginError, ut as endCommandInterruptScope, v as bindingSkippedOutputs, vn as maskSecretsInError, vr as AssetManifestLoader, vt as deleteSkipReason, w as isFinalSnapshotError, wi as isTransientServerError, wn as LockManager, wr as resolveApp, wt as coerceParameterTypedValue, x as buildFinalSnapshotIdentifier, xi as isMarkedNonRetryable, xn as recoverMaskedOutput, xr as synthesisStatusMessage, xt as IntrinsicFunctionResolver, y as ATOMIC_FINAL_SNAPSHOT_TYPES, yi as normalizeAwsError, yn as maskSecretsInText, yr as getDockerImageBySourceHash, yt as disableInstanceApiTermination, z as toFiniteNumber, zn as shouldRetainResource, zr as expectedOwnerParam, zt as readConfigString } from "./deploy-engine-0Wg4MwP2.js";
|
|
2
|
+
import { S as truncateCodePoints, _ as withSkipPrefix, b as displayIdent, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, x as displaySafe, y as UNRENDERABLE } from "./logger-th1myfgh.js";
|
|
3
3
|
import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import {
|
|
4
|
+
import { t as getCdkdVersion } from "./version-BTao4oxo.js";
|
|
5
|
+
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-RLnpBIKj.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
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, NotFound, 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";
|
|
@@ -26,9 +26,9 @@ import { AddTagsToResourceCommand as AddTagsToResourceCommand$1, CreateCacheClus
|
|
|
26
26
|
import { ACMClient, AddTagsToCertificateCommand, DeleteCertificateCommand, DescribeCertificateCommand, ListTagsForCertificateCommand, RemoveTagsFromCertificateCommand, RequestCertificateCommand, ResourceNotFoundException as ResourceNotFoundException$7, UpdateCertificateOptionsCommand } from "@aws-sdk/client-acm";
|
|
27
27
|
import { CreateMicrovmImageCommand, DeleteMicrovmImageCommand, GetMicrovmImageCommand, LambdaMicrovmsClient, ListTagsCommand as ListTagsCommand$1, ResourceNotFoundException as ResourceNotFoundException$8, TagResourceCommand as TagResourceCommand$10, UntagResourceCommand as UntagResourceCommand$10, UpdateMicrovmImageCommand } from "@aws-sdk/client-lambda-microvms";
|
|
28
28
|
import * as fs from "node:fs";
|
|
29
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
30
30
|
import * as path from "node:path";
|
|
31
|
-
import { basename, dirname, isAbsolute, resolve } from "node:path";
|
|
31
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
32
32
|
import { execFile, spawn } from "node:child_process";
|
|
33
33
|
import { tmpdir } from "node:os";
|
|
34
34
|
import { AssociateVPCWithHostedZoneCommand, ChangeResourceRecordSetsCommand, ChangeTagsForResourceCommand, CreateHostedZoneCommand, CreateQueryLoggingConfigCommand, DeleteHostedZoneCommand, DeleteQueryLoggingConfigCommand, DisassociateVPCFromHostedZoneCommand, GetHostedZoneCommand, HostedZoneAlreadyExists, ListHostedZonesByNameCommand, ListQueryLoggingConfigsCommand, ListResourceRecordSetsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$8, Route53Client, UpdateHostedZoneCommentCommand, UpdateHostedZoneFeaturesCommand } from "@aws-sdk/client-route-53";
|
|
@@ -246,7 +246,7 @@ function parseContextOptions(contextArgs) {
|
|
|
246
246
|
const commonOptions = [
|
|
247
247
|
new Option("--verbose", "Enable verbose logging").default(false),
|
|
248
248
|
new Option("--profile <profile>", "AWS profile"),
|
|
249
|
-
new Option("--role-arn <arn>", "IAM role ARN to assume for AWS API calls (env: CDKD_ROLE_ARN); the role
|
|
249
|
+
new Option("--role-arn <arn>", "IAM role ARN to assume for AWS API calls (env: CDKD_ROLE_ARN); the role needs the actions for the resource types you deploy plus the bookkeeping actions cdkd itself issues, because cdkd makes raw service API calls and does not route through CloudFormation, so CDK CLI deploy-roles will NOT work. Do not combine with --profile: the SDK then ignores the assumed role"),
|
|
250
250
|
new Option("-y, --yes", "Automatically answer interactive prompts with the recommended response (e.g. confirm destroy)").default(false)
|
|
251
251
|
];
|
|
252
252
|
/**
|
|
@@ -23208,7 +23208,43 @@ function deepEqual$1(a, b) {
|
|
|
23208
23208
|
//#endregion
|
|
23209
23209
|
//#region src/provisioning/providers/logs-loggroup-provider.ts
|
|
23210
23210
|
/**
|
|
23211
|
-
*
|
|
23211
|
+
* Whether a raw `RetentionInDays` is CloudFormation's spelling of "no
|
|
23212
|
+
* retention" — the shapes the send path SKIPS on create and routes to
|
|
23213
|
+
* `DeleteRetentionPolicy` on update, rather than refusing.
|
|
23214
|
+
*
|
|
23215
|
+
* MEASURED, not read off the docs (issue
|
|
23216
|
+
* [#2699](https://github.com/go-to-k/cdkd/issues/2699), live CloudFormation
|
|
23217
|
+
* A/B on `AWS::Logs::LogGroup`, us-east-1, 2026-09-14; the full table is on
|
|
23218
|
+
* `toCfnInteger` in `../dynamodb-warm-throughput.ts`): an ABSENT property, an
|
|
23219
|
+
* EMPTY string and a WHITESPACE-ONLY string are all accepted by CloudFormation
|
|
23220
|
+
* and all REMOVE the live retention policy. Nothing else in the falsy family
|
|
23221
|
+
* is: `false`, `0`, `"0"` and `null` are every one REJECTED (the first three by
|
|
23222
|
+
* the Logs handler, `null` at `update-stack` itself), so routing them here
|
|
23223
|
+
* would make cdkd delete a live retention on a template CloudFormation refuses
|
|
23224
|
+
* to deploy. They go to {@link assertUsableRetention}.
|
|
23225
|
+
*
|
|
23226
|
+
* The ONE cdkd-side exception is a numeric `0` when `desiredFromAwsReadback`
|
|
23227
|
+
* is set. `0` is what {@link LogsLogGroupProvider.readCurrentState} records for
|
|
23228
|
+
* a log group with no retention policy — cdkd's OWN never-expire spelling,
|
|
23229
|
+
* persisted into `observedProperties` at every deploy — and `cdkd drift
|
|
23230
|
+
* --revert` hands `update()` that baseline as the DESIRED bag. Reverting a
|
|
23231
|
+
* console-added retention on a never-expiring log group is therefore desired
|
|
23232
|
+
* `0` over live `30`, and it must reach the delete arm; refusing it would leave
|
|
23233
|
+
* `--revert` unable to undo exactly the drift it reports. `UpdateContext`'s
|
|
23234
|
+
* own contract licenses this reading and nothing wider: the flag says every
|
|
23235
|
+
* value in the bag describes what AWS held, so `0` there is a report of "no
|
|
23236
|
+
* retention", not a template's request. On the template path `0` stays
|
|
23237
|
+
* refused, matching CloudFormation. `"0"` is never cdkd-produced (the readback
|
|
23238
|
+
* writes a number) and is refused everywhere.
|
|
23239
|
+
*/
|
|
23240
|
+
function isAbsentRetention(raw, desiredFromAwsReadback) {
|
|
23241
|
+
if (raw === void 0) return true;
|
|
23242
|
+
if (typeof raw === "string" && raw.trim() === "") return true;
|
|
23243
|
+
return desiredFromAwsReadback && raw === 0;
|
|
23244
|
+
}
|
|
23245
|
+
/**
|
|
23246
|
+
* Refuse a `RetentionInDays` the send path cannot USE (issues [#2521],
|
|
23247
|
+
* [#2698], [#2699]).
|
|
23212
23248
|
*
|
|
23213
23249
|
* **The scope of this refusal is a TEST, not a paragraph.** Every review round
|
|
23214
23250
|
* on issue [#2521] found a hole in a PROSE statement of it — a coercible
|
|
@@ -23225,56 +23261,72 @@ function deepEqual$1(a, b) {
|
|
|
23225
23261
|
* not cover; do not restate the rule here, because a fourth restatement is
|
|
23226
23262
|
* the thing that keeps being wrong.
|
|
23227
23263
|
*
|
|
23228
|
-
*
|
|
23229
|
-
*
|
|
23230
|
-
*
|
|
23231
|
-
*
|
|
23232
|
-
*
|
|
23233
|
-
*
|
|
23234
|
-
*
|
|
23235
|
-
*
|
|
23236
|
-
*
|
|
23237
|
-
*
|
|
23238
|
-
*
|
|
23239
|
-
*
|
|
23240
|
-
*
|
|
23241
|
-
*
|
|
23242
|
-
*
|
|
23264
|
+
* What IS stated here is the RULE the rows pin, because since issues #2698 /
|
|
23265
|
+
* #2699 it is a measurement rather than a guess: the value is read through
|
|
23266
|
+
* `toCfnInteger` — CloudFormation's own Integer grammar, `optional sign +
|
|
23267
|
+
* decimal digits`, surrounding whitespace trimmed — and anything that grammar
|
|
23268
|
+
* rejects is refused here where CloudFormation would have refused it at
|
|
23269
|
+
* deploy time. The pre-#2698 reader was `Number()`, which FORWARDED `'0x1e'`
|
|
23270
|
+
* as 30 and `'30.5'` as 30.5 on a template CloudFormation rejects outright.
|
|
23271
|
+
* Five refusal ARMS, each with its own `detail` clause; only the WHY of each:
|
|
23272
|
+
*
|
|
23273
|
+
* - **a non-finite NUMBER** (`NaN`, `Infinity`, `-Infinity`). Unreachable in
|
|
23274
|
+
* production — both bags are `JSON.parse` output, which cannot express one —
|
|
23275
|
+
* and kept as defence so the family is named whole. Sits BEFORE the null /
|
|
23276
|
+
* type arms because `NaN` is a number whose siblings were already refused.
|
|
23277
|
+
* - **`null`**. CloudFormation rejects it at `update-stack` (`'null' values
|
|
23278
|
+
* are not allowed in templates`), and cdkd's own resolver never
|
|
23279
|
+
* manufactures one for this key: an `AWS::NoValue` — bare or as an `Fn::If`
|
|
23280
|
+
* branch — OMITS the property (`intrinsic-function-resolver.ts`,
|
|
23281
|
+
* `resolveValue`'s object arm), so `null` arriving here is a literal the
|
|
23282
|
+
* template wrote, not cdkd's spelling of absent. Issue #2521 left it in the
|
|
23283
|
+
* delete arm on the guess that it was; the measurement settled it.
|
|
23284
|
+
* - **not a CloudFormation Integer at all** (`'abc'`, `'0x1e'`, `'1e3'`,
|
|
23285
|
+
* `'30.5'`, `[]`, `{}`, `true`, `false`). The pre-coercion code handed the
|
|
23286
|
+
* raw value to `PutRetentionPolicyCommand` and CloudWatch Logs rejected it;
|
|
23287
|
+
* refusing moves that rejection one layer earlier and names the property.
|
|
23288
|
+
* `false` joined this arm with #2699 — it was in the delete arm before, on
|
|
23289
|
+
* the same guess as `null`.
|
|
23290
|
+
* - **ZERO** (`0`, `'0'`). The Logs handler rejects it by enum (`Valid values
|
|
23291
|
+
* are: [1, 3, 5, ...]`), so it is not CloudFormation's never-expire — the
|
|
23292
|
+
* ABSENT property or an empty string is. Numeric `0` under
|
|
23293
|
+
* `desiredFromAwsReadback` never reaches this arm; see
|
|
23294
|
+
* {@link isAbsentRetention} for why that one is cdkd's own spelling.
|
|
23243
23295
|
* - **a NEGATIVE number** (`-1`, `'-1'`). It coerces, so a refusal gated on
|
|
23244
23296
|
* coercibility alone let it through; it then failed the `> 0` send test and
|
|
23245
23297
|
* fell into the UPDATE path's `else`, which issues
|
|
23246
23298
|
* `DeleteRetentionPolicy` — a template typo SILENTLY REMOVING a live
|
|
23247
23299
|
* retention where the old code sent `-1` and failed loudly.
|
|
23248
23300
|
*
|
|
23249
|
-
* The FALSY values pass through, as they did before — `0`, `''`, `false`,
|
|
23250
|
-
* `null` and absent; `NaN` is the one exception, refused above because it is a
|
|
23251
|
-
* malformed number rather than a spelling of "no retention".
|
|
23252
|
-
*
|
|
23253
|
-
* `'0'` is TRUTHY, so it reaches this function and passes too: zero is
|
|
23254
|
-
* what {@link LogsLogGroupProvider.readCurrentState} records for a group with
|
|
23255
|
-
* no retention policy, and CloudFormation coerces `'0'` to `0`, so routing the
|
|
23256
|
-
* string to the same arm as the number is the consistency the coercion is for.
|
|
23257
|
-
* On the UPDATE path against a live positive retention that is a real change —
|
|
23258
|
-
* it DELETES where the old code sent and was rejected — and `UPDATE_MATRIX`
|
|
23259
|
-
* pins it.
|
|
23260
|
-
*
|
|
23261
23301
|
* The message names the value's TYPE and never the value. A provider's
|
|
23262
23302
|
* `properties` bag arrives with dynamic references already RESOLVED, so a
|
|
23263
23303
|
* `{{resolve:secretsmanager:...}}` scalar is PLAINTEXT by the time it reaches
|
|
23264
|
-
* here, and this function has no masker
|
|
23265
|
-
* `CreateContext` and `applyUpdate` no `UpdateContext`
|
|
23304
|
+
* here, and this function has no masker threaded
|
|
23266
23305
|
* (`.claude/rules/provider-masking.md`). Interpolating it as
|
|
23267
23306
|
* `${JSON.stringify(raw)}` was the first draft and
|
|
23268
23307
|
* `vp run audit:provider-secret-mask:check` refused it — but read that as one
|
|
23269
23308
|
* SPELLING being fenced, not the rule: the critic's population is
|
|
23270
23309
|
* interpolated `JSON.stringify` calls, so a bare `${raw}` here would leave it
|
|
23271
23310
|
* green. Do not put the value back without threading the capability first.
|
|
23311
|
+
*
|
|
23312
|
+
* The caller decides the ABSENT family FIRST, through {@link isAbsentRetention},
|
|
23313
|
+
* and never calls this for one: an absent value is not an error, and the
|
|
23314
|
+
* readback-only reading of `0` lives in that predicate rather than here, so
|
|
23315
|
+
* the zero arm below can stay unconditional.
|
|
23272
23316
|
*/
|
|
23273
23317
|
function assertUsableRetention(raw, coerced, logicalId, resourceType) {
|
|
23274
|
-
|
|
23275
|
-
|
|
23276
|
-
|
|
23277
|
-
|
|
23318
|
+
const detail = (() => {
|
|
23319
|
+
if (typeof raw === "number" && !Number.isFinite(raw)) return "the template declares a non-finite number";
|
|
23320
|
+
if (raw === null) return "the template declares null, which CloudFormation rejects ('null' values are not allowed in templates); to declare no retention, omit the property";
|
|
23321
|
+
if (coerced === void 0) {
|
|
23322
|
+
if (typeof raw === "string") return "the template declares a string that is not a CloudFormation Integer (hex, exponent and decimal-point spellings are rejected, as they are by CloudFormation)";
|
|
23323
|
+
return `the template's value is of type ${Array.isArray(raw) ? "array" : typeof raw}`;
|
|
23324
|
+
}
|
|
23325
|
+
if (coerced === 0) return "the template declares zero, which CloudWatch Logs rejects; to declare no retention, omit the property or declare an empty string";
|
|
23326
|
+
if (coerced < 0) return "the template declares a negative number";
|
|
23327
|
+
})();
|
|
23328
|
+
if (detail === void 0) return;
|
|
23329
|
+
throw new ProvisioningError(`${logicalId} (${resourceType}): RetentionInDays must be a positive CloudFormation Integer (optional sign then decimal digits, as a number or a string — 30 or '30'); ${detail}`, resourceType, logicalId);
|
|
23278
23330
|
}
|
|
23279
23331
|
/**
|
|
23280
23332
|
* AWS CloudWatch Logs LogGroup Provider
|
|
@@ -23333,7 +23385,7 @@ var LogsLogGroupProvider = class {
|
|
|
23333
23385
|
/**
|
|
23334
23386
|
* Create a CloudWatch Logs log group
|
|
23335
23387
|
*/
|
|
23336
|
-
async create(logicalId, resourceType, properties) {
|
|
23388
|
+
async create(logicalId, resourceType, properties, context) {
|
|
23337
23389
|
this.logger.debug(`Creating log group ${logicalId}`);
|
|
23338
23390
|
const logGroupName = properties["LogGroupName"] || `/cdkd/${generateResourceName(logicalId, {
|
|
23339
23391
|
maxLength: 506,
|
|
@@ -23358,8 +23410,17 @@ var LogsLogGroupProvider = class {
|
|
|
23358
23410
|
}
|
|
23359
23411
|
try {
|
|
23360
23412
|
const rawRetention = properties["RetentionInDays"];
|
|
23361
|
-
|
|
23362
|
-
|
|
23413
|
+
let retentionInDays = toCfnInteger(rawRetention);
|
|
23414
|
+
if (!isAbsentRetention(rawRetention, false)) try {
|
|
23415
|
+
assertUsableRetention(rawRetention, retentionInDays, logicalId, resourceType);
|
|
23416
|
+
} catch (refusal) {
|
|
23417
|
+
if (context?.replayingState !== true || !(refusal instanceof ProvisioningError)) throw refusal;
|
|
23418
|
+
const legacyReading = toFiniteNumber(rawRetention);
|
|
23419
|
+
if (legacyReading !== void 0 && Number.isInteger(legacyReading) && legacyReading > 0) {
|
|
23420
|
+
retentionInDays = legacyReading;
|
|
23421
|
+
this.logger.warn(`${refusal.message}. This create replays a cdkd state record (rollback) written by a cdkd that read the value as ${legacyReading} and applied it, so the retention is RESTORED as ${legacyReading} days rather than refused. Fix the template spelling and re-deploy.`);
|
|
23422
|
+
} else this.logger.warn(`${refusal.message}. This create replays a cdkd state record (rollback), so the retention is SKIPPED rather than refused: the log group is re-created with no retention policy. Re-apply it by hand (aws logs put-retention-policy) or fix the template and re-deploy.`);
|
|
23423
|
+
}
|
|
23363
23424
|
if (retentionInDays !== void 0 && retentionInDays > 0) await this.logsClient.send(new PutRetentionPolicyCommand({
|
|
23364
23425
|
logGroupName,
|
|
23365
23426
|
retentionInDays
|
|
@@ -23390,7 +23451,12 @@ var LogsLogGroupProvider = class {
|
|
|
23390
23451
|
await this.logsClient.send(new DeleteLogGroupCommand({ logGroupName }));
|
|
23391
23452
|
this.logger.debug(`Cleaned up partially-created log group ${logicalId} (${logGroupName}) after wiring failure`);
|
|
23392
23453
|
} catch (cleanupError) {
|
|
23393
|
-
|
|
23454
|
+
const deleteCommand = renderDisableCommand({
|
|
23455
|
+
before: "aws logs delete-log-group --log-group-name",
|
|
23456
|
+
identifier: logGroupName
|
|
23457
|
+
});
|
|
23458
|
+
const manualStep = deleteCommand ? `Manual deletion may be required before the next deploy: ${deleteCommand}` : "Manual deletion may be required before the next deploy, via the console: the log group name cannot be reproduced safely on a command line.";
|
|
23459
|
+
this.logger.warn(`Failed to clean up partially-created log group ${logicalId} (${logGroupName}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. ${manualStep}`);
|
|
23394
23460
|
}
|
|
23395
23461
|
throw innerError;
|
|
23396
23462
|
}
|
|
@@ -23428,32 +23494,40 @@ var LogsLogGroupProvider = class {
|
|
|
23428
23494
|
* fall back to replacement, so swallowing it into a ProvisioningError would
|
|
23429
23495
|
* turn a recoverable class change into a hard failure.
|
|
23430
23496
|
*/
|
|
23431
|
-
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
23497
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
23432
23498
|
try {
|
|
23433
|
-
return await this.applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
23499
|
+
return await this.applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
23434
23500
|
} catch (error) {
|
|
23435
23501
|
if (error instanceof CdkdError) throw error;
|
|
23436
23502
|
const cause = error instanceof Error ? error : void 0;
|
|
23437
23503
|
throw new ProvisioningError(`Failed to update log group ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
23438
23504
|
}
|
|
23439
23505
|
}
|
|
23440
|
-
async applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
23506
|
+
async applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
23441
23507
|
this.logger.debug(`Updating log group ${logicalId}: ${physicalId}`);
|
|
23442
23508
|
const normalizeClass = (v) => typeof v === "string" && v.length > 0 ? v : "STANDARD";
|
|
23443
23509
|
const prevClass = normalizeClass(previousProperties["LogGroupClass"]);
|
|
23444
23510
|
const nextClass = normalizeClass(properties["LogGroupClass"]);
|
|
23445
23511
|
if (prevClass !== nextClass) {
|
|
23446
23512
|
const replaceFlags = "--replace --force-stateful-recreation";
|
|
23447
|
-
const
|
|
23513
|
+
const deletionProtected = isTruthyCfnBoolean(previousProperties["DeletionProtectionEnabled"]);
|
|
23514
|
+
const disableCommand = renderDisableCommand({
|
|
23515
|
+
before: "aws logs put-log-group-deletion-protection --log-group-identifier",
|
|
23516
|
+
identifier: physicalId,
|
|
23517
|
+
after: "--no-deletion-protection-enabled"
|
|
23518
|
+
});
|
|
23519
|
+
const disableStep = disableCommand ? `Then disable deletion protection — \`${disableCommand}\`, or via the console — and re-deploy with ${replaceFlags}` : `${UNNAMEABLE_ID_CLAUSE} Then re-deploy with ${replaceFlags}`;
|
|
23520
|
+
const remedy = deletionProtected ? `cdkd's recorded properties for this log group carry DeletionProtectionEnabled, so ${replaceFlags} alone will NOT succeed while AWS still has it on: the replacement normally deletes the log group, AWS refuses that delete while protection is on, and cdkd deploy has no --remove-protection flag to clear it (only cdkd destroy and cdkd state destroy act on one). Read "Deletion protection blocks a replacement" in docs/cli-deploy-safety.md BEFORE you disable anything: whether disabling helps at all, and what the flag ends up as, depend on your UpdateReplacePolicy and on whether the deploy completes — neither of which this refusal can see. ${disableStep} to delete + recreate the log group under the new class (its stored log events are lost). Setting DeletionProtectionEnabled: false in the template does NOT clear it in the same deploy: this refusal fires before that property is applied, so that route needs its own deploy with the LogGroupClass change reverted. Or revert the LogGroupClass change and keep the current class.` : `Re-deploy with ${replaceFlags} to delete + recreate the log group under the new class (its stored log events are lost), or revert the LogGroupClass change.`;
|
|
23448
23521
|
throw new ResourceUpdateNotSupportedError("AWS::Logs::LogGroup", logicalId, `the LogGroupClass ('${prevClass}' -> '${nextClass}') cannot be changed after creation. ${remedy} Note --force-stateful-recreation has NO per-resource granularity: it clears the data guard for every replacement target in the run`);
|
|
23449
23522
|
}
|
|
23450
23523
|
const rawRetention = properties["RetentionInDays"];
|
|
23451
23524
|
const rawPreviousRetention = previousProperties["RetentionInDays"];
|
|
23452
23525
|
const renderedRetention = JSON.stringify(rawRetention);
|
|
23453
23526
|
const sameRawRetention = Object.is(rawRetention, rawPreviousRetention) || renderedRetention !== void 0 && renderedRetention !== "null" && renderedRetention === JSON.stringify(rawPreviousRetention);
|
|
23454
|
-
const retentionInDays =
|
|
23455
|
-
const oldRetentionInDays =
|
|
23456
|
-
|
|
23527
|
+
const retentionInDays = toCfnInteger(rawRetention);
|
|
23528
|
+
const oldRetentionInDays = toCfnInteger(rawPreviousRetention);
|
|
23529
|
+
const desiredFromAwsReadback = context?.desiredFromAwsReadback === true;
|
|
23530
|
+
if (!sameRawRetention && !isAbsentRetention(rawRetention, desiredFromAwsReadback)) assertUsableRetention(rawRetention, retentionInDays, logicalId, resourceType);
|
|
23457
23531
|
const newKmsKeyId = properties["KmsKeyId"];
|
|
23458
23532
|
if (newKmsKeyId !== previousProperties["KmsKeyId"]) {
|
|
23459
23533
|
if (newKmsKeyId) await this.logsClient.send(new AssociateKmsKeyCommand({
|
|
@@ -23463,7 +23537,8 @@ var LogsLogGroupProvider = class {
|
|
|
23463
23537
|
else await this.logsClient.send(new DisassociateKmsKeyCommand({ logGroupName: physicalId }));
|
|
23464
23538
|
this.logger.debug(`Updated KMS key association for log group ${physicalId}`);
|
|
23465
23539
|
}
|
|
23466
|
-
|
|
23540
|
+
const previousRetentionUnknown = !sameRawRetention && Boolean(rawPreviousRetention) && !isAbsentRetention(rawPreviousRetention, false) && oldRetentionInDays === void 0;
|
|
23541
|
+
if (retentionInDays !== oldRetentionInDays || previousRetentionUnknown) {
|
|
23467
23542
|
if (retentionInDays !== void 0 && retentionInDays > 0) await this.logsClient.send(new PutRetentionPolicyCommand({
|
|
23468
23543
|
logGroupName: physicalId,
|
|
23469
23544
|
retentionInDays
|
|
@@ -26663,17 +26738,18 @@ function canonicalizeSgInlineRuleProtocols(properties) {
|
|
|
26663
26738
|
* `definedAttributes` then drops the `undefined` members; it keeps `''`, so the
|
|
26664
26739
|
* pending-window normalization has to happen HERE, where the state is known.
|
|
26665
26740
|
*
|
|
26666
|
-
*
|
|
26667
|
-
*
|
|
26668
|
-
*
|
|
26669
|
-
* `attributes` -- so `Fn::GetAtt [.., PublicIp]` takes the resolver's live arm
|
|
26670
|
-
*
|
|
26671
|
-
*
|
|
26672
|
-
*
|
|
26741
|
+
* What follows from the pending rule: a `--no-wait` create of an instance in
|
|
26742
|
+
* a PRIVATE subnet omits the public pair, and nothing later writes it -- a
|
|
26743
|
+
* no-change deploy's auto-refresh touches `observedProperties`, never
|
|
26744
|
+
* `attributes` -- so `Fn::GetAtt [.., PublicIp]` takes the resolver's live arm
|
|
26745
|
+
* on every resolution. That arm applies THIS SAME three-state rule at read
|
|
26746
|
+
* time (issue #3096): a settled instance's missing public member resolves to
|
|
26747
|
+
* the known `''`, while a still-`pending` one is REFUSED rather than degraded
|
|
26748
|
+
* to the instance id, and nothing is cached so the next deploy re-reads.
|
|
26673
26749
|
*/
|
|
26674
26750
|
function describedInstanceAttributes(instanceId, instance) {
|
|
26675
26751
|
const stateName = instance?.State?.Name;
|
|
26676
|
-
const settled = stateName
|
|
26752
|
+
const settled = isSettledInstanceState(stateName);
|
|
26677
26753
|
const publicMember = (value) => settled ? value ?? "" : value || void 0;
|
|
26678
26754
|
return definedAttributes({
|
|
26679
26755
|
InstanceId: instanceId,
|
|
@@ -70465,7 +70541,7 @@ async function resolveStateSecretExpressions(props, resolvers, secrets, options)
|
|
|
70465
70541
|
return out;
|
|
70466
70542
|
}
|
|
70467
70543
|
if (v !== null && typeof v === "object") {
|
|
70468
|
-
const out =
|
|
70544
|
+
const out = Object.create(null);
|
|
70469
70545
|
for (const [k, val] of Object.entries(v)) out[k] = await walk(val, path === "" ? k : `${path}.${k}`);
|
|
70470
70546
|
return out;
|
|
70471
70547
|
}
|
|
@@ -70863,29 +70939,74 @@ function buildReadCurrentStateContext(state, excludedLogicalId) {
|
|
|
70863
70939
|
* The read counterpart of {@link setAtPath}, and it parses the same subset:
|
|
70864
70940
|
* the drift comparator only synthesizes paths through plain objects.
|
|
70865
70941
|
*/
|
|
70942
|
+
/**
|
|
70943
|
+
* Read a dotted path out of a bag, OWN keys only.
|
|
70944
|
+
*
|
|
70945
|
+
* Own keys, because the bag is `JSON.parse`d state, whose one exotic key is an
|
|
70946
|
+
* OWN `__proto__` (issue #2899's class): on a plain object a bare
|
|
70947
|
+
* `cursor['__proto__']` read for a segment the bag does NOT own answers with
|
|
70948
|
+
* `Object.prototype` — an inherited value that the `--accept` post-write check
|
|
70949
|
+
* below would then compare against the accepted one. `undefined` is the answer
|
|
70950
|
+
* "the bag has nothing here", which is what an absent segment means.
|
|
70951
|
+
*
|
|
70952
|
+
* Exported, like `setAtPath`, as a test seam only: a `__proto__` path cannot
|
|
70953
|
+
* reach either through the command today (the comparison chain's normalisers
|
|
70954
|
+
* drop the key on both sides — issue #3121), so the own-key rule is pinned on
|
|
70955
|
+
* the helpers directly.
|
|
70956
|
+
*/
|
|
70866
70957
|
function getAtPath(source, path) {
|
|
70867
70958
|
if (path.length === 0) return source;
|
|
70868
70959
|
let cursor = source;
|
|
70869
70960
|
for (const segment of path.split(".")) {
|
|
70870
70961
|
if (cursor === null || typeof cursor !== "object" || Array.isArray(cursor)) return void 0;
|
|
70962
|
+
if (!hasOwnKey(cursor, segment)) return void 0;
|
|
70871
70963
|
cursor = cursor[segment];
|
|
70872
70964
|
}
|
|
70873
70965
|
return cursor;
|
|
70874
70966
|
}
|
|
70967
|
+
/**
|
|
70968
|
+
* Write a dotted path into a bag as OWN keys.
|
|
70969
|
+
*
|
|
70970
|
+
* `Object.defineProperty` rather than assignment, and a null-prototype
|
|
70971
|
+
* intermediate node (issue #2899's class): the target is `JSON.parse`d state
|
|
70972
|
+
* cloned by `runAccept`, a plain object, and a drifted top-level key literally
|
|
70973
|
+
* named `__proto__` (which `JSON.parse` yields as an ordinary own key and the
|
|
70974
|
+
* comparator enumerates like any other) would be ASSIGNED as the prototype —
|
|
70975
|
+
* the accepted value silently dropped from the baseline while the summary
|
|
70976
|
+
* counts it recorded. Defining it makes it the own data property the record
|
|
70977
|
+
* round-trips through `JSON.stringify`.
|
|
70978
|
+
*/
|
|
70875
70979
|
function setAtPath(target, path, value) {
|
|
70876
70980
|
if (path.length === 0) return;
|
|
70877
70981
|
const segments = path.split(".");
|
|
70878
70982
|
let cursor = target;
|
|
70879
70983
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
70880
70984
|
const key = segments[i];
|
|
70881
|
-
const next = cursor
|
|
70985
|
+
const next = ownValue(cursor, key);
|
|
70882
70986
|
if (next === void 0 || next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
70883
|
-
const fresh =
|
|
70884
|
-
cursor
|
|
70987
|
+
const fresh = Object.create(null);
|
|
70988
|
+
defineOwnKey(cursor, key, fresh);
|
|
70885
70989
|
cursor = fresh;
|
|
70886
70990
|
} else cursor = next;
|
|
70887
70991
|
}
|
|
70888
|
-
cursor
|
|
70992
|
+
defineOwnKey(cursor, segments[segments.length - 1], value);
|
|
70993
|
+
}
|
|
70994
|
+
/** Own-key membership: `in` reads the prototype chain, which a `JSON.parse`d bag never means. */
|
|
70995
|
+
function hasOwnKey(target, key) {
|
|
70996
|
+
return Object.prototype.hasOwnProperty.call(target, key);
|
|
70997
|
+
}
|
|
70998
|
+
/** An own-key read: `undefined` for an inherited name (`__proto__`, `constructor`, ...). */
|
|
70999
|
+
function ownValue(target, key) {
|
|
71000
|
+
return hasOwnKey(target, key) ? target[key] : void 0;
|
|
71001
|
+
}
|
|
71002
|
+
/** An ordinary (writable, enumerable, configurable) own data property — never the prototype. */
|
|
71003
|
+
function defineOwnKey(target, key, value) {
|
|
71004
|
+
Object.defineProperty(target, key, {
|
|
71005
|
+
value,
|
|
71006
|
+
writable: true,
|
|
71007
|
+
enumerable: true,
|
|
71008
|
+
configurable: true
|
|
71009
|
+
});
|
|
70889
71010
|
}
|
|
70890
71011
|
/**
|
|
70891
71012
|
* `--accept`: state ← AWS.
|
|
@@ -70956,14 +71077,14 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
|
|
|
70956
71077
|
setAtPath(newBaseline, change.path, change.awsValue);
|
|
70957
71078
|
accepted.push(change);
|
|
70958
71079
|
}
|
|
70959
|
-
const redactedBaseline = redactSecretsForState(newBaseline, outcome.secrets, existing.properties ?? {}, STATE_SOURCED_READBACK_RULES);
|
|
71080
|
+
const redactedBaseline = redactSecretsForState(newBaseline, outcome.secrets, existing.properties ?? {}, hasObserved ? STATE_SOURCED_BASELINE_RULES : STATE_SOURCED_READBACK_RULES);
|
|
70960
71081
|
let recordedChanges = 0;
|
|
70961
71082
|
for (const change of accepted) {
|
|
70962
71083
|
if (deepEqualUnordered(getAtPath(redactedBaseline, change.path), change.awsValue)) {
|
|
70963
71084
|
recordedChanges++;
|
|
70964
71085
|
continue;
|
|
70965
71086
|
}
|
|
70966
|
-
logger.warn(` ! ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): '${change.path}' was NOT recorded — cdkd state holds an unresolved '{{resolve:...}}' reference
|
|
71087
|
+
logger.warn(` ! ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): '${change.path}' was NOT recorded — after redaction cdkd state holds a different value at that property. Usually an unresolved '{{resolve:...}}' reference sits there and wins over an accepted value (change the template if that reference is wrong); a readback value cdkd cannot compare is never called recorded.`);
|
|
70967
71088
|
}
|
|
70968
71089
|
if (recordedChanges > 0) acceptedResourceCount++;
|
|
70969
71090
|
resources[outcome.logicalId] = hasObserved ? {
|
|
@@ -71104,10 +71225,10 @@ function findRevertPreservedTagKeys(drifts, desiredProperties, awsProperties) {
|
|
|
71104
71225
|
if (topLevelKey) driftedTopLevelKeys.add(topLevelKey);
|
|
71105
71226
|
}
|
|
71106
71227
|
for (const key of driftedTopLevelKeys) {
|
|
71107
|
-
if (!(key
|
|
71228
|
+
if (!hasOwnKey(desiredProperties, key)) continue;
|
|
71108
71229
|
if (!isTagListKey(key)) continue;
|
|
71109
71230
|
const desiredValue = desiredProperties[key];
|
|
71110
|
-
const awsValue = awsProperties
|
|
71231
|
+
const awsValue = ownValue(awsProperties, key);
|
|
71111
71232
|
if (!(isCfnTagList(desiredValue) || Array.isArray(desiredValue) && desiredValue.length === 0) || !isCfnTagList(awsValue)) continue;
|
|
71112
71233
|
const baselineKeys = new Set(tagKeys(desiredValue));
|
|
71113
71234
|
for (const tagKey of tagKeys(awsValue)) if (!baselineKeys.has(tagKey) && isServiceManagedTagKey(tagKey)) preserved.add(`${key}.${tagKey}`);
|
|
@@ -71163,8 +71284,8 @@ function findRevertUnbaselinedAwsKeys(drifts, desiredProperties, awsProperties)
|
|
|
71163
71284
|
if (topLevelKey) driftedTopLevelKeys.add(topLevelKey);
|
|
71164
71285
|
}
|
|
71165
71286
|
for (const key of driftedTopLevelKeys) {
|
|
71166
|
-
if (!(key
|
|
71167
|
-
collectMissingPaths(awsProperties
|
|
71287
|
+
if (!hasOwnKey(desiredProperties, key)) continue;
|
|
71288
|
+
collectMissingPaths(ownValue(awsProperties, key), desiredProperties[key], key, missing, isTagListKey(key) ? isServiceManagedTagKey : void 0);
|
|
71168
71289
|
}
|
|
71169
71290
|
return [...missing].sort();
|
|
71170
71291
|
}
|
|
@@ -71220,7 +71341,7 @@ function collectMissingPaths(awsValue, desiredValue, path, out, isPreservedKey)
|
|
|
71220
71341
|
}
|
|
71221
71342
|
for (const [key, value] of Object.entries(awsValue)) {
|
|
71222
71343
|
const childPath = `${path}.${key}`;
|
|
71223
|
-
if (!(key
|
|
71344
|
+
if (!hasOwnKey(desiredValue, key)) {
|
|
71224
71345
|
out.add(childPath);
|
|
71225
71346
|
continue;
|
|
71226
71347
|
}
|
|
@@ -71345,8 +71466,8 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
|
|
|
71345
71466
|
if (!isPlainRecord(awsValue) || !isPlainRecord(desiredValue)) return desiredValue;
|
|
71346
71467
|
if (!hasPlainPrototype(awsValue) || !hasPlainPrototype(desiredValue)) return desiredValue;
|
|
71347
71468
|
const merged = Object.create(null);
|
|
71348
|
-
for (const [key, value] of Object.entries(awsValue)) merged[key] = key
|
|
71349
|
-
for (const [key, value] of Object.entries(desiredValue)) if (!(key
|
|
71469
|
+
for (const [key, value] of Object.entries(awsValue)) merged[key] = hasOwnKey(desiredValue, key) ? mergeUntemplatedValue(value, desiredValue[key]) : value;
|
|
71470
|
+
for (const [key, value] of Object.entries(desiredValue)) if (!hasOwnKey(awsValue, key)) merged[key] = value;
|
|
71350
71471
|
return merged;
|
|
71351
71472
|
}
|
|
71352
71473
|
/**
|
|
@@ -71404,7 +71525,7 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
71404
71525
|
if (typeof value === "string" && value.includes("{{resolve:")) {
|
|
71405
71526
|
if (!isSingleDynamicReferenceToken(value)) return value;
|
|
71406
71527
|
if (live === void 0) return value;
|
|
71407
|
-
return live;
|
|
71528
|
+
return typeof live === "string" ? live : value;
|
|
71408
71529
|
}
|
|
71409
71530
|
if (Array.isArray(value)) {
|
|
71410
71531
|
const liveItems = pairedLiveItems(value, live, isTokenOrMaskLeaf, carriesTokenOrMask, true);
|
|
@@ -71414,7 +71535,7 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
71414
71535
|
if (!hasPlainPrototype(value)) return value;
|
|
71415
71536
|
const liveObject = isPlainRecord(live) ? live : void 0;
|
|
71416
71537
|
const out = Object.create(null);
|
|
71417
|
-
for (const [k, v] of Object.entries(value)) out[k] = walk(v, liveObject
|
|
71538
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk(v, liveObject === void 0 ? void 0 : ownValue(liveObject, k));
|
|
71418
71539
|
return out;
|
|
71419
71540
|
}
|
|
71420
71541
|
return value;
|
|
@@ -71649,7 +71770,7 @@ function pairedLiveItems(send, live, wildcardLeaf = isMaskLeaf, carriesWildcard
|
|
|
71649
71770
|
for (const item of live) byIdentity.set(item[key], item);
|
|
71650
71771
|
return send.map((item) => {
|
|
71651
71772
|
const identity = item[key];
|
|
71652
|
-
return
|
|
71773
|
+
return isTokenOrMaskLeaf(identity) ? void 0 : byIdentity.get(identity);
|
|
71653
71774
|
});
|
|
71654
71775
|
}
|
|
71655
71776
|
if (live.length !== send.length) return void 0;
|
|
@@ -71843,12 +71964,13 @@ function corroboratedLeafCount(send, live, wildcardLeaf = isMaskLeaf) {
|
|
|
71843
71964
|
* masked VALUES the walk writes still come from `send` — only the evidence
|
|
71844
71965
|
* source changes. Defaults to `send` (self-corroboration, the pre-round-4
|
|
71845
71966
|
* behaviour) for a caller with no sibling pass; a structural mismatch between
|
|
71846
|
-
* the two bags
|
|
71847
|
-
*
|
|
71848
|
-
*
|
|
71849
|
-
*
|
|
71850
|
-
*
|
|
71851
|
-
*
|
|
71967
|
+
* the two bags refuses the masks beneath it rather than falling back to the
|
|
71968
|
+
* fabricable bag. Until issue #2920 the token pass COULD produce one, by
|
|
71969
|
+
* returning the live CONTAINER AWS holds over a whole-token string leaf; it
|
|
71970
|
+
* now copies only a string, so no sibling pass produces the mismatch today.
|
|
71971
|
+
* The refusal is KEPT as a deliberate fail-closed cost rather than retired on
|
|
71972
|
+
* that reachability argument: a bag that disagrees with its evidence source is
|
|
71973
|
+
* exactly where falling back would hand the fabricable bag back.
|
|
71852
71974
|
*
|
|
71853
71975
|
* Returns the input bag BY IDENTITY when it holds no mask — checked FIRST via
|
|
71854
71976
|
* {@link carriesSecretMask}, so a mask-free revert (the ordinary case) pays
|
|
@@ -71884,7 +72006,7 @@ function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets, corrobor
|
|
|
71884
72006
|
const liveObject = isPlainRecord(live) ? live : void 0;
|
|
71885
72007
|
const corrObject = isPlainRecord(corr) ? corr : void 0;
|
|
71886
72008
|
const out = Object.create(null);
|
|
71887
|
-
for (const [k, v] of Object.entries(value)) out[k] = walk(v, corrObject
|
|
72009
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk(v, corrObject === void 0 ? void 0 : ownValue(corrObject, k), liveObject === void 0 ? void 0 : ownValue(liveObject, k), path === "" ? k : `${path}.${k}`);
|
|
71888
72010
|
return out;
|
|
71889
72011
|
}
|
|
71890
72012
|
return value;
|
|
@@ -71955,13 +72077,13 @@ function buildRevertNewProperties(drifts, desiredProperties, awsProperties, opti
|
|
|
71955
72077
|
for (const d of drifts) {
|
|
71956
72078
|
const topLevelKey = d.path.split(".", 1)[0];
|
|
71957
72079
|
if (!topLevelKey) continue;
|
|
71958
|
-
if (topLevelKey
|
|
72080
|
+
if (hasOwnKey(desiredProperties, topLevelKey)) {
|
|
71959
72081
|
const desiredValue = desiredProperties[topLevelKey];
|
|
71960
|
-
const awsValue = awsProperties
|
|
71961
|
-
if (preserveUntemplated) result
|
|
72082
|
+
const awsValue = ownValue(awsProperties, topLevelKey);
|
|
72083
|
+
if (preserveUntemplated) defineOwnKey(result, topLevelKey, mergeUntemplatedValue(awsValue, desiredValue));
|
|
71962
72084
|
else {
|
|
71963
72085
|
const baselineIsTagList = isCfnTagList(desiredValue) || Array.isArray(desiredValue) && desiredValue.length === 0;
|
|
71964
|
-
result
|
|
72086
|
+
defineOwnKey(result, topLevelKey, isTagListKey(topLevelKey) && baselineIsTagList && isCfnTagList(awsValue) ? mergeTagListForRevert(desiredValue, awsValue) : desiredValue);
|
|
71965
72087
|
}
|
|
71966
72088
|
}
|
|
71967
72089
|
}
|
|
@@ -71992,7 +72114,7 @@ function buildRevertNewProperties(drifts, desiredProperties, awsProperties, opti
|
|
|
71992
72114
|
* too. That is represented by an explicit `undefined` value, which the caller
|
|
71993
72115
|
* turns into a `delete` — a plain `{...baseline, ...delta}` spread would leave
|
|
71994
72116
|
* an `undefined`-valued key in the JSON instead. Presence is decided by
|
|
71995
|
-
*
|
|
72117
|
+
* OWN-key membership in `effective`, NOT by comparing against `undefined`: a provider that
|
|
71996
72118
|
* delivers an explicit `undefined` and one that omits the key both mean "AWS
|
|
71997
72119
|
* does not hold it", while a key whose value genuinely IS `null` on both sides
|
|
71998
72120
|
* must not read as a drop.
|
|
@@ -72005,17 +72127,34 @@ function buildRevertNewProperties(drifts, desiredProperties, awsProperties, opti
|
|
|
72005
72127
|
* prevent.
|
|
72006
72128
|
*/
|
|
72007
72129
|
function collectNarrowedTopLevelKeys(sent, effective) {
|
|
72008
|
-
const delta =
|
|
72130
|
+
const delta = Object.create(null);
|
|
72009
72131
|
for (const key of /* @__PURE__ */ new Set([...Object.keys(sent), ...Object.keys(effective)])) {
|
|
72010
|
-
const inSent = key
|
|
72011
|
-
const inEffective = key
|
|
72132
|
+
const inSent = hasOwnKey(sent, key);
|
|
72133
|
+
const inEffective = hasOwnKey(effective, key);
|
|
72012
72134
|
if (inSent && inEffective && deepEqualUnordered(sent[key], effective[key])) continue;
|
|
72013
72135
|
if (!inSent && !inEffective) continue;
|
|
72014
72136
|
delta[key] = inEffective ? effective[key] : void 0;
|
|
72015
72137
|
}
|
|
72016
72138
|
return delta;
|
|
72017
72139
|
}
|
|
72018
|
-
/**
|
|
72140
|
+
/**
|
|
72141
|
+
* Deep equality that does not depend on object key ORDER.
|
|
72142
|
+
*
|
|
72143
|
+
* A NON-PLAIN object (`Date`, `Uint8Array`, `Map`, a class instance) on either
|
|
72144
|
+
* side compares UNEQUAL unless it is the same object (issue
|
|
72145
|
+
* [#2897](https://github.com/go-to-k/cdkd/issues/2897)): its own enumerable
|
|
72146
|
+
* keys are `[]`, so the key walk below reported any two `Date`s equal —
|
|
72147
|
+
* `0 === 0` — which HID a narrowing in {@link collectNarrowedTopLevelKeys}
|
|
72148
|
+
* (the changed value never reached `observedProperties`, leaving the baseline
|
|
72149
|
+
* stale there) and SUPPRESSED `runAccept`'s "was NOT recorded" warning. Same
|
|
72150
|
+
* guard, same reason as `secret-redaction.ts`'s `deepEqualJsonValue` and the
|
|
72151
|
+
* corroboration count above. Reachable: `sent` descends from
|
|
72152
|
+
* `buildRevertNewProperties`, which copies `readCurrentState`'s RAW SDK return
|
|
72153
|
+
* with no JSON round-trip, and `effective` is a provider's own echo. The
|
|
72154
|
+
* direction is deliberate — a value cdkd cannot compare is a value it must not
|
|
72155
|
+
* call unchanged, and for the narrowing that means the provider's echo is
|
|
72156
|
+
* recorded (the truthful answer) rather than silently kept stale.
|
|
72157
|
+
*/
|
|
72019
72158
|
function deepEqualUnordered(a, b) {
|
|
72020
72159
|
if (a === b) return true;
|
|
72021
72160
|
if (a == null || b == null) return a === b;
|
|
@@ -72024,6 +72163,7 @@ function deepEqualUnordered(a, b) {
|
|
|
72024
72163
|
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
72025
72164
|
return a.every((v, i) => deepEqualUnordered(v, b[i]));
|
|
72026
72165
|
}
|
|
72166
|
+
if (!hasPlainPrototype(a) || !hasPlainPrototype(b)) return false;
|
|
72027
72167
|
const ao = a;
|
|
72028
72168
|
const bo = b;
|
|
72029
72169
|
const ak = Object.keys(ao);
|
|
@@ -72184,7 +72324,7 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
72184
72324
|
try {
|
|
72185
72325
|
if (updateResult?.effectiveProperties) {
|
|
72186
72326
|
const delta = collectNarrowedTopLevelKeys(newProperties, updateResult.effectiveProperties);
|
|
72187
|
-
if (Object.keys(delta).length > 0) narrowedByLogicalId.set(outcome.logicalId, redactSecretsForState(delta, secrets, revertBaseline, STATE_SOURCED_READBACK_RULES));
|
|
72327
|
+
if (Object.keys(delta).length > 0) narrowedByLogicalId.set(outcome.logicalId, redactSecretsForState(delta, secrets, revertBaseline, stateResource.observedProperties !== void 0 ? STATE_SOURCED_BASELINE_RULES : STATE_SOURCED_READBACK_RULES));
|
|
72188
72328
|
}
|
|
72189
72329
|
} catch (captureErr) {
|
|
72190
72330
|
logger.warn(` ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted, but the provider's reported effective properties could not be read — ${maskSecretsInText(captureErr instanceof Error ? captureErr.message : String(captureErr), secrets)}`);
|
|
@@ -72211,14 +72351,14 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
72211
72351
|
const newBaseline = JSON.parse(JSON.stringify(baselineSource ?? {}));
|
|
72212
72352
|
let changed = false;
|
|
72213
72353
|
for (const [key, value] of Object.entries(delta)) {
|
|
72214
|
-
if (!
|
|
72354
|
+
if (!hasOwnKey(newBaseline, key)) continue;
|
|
72215
72355
|
if (value === void 0) {
|
|
72216
72356
|
delete newBaseline[key];
|
|
72217
72357
|
changed = true;
|
|
72218
72358
|
continue;
|
|
72219
72359
|
}
|
|
72220
72360
|
if (!hasObserved) continue;
|
|
72221
|
-
newBaseline
|
|
72361
|
+
defineOwnKey(newBaseline, key, value);
|
|
72222
72362
|
changed = true;
|
|
72223
72363
|
}
|
|
72224
72364
|
if (!changed) continue;
|
|
@@ -74274,11 +74414,25 @@ async function resolveIdentifierValue(entry, ctx, deps) {
|
|
|
74274
74414
|
try {
|
|
74275
74415
|
return entry.resolve(ctx);
|
|
74276
74416
|
} catch (stateOnlyError) {
|
|
74277
|
-
if (
|
|
74278
|
-
|
|
74417
|
+
if (entry.backfill && deps) return await entry.backfill(ctx, deps);
|
|
74418
|
+
if (carriesSecretMask(ctx.attributes[entry.field])) throw new Error(maskedIdentifierAttributeReason(entry.field, ctx.logicalId));
|
|
74419
|
+
throw stateOnlyError;
|
|
74279
74420
|
}
|
|
74280
74421
|
}
|
|
74281
74422
|
/**
|
|
74423
|
+
* The refusal for a recorded identifier attribute that holds the redaction
|
|
74424
|
+
* mask (issue [#2932](https://github.com/go-to-k/cdkd/issues/2932)) — the
|
|
74425
|
+
* `attributes` twin of the `properties` blocker in {@link buildImportPlan},
|
|
74426
|
+
* scoped to the ONE position `cdkd export` reads out of that bag rather than
|
|
74427
|
+
* to the whole bag. Widening it to any masked attribute would block every
|
|
74428
|
+
* Cloud-Control-imported record permanently: since issue #2847 that import
|
|
74429
|
+
* masks each writable model key BY DESIGN, so the population exports fine
|
|
74430
|
+
* today and would never stop being blocked by a re-import.
|
|
74431
|
+
*/
|
|
74432
|
+
function maskedIdentifierAttributeReason(field, logicalId) {
|
|
74433
|
+
return `cdkd state holds only the redaction mask ('***') at attributes.${field} for '${logicalId}', and that attribute is the value cdkd export reads as this resource type's CloudFormation import identifier (${field}); nothing masked may reach the exported template, since CloudFormation would either refuse it at IMPORT or write it onto the live resource at the next update. The mask is what 'cdkd import' writes for a Cloud Control model key it could not certify as read-only — every key when cloudformation:DescribeType was unavailable. Re-deploying does NOT clear it. Repair the record with 'cdkd import <stack> --resource ${logicalId}=<physicalId> --force' after granting cloudformation:DescribeType, or export the stack without this resource and adopt it into CloudFormation by hand. See https://github.com/go-to-k/cdkd/issues/2932.`;
|
|
74434
|
+
}
|
|
74435
|
+
/**
|
|
74282
74436
|
* Build the {@link CompositePhysicalIdIdentifier} for a type whose CFn
|
|
74283
74437
|
* identifier is an ARN cdkd records in `attributes` under the SAME name as the
|
|
74284
74438
|
* `primaryIdentifier` field.
|
|
@@ -75151,6 +75305,14 @@ async function buildImportPlan(state, template, cfnClient, parentStackName, opti
|
|
|
75151
75305
|
continue;
|
|
75152
75306
|
}
|
|
75153
75307
|
const propertiesOverlay = resolved.propertiesOverlay ?? resolved.resourceIdentifier;
|
|
75308
|
+
if (carriesSecretMask(resolved.resourceIdentifier) || carriesSecretMask(propertiesOverlay)) {
|
|
75309
|
+
blocked.push({
|
|
75310
|
+
logicalId,
|
|
75311
|
+
resourceType,
|
|
75312
|
+
reason: `the CloudFormation import identifier cdkd resolved for this resource is the redaction mask ('***'), so the exported template would declare the mask as the resource's identity — CloudFormation would either refuse it at IMPORT or write it onto the live resource at the next update. cdkd state holds only the mask where the identifier should be (a masked attribute, or a masked physical id). Repair the record ('cdkd import <stack> --resource ${logicalId}=<physicalId> --force', granting cloudformation:DescribeType first if the import warned that it could not read the schema), or export the stack without this resource and adopt it into CloudFormation by hand. See https://github.com/go-to-k/cdkd/issues/2932.`
|
|
75313
|
+
});
|
|
75314
|
+
continue;
|
|
75315
|
+
}
|
|
75154
75316
|
const unrepresentable = unrepresentableOverlayFields(raw, propertiesOverlay);
|
|
75155
75317
|
if (unrepresentable.length > 0) {
|
|
75156
75318
|
for (const u of unrepresentable) blocked.push({
|
|
@@ -78522,10 +78684,14 @@ function snapshotNote(resourceType, effectiveProvisionedBy, skipFinalSnapshot) {
|
|
|
78522
78684
|
* worse than a forged diagnostic: the plan preview below is what the user
|
|
78523
78685
|
* CONFIRMS against, so a planted journal could forge the plan rows themselves.
|
|
78524
78686
|
*
|
|
78525
|
-
*
|
|
78687
|
+
* `displayIdent`, because every value it guards has a known charset: a
|
|
78526
78688
|
* logical id, a CFn resource type, a change type, a run id, a segment reason,
|
|
78527
|
-
* a stack name, an AWS region.
|
|
78528
|
-
*
|
|
78689
|
+
* a stack name, an AWS region. The ASCII allowlist, a length cap, and -- since
|
|
78690
|
+
* issue #3092 -- a visible boundary: an all-ASCII id such as
|
|
78691
|
+
* `X (AWS::RDS::DBInstance) -- already reverted` survives the allowlist and
|
|
78692
|
+
* plants this preview's own annotation wording inside a real row, so a value
|
|
78693
|
+
* that is not a plain identifier renders JSON-quoted. Call it for those;
|
|
78694
|
+
* `grep safe(` answers the scope and this comment does not.
|
|
78529
78695
|
*
|
|
78530
78696
|
* NOT for a value that is about to be USED rather than shown -- the preview
|
|
78531
78697
|
* indexes `previewState` by the RAW `op.logicalId`, and sanitising a lookup
|
|
@@ -78538,7 +78704,7 @@ function snapshotNote(resourceType, effectiveProvisionedBy, skipFinalSnapshot) {
|
|
|
78538
78704
|
* answers how many; a count written here was wrong on its first revision.
|
|
78539
78705
|
*/
|
|
78540
78706
|
function safe(value) {
|
|
78541
|
-
return
|
|
78707
|
+
return displayIdent(value);
|
|
78542
78708
|
}
|
|
78543
78709
|
/**
|
|
78544
78710
|
* Human label for a planned rollback action (plan preview). `skipFinalSnapshot`
|
|
@@ -83846,7 +84012,7 @@ function resolveAssetCodePath$1(stack, logicalId, resource) {
|
|
|
83846
84012
|
* **Order is preserved**: `Properties.Layers` is iterated left-to-right
|
|
83847
84013
|
* and the resulting `ResolvedLambdaLayer[]` carries the same order. The
|
|
83848
84014
|
* caller (`local-invoke.ts`'s `materializeLambdaLayers` and
|
|
83849
|
-
* `local-start-api.ts`'s server-boot pre-merge)
|
|
84015
|
+
* `local-start-api.ts`'s server-boot pre-merge) merges every
|
|
83850
84016
|
* entry into one host tmpdir in template order to honor AWS's
|
|
83851
84017
|
* "last-layer-wins" file-collision semantics — Docker rejects multiple
|
|
83852
84018
|
* bind mounts at the same target so cdkd cannot rely on overlay
|
|
@@ -84117,6 +84283,110 @@ function notFoundError$1(target, stack, resources) {
|
|
|
84117
84283
|
return new LocalInvokeResolutionError(msg.trimEnd());
|
|
84118
84284
|
}
|
|
84119
84285
|
|
|
84286
|
+
//#endregion
|
|
84287
|
+
//#region src/local/layer-tree-copy.ts
|
|
84288
|
+
/**
|
|
84289
|
+
* Merge one Lambda Layer's asset tree into the directory that will be
|
|
84290
|
+
* bind-mounted at `/opt`, with AWS's "last layer wins" semantic and with
|
|
84291
|
+
* symlinks kept VERBATIM (issue #3106).
|
|
84292
|
+
*
|
|
84293
|
+
* Why this is a hand-written walk and not `cpSync`. The merge used to be
|
|
84294
|
+
* `cpSync(src, dest, { recursive: true, force: true })`, and every
|
|
84295
|
+
* `cpSync`-shaped repair of it was measured wrong in some direction:
|
|
84296
|
+
*
|
|
84297
|
+
* - Without `verbatimSymlinks: true` (the default), `cpSync` rewrites a
|
|
84298
|
+
* RELATIVE link target to the ABSOLUTE path of the source on the host —
|
|
84299
|
+
* `bin/rel-link -> real.sh` arrives as `-> <cdk.out>/asset.<hash>/bin/
|
|
84300
|
+
* real.sh`, which is dangling inside the container, where only `dest`
|
|
84301
|
+
* is mounted. Measured on Node 22.12 / 24.21.
|
|
84302
|
+
* - WITH `verbatimSymlinks: true`, Node's C++ `cpSync` (every release
|
|
84303
|
+
* after 22.12) throws `EEXIST` when a later layer carries a symlink at a
|
|
84304
|
+
* path an earlier layer already placed one — `nodejs/node_modules/.bin/
|
|
84305
|
+
* <tool>` in two layers built from the same dependency is the ordinary
|
|
84306
|
+
* case. Measured by the go-to-k/cdkd#3118 review on 22.23 / 24.15 /
|
|
84307
|
+
* 24.21; the JS implementation on 22.12 does not throw, which is why
|
|
84308
|
+
* the floor alone did not show it.
|
|
84309
|
+
* - A `cpSync` + `readdirSync({ recursive: true })` pair was worse:
|
|
84310
|
+
* the recursive readdir DESCENDS INTO directory symlinks on every Node
|
|
84311
|
+
* after 22.12 (not on 22.12), so a cyclic link (`sub/up -> ..`, or a
|
|
84312
|
+
* pnpm-style `node_modules`) hung the merge, and an absolute link to a
|
|
84313
|
+
* host directory made the walk delete and write INSIDE that host
|
|
84314
|
+
* directory. And `cpSync`'s `force` writes THROUGH a destination that
|
|
84315
|
+
* is already a directory symlink, so a later layer's real directory
|
|
84316
|
+
* landed in the earlier layer's link target — on the host, when the
|
|
84317
|
+
* link was absolute. Same review, same versions.
|
|
84318
|
+
*
|
|
84319
|
+
* So the walk is explicit and recurses only on a real directory (`d_type`,
|
|
84320
|
+
* via `Dirent.isDirectory()`, which reports a directory symlink as a
|
|
84321
|
+
* symlink on 22.12 and 24.21 alike). Per entry, "last layer wins" is applied
|
|
84322
|
+
* the same way for every kind: whatever sits at the destination path that is
|
|
84323
|
+
* not of the same kind is removed first — a link, a file, or a whole
|
|
84324
|
+
* directory an earlier layer placed — then the entry lands. A symlink is
|
|
84325
|
+
* recreated with the SOURCE's own target string and never resolved, so an
|
|
84326
|
+
* absolute link stays absolute and a dangling one stays dangling, exactly as
|
|
84327
|
+
* AWS extracts the layer ZIP into `/opt`; nothing is ever written through a
|
|
84328
|
+
* link. A file is copied with `cpSync` (single file, `force`), which is what
|
|
84329
|
+
* preserves the mode bits, so a layer's `bin/<script>` keeps its `+x` — the
|
|
84330
|
+
* property the `local-invoke-layers` fixture executes through.
|
|
84331
|
+
*
|
|
84332
|
+
* The ROOT may itself be a symlink (an asset dir handed over through a link
|
|
84333
|
+
* passes `resolveAssetCodePath`'s `statSync`). `readdirSync` follows a root
|
|
84334
|
+
* link by itself, so the `realpathSync` is not what makes that case work,
|
|
84335
|
+
* and it does not move where a dangling or looping root fails either (the
|
|
84336
|
+
* first `readdirSync` raises the same `ENOENT` / `ELOOP` naming the asset
|
|
84337
|
+
* path); it only keeps every path the walk joins real.
|
|
84338
|
+
*
|
|
84339
|
+
* Bound, stated rather than handled: a FIFO or a socket inside a layer
|
|
84340
|
+
* makes the single-file `cpSync` throw (`ERR_FS_CP_FIFO_PIPE` /
|
|
84341
|
+
* `ERR_FS_CP_SOCKET`, immediately, never blocking) where the old recursive
|
|
84342
|
+
* copy skipped it in silence on some releases. CDK's own asset staging
|
|
84343
|
+
* cannot stage such an entry, so no real layer carries one.
|
|
84344
|
+
*
|
|
84345
|
+
* Both layer merges (`cdkd local invoke` and `cdkd local start-api`) call
|
|
84346
|
+
* this; keep them on the one helper rather than re-spelling copy options.
|
|
84347
|
+
*/
|
|
84348
|
+
function copyLayerTreeLastWins(src, dest) {
|
|
84349
|
+
mkdirSync(dest, { recursive: true });
|
|
84350
|
+
copyDirLastWins(realpathSync(src), dest);
|
|
84351
|
+
}
|
|
84352
|
+
function copyDirLastWins(fromDir, toDir) {
|
|
84353
|
+
for (const entry of readdirSync(fromDir, { withFileTypes: true })) {
|
|
84354
|
+
const from = join(fromDir, entry.name);
|
|
84355
|
+
const to = join(toDir, entry.name);
|
|
84356
|
+
if (entry.isSymbolicLink()) {
|
|
84357
|
+
removeUnless(to, "symlink");
|
|
84358
|
+
symlinkSync(readlinkSync(from), to);
|
|
84359
|
+
} else if (entry.isDirectory()) {
|
|
84360
|
+
removeUnless(to, "directory");
|
|
84361
|
+
mkdirSync(to, { recursive: true });
|
|
84362
|
+
copyDirLastWins(from, to);
|
|
84363
|
+
} else {
|
|
84364
|
+
removeUnless(to, "file");
|
|
84365
|
+
cpSync(from, to, { force: true });
|
|
84366
|
+
}
|
|
84367
|
+
}
|
|
84368
|
+
}
|
|
84369
|
+
/**
|
|
84370
|
+
* Clear the destination path unless what is there is already of `kind`. A
|
|
84371
|
+
* directory is kept so a later layer MERGES into it; a file is kept because
|
|
84372
|
+
* `cpSync` overwrites it in place; a symlink is always replaced (its target
|
|
84373
|
+
* string comes from the later layer).
|
|
84374
|
+
*/
|
|
84375
|
+
function removeUnless(to, kind) {
|
|
84376
|
+
let st;
|
|
84377
|
+
try {
|
|
84378
|
+
st = lstatSync(to);
|
|
84379
|
+
} catch {
|
|
84380
|
+
return;
|
|
84381
|
+
}
|
|
84382
|
+
if (kind === "directory" && st.isDirectory()) return;
|
|
84383
|
+
if (kind === "file" && st.isFile()) return;
|
|
84384
|
+
rmSync(to, {
|
|
84385
|
+
recursive: true,
|
|
84386
|
+
force: true
|
|
84387
|
+
});
|
|
84388
|
+
}
|
|
84389
|
+
|
|
84120
84390
|
//#endregion
|
|
84121
84391
|
//#region src/local/ecs-task-resolver.ts
|
|
84122
84392
|
function detectEcsImageResolutionNeeds(stack) {
|
|
@@ -87533,12 +87803,12 @@ async function resolveLocalBuildPlan$1(lambda) {
|
|
|
87533
87803
|
* when the entry is a same-stack asset. Literal-ARN entries always
|
|
87534
87804
|
* pre-materialize first.
|
|
87535
87805
|
* - 2+ layers → copy each into a fresh tmpdir IN ORDER (later
|
|
87536
|
-
* layers overwrite earlier files via `
|
|
87806
|
+
* layers overwrite earlier files via `copyLayerTreeLastWins`),
|
|
87537
87807
|
* bind-mount the tmpdir at `/opt`. Records the tmpdir in
|
|
87538
87808
|
* `layerTmpDirs` so `shutdown(...)` removes it.
|
|
87539
87809
|
*
|
|
87540
87810
|
* Issue #448: literal-ARN entries (`{kind: 'arn', ...}`) are downloaded
|
|
87541
|
-
* + unzipped via `lambda:GetLayerVersion` BEFORE the
|
|
87811
|
+
* + unzipped via `lambda:GetLayerVersion` BEFORE the merge
|
|
87542
87812
|
* branches run. Every per-ARN tmpdir is also recorded in `layerTmpDirs`
|
|
87543
87813
|
* so the same shutdown path cleans it up — even for the single-layer
|
|
87544
87814
|
* fast path that bind-mounts the dir directly.
|
|
@@ -87568,10 +87838,15 @@ async function materializeLambdaLayers$1(layers, layerTmpDirs, layerRoleArn) {
|
|
|
87568
87838
|
}
|
|
87569
87839
|
if (flat.length === 1) return flat[0].assetPath;
|
|
87570
87840
|
const dir = mkdtempSync(path.join(tmpdir(), "cdkd-local-start-api-layers-"));
|
|
87571
|
-
|
|
87572
|
-
|
|
87573
|
-
|
|
87574
|
-
|
|
87841
|
+
try {
|
|
87842
|
+
for (const layer of flat) copyLayerTreeLastWins(layer.assetPath, dir);
|
|
87843
|
+
} catch (error) {
|
|
87844
|
+
rmSync(dir, {
|
|
87845
|
+
recursive: true,
|
|
87846
|
+
force: true
|
|
87847
|
+
});
|
|
87848
|
+
throw error;
|
|
87849
|
+
}
|
|
87575
87850
|
layerTmpDirs.add(dir);
|
|
87576
87851
|
return dir;
|
|
87577
87852
|
}
|
|
@@ -91522,7 +91797,7 @@ async function resolveZipImagePlan(lambda, options) {
|
|
|
91522
91797
|
* - Stage 2: the resulting `{logicalId, assetPath}[]` list (in
|
|
91523
91798
|
* template order — ARN entries surface their `arn` as the
|
|
91524
91799
|
* `logicalId` for log lines) is handed to the existing
|
|
91525
|
-
* `materializeLambdaLayers`
|
|
91800
|
+
* `materializeLambdaLayers` merge path. AWS's "last layer
|
|
91526
91801
|
* wins" file-collision semantic is preserved across both layer
|
|
91527
91802
|
* kinds because the merge step is unchanged.
|
|
91528
91803
|
*/
|
|
@@ -91590,7 +91865,7 @@ function resolveTmpfsForLambda(lambda) {
|
|
|
91590
91865
|
* than copying since CDK has already unzipped the asset.
|
|
91591
91866
|
* 3. **Multiple layers**: copies each layer's contents into a fresh
|
|
91592
91867
|
* tmpdir IN ORDER (later layers overwrite earlier files via
|
|
91593
|
-
* `
|
|
91868
|
+
* `copyLayerTreeLastWins`), then bind-mounts the merged tmpdir at
|
|
91594
91869
|
* `/opt`. Returns `{ mount, tmpDir: <path> }` so the caller can
|
|
91595
91870
|
* `rmSync` the tmpdir on cleanup.
|
|
91596
91871
|
*
|
|
@@ -91607,10 +91882,15 @@ function materializeLambdaLayers(layers) {
|
|
|
91607
91882
|
readOnly: true
|
|
91608
91883
|
} };
|
|
91609
91884
|
const tmpDir = mkdtempSync(path.join(tmpdir(), "cdkd-local-invoke-layers-"));
|
|
91610
|
-
|
|
91611
|
-
|
|
91612
|
-
|
|
91613
|
-
|
|
91885
|
+
try {
|
|
91886
|
+
for (const layer of layers) copyLayerTreeLastWins(layer.assetPath, tmpDir);
|
|
91887
|
+
} catch (error) {
|
|
91888
|
+
rmSync(tmpDir, {
|
|
91889
|
+
recursive: true,
|
|
91890
|
+
force: true
|
|
91891
|
+
});
|
|
91892
|
+
throw error;
|
|
91893
|
+
}
|
|
91614
91894
|
return {
|
|
91615
91895
|
mount: {
|
|
91616
91896
|
hostPath: tmpDir,
|
|
@@ -92177,4 +92457,4 @@ function buildProgram() {
|
|
|
92177
92457
|
|
|
92178
92458
|
//#endregion
|
|
92179
92459
|
export { buildProgram };
|
|
92180
|
-
//# sourceMappingURL=program-
|
|
92460
|
+
//# sourceMappingURL=program-CoRRfEe_.js.map
|