@go-to-k/cdkd 0.291.8 → 0.291.10
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-B3RJYZnx.js → asg-provider-BY-tj21g.js} +2 -2
- package/dist/{asg-provider-B3RJYZnx.js.map → asg-provider-BY-tj21g.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-eLdrQQ2R.js → deploy-engine-BomFOZF4.js} +902 -205
- package/dist/deploy-engine-BomFOZF4.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-Fgtmg1PN.js → program-BfCsjuyU.js} +551 -257
- package/dist/{program-Fgtmg1PN.js.map → program-BfCsjuyU.js.map} +1 -1
- package/dist/{version-D_XcHZoy.js → version-5OUy_OlQ.js} +2 -2
- package/dist/{version-D_XcHZoy.js.map → version-5OUy_OlQ.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-eLdrQQ2R.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as yellow, $a as
|
|
1
|
+
import { $ as yellow, $a as markNonRetryable, $i as expectedOwnerParam, $n as repairMalformedResourcePropertiesForReadOnly, $r as AssetModeResolver, $t as requireConfigObject, A as createPreDeleteFinalSnapshot, Aa as LocalInvokeBuildError$1, Ai as getDefaultStateBucketName, An as malformedOutputsRefusalMessage, Ar as buildForceUnlockCommand, At as carriesDynamicReference, B as isStatefulRecreateTargetSync, Ba as StackTerminationProtectionError, Bi as warnDeprecatedNoPrefixCliFlag, Bn as refuseMalformedOrphansForDestroy, Br as importableOutputKeys, Bt as isSettledInstanceState, C as namesAnyDeclaredParameter, Ca as CdkdError, Ci as runDockerStreaming, Cn as isReadableResourceEntry, Cr as recoverMaskedOutput, Ct as UNSPECIFIED_SKIP_REASON, D as PRE_DELETE_SNAPSHOT_TYPES, Da as DeployCancelledError, Di as getDockerImageBySourceHash, Dn as malformedOrphanRecordsWarning, Dr as S3StateBackend, Dt as disableInstanceApiTermination, E as ATOMIC_FINAL_SNAPSHOT_TYPES, Ei as AssetManifestLoader, En as malformedLocalOutputsWarning, Er as LockManager, Et as isWaitAbandonedError, F as extractDeploymentEventError, Fa as PartialFailureError, Fi as resolveSkipPrefix, Fn as malformedResourcesWarning, Fr as DEFAULT_STATE_PREFIX, Ft as isImpossibleEmptyStoredAttribute, G as toCfnInteger, Ga as normalizeAwsError, Gi as findLargeInlineResources, Gn as refuseMalformedResourceEntries, Gr as shouldRetainResource, Gt as coerceCfnBoolean, H as WARM_THROUGHPUT_MEMBERS, Ha as SynthesisError, Hi as CFN_TEMPLATE_BODY_LIMIT, Hn as refuseMalformedOutputs, Hr as isReadableBag, Ht as normalizeAwsTagsToCfn, I as makeSiblingClaimReader, Ia as ProvisioningError, Ii as resolveStateBucketWithDefault, In as malformedStateRefusalMessage, Ir as exportNamesCarriedFrom, It as isNamelessDynamicReferenceError, J as bold, Ja as shellQuote, Ji as injectiveKey, Jn as refuseMalformedResourcesForDestroy, Jr as WorkGraph, Jt as configIntegerRefusal, K as toFiniteNumber, Ka as withErrorHandling, Ki as uploadCfnTemplate, Kn as refuseMalformedResourceEntriesForOrphan, Kr as AssetPublisher, Kt as coerceCfnInteger, L as planOrphanAdoption, La as ResourceTimeoutError, Li as resolveStateBucketWithDefaultAndSource, Ln as refuseDivergentRecordRegionForDestroy, Lr as hasReadableExportSet, Lt as isUnboundTemplateParameter, M as refusesFinalSnapshot, Mi as resolveApp, Mn as malformedRenderedContainersWarning, Mr as forceQuitRecoveryClause, Mt as cfnRefValueFromPhysicalId, N as unsupportedFinalSnapshotError, Na as MalformedProducerRecordRefusalError, Ni as resolveAutoAssetStorage, Nn as malformedResourceEntriesWarning, Nr as formatLockExpiry, Nt as coerceParameterTypedValue, O as buildFinalSnapshotIdentifier, Oa as DynamicReferenceRegionAmbiguousError, Oi as Synthesizer, On as malformedOrphansRefusalMessage, Or as rebuildClientForBucketRegion, Ot as isTerminationProtectionPropagationError, P as makeCanonicalizePropertiesFn, Pa as NestedStackChildDirectDestroyError, Pi as resolveCaptureObservedState, Pn as malformedResourcePropertiesWarning, Pr as CUSTOM_RESOURCE_RESPONSE_PREFIX, Pt as getAccountInfo, Q as red, Qa as isTransientServerError, Qi as decodeListingKey, Qn as repairMalformedResourceEntriesForReadOnly, Qr as rewriteTemplateAssetReferences, Qt as requireConfigArray, R as MULTI_REGION_RECREATE_BLOCKED_TYPES, Ra as ResourceUpdateNotSupportedError, Ri as resolveUseCdkBootstrapAssets, Rn as refuseMalformedNestedChildOutputs, Rr as hasReasonlessBaselineRefusal, Rt as parameterTypeMayLoseSecretIdentity, S as computeParameterTaint, Si as runDockerForeground, Sn as hasReadableResources, Sr as recordMaskOnlyValue, St as slowCcOperationTimeoutMs, T as resourcesNamingDeclaredParameter, Ta as CrossAccountSecretRefusalError, Ti as stripControlChars, Tn as malformedExportSourceWarning, Tr as scrubResourceRecord, Tt as deleteSkipReason, U as coerceWarmThroughput$1, Ui as CFN_TEMPLATE_URL_LIMIT, Un as refuseMalformedOutputsForDestroy, Ur as orphansAfterRollback, Ut as resolveExplicitPhysicalId, V as renderStatefulReason, Vi as ssmResolvedValueType, Vn as refuseMalformedOrphansForOrphan, Vr as importableOutputs, Vt as WAFv2WebACLProvider, W as isWarmThroughputDecrease, Wi as MIGRATE_TMP_PREFIX, Wn as refuseMalformedResourceAttributesForOrphan, Wr as orphansCarriedFrom, Wt as assertRegionMatch, X as gray, Xa as isRetryableTransientError, Xi as producerCoordinateKey, Xn as repairMalformedOrphansForReadOnly, Xr as createAssetRedirectResolver, Xt as readConfigString, Y as cyan, Ya as isMarkedNonRetryable, Yi as injectiveKeyPrefix, Yn as refuseMalformedState, Yr as buildAssetRedirectMap, Yt as configStringRefusal, Z as green, Za as isThrottlingError, Zi as producerRecordKey, Zn as repairMalformedOutputsForReadOnly, Zr as loadPublishableAssetManifest, Zt as replayWarn, _ as bagHoldsSecretExpression, _a as AwsClients, _i as dockerSpawnEnvWithSensitive, _n as UNREADABLE_ORPHANS_CONTAINER_ROW, _r as identityKeyFor, _t as endCommandInterruptScope, a as DeploymentEventsStore, aa as globMatches, ai as parseBootstrapMarker, an as s3BucketDomainName, ar as describeTypeWithThrottleRetry, at as secretBearingStateKeyWarning, b as mergeNoChangeOutputs, ba as runWithStackAwsClients, bi as partitionSensitiveEnv, bn as hasReadableOrphans, br as maskSecretsInError, bt as startInterruptWatch, c as replayFailedOperations, ca as hasOwnKey, ci as validateContainerRepoName, cn as s3BucketWebsiteUrl, cr as TemplateParser, ct as IAMRoleProvider, d as updatePartialMessage, da as ownValue, di as buildDockerImage, dn as applyCallerIdentityCredentials, dr as STATE_SOURCED_READBACK_RULES, dt as ProviderRegistry, ea as PARTITION_TABLE, ei as BOOTSTRAP_MARKER_PREFIX, en as requireConfigString, eo as markRedactedCause, er as repairMalformedResourcesForReadOnly, et as collectDeclaredOutputNames, f as updatePartialReason, fa as absoluteAssemblyPathEscape, fi as resolveDockerContextDirectory, fn as callerEnvCredentials, fr as TEMPLATE_SOURCED_RULES, ft as wouldReturnToSdkProvider, g as computeImplicitDeleteEdges, ga as resolveBucketRegion, gi as describeDockerFailure, gn as STATE_RESOURCES_MALFORMED, gr as errorCauseChain, gt as beginCommandInterruptScope, h as IMPLICIT_DELETE_DEPENDENCIES, hi as describeDockerExecFailure, hn as INTRINSIC_KEYS, hr as dynamicReferenceTokens, ht as maskerOrIdentity, i as DeploymentEventsReader, ia as failedStageNote, ii as isCrossRegionRedirect, in as s3BucketArn, ir as findSilentDropProperties, it as secretBearing, j as isFinalSnapshotError, ja as LocalStartServiceError, ji as getLegacyStateBucketName, jn as malformedOutputsWarning, jr as buildLockContentionMessage, jt as carriesFetchableDynamicReference, k as ccRoutedFinalSnapshotError, ka as IntrinsicResolutionRefusalError, ki as synthesisStatusMessage, kn as malformedOrphansWarning, kr as UNREPRODUCIBLE_LOCK_CLAUSE, kt as IntrinsicFunctionResolver, l as replayRollback, la as hasPlainPrototype, li as buildDenyExternalAccessPolicy, ln as applyRoleArnIfSet, lr as STATE_SOURCED_BASELINE_RULES, lt as collectInlinePolicyNamesManagedBySiblings, m as maskingRetryLogger, ma as resolveAssemblyPath, mi as describeDockerCapturedOutput, mn as DiffCalculator, mr as createSecretMasker, mt as maskDeep, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as derivePartitionAndUrlSuffix, ni as ensureAssetStorage, nn as producerRegionsFromState, no as describeAwsFailure, nr as safeStackName, nt as exportAliasCollisionScrubWarning, o as planFailedOps, oa as processStackMessages, oi as readBootstrapMarkerBody, on as s3BucketDualStackDomainName, or as withRetry, ot as secretSafeKeyDisplay, p as withResourceDeadline, pa as renderAssemblyPathEscape, pi as warnManifestExecutable, pn as envCredentialsAreAssumedRole, pr as carriesSecretMask, pt as createMaskedRetryLogger, q as formatResourceLine, qa as pasteableCommand, qi as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, qn as refuseMalformedResourcePropertiesForOrphan, qr as stringifyValue, qt as configBooleanRefusal, r as DeployEngine, ra as AssemblyReader, ri as getBootstrapMarkerKey, rn as withSharedDrainBudget, ro as safeStringify, rr as findActionableSilentDrops, rt as isExportAliasCollision, s as planRollback, sa as defineOwnKey, si as validateAssetBucketName, sn as s3BucketRegionalDomainName, sr as DagBuilder, st as getCurrentResourceSecrets, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as canonicalizeRegion, ti as assertAssetBucketRegion, tn as classifyReplaySecretRegion, to as retryClassificationText, tr as safeRegion, tt as collectPublishedOutputNames, u as resolveReplacementOldType, ua as nullPrototypeRecord, ui as assertCdkLocalDockerContextContained, un as AWS_CREDENTIAL_ENV_KEYS, ur as STATE_SOURCED_CROSS_GENERATION_RULES, ut as clearOnUpdateRemoval, v as isSecretBearingReferenceString, va as getAwsClients, vi as formatDockerLoginError, vn as UNREADABLE_RESOURCES_MAP_ROW, vr as isSingleDynamicReferenceToken, vt as interruptWatchListenerCount, w as reachableDivergentParameters, wi as escapeRegExp, wn as malformedExportNamesWarning, wr as redactSecretsForState, wt as deleteIndeterminateGuards, x as bindingSkippedOutputs, xa as setAwsClients, xi as redactDockerArgvValues, xn as hasReadableOutputs, xr as maskSecretsInText, xt as CloudControlProvider, y as keptWholeReasonText, ya as resetAwsClients, yi as getDockerCmd, yn as displayLogicalId, yr as markSameGenerationBag, yt as isInterruptedWaitError, z as isStatefulRecreateTargetForReplace, za as StackHasActiveImportsError, zi as stateBucketExistenceConfirmed, zn as refuseMalformedOrphans, zr as hasUnverifiableParameterRefusal, zt as refStateLookupFromResource } from "./deploy-engine-BomFOZF4.js";
|
|
2
2
|
import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, _ as withSkipPrefix, b as SECRET_REF_MAX_CODE_POINTS, 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, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS } from "./logger-GASr9bAF.js";
|
|
3
3
|
import { n as awsClientDefaults, s as ambientRegion } from "./aws-client-defaults-jISU4PHo.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-5OUy_OlQ.js";
|
|
5
|
+
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-BY-tj21g.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";
|
|
@@ -1759,10 +1759,18 @@ async function bootstrapDestroyCommand(options) {
|
|
|
1759
1759
|
if (markerBody === null) {
|
|
1760
1760
|
const probed = rawMarkerKey === markerKey ? markerKey : `${markerKey}, ${rawMarkerKey}`;
|
|
1761
1761
|
logger.info(`No bootstrap marker for region '${region}' (${probed}) — asset storage is not opted in for this region (or was already destroyed); nothing to delete.`);
|
|
1762
|
-
if (markerSiblings.sameRegionKeys.length > 0) for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A bootstrap marker for this region DOES exist at '${siblingKey}', under a different spelling of the region name — its asset bucket and ECR repository are still alive
|
|
1762
|
+
if (markerSiblings.sameRegionKeys.length > 0) for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A bootstrap marker for this region DOES exist at '${siblingKey}', under a different spelling of the region name — its asset bucket and ECR repository are still alive.\nTear it down with: ${pasteableCommand("cdkd bootstrap", [{ literal: "--destroy" }, {
|
|
1763
|
+
flag: "--region",
|
|
1764
|
+
value: markerRegionOfKey(siblingKey),
|
|
1765
|
+
hole: "region"
|
|
1766
|
+
}]).command}`);
|
|
1763
1767
|
if (!options.includeStateBucket) {
|
|
1764
1768
|
if (!markerListingSucceeded) logger.warn(`The bootstrap-marker listing could not be made, so it is NOT known whether this region has a marker under another spelling of its name. Check 's3://${bucketName}/${BOOTSTRAP_MARKER_PREFIX}' before re-bootstrapping — re-running 'cdkd bootstrap' while one exists writes a SECOND marker with DEFAULT names, and the next destroy would then tear down that storage while the storage the existing marker names survives.`);
|
|
1765
|
-
else if (markerSiblings.sameRegionKeys.length === 0) logger.info(`If the asset bucket / ECR repo still exist without a marker,
|
|
1769
|
+
else if (markerSiblings.sameRegionKeys.length === 0) logger.info(`If the asset bucket / ECR repo still exist without a marker, recreate the marker and destroy again.\nRecreate it with: ${pasteableCommand("cdkd bootstrap", [{
|
|
1770
|
+
flag: "--region",
|
|
1771
|
+
value: region,
|
|
1772
|
+
hole: "region"
|
|
1773
|
+
}]).command}`);
|
|
1766
1774
|
return;
|
|
1767
1775
|
}
|
|
1768
1776
|
} else marker = parseBootstrapMarker(markerBody, resolvedMarkerKey);
|
|
@@ -1780,9 +1788,14 @@ async function bootstrapDestroyCommand(options) {
|
|
|
1780
1788
|
if (markerSiblings.otherRegions.length > 0) throw new CdkdError(`Refusing to delete state bucket '${bucketName}': region(s) ${markerSiblings.otherRegions.map((r) => displayIdent(r)).join(", ")} are still opted in to cdkd asset storage (their bootstrap markers live in this bucket). Run 'cdkd bootstrap --destroy --region <r>' for each first.`, "STATE_BUCKET_HOLDS_MARKERS");
|
|
1781
1789
|
if (markerSiblings.sameRegionKeys.length > 0) {
|
|
1782
1790
|
const canonicalStillPresent = markerBody !== null && resolvedMarkerKey === markerKey;
|
|
1783
|
-
const listing = markerSiblings.sameRegionKeys.map((k) =>
|
|
1784
|
-
const
|
|
1785
|
-
|
|
1791
|
+
const listing = markerSiblings.sameRegionKeys.map((k, i) => ` ${i + 1}. ${k}`).join("\n");
|
|
1792
|
+
const teardownCommands = canonicalStillPresent ? "" : markerSiblings.sameRegionKeys.map((k, i) => `\n${i + 1}. Tear it down with: ${pasteableCommand("cdkd bootstrap", [{ literal: "--destroy" }, {
|
|
1793
|
+
flag: "--region",
|
|
1794
|
+
value: markerRegionOfKey(k),
|
|
1795
|
+
hole: "region"
|
|
1796
|
+
}]).command}`).join("");
|
|
1797
|
+
const remedy = canonicalStillPresent ? `Re-run the destroy for this region WITHOUT --include-state-bucket ONCE: it tears down '${resolvedMarkerKey}' and then prints the exact --region spelling for each marker still standing. Follow those, then re-run with --include-state-bucket. (No per-marker spelling is given here because '${resolvedMarkerKey}' still exists and would be resolved first — and simply repeating this same command would not clear the rest, since once it is gone this region's canonical key no longer resolves anything.)` : `Run each command below, then re-run with --include-state-bucket.`;
|
|
1798
|
+
throw new CdkdError(`Refusing to delete state bucket '${bucketName}': region '${region}' has ${markerSiblings.sameRegionKeys.length} further bootstrap marker(s) under a different spelling of its name, which this teardown does not delete:\n${listing}\n${remedy}\nDestroy each one first — deleting the state bucket now would remove those markers while the asset storage they name survives, with no record of its names.` + teardownCommands, "STATE_BUCKET_HOLDS_SIBLING_MARKERS");
|
|
1786
1799
|
}
|
|
1787
1800
|
}
|
|
1788
1801
|
const planLines = [];
|
|
@@ -1808,7 +1821,11 @@ async function bootstrapDestroyCommand(options) {
|
|
|
1808
1821
|
await stateBackend.purgeNoncurrentVersions([resolvedMarkerKey], { objectDescription: "the bootstrap marker, which names this region's asset bucket and container-asset ECR repository" });
|
|
1809
1822
|
}
|
|
1810
1823
|
logger.info(`✓ Deleted bootstrap marker (${resolvedMarkerKey})`);
|
|
1811
|
-
for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A further bootstrap marker for this region remains at '${siblingKey}' (this region was bootstrapped under more than one spelling of its name). It names asset storage that was NOT destroyed by this run
|
|
1824
|
+
for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A further bootstrap marker for this region remains at '${siblingKey}' (this region was bootstrapped under more than one spelling of its name). It names asset storage that was NOT destroyed by this run.\nTear it down with: ${pasteableCommand("cdkd bootstrap", [{ literal: "--destroy" }, {
|
|
1825
|
+
flag: "--region",
|
|
1826
|
+
value: markerRegionOfKey(siblingKey),
|
|
1827
|
+
hole: "region"
|
|
1828
|
+
}]).command}`);
|
|
1812
1829
|
if (!markerListingSucceeded) logger.warn(`The bootstrap-marker listing could not be made, so a marker under another spelling of this region may still be live. Check 's3://${bucketName}/${BOOTSTRAP_MARKER_PREFIX}'.`);
|
|
1813
1830
|
logger.info(`\ncdkd asset storage is now OFF for region ${region}: future deploys in this region fall back to legacy mode (CDK bootstrap destinations) unless the region is bootstrapped again.`);
|
|
1814
1831
|
}
|
|
@@ -7346,86 +7363,41 @@ function isPlainObject$2(value) {
|
|
|
7346
7363
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7347
7364
|
}
|
|
7348
7365
|
/**
|
|
7349
|
-
*
|
|
7350
|
-
*
|
|
7351
|
-
*
|
|
7352
|
-
*
|
|
7353
|
-
* ONE predicate, called from both `resolveS3BucketDestination` (which picks the
|
|
7354
|
-
* bag) and `s3BucketDestinationPath` (which names it). They were written out
|
|
7355
|
-
* twice and a reviewer rightly pointed out that two copies of a branch
|
|
7356
|
-
* condition must be edited together or a refusal starts naming the wrong key.
|
|
7357
|
-
*
|
|
7358
|
-
* `Bucket` is probed alongside `BucketArn` because the readers accept it
|
|
7359
|
-
* (`s3Dest['BucketArn'] ?? s3Dest['Bucket']`); omitting it made a
|
|
7360
|
-
* `{ Bucket }`-only block take the nested branch, find nothing, and drop — the
|
|
7361
|
-
* same silent drop one shape over.
|
|
7362
|
-
*/
|
|
7363
|
-
function isFlattenedDestination(dest) {
|
|
7364
|
-
return Boolean(dest["BucketArn"] || dest["Bucket"] || dest["BucketAccountId"] || dest["Format"]);
|
|
7365
|
-
}
|
|
7366
|
-
/**
|
|
7367
|
-
* The CFn path of the destination bag `resolveS3BucketDestination` will pick —
|
|
7368
|
-
* issue #1493 item 3, which is a MESSAGE concern, not a value one.
|
|
7369
|
-
*
|
|
7370
|
-
* Kept separate from the resolver rather than returned alongside the bag:
|
|
7371
|
-
* `gen-nested-key-coverage`'s write-evidence walk follows a plain
|
|
7372
|
-
* `const x = this.helper(...)` binding to the members written beneath it, and
|
|
7373
|
-
* a DESTRUCTURED `const { bag, path } = ...` is a shape it cannot follow — so
|
|
7374
|
-
* pairing the two silently withdrew the write evidence for
|
|
7375
|
-
* `Destination.BucketArn` / `.BucketAccountId` and turned an opted-in target
|
|
7376
|
-
* red with no behavior change at all. Measured against the pre-change provider
|
|
7377
|
-
* via the critic's `--providers-dir=` seam.
|
|
7378
|
-
*/
|
|
7379
|
-
function s3BucketDestinationPath(dest, destinationPath) {
|
|
7380
|
-
return isPlainObject$2(dest) && isFlattenedDestination(dest) ? destinationPath : `${destinationPath}.S3BucketDestination`;
|
|
7381
|
-
}
|
|
7382
|
-
/**
|
|
7383
|
-
* The FLATTENED CFn `Destination` block cdkd actually SENDS for `declared`
|
|
7384
|
-
* (issue #1707) — the shared fold behind BOTH halves of the #1633 pair: the
|
|
7385
|
-
* per-item `effectiveProperties` record and
|
|
7366
|
+
* The CFn `Destination` block cdkd actually SENDS for `declared` (issue #1707) —
|
|
7367
|
+
* the shared fold behind BOTH halves of the #1633 pair: the per-item
|
|
7368
|
+
* `effectiveProperties` record and
|
|
7386
7369
|
* {@link S3BucketProvider.canonicalizeDesiredProperties}.
|
|
7387
7370
|
*
|
|
7388
|
-
* The
|
|
7389
|
-
*
|
|
7390
|
-
*
|
|
7391
|
-
*
|
|
7392
|
-
*
|
|
7393
|
-
*
|
|
7394
|
-
*
|
|
7395
|
-
*
|
|
7396
|
-
*
|
|
7397
|
-
* `
|
|
7398
|
-
*
|
|
7399
|
-
*
|
|
7400
|
-
* This NORMALIZES rather than retracting the tolerance, per the #1686 rule:
|
|
7401
|
-
* refusing the SDK spelling would break templates that deploy today. It also
|
|
7402
|
-
* supersedes #1670's write-back-at-the-DECLARED-branch decision, whose whole
|
|
7403
|
-
* reason was that writing at a hardcoded branch would leave the malformed value
|
|
7404
|
-
* alive at the other key and add a stray one — there is no other key left once
|
|
7405
|
-
* the block is normalized wholesale.
|
|
7371
|
+
* The readback (`inventorySdkToCfn` / `analyticsSdkToCfn`) emits only
|
|
7372
|
+
* `{BucketArn, BucketAccountId, Format, Prefix}`, the members the live CFn
|
|
7373
|
+
* registry schema declares (`additionalProperties: false`). The block is rebuilt
|
|
7374
|
+
* from exactly those members so a record never carries a key the readback cannot
|
|
7375
|
+
* report.
|
|
7376
|
+
*
|
|
7377
|
+
* The SDK spellings this once also folded (the nested
|
|
7378
|
+
* `{ S3BucketDestination: { ... } }` shape and a `Bucket` alias for
|
|
7379
|
+
* `BucketArn`) are gone: `BucketArn` and `Format` are schema-required here and
|
|
7380
|
+
* `AWS::S3::Bucket` is in `CFN_ENFORCED_TYPES`, so a template using either is
|
|
7381
|
+
* refused pre-flight by `nested-required.ts` (issue #3602).
|
|
7406
7382
|
*
|
|
7407
7383
|
* `Format` is the one member a caller can override: the applier passes the
|
|
7408
7384
|
* value it SENT, which differs from the declared one when the warn-and-
|
|
7409
|
-
* SUBSTITUTE read replaced
|
|
7410
|
-
* `'CSV'` the appliers send, so the record carries what AWS
|
|
7411
|
-
* rather than one key fewer.
|
|
7385
|
+
* SUBSTITUTE read replaced a malformed value (#1670). Absent on BOTH sides it
|
|
7386
|
+
* defaults to the `'CSV'` the appliers send, so the record carries what AWS
|
|
7387
|
+
* will report back rather than one key fewer.
|
|
7412
7388
|
*
|
|
7413
|
-
* @returns the
|
|
7414
|
-
*
|
|
7415
|
-
*
|
|
7416
|
-
* resolver's own refusal to report).
|
|
7389
|
+
* @returns the rebuilt block, or `declared` BY IDENTITY when it already is one
|
|
7390
|
+
* (so an ordinary template records byte-for-byte what it declared) or is not
|
|
7391
|
+
* a shape this can fold at all (absent / malformed / no `BucketArn` — left for
|
|
7392
|
+
* the resolver's own refusal to report).
|
|
7417
7393
|
*/
|
|
7418
7394
|
function effectiveS3BucketDestination(declared, sentFormat) {
|
|
7419
7395
|
if (!isPlainObject$2(declared)) return declared;
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
if (
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
if (bucketArn !== void 0) out["BucketArn"] = bucketArn;
|
|
7426
|
-
if (bag["BucketAccountId"] !== void 0) out["BucketAccountId"] = bag["BucketAccountId"];
|
|
7427
|
-
out["Format"] = sentFormat ?? declaredOrDefault(bag, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT);
|
|
7428
|
-
if (bag["Prefix"] !== void 0) out["Prefix"] = bag["Prefix"];
|
|
7396
|
+
if (!declared["BucketArn"]) return declared;
|
|
7397
|
+
const out = { BucketArn: declared["BucketArn"] };
|
|
7398
|
+
if (declared["BucketAccountId"] !== void 0) out["BucketAccountId"] = declared["BucketAccountId"];
|
|
7399
|
+
out["Format"] = sentFormat ?? declaredOrDefault(declared, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT);
|
|
7400
|
+
if (declared["Prefix"] !== void 0) out["Prefix"] = declared["Prefix"];
|
|
7429
7401
|
return sameScalarRecord(out, declared) ? declared : out;
|
|
7430
7402
|
}
|
|
7431
7403
|
/**
|
|
@@ -7433,14 +7405,6 @@ function effectiveS3BucketDestination(declared, sentFormat) {
|
|
|
7433
7405
|
* NEVER `??`, and that distinction is the whole guard against issue #1670's
|
|
7434
7406
|
* finding 3.
|
|
7435
7407
|
*
|
|
7436
|
-
* The ONE licensed `??` in these folds is the ALIAS read
|
|
7437
|
-
* `bag['BucketArn'] ?? bag['Bucket']`, and it is not an exception to this rule —
|
|
7438
|
-
* it supplies no DEFAULT. It picks between two spellings of the same declared
|
|
7439
|
-
* value, exactly as the applier's own read does, so a nullish `BucketArn`
|
|
7440
|
-
* correctly falls through to the alias the wire would have used. When neither is
|
|
7441
|
-
* declared the key is simply absent from the fold's output, which is what makes
|
|
7442
|
-
* a bucket-less block identity-return rather than gain a phantom key.
|
|
7443
|
-
*
|
|
7444
7408
|
* The default belongs to a key the template OMITTED: there the provider sends
|
|
7445
7409
|
* it, the readback reports it, and folding both sides is what makes them agree.
|
|
7446
7410
|
* A key DECLARED with a malformed value is a different case entirely — the
|
|
@@ -7662,10 +7626,14 @@ function foldToleratedAliases(bag, aliases) {
|
|
|
7662
7626
|
}
|
|
7663
7627
|
/** The transition date member, held in a CONST so the fold writes it COMPUTED. */
|
|
7664
7628
|
const TRANSITION_DATE_KEY = "TransitionDate";
|
|
7665
|
-
/**
|
|
7629
|
+
/**
|
|
7630
|
+
* The lifecycle `Transitions[]` aliases the applier accepts (issue #1748).
|
|
7631
|
+
*
|
|
7632
|
+
* `NoncurrentVersionTransitions[]` has none: its `TransitionInDays` is a
|
|
7633
|
+
* schema-required member, so a template spelling it `NoncurrentDays` is refused
|
|
7634
|
+
* pre-flight by `nested-required.ts` and the alias was dropped (issue #3585).
|
|
7635
|
+
*/
|
|
7666
7636
|
const TRANSITION_ALIASES = [["TransitionInDays", "Days"], ["TransitionDate", "Date"]];
|
|
7667
|
-
/** The lifecycle `NoncurrentVersionTransitions[]` alias (issue #1748). */
|
|
7668
|
-
const NONCURRENT_VERSION_TRANSITION_ALIASES = [["TransitionInDays", "NoncurrentDays"]];
|
|
7669
7637
|
/** The refusal path for the notification EventBridge boolean (issue #1759). */
|
|
7670
7638
|
const EVENTBRIDGE_CONFIG_PATH = "AWS::S3::Bucket NotificationConfiguration.EventBridgeConfiguration";
|
|
7671
7639
|
/**
|
|
@@ -7736,27 +7704,25 @@ function dropEmptyNotificationFamilies(config) {
|
|
|
7736
7704
|
}
|
|
7737
7705
|
return out ?? config;
|
|
7738
7706
|
}
|
|
7739
|
-
/** The `NotificationConfiguration` ARN aliases, per family (issue #1748). */
|
|
7740
|
-
const NOTIFICATION_ARN_ALIASES = [
|
|
7741
|
-
["TopicConfigurations", [["Topic", "TopicArn"]]],
|
|
7742
|
-
["QueueConfigurations", [["Queue", "QueueArn"]]],
|
|
7743
|
-
["LambdaConfigurations", [["Function", "LambdaFunctionArn"]]]
|
|
7744
|
-
];
|
|
7745
7707
|
/**
|
|
7746
|
-
* The `Event` / `Events`
|
|
7708
|
+
* The `Event` / `Events` spelling on a notification item (issue #1748).
|
|
7709
|
+
*
|
|
7710
|
+
* `Event` is the member the CFn registry schema declares (`TopicConfiguration`
|
|
7711
|
+
* is `{Event, Filter, Topic}` — there is no `Events`) and therefore the one
|
|
7712
|
+
* EVERY template carries. The applier accepts both
|
|
7713
|
+
* (`t['Event'] !== undefined ? [t['Event']] : t['Events']`), because the
|
|
7714
|
+
* readback emits `Events` for a multi-event live configuration and
|
|
7715
|
+
* `drift --revert` sends a readback-shaped bag.
|
|
7747
7716
|
*
|
|
7748
|
-
*
|
|
7749
|
-
*
|
|
7750
|
-
*
|
|
7751
|
-
*
|
|
7752
|
-
* `Events`) and therefore the one EVERY template carries. The applier accepts
|
|
7753
|
-
* both (`t['Event'] !== undefined ? [t['Event']] : t['Events']`).
|
|
7717
|
+
* This is not a pure rename — the SDK member is a LIST — so it folds by ARITY:
|
|
7718
|
+
* a single-element list is the CFn `Event`, and a longer one has no CFn
|
|
7719
|
+
* spelling at all and is left as `Events`, which is what the readback emits for
|
|
7720
|
+
* it.
|
|
7754
7721
|
*
|
|
7755
|
-
*
|
|
7756
|
-
*
|
|
7757
|
-
*
|
|
7758
|
-
*
|
|
7759
|
-
* {@link foldToleratedAliases} entry.
|
|
7722
|
+
* The destination ARN has no alias: `Topic` / `Queue` / `Function` are
|
|
7723
|
+
* schema-required members, so the SDK spellings (`TopicArn`, …) are refused
|
|
7724
|
+
* pre-flight by `nested-required.ts` and the applier no longer reads them
|
|
7725
|
+
* (issue #3585).
|
|
7760
7726
|
*
|
|
7761
7727
|
* `readNotification` was emitting `Events` unconditionally, so the record and
|
|
7762
7728
|
* the readback disagreed on every notification-configured bucket; it now
|
|
@@ -7781,20 +7747,12 @@ function foldNotificationEvents(item) {
|
|
|
7781
7747
|
* `readNotification` emits (issue #1748) — the never-emitted-KEY class of issue
|
|
7782
7748
|
* #1686 reached through the SHAPE rather than through a value.
|
|
7783
7749
|
*
|
|
7784
|
-
* `applyNotificationConfiguration` accepts
|
|
7785
|
-
*
|
|
7786
|
-
*
|
|
7787
|
-
*
|
|
7788
|
-
*
|
|
7789
|
-
*
|
|
7790
|
-
* written in the tolerated spelling therefore can never match the readback:
|
|
7791
|
-
* `cdkd drift` re-reports it forever and `--revert` re-issues the same Put,
|
|
7792
|
-
* with NO warning anywhere, because nothing is malformed and nothing is
|
|
7793
|
-
* substituted.
|
|
7794
|
-
*
|
|
7795
|
-
* This NORMALIZES rather than retracting the tolerance, per #1686's third
|
|
7796
|
-
* generalization: refusing the SDK spelling would break templates that deploy
|
|
7797
|
-
* today, and buys nothing the normalization does not.
|
|
7750
|
+
* `applyNotificationConfiguration` accepts a single-element `Events` list
|
|
7751
|
+
* alongside the CFn scalar `Event` ({@link foldNotificationEvents}), while
|
|
7752
|
+
* `readNotification` emits `Event` for it. A record written in the list
|
|
7753
|
+
* spelling therefore can never match the readback: `cdkd drift` re-reports it
|
|
7754
|
+
* forever and `--revert` re-issues the same Put, with NO warning anywhere,
|
|
7755
|
+
* because nothing is malformed and nothing is substituted.
|
|
7798
7756
|
*
|
|
7799
7757
|
* The Put is a FULL REPLACE with no skip arm, so there is no
|
|
7800
7758
|
* previous-value-retained case to distinguish — whenever it succeeds, this is
|
|
@@ -7807,7 +7765,7 @@ function foldNotificationEvents(item) {
|
|
|
7807
7765
|
function effectiveNotificationConfiguration(config) {
|
|
7808
7766
|
if (!isPlainObject$2(config)) return config;
|
|
7809
7767
|
let out = config;
|
|
7810
|
-
for (const
|
|
7768
|
+
for (const listKey of NOTIFICATION_LIST_KEYS) out = canonicalizeItemList(out, listKey, foldNotificationEvents);
|
|
7811
7769
|
out = dropEmptyNotificationFamilies(out);
|
|
7812
7770
|
return foldEventBridgeConfiguration(out);
|
|
7813
7771
|
}
|
|
@@ -8248,18 +8206,13 @@ function foldLifecycleScope(rule, useFilterForm) {
|
|
|
8248
8206
|
* `readLifecycle` emits (issue #1748) — the lifecycle half of the same class.
|
|
8249
8207
|
*
|
|
8250
8208
|
* `applyLifecycleConfiguration` accepts the SDK day/date spellings alongside the
|
|
8251
|
-
* CFn ones on
|
|
8252
|
-
* `t['TransitionDate'] ?? t['Date']
|
|
8253
|
-
*
|
|
8254
|
-
* `
|
|
8255
|
-
*
|
|
8256
|
-
* `
|
|
8257
|
-
*
|
|
8258
|
-
*
|
|
8259
|
-
* The issue named the `Date` alias; the mechanical audit it asks for (match the
|
|
8260
|
-
* `(a['X'] ?? a['Y'])` ALIAS form, not a plain bracket read) found all THREE in
|
|
8261
|
-
* the same two item shapes, so fixing only the reported one would have left its
|
|
8262
|
-
* siblings broken — the "diff the WHOLE blob, not the reported key" rule.
|
|
8209
|
+
* CFn ones on `Transitions` (`t['TransitionInDays'] ?? t['Days']`,
|
|
8210
|
+
* `t['TransitionDate'] ?? t['Date']`) while `readLifecycle` emits only
|
|
8211
|
+
* `TransitionInDays` / `TransitionDate` — again the only spellings the registry
|
|
8212
|
+
* schema declares (`Transition` is `{StorageClass, TransitionDate,
|
|
8213
|
+
* TransitionInDays}`). `NoncurrentVersionTransitions` has no alias to fold: its
|
|
8214
|
+
* `TransitionInDays` is schema-required, so the `NoncurrentDays` spelling is
|
|
8215
|
+
* refused pre-flight and the applier no longer reads it (issue #3585).
|
|
8263
8216
|
*
|
|
8264
8217
|
* Issues #1754 / #1755 finished the block, so the "still differs on those keys"
|
|
8265
8218
|
* caveat this doc carried is gone: the legacy SINGULAR `Transition` /
|
|
@@ -8309,7 +8262,6 @@ function effectiveLifecycleConfiguration(config) {
|
|
|
8309
8262
|
function effectiveLifecycleRule(rule, useFilterForm) {
|
|
8310
8263
|
let out = foldLegacySingularTransitions(rule);
|
|
8311
8264
|
out = canonicalizeItemList(out, "Transitions", (t) => foldTransitionDate(foldToleratedAliases(t, TRANSITION_ALIASES)));
|
|
8312
|
-
out = canonicalizeItemList(out, "NoncurrentVersionTransitions", (nvt) => foldToleratedAliases(nvt, NONCURRENT_VERSION_TRANSITION_ALIASES));
|
|
8313
8265
|
out = dropEmptyTransitionLists(out);
|
|
8314
8266
|
out = foldLifecycleExpiration(out);
|
|
8315
8267
|
out = foldNoncurrentVersionExpiration(out);
|
|
@@ -8376,8 +8328,8 @@ function canonicalizeItemList(properties, key, fold) {
|
|
|
8376
8328
|
/**
|
|
8377
8329
|
* Name a malformed value in a refusal message, the way `config-shape.ts`'s
|
|
8378
8330
|
* private `describe` does for the string-reading guards. Kept local rather than
|
|
8379
|
-
* exported from there because `resolveS3BucketDestination` is S3-specific
|
|
8380
|
-
*
|
|
8331
|
+
* exported from there because `resolveS3BucketDestination` is an S3-specific
|
|
8332
|
+
* refusal, not a shared shape guard — the two only share the wording.
|
|
8381
8333
|
*/
|
|
8382
8334
|
function describeValue$1(value, maskSecrets = (text) => text) {
|
|
8383
8335
|
if (value === null) return "null";
|
|
@@ -8915,7 +8867,7 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8915
8867
|
};
|
|
8916
8868
|
else if (coerceCfnNumber(legacyNveDays) !== void 0) sdkRule.NoncurrentVersionExpiration = { NoncurrentDays: coerceCfnNumber(legacyNveDays) };
|
|
8917
8869
|
const toSdkNvt = (nvt) => ({
|
|
8918
|
-
NoncurrentDays: nvt["TransitionInDays"]
|
|
8870
|
+
NoncurrentDays: nvt["TransitionInDays"],
|
|
8919
8871
|
StorageClass: nvt["StorageClass"],
|
|
8920
8872
|
NewerNoncurrentVersions: nvt["NewerNoncurrentVersions"]
|
|
8921
8873
|
});
|
|
@@ -9126,21 +9078,21 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9126
9078
|
const topics = notifConfig["TopicConfigurations"];
|
|
9127
9079
|
if (topics && Array.isArray(topics) && topics.length > 0) cfg.TopicConfigurations = topics.map((t) => ({
|
|
9128
9080
|
Id: t["Id"],
|
|
9129
|
-
TopicArn: t["Topic"]
|
|
9081
|
+
TopicArn: t["Topic"],
|
|
9130
9082
|
Events: t["Event"] !== void 0 ? [t["Event"]] : t["Events"],
|
|
9131
9083
|
Filter: this.cfnNotifFilterToSdk(t["Filter"])
|
|
9132
9084
|
}));
|
|
9133
9085
|
const queues = notifConfig["QueueConfigurations"];
|
|
9134
9086
|
if (queues && Array.isArray(queues) && queues.length > 0) cfg.QueueConfigurations = queues.map((q) => ({
|
|
9135
9087
|
Id: q["Id"],
|
|
9136
|
-
QueueArn: q["Queue"]
|
|
9088
|
+
QueueArn: q["Queue"],
|
|
9137
9089
|
Events: q["Event"] !== void 0 ? [q["Event"]] : q["Events"],
|
|
9138
9090
|
Filter: this.cfnNotifFilterToSdk(q["Filter"])
|
|
9139
9091
|
}));
|
|
9140
9092
|
const lambdas = notifConfig["LambdaConfigurations"];
|
|
9141
9093
|
if (lambdas && Array.isArray(lambdas) && lambdas.length > 0) cfg.LambdaFunctionConfigurations = lambdas.map((l) => ({
|
|
9142
9094
|
Id: l["Id"],
|
|
9143
|
-
LambdaFunctionArn: l["Function"]
|
|
9095
|
+
LambdaFunctionArn: l["Function"],
|
|
9144
9096
|
Events: l["Event"] !== void 0 ? [l["Event"]] : l["Events"],
|
|
9145
9097
|
Filter: this.cfnNotifFilterToSdk(l["Filter"])
|
|
9146
9098
|
}));
|
|
@@ -9225,39 +9177,32 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9225
9177
|
};
|
|
9226
9178
|
}
|
|
9227
9179
|
/**
|
|
9228
|
-
*
|
|
9229
|
-
*
|
|
9230
|
-
*
|
|
9231
|
-
*
|
|
9232
|
-
* form (`
|
|
9233
|
-
* and
|
|
9234
|
-
*
|
|
9235
|
-
*
|
|
9236
|
-
*
|
|
9237
|
-
*
|
|
9238
|
-
*
|
|
9239
|
-
*
|
|
9240
|
-
* `
|
|
9241
|
-
* the whole block from the Put — a
|
|
9242
|
-
* the destination it declared. Unlike
|
|
9243
|
-
* DROP, not a substituted default, so
|
|
9244
|
-
*
|
|
9245
|
-
* REFUSE on the template-borne create path, WARN on the
|
|
9246
|
-
* update path (`onUnusable`), where the desired bag can be a
|
|
9247
|
-
* state record with no template-side remedy.
|
|
9248
|
-
*
|
|
9249
|
-
*
|
|
9250
|
-
*
|
|
9251
|
-
*
|
|
9252
|
-
* FLATTENED branch — where the bag IS `dest` itself — named a key the user's
|
|
9253
|
-
* template does not contain.
|
|
9254
|
-
*
|
|
9255
|
-
* @returns the picked bag, or `undefined` when the block was ABSENT or was
|
|
9256
|
-
* refused on a warn-only path. The caller distinguishes the two by testing
|
|
9257
|
-
* its own `Destination` value — see the `continue` at each call site.
|
|
9180
|
+
* Validate an analytics / inventory `Destination` block, refusing a shape
|
|
9181
|
+
* that would otherwise be dropped.
|
|
9182
|
+
*
|
|
9183
|
+
* The block is the CFn form the schema declares (`Destination: { BucketArn,
|
|
9184
|
+
* Format, ... }`). The SDK NESTED form (`{ S3BucketDestination: { ... } }`)
|
|
9185
|
+
* and a `Bucket` alias for `BucketArn` are no longer read: `BucketArn` and
|
|
9186
|
+
* `Format` are schema-required, so a template using either is refused
|
|
9187
|
+
* pre-flight by `nested-required.ts` (issue #3602). What still reaches this
|
|
9188
|
+
* guard with such a shape (an unresolved intrinsic on the path, or a replayed
|
|
9189
|
+
* pre-#1707 state record) is refused here like any other bucket-less block.
|
|
9190
|
+
*
|
|
9191
|
+
* Issue #1493 item 2: a `Destination` that is a STRING / array / unresolved
|
|
9192
|
+
* intrinsic used to index every probe to `undefined`, and the caller's
|
|
9193
|
+
* `s3Dest ? … : undefined` omitted the whole block from the Put — a
|
|
9194
|
+
* configuration silently deployed without the destination it declared. Unlike
|
|
9195
|
+
* the sibling defaulting class this is a DROP, not a substituted default, so
|
|
9196
|
+
* `readConfigString` does not cover it: per issue #1513's precedent the
|
|
9197
|
+
* decision is REFUSE on the template-borne create path, WARN on the
|
|
9198
|
+
* replay-reachable update path (`onUnusable`), where the desired bag can be a
|
|
9199
|
+
* historical cdkd state record with no template-side remedy.
|
|
9200
|
+
*
|
|
9201
|
+
* @returns the block, or `undefined` when it was ABSENT or was refused on a
|
|
9202
|
+
* warn-only path. The caller distinguishes the two by testing its own
|
|
9203
|
+
* `Destination` value — see the `continue` at each call site.
|
|
9258
9204
|
*/
|
|
9259
9205
|
resolveS3BucketDestination(dest, destinationPath, onUnusable, maskSecrets = (text) => text) {
|
|
9260
|
-
const nestedPath = `${destinationPath}.S3BucketDestination`;
|
|
9261
9206
|
const drop = (message) => {
|
|
9262
9207
|
if (onUnusable) {
|
|
9263
9208
|
onUnusable(`${message}. Leaving this configuration unchanged; the same value is REFUSED on a template-path create`);
|
|
@@ -9267,11 +9212,8 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9267
9212
|
};
|
|
9268
9213
|
if (dest === void 0 || dest === null) return;
|
|
9269
9214
|
if (!isPlainObject$2(dest)) return drop(`${destinationPath} must be an object (got ${describeValue$1(dest, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
if (!isPlainObject$2(bag)) return drop(`${nestedPath} must be an object (got ${describeValue$1(bag, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
|
|
9273
|
-
if (!bag["BucketArn"] && !bag["Bucket"]) return drop(`${bag === dest ? destinationPath : nestedPath} has no destination bucket (BucketArn / Bucket) (got ${describeValue$1(bag, maskSecrets)}) — the request would be rejected by S3, or silently carry no destination`);
|
|
9274
|
-
return bag;
|
|
9215
|
+
if (!dest["BucketArn"]) return drop(`${destinationPath} has no destination bucket (BucketArn) (got ${describeValue$1(dest, maskSecrets)}) — the request would be rejected by S3, or silently carry no destination`);
|
|
9216
|
+
return dest;
|
|
9275
9217
|
}
|
|
9276
9218
|
/**
|
|
9277
9219
|
* Apply analytics configurations
|
|
@@ -9332,13 +9274,12 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9332
9274
|
skipped.push(index);
|
|
9333
9275
|
continue;
|
|
9334
9276
|
}
|
|
9335
|
-
const destPath = s3BucketDestinationPath(rawDest, analyticsDestPath);
|
|
9336
9277
|
analyticsConfig.StorageClassAnalysis = { DataExport: {
|
|
9337
9278
|
OutputSchemaVersion: sentOutputSchemaVersion = this.readSubstitutedConfigString(dataExport, "OutputSchemaVersion", ANALYTICS_DEFAULT_OUTPUT_SCHEMA_VERSION, "AWS::S3::Bucket AnalyticsConfigurations[].StorageClassAnalysis.DataExport", { ...onUnusable ? { onUnusable } : {} }),
|
|
9338
9279
|
Destination: s3Dest ? { S3BucketDestination: {
|
|
9339
|
-
Bucket: s3Dest["BucketArn"]
|
|
9280
|
+
Bucket: s3Dest["BucketArn"],
|
|
9340
9281
|
BucketAccountId: s3Dest["BucketAccountId"],
|
|
9341
|
-
Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT,
|
|
9282
|
+
Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT, analyticsDestPath, { ...onUnusable ? { onUnusable } : {} }),
|
|
9342
9283
|
Prefix: s3Dest["Prefix"]
|
|
9343
9284
|
} } : void 0
|
|
9344
9285
|
} };
|
|
@@ -9462,7 +9403,6 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9462
9403
|
skipped.push(index);
|
|
9463
9404
|
continue;
|
|
9464
9405
|
}
|
|
9465
|
-
const destPath = s3BucketDestinationPath(rawDest, inventoryDestPath);
|
|
9466
9406
|
let sentFormat;
|
|
9467
9407
|
const scheduleFrequencyRefusal = onUnusable ? configStringRefusal(config, "ScheduleFrequency", "Weekly", "AWS::S3::Bucket InventoryConfigurations[]") : void 0;
|
|
9468
9408
|
if (scheduleFrequencyRefusal !== void 0 && config["Schedule"] == null) {
|
|
@@ -9483,9 +9423,9 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9483
9423
|
IncludedObjectVersions: readConfigString(config, "IncludedObjectVersions", INVENTORY_DEFAULT_INCLUDED_OBJECT_VERSIONS, "AWS::S3::Bucket InventoryConfigurations[]"),
|
|
9484
9424
|
Schedule: { Frequency: frequency },
|
|
9485
9425
|
Destination: { S3BucketDestination: s3Dest ? {
|
|
9486
|
-
Bucket: s3Dest["BucketArn"]
|
|
9426
|
+
Bucket: s3Dest["BucketArn"],
|
|
9487
9427
|
AccountId: s3Dest["BucketAccountId"],
|
|
9488
|
-
Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT,
|
|
9428
|
+
Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT, inventoryDestPath, { ...onUnusable ? { onUnusable } : {} }),
|
|
9489
9429
|
Prefix: s3Dest["Prefix"]
|
|
9490
9430
|
} : void 0 },
|
|
9491
9431
|
OptionalFields: config["OptionalFields"],
|
|
@@ -64681,8 +64621,25 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
64681
64621
|
const stateTargetFor = (logicalId, resourceType) => resourceType === NESTED_STACK_TYPE ? `${stackName}~${logicalId}` : stackName;
|
|
64682
64622
|
const skippedStateTargets = /* @__PURE__ */ new Set();
|
|
64683
64623
|
const failedStateTargets = /* @__PURE__ */ new Set();
|
|
64684
|
-
/**
|
|
64685
|
-
|
|
64624
|
+
/**
|
|
64625
|
+
* One labelled line per target — the hint both summary arms print.
|
|
64626
|
+
*
|
|
64627
|
+
* The PROSE quotes are gone, every target goes through the shared gate, and
|
|
64628
|
+
* each command gets its OWN line (go-to-k/cdkd#3436). The old
|
|
64629
|
+
* `'${command} ${t}'` wrapper is the shape whose paste ran an interpolated
|
|
64630
|
+
* value, and this builder was the issue's example of a command the repo-wide
|
|
64631
|
+
* grep cannot see, since neither `'cdkd ` nor the value is on the same line
|
|
64632
|
+
* as the other. The ` / ` join that first replaced it was its own defect:
|
|
64633
|
+
* pasted, it handed the second command to the first as arguments.
|
|
64634
|
+
*/
|
|
64635
|
+
const hintFor = (command, targets, label) => [...new Set(targets.map((t) => `\n${label}: ${pasteableCommand(command, [{
|
|
64636
|
+
value: t,
|
|
64637
|
+
hole: "stack"
|
|
64638
|
+
}, {
|
|
64639
|
+
flag: "--stack-region",
|
|
64640
|
+
value: regionForState,
|
|
64641
|
+
hole: "region"
|
|
64642
|
+
}]).command}`))].join("");
|
|
64686
64643
|
const buildDestroySnapshot = () => {
|
|
64687
64644
|
const { imports: _imports, outputReads: _outputReads, ...rest } = state;
|
|
64688
64645
|
return {
|
|
@@ -64970,21 +64927,26 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
64970
64927
|
const skippedSuffix = result.skippedCount > 0 ? `, ${yellow(result.skippedCount)} skipped` : "";
|
|
64971
64928
|
const guardSuffix = result.guardIndeterminateCount > 0 ? `, ${yellow(result.guardIndeterminateCount)} unverified` : "";
|
|
64972
64929
|
if (result.guardIndeterminateCount > 0) {
|
|
64973
|
-
const durablePointer = ctx.eventRecorder === void 0 ? "This summary is the only record: this run wrote no deployment events, either because it is a 'cdkd state destroy' (go-to-k/cdkd#2423) or because it is a nested-stack child, neither of which threads an event recorder." : `
|
|
64930
|
+
const durablePointer = ctx.eventRecorder === void 0 ? "This summary is the only record: this run wrote no deployment events, either because it is a 'cdkd state destroy' (go-to-k/cdkd#2423) or because it is a nested-stack child, neither of which threads an event recorder." : `The RESOURCE_GUARD_INDETERMINATE entries name the check and the reason and survive the run.\nRead them with: ${pasteableCommand("cdkd events", [{
|
|
64931
|
+
value: stackName,
|
|
64932
|
+
hole: "stack"
|
|
64933
|
+
}]).command}`;
|
|
64974
64934
|
logger.warn(`\n${yellow("⚠")} ${result.guardIndeterminateCount} pre-flight safety check(s) could NOT be completed during this destroy and cdkd proceeded anyway: ${[...guardIndeterminateTargets].join(", ")}. A check can be suppressed by DENYING the permission it needs, so treat this as unconfirmed rather than benign. ${durablePointer}`);
|
|
64975
64935
|
}
|
|
64976
64936
|
if (!preserveState) logger.info(`\n${green("✓")} ${bold(`Stack ${stackName} destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${guardSuffix}, ${result.errorCount} errors)`);
|
|
64977
64937
|
else if (result.interrupted && result.errorCount === 0) logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} destroy interrupted`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${result.errorCount} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to finish.`);
|
|
64978
64938
|
else if (result.errorCount === 0) {
|
|
64979
64939
|
const targets = [...skippedStateTargets];
|
|
64980
|
-
const showHint = hintFor("cdkd state show", targets);
|
|
64981
|
-
const orphanHint = hintFor("cdkd state orphan", targets);
|
|
64982
|
-
logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${result.errorCount} errors). cdkd could not address the skipped resource(s), so they may still exist in AWS. Fix the physicalId in state.json
|
|
64940
|
+
const showHint = hintFor("cdkd state show", targets, "Inspect it with");
|
|
64941
|
+
const orphanHint = hintFor("cdkd state orphan", targets, "Drop the record with");
|
|
64942
|
+
logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${result.errorCount} errors). cdkd could not address the skipped resource(s), so they may still exist in AWS. Fix the physicalId in state.json and re-run, or delete them by hand and drop the records.` + showHint + orphanHint);
|
|
64983
64943
|
} else {
|
|
64984
|
-
const orphanHint = hintFor("cdkd state orphan", failedStateTargets.size > 0 ? [...failedStateTargets] : [stackName]);
|
|
64944
|
+
const orphanHint = hintFor("cdkd state orphan", failedStateTargets.size > 0 ? [...failedStateTargets] : [stackName], "Drop the record with");
|
|
64985
64945
|
const skippedTargets = [...skippedStateTargets];
|
|
64986
|
-
const skippedClause = skippedTargets.length > 0 ? ` Separately, ${result.skippedCount} resource(s) were SKIPPED — cdkd could not address them, so no delete was issued and they may still exist in AWS. Fix the physicalId in state.json
|
|
64987
|
-
|
|
64946
|
+
const skippedClause = skippedTargets.length > 0 ? ` Separately, ${result.skippedCount} resource(s) were SKIPPED — cdkd could not address them, so no delete was issued and they may still exist in AWS. Fix the physicalId in state.json and re-run, or delete them by hand and drop their records.` : "";
|
|
64947
|
+
const skippedCommands = hintFor("cdkd state show", skippedTargets, "Inspect it with") + hintFor("cdkd state orphan", skippedTargets, "Drop the record with");
|
|
64948
|
+
const dedupedCommands = (lines) => [...new Set(lines.split("\n").filter((l) => l !== ""))].map((l) => `\n${l}`).join("");
|
|
64949
|
+
logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${red(result.errorCount)} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to clean up. If the same resource keeps failing, dropping the state record is the last resort: it removes the record without deleting AWS resources.` + skippedClause + dedupedCommands(orphanHint + skippedCommands));
|
|
64988
64950
|
}
|
|
64989
64951
|
} finally {
|
|
64990
64952
|
try {
|
|
@@ -65104,11 +65066,11 @@ function getCurrentNestedStackContext() {
|
|
|
65104
65066
|
* @param alsoSkippedCount resources the child SKIPPED (no AWS call issued)
|
|
65105
65067
|
* @param alsoInterrupted whether the child's destroy was also interrupted
|
|
65106
65068
|
*/
|
|
65107
|
-
function nestedStackChildFailureMessage(childStackName, errorCount, alsoSkippedCount, alsoInterrupted) {
|
|
65069
|
+
function nestedStackChildFailureMessage(childStackName, errorCount, alsoSkippedCount, alsoInterrupted, inspectCommand) {
|
|
65108
65070
|
const extra = [];
|
|
65109
65071
|
if (alsoSkippedCount > 0) extra.push(`${alsoSkippedCount} resource(s) were also skipped`);
|
|
65110
65072
|
if (alsoInterrupted) extra.push("the child destroy was also interrupted");
|
|
65111
|
-
return `Nested stack ${childStackName} failed to destroy: ${errorCount} resource(s) failed to delete` + (extra.length > 0 ? ` (${extra.join("; ")})` : "") + `. The child's state is PRESERVED and still lists them — inspect it
|
|
65073
|
+
return `Nested stack ${childStackName} failed to destroy: ${errorCount} resource(s) failed to delete` + (extra.length > 0 ? ` (${extra.join("; ")})` : "") + `. The child's state is PRESERVED and still lists them — inspect it, resolve the failure, and re-run the destroy. The parent's record of this nested stack is kept so the child stays reachable.\nInspect it with: ${inspectCommand}`;
|
|
65112
65074
|
}
|
|
65113
65075
|
|
|
65114
65076
|
//#endregion
|
|
@@ -65285,7 +65247,10 @@ var NestedStackProvider = class {
|
|
|
65285
65247
|
...ctx.destroyOptions?.resourceTimeoutByType && { resourceTimeoutByType: ctx.destroyOptions.resourceTimeoutByType }
|
|
65286
65248
|
}));
|
|
65287
65249
|
if (childResult.errorCount > 0) {
|
|
65288
|
-
const failure = new Error(nestedStackChildFailureMessage(childStackName, childResult.errorCount, childResult.skippedCount, childResult.interrupted
|
|
65250
|
+
const failure = new Error(nestedStackChildFailureMessage(childStackName, childResult.errorCount, childResult.skippedCount, childResult.interrupted, pasteableCommand("cdkd state show", [{
|
|
65251
|
+
value: childStackName,
|
|
65252
|
+
hole: "stack"
|
|
65253
|
+
}]).command));
|
|
65289
65254
|
throw childResult.interrupted ? markNonRetryable(failure) : failure;
|
|
65290
65255
|
}
|
|
65291
65256
|
if (childResult.skippedCount > 0 || childResult.interrupted) {
|
|
@@ -68325,6 +68290,36 @@ function conditionsDependOnTokenParameters(template, tokenParameterNames) {
|
|
|
68325
68290
|
}
|
|
68326
68291
|
return false;
|
|
68327
68292
|
}
|
|
68293
|
+
/**
|
|
68294
|
+
* The reason a repaired `properties` map puts on the node's `blocking`
|
|
68295
|
+
* (go-to-k/cdkd#3335).
|
|
68296
|
+
*
|
|
68297
|
+
* `cdkd diff` REPAIRS this container and previews the record; `cdkd deploy`
|
|
68298
|
+
* REFUSES it inside `DiffCalculator.calculateDiff`, which both deploy paths
|
|
68299
|
+
* reach. So the preview is honest about the rows and wrong about what happens
|
|
68300
|
+
* next, and this is the sentence that says so.
|
|
68301
|
+
*
|
|
68302
|
+
* The ids are NOT re-rendered here: they have already been through
|
|
68303
|
+
* `malformedResourcePropertiesWarning`'s sanitizer in the warning printed
|
|
68304
|
+
* beside this, and the renderer strips control characters from every blocking
|
|
68305
|
+
* reason on the way out. The count is what this line adds — and the line does
|
|
68306
|
+
* not send the reader to that warning for the names, because the warning caps
|
|
68307
|
+
* at five before `and N more`, and under `--json` this reason ships in the
|
|
68308
|
+
* payload, where "above" has no referent.
|
|
68309
|
+
*/
|
|
68310
|
+
function deployRefusesPropertiesReason(logicalIds) {
|
|
68311
|
+
return `${logicalIds.length} resource record(s) hold a 'properties' map that cannot be read. This preview repaired them to empty; 'cdkd deploy' refuses the record instead, so the deploy this previews will not start.`;
|
|
68312
|
+
}
|
|
68313
|
+
/**
|
|
68314
|
+
* The `outputs` twin of {@link deployRefusesPropertiesReason}, a constant
|
|
68315
|
+
* because the bag is refused as a whole and there is nothing to count.
|
|
68316
|
+
*
|
|
68317
|
+
* `refuseMalformedOutputs` in `DeployEngine` is what refuses it. This arm is
|
|
68318
|
+
* the one the issue could not drive end to end — a record whose template
|
|
68319
|
+
* declares no Outputs produces no delta at all, so before this the node had
|
|
68320
|
+
* nothing to report and `--fail` exited 0.
|
|
68321
|
+
*/
|
|
68322
|
+
const DEPLOY_REFUSES_OUTPUTS_REASON = "The 'outputs' bag cannot be read. This preview repaired it to empty; 'cdkd deploy' refuses the record instead, so the deploy this previews will not start.";
|
|
68328
68323
|
/** How many unreadable row names the human preview lists before summarizing. */
|
|
68329
68324
|
const UNREADABLE_PREVIEW_NAMES = 10;
|
|
68330
68325
|
/** Empty template used to diff a removed nested child's state → all DELETE. */
|
|
@@ -68392,6 +68387,7 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
|
|
|
68392
68387
|
const result = await stateBackend.getState(stackName, region);
|
|
68393
68388
|
if (result) {
|
|
68394
68389
|
const unreadable = [];
|
|
68390
|
+
const deployRefusals = [];
|
|
68395
68391
|
if (repairMalformedResourcesForReadOnly(result.state)) {
|
|
68396
68392
|
logger.warn(malformedResourcesWarning(stackName, region));
|
|
68397
68393
|
unreadable.push(UNREADABLE_RESOURCES_MAP_ROW);
|
|
@@ -68402,8 +68398,14 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
|
|
|
68402
68398
|
unreadable.push(...dropped);
|
|
68403
68399
|
}
|
|
68404
68400
|
const unreadableProps = repairMalformedResourcePropertiesForReadOnly(result.state);
|
|
68405
|
-
if (unreadableProps.length > 0)
|
|
68406
|
-
|
|
68401
|
+
if (unreadableProps.length > 0) {
|
|
68402
|
+
logger.warn(malformedResourcePropertiesWarning(stackName, region, unreadableProps));
|
|
68403
|
+
deployRefusals.push(deployRefusesPropertiesReason(unreadableProps));
|
|
68404
|
+
}
|
|
68405
|
+
if (repairMalformedOutputsForReadOnly(result.state)) {
|
|
68406
|
+
logger.warn(malformedOutputsWarning(stackName, region));
|
|
68407
|
+
deployRefusals.push(DEPLOY_REFUSES_OUTPUTS_REASON);
|
|
68408
|
+
}
|
|
68407
68409
|
if (repairMalformedOrphansForReadOnly(result.state)) {
|
|
68408
68410
|
logger.warn(malformedOrphansWarning(stackName, region));
|
|
68409
68411
|
unreadable.push(UNREADABLE_ORPHANS_CONTAINER_ROW);
|
|
@@ -68411,7 +68413,8 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
|
|
|
68411
68413
|
if (isReadableBag(result.state.outputs) && !hasReadableExportSet(result.state)) logger.warn(malformedExportNamesWarning(stackName, region));
|
|
68412
68414
|
return {
|
|
68413
68415
|
state: result.state,
|
|
68414
|
-
unreadable
|
|
68416
|
+
unreadable,
|
|
68417
|
+
deployRefusals
|
|
68415
68418
|
};
|
|
68416
68419
|
}
|
|
68417
68420
|
return {
|
|
@@ -68423,7 +68426,8 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
|
|
|
68423
68426
|
version: 10,
|
|
68424
68427
|
lastModified: Date.now()
|
|
68425
68428
|
},
|
|
68426
|
-
unreadable: []
|
|
68429
|
+
unreadable: [],
|
|
68430
|
+
deployRefusals: []
|
|
68427
68431
|
};
|
|
68428
68432
|
}
|
|
68429
68433
|
/**
|
|
@@ -68572,6 +68576,7 @@ async function computeStackDiff(currentState, template, region, stackName, state
|
|
|
68572
68576
|
let adoptedOrphans = [];
|
|
68573
68577
|
let adoptedRecords = {};
|
|
68574
68578
|
let blocking = [];
|
|
68579
|
+
const deployRefusals = [];
|
|
68575
68580
|
let stateForDiff = currentState;
|
|
68576
68581
|
const unreadableOrphans = [];
|
|
68577
68582
|
if (currentState.orphans?.length && options.previewOrphanAdoption) {
|
|
@@ -68598,7 +68603,10 @@ async function computeStackDiff(currentState, template, region, stackName, state
|
|
|
68598
68603
|
}
|
|
68599
68604
|
};
|
|
68600
68605
|
const tornAdopted = repairMalformedResourcePropertiesForReadOnly(stateForDiff);
|
|
68601
|
-
if (tornAdopted.length > 0)
|
|
68606
|
+
if (tornAdopted.length > 0) {
|
|
68607
|
+
logger.warn(malformedResourcePropertiesWarning(stackName, region, tornAdopted));
|
|
68608
|
+
deployRefusals.push(deployRefusesPropertiesReason(tornAdopted));
|
|
68609
|
+
}
|
|
68602
68610
|
}
|
|
68603
68611
|
}
|
|
68604
68612
|
const changes = await diffCalculator.calculateDiff(stateForDiff, effectiveTemplate, resolveFn, canonicalizeProperties);
|
|
@@ -68641,7 +68649,8 @@ async function computeStackDiff(currentState, template, region, stackName, state
|
|
|
68641
68649
|
adoptedOrphans,
|
|
68642
68650
|
adoptedRecords,
|
|
68643
68651
|
blocking,
|
|
68644
|
-
unreadableOrphans
|
|
68652
|
+
unreadableOrphans,
|
|
68653
|
+
deployRefusals
|
|
68645
68654
|
};
|
|
68646
68655
|
}
|
|
68647
68656
|
/**
|
|
@@ -68719,10 +68728,10 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
|
|
|
68719
68728
|
* fail at deploy time.
|
|
68720
68729
|
*/
|
|
68721
68730
|
async function buildDiffTree(args) {
|
|
68722
|
-
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption, ancestorTemplatePaths } = args;
|
|
68723
|
-
const { state, unreadable } = await loadStateOrEmpty(stackName, region, stateBackend);
|
|
68731
|
+
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption, ancestorTemplatePaths, isNestedChild } = args;
|
|
68732
|
+
const { state, unreadable, deployRefusals } = await loadStateOrEmpty(stackName, region, stateBackend);
|
|
68724
68733
|
const secretBearingAbove = parentHasSecretReference === true || templateHasSecretDynamicReference(template);
|
|
68725
|
-
const { changes, outputChanges, adoptedOrphans, adoptedRecords, blocking, unreadableOrphans } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, {
|
|
68734
|
+
const { changes, outputChanges, adoptedOrphans, adoptedRecords, blocking, unreadableOrphans, deployRefusals: adoptedDeployRefusals } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, {
|
|
68726
68735
|
...parameters && { parameters },
|
|
68727
68736
|
...canonicalizeProperties && { canonicalizeProperties },
|
|
68728
68737
|
...cfnFallback !== void 0 && { cfnFallback },
|
|
@@ -68744,7 +68753,11 @@ async function buildDiffTree(args) {
|
|
|
68744
68753
|
ccApiRoutes: collectCcApiRoutes(template, stateAfterAdoption),
|
|
68745
68754
|
outputChanges,
|
|
68746
68755
|
adoptedOrphans,
|
|
68747
|
-
blocking
|
|
68756
|
+
blocking: isNestedChild ? blocking : [
|
|
68757
|
+
...deployRefusals,
|
|
68758
|
+
...adoptedDeployRefusals,
|
|
68759
|
+
...blocking
|
|
68760
|
+
],
|
|
68748
68761
|
unreadable: [...unreadable, ...unreadableOrphans],
|
|
68749
68762
|
children: []
|
|
68750
68763
|
};
|
|
@@ -68778,6 +68791,7 @@ async function buildDiffTree(args) {
|
|
|
68778
68791
|
...cfnFallback !== void 0 && { cfnFallback },
|
|
68779
68792
|
...previewOrphanAdoption && { previewOrphanAdoption },
|
|
68780
68793
|
ancestorTemplatePaths: childAncestorTemplatePaths,
|
|
68794
|
+
isNestedChild: true,
|
|
68781
68795
|
parentHasSecretReference: secretBearingAbove
|
|
68782
68796
|
}));
|
|
68783
68797
|
}
|
|
@@ -69214,9 +69228,18 @@ var DiffDetectedError = class DiffDetectedError extends CdkdError {
|
|
|
69214
69228
|
}
|
|
69215
69229
|
};
|
|
69216
69230
|
/**
|
|
69217
|
-
* Signals that the preview completed and `cdkd deploy` would REFUSE to start
|
|
69218
|
-
*
|
|
69219
|
-
*
|
|
69231
|
+
* Signals that the preview completed and `cdkd deploy` would REFUSE to start.
|
|
69232
|
+
* Two conditions reach it, and the message must fit BOTH:
|
|
69233
|
+
*
|
|
69234
|
+
* - a rollback-orphan record whose physical id another cdkd stack already
|
|
69235
|
+
* manages (issue go-to-k/cdkd#2943), where the remedy is resolving the
|
|
69236
|
+
* ownership conflict between two stacks;
|
|
69237
|
+
* - a container this preview REPAIRED and the deploy refuses — a resource's
|
|
69238
|
+
* unreadable `properties` map or an unreadable `outputs` bag (issue
|
|
69239
|
+
* go-to-k/cdkd#3335), where there is no second stack at all and the remedy
|
|
69240
|
+
* is repairing the one record.
|
|
69241
|
+
*
|
|
69242
|
+
* So the message names no remedy at all and closes on the count.
|
|
69220
69243
|
*
|
|
69221
69244
|
* Exit **3**, and neither 1 nor 2, for reasons that are about what a caller
|
|
69222
69245
|
* can conclude:
|
|
@@ -69226,18 +69249,19 @@ var DiffDetectedError = class DiffDetectedError extends CdkdError {
|
|
|
69226
69249
|
* code for "there is work to do" and "the work cannot begin".
|
|
69227
69250
|
* - **2** is this CLI's partial-failure family, documented as "work
|
|
69228
69251
|
* completed, re-running typically resolves it" (`docs/cli-reference.md`).
|
|
69229
|
-
* A refusal is the opposite: re-running changes nothing until a human
|
|
69230
|
-
*
|
|
69252
|
+
* A refusal is the opposite: re-running changes nothing until a human acts
|
|
69253
|
+
* — repairing the record, or resolving the ownership conflict, depending on
|
|
69254
|
+
* which condition fired.
|
|
69231
69255
|
*
|
|
69232
69256
|
* It is NOT silent: unlike `--fail`, this is not a flag the user opted into,
|
|
69233
69257
|
* so the reason has to be visible even when the diff report scrolled away.
|
|
69234
|
-
* The `Blocking` section already printed the per-
|
|
69235
|
-
* one line that says the run cannot proceed.
|
|
69258
|
+
* The `Blocking` section already printed the per-condition detail; this adds
|
|
69259
|
+
* the one line that says the run cannot proceed.
|
|
69236
69260
|
*/
|
|
69237
69261
|
var DeployRefusalPreviewError = class DeployRefusalPreviewError extends CdkdError {
|
|
69238
69262
|
exitCode = 3;
|
|
69239
69263
|
constructor(count) {
|
|
69240
|
-
super(`cdkd deploy would refuse to start: ${count} blocking condition(s) reported above
|
|
69264
|
+
super(`cdkd deploy would refuse to start: ${count} blocking condition(s) reported above.`, "DEPLOY_REFUSAL_PREVIEW");
|
|
69241
69265
|
this.name = "DeployRefusalPreviewError";
|
|
69242
69266
|
Object.setPrototypeOf(this, DeployRefusalPreviewError.prototype);
|
|
69243
69267
|
}
|
|
@@ -69362,7 +69386,8 @@ async function diffCommand(stacks, options) {
|
|
|
69362
69386
|
canonicalizeProperties,
|
|
69363
69387
|
...assetRedirect && { assetRedirect },
|
|
69364
69388
|
...options.cfnFallback === false && { cfnFallback: false },
|
|
69365
|
-
previewOrphanAdoption
|
|
69389
|
+
previewOrphanAdoption,
|
|
69390
|
+
isNestedChild: false
|
|
69366
69391
|
}));
|
|
69367
69392
|
}
|
|
69368
69393
|
if (options.json) process.stdout.write(`${JSON.stringify(trees.map(diffTreeToJson), null, 2)}\n`);
|
|
@@ -70735,9 +70760,15 @@ function scrubRefusalWording(verdict, loggedExportKey, loggedProducerStack, logg
|
|
|
70735
70760
|
const through = chain.length > 1 ? ` (through ${chain.slice(0, -1).map((s) => `'${s}'`).join(", ")})` : "";
|
|
70736
70761
|
const templateClaim = verdict.kind === "declared" ? `declares '${loggedExportKey}' from a {{resolve:...}} expression` : verdict.kind === "chained" ? chainRoot !== void 0 ? `publishes '${loggedExportKey}' by RE-EXPORTING a value that '${chainRoot}' declares from a {{resolve:...}} expression${through}` : `publishes '${loggedExportKey}' by RE-EXPORTING a value another stack of this app declares from a {{resolve:...}} expression` : chainRoot !== void 0 ? `publishes at least one output that RE-EXPORTS a value '${chainRoot}' declares from a {{resolve:...}} expression${through} (this run could not match '${loggedExportKey}' to a declared output, so it cannot say which)` : `publishes at least one output from a {{resolve:...}} expression (this run could not match '${loggedExportKey}' to a declared output, so it cannot say which)`;
|
|
70737
70762
|
const scrubOrder = dedupePreservingOrder([...chain].reverse().concat(loggedProducerStack), "last");
|
|
70763
|
+
const remedyCommands = scrubOrder.map((name) => `Scrub with: ${pasteableCommand("cdkd scrub", [{
|
|
70764
|
+
value: name,
|
|
70765
|
+
hole: "stack",
|
|
70766
|
+
opts: { patternMatched: true }
|
|
70767
|
+
}]).command}`);
|
|
70738
70768
|
return {
|
|
70739
70769
|
templateClaim,
|
|
70740
|
-
remedy: scrubOrder.length > 1 ? `Scrub the producers first, from the head of the chain
|
|
70770
|
+
remedy: scrubOrder.length > 1 ? `Scrub the producers first, from the head of the chain, in the order below` : `Scrub the producer first`,
|
|
70771
|
+
remedyCommands
|
|
70741
70772
|
};
|
|
70742
70773
|
}
|
|
70743
70774
|
/**
|
|
@@ -70799,8 +70830,12 @@ function dedupePreservingOrder(values, keep) {
|
|
|
70799
70830
|
function plaintextProducerCrossStackReadError(origin, stackName, intrinsic, path, producerStack, exportKey, verdict, secrets) {
|
|
70800
70831
|
const loggedExportKey = maskSecretsInText(exportKey, secrets);
|
|
70801
70832
|
const loggedProducerStack = maskSecretsInText(producerStack, secrets);
|
|
70802
|
-
const { templateClaim, remedy } = scrubRefusalWording(verdict, loggedExportKey, loggedProducerStack, verdict.via.map((s) => maskSecretsInText(s, secrets)));
|
|
70803
|
-
return new ScrubRefusalError(`Scrub of ${stackName} resolved the ${intrinsic} in ${origin}${path ? ` at ${path}` : ""} to a PLAINTEXT value: the producer stack '${loggedProducerStack}' ${templateClaim}, but its own state still stores the resolved plaintext rather than that expression. scrub has no expression to write in this stack's place, so it cannot redact the imported secret and must not report this stack clean. ${remedy}, then re-run
|
|
70833
|
+
const { templateClaim, remedy, remedyCommands } = scrubRefusalWording(verdict, loggedExportKey, loggedProducerStack, verdict.via.map((s) => maskSecretsInText(s, secrets)));
|
|
70834
|
+
return new ScrubRefusalError(`Scrub of ${stackName} resolved the ${intrinsic} in ${origin}${path ? ` at ${path}` : ""} to a PLAINTEXT value: the producer stack '${loggedProducerStack}' ${templateClaim}, but its own state still stores the resolved plaintext rather than that expression. scrub has no expression to write in this stack's place, so it cannot redact the imported secret and must not report this stack clean. ${remedy}, then re-run scrub for this stack — the read will then return the expression and this stack is scrubbed normally.\n${remedyCommands.join("\n")}\nThen re-run: ${pasteableCommand("cdkd scrub", [{
|
|
70835
|
+
value: stackName,
|
|
70836
|
+
hole: "stack",
|
|
70837
|
+
opts: { patternMatched: true }
|
|
70838
|
+
}]).command}`, "SCRUB_CROSS_STACK_PRODUCER_PLAINTEXT");
|
|
70804
70839
|
}
|
|
70805
70840
|
/** The `no` verdict, shared so the walk allocates nothing for the common case. */
|
|
70806
70841
|
const NO_SECRET_EXPRESSION = {
|
|
@@ -72421,10 +72456,9 @@ function createScrubCommand() {
|
|
|
72421
72456
|
* (phantom drift against an ISO-string baseline, and `--accept` persisted the
|
|
72422
72457
|
* `{}`). The identity return is what makes the second visible to the
|
|
72423
72458
|
* comparator; the null-prototype target is what keeps the first an own key.
|
|
72424
|
-
* One layer LATER
|
|
72425
|
-
*
|
|
72426
|
-
*
|
|
72427
|
-
* walk does not run.
|
|
72459
|
+
* One layer LATER `secret-redaction.ts`'s value walk, which `--accept` runs
|
|
72460
|
+
* over a resource that RECORDS a secret, keeps a `Date` by identity too (issue
|
|
72461
|
+
* #2427), so the ISO string reaches the baseline on both kinds of resource.
|
|
72428
72462
|
*/
|
|
72429
72463
|
function canonicalizeTagListsDeep(v) {
|
|
72430
72464
|
if (Array.isArray(v)) {
|
|
@@ -76854,7 +76888,13 @@ async function destroyCommand(stackArgs, options) {
|
|
|
76854
76888
|
} else if (synthRegion && refs.some((r) => r.region === synthRegion)) stackTargetRegion = synthRegion;
|
|
76855
76889
|
else {
|
|
76856
76890
|
const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
76857
|
-
throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}.
|
|
76891
|
+
throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}. Remove cdkd's record for ONE region with the command below (fill in the region), or run destroy from a CDK app whose env.region matches one of them.\nRemove one record with: ${pasteableCommand("cdkd state orphan", [{
|
|
76892
|
+
value: stackName,
|
|
76893
|
+
hole: "stack"
|
|
76894
|
+
}, {
|
|
76895
|
+
flag: "--stack-region",
|
|
76896
|
+
hole: "region"
|
|
76897
|
+
}]).command}`);
|
|
76858
76898
|
}
|
|
76859
76899
|
const stateResult = await stateBackend.getState(stackName, stackTargetRegion);
|
|
76860
76900
|
if (!stateResult) {
|
|
@@ -78744,7 +78784,10 @@ async function exportCommand(stackArg, options) {
|
|
|
78744
78784
|
const { state, etag, migrationPending } = stateData;
|
|
78745
78785
|
const exportJournal = await stateBackend.loadRollbackJournal(resolvedStackName, targetRegion);
|
|
78746
78786
|
if (exportJournal && exportJournal.segments.length > 0) {
|
|
78747
|
-
logger.warn(`Stack '${resolvedStackName}' has a rollback journal — a previous deploy failed or was interrupted and has not been reverted. Exporting this half-deployed state to CloudFormation is likely unintended;
|
|
78787
|
+
logger.warn(`Stack '${resolvedStackName}' has a rollback journal — a previous deploy failed or was interrupted and has not been reverted. Exporting this half-deployed state to CloudFormation is likely unintended; revert it first (or 'cdkd deploy' to fix forward).\nRevert it with: ${pasteableCommand("cdkd rollback", [{
|
|
78788
|
+
value: resolvedStackName,
|
|
78789
|
+
hole: "stack"
|
|
78790
|
+
}]).command}`);
|
|
78748
78791
|
if (!options.yes && !options.dryRun) {
|
|
78749
78792
|
if (!await confirmPrompt$3("Export anyway?")) {
|
|
78750
78793
|
logger.info("Export cancelled");
|
|
@@ -79094,7 +79137,7 @@ function safeDetail(value) {
|
|
|
79094
79137
|
*/
|
|
79095
79138
|
function orphanCommandFor(stackName, region) {
|
|
79096
79139
|
const shownStack = safeSegment(stackName);
|
|
79097
|
-
if (!(typeof stackName === "string" && shownStack === stackName && typeof region === "string" && safeSegment(region) === region)) return "cdkd state orphan <stack> --stack-region <region> — spelled out because this record's name or region does NOT render exactly, so another record may render identically; list them as stored with 'cdkd state list --long' and act on the one whose key matches";
|
|
79140
|
+
if (!(typeof stackName === "string" && shownStack === stackName && !stackName.startsWith("-") && typeof region === "string" && safeSegment(region) === region && !region.startsWith("-"))) return "cdkd state orphan <stack> --stack-region <region> — spelled out because this record's name or region does NOT render exactly, so another record may render identically; list them as stored with 'cdkd state list --long' and act on the one whose key matches";
|
|
79098
79141
|
return `cdkd state orphan ${shellQuote(shownStack)} --stack-region ${shellQuote(safeSegment(region))}`;
|
|
79099
79142
|
}
|
|
79100
79143
|
/**
|
|
@@ -80975,7 +81018,10 @@ async function runPerStackImportLoop(args) {
|
|
|
80975
81018
|
} catch (err) {
|
|
80976
81019
|
const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${safeSegment(s.cdkdStackName)} → ${safeSegment(s.cfnStackName)}`).join(", ") : "(none)";
|
|
80977
81020
|
const remainingSummary = perStackPlans.slice(i).map((p) => safeSegment(p.cdkdName)).join(", ");
|
|
80978
|
-
throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${safeSegment(plan.cdkdName)}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run
|
|
81021
|
+
throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${safeSegment(plan.cdkdName)}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run the export — already-imported children will be adopted as nested references on retry. Cause: ${err instanceof Error ? err.message : String(err)}\nRe-run with: ${pasteableCommand("cdkd export", [{
|
|
81022
|
+
value: rootStackName,
|
|
81023
|
+
hole: "stack"
|
|
81024
|
+
}]).command}`, { cause: err instanceof Error ? err : void 0 });
|
|
80979
81025
|
}
|
|
80980
81026
|
const cfnArn = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: plan.cfnName }))).Stacks?.[0]?.StackId;
|
|
80981
81027
|
if (!cfnArn) throw new Error(`runPerStackImportLoop: DescribeStacks returned no StackId for '${plan.cfnName}' immediately after Phase 1A IMPORT — AWS may be in an unexpected state.`);
|
|
@@ -81757,7 +81803,11 @@ async function stateResourcesCommand(stackName, options) {
|
|
|
81757
81803
|
const setup = await setupStateBackend(options);
|
|
81758
81804
|
try {
|
|
81759
81805
|
const ref = resolveSingleRegion(stackName, await setup.stateBackend.listStacks(), options.stackRegion);
|
|
81760
|
-
if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region.
|
|
81806
|
+
if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region. A cdkd write migrates it to the region-scoped layout; re-run this command after it.\nMigrate with: ${pasteableCommand("cdkd deploy", [{
|
|
81807
|
+
value: stackName,
|
|
81808
|
+
hole: "stack",
|
|
81809
|
+
opts: { patternMatched: true }
|
|
81810
|
+
}]).command}`);
|
|
81761
81811
|
const stateResult = await setup.stateBackend.getState(stackName, ref.region);
|
|
81762
81812
|
if (!stateResult) throw new Error(`No state found for stack '${safe$1(stackName)}' (${safe$1(ref.region)}) in s3://${setup.bucket}/${setup.prefix}/. Run 'cdkd state list' to see available stacks.`);
|
|
81763
81813
|
if (repairMalformedResourcesForReadOnly(stateResult.state)) logger.warn(malformedResourcesWarning(stackName, ref.region));
|
|
@@ -81964,7 +82014,11 @@ async function stateShowCommand(stackName, options) {
|
|
|
81964
82014
|
const setup = await setupStateBackend(options);
|
|
81965
82015
|
try {
|
|
81966
82016
|
const ref = resolveSingleRegion(stackName, await setup.stateBackend.listStacks(), options.stackRegion);
|
|
81967
|
-
if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region.
|
|
82017
|
+
if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region. A cdkd write migrates it to the region-scoped layout; re-run this command after it.\nMigrate with: ${pasteableCommand("cdkd deploy", [{
|
|
82018
|
+
value: stackName,
|
|
82019
|
+
hole: "stack",
|
|
82020
|
+
opts: { patternMatched: true }
|
|
82021
|
+
}]).command}`);
|
|
81968
82022
|
const [stateResult, lockInfo] = await Promise.all([setup.stateBackend.getState(stackName, ref.region), setup.lockManager.getLockInfo(stackName, ref.region)]);
|
|
81969
82023
|
if (!stateResult) throw new Error(`No state found for stack '${safe$1(stackName)}' (${safe$1(ref.region)}) in s3://${setup.bucket}/${setup.prefix}/. Run 'cdkd state list' to see available stacks.`);
|
|
81970
82024
|
if (options.showNested) {
|
|
@@ -82432,7 +82486,11 @@ async function stateOrphanCommand(stackArgs, options) {
|
|
|
82432
82486
|
}
|
|
82433
82487
|
if (!options.yes && !options.force) {
|
|
82434
82488
|
const targetList = targets.map((t) => formatStackRefSafe(t)).join(", ");
|
|
82435
|
-
process.stdout.write(`\nWARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.\
|
|
82489
|
+
process.stdout.write(`\nWARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.\nDelete the actual resources instead with the command below.\nDestroy with: ${pasteableCommand("cdkd destroy", [{
|
|
82490
|
+
value: stackName,
|
|
82491
|
+
hole: "stack",
|
|
82492
|
+
opts: { patternMatched: true }
|
|
82493
|
+
}]).command}\n\n`);
|
|
82436
82494
|
if (!await confirmStateOrphanRemoval(`Remove state for ${targetList} from s3://${setup.bucket}/${setup.prefix}/?`)) {
|
|
82437
82495
|
logger.info(`Cancelled removal of state for stack: ${stackName}`);
|
|
82438
82496
|
continue;
|
|
@@ -82964,6 +83022,68 @@ function createStateInfoCommand() {
|
|
|
82964
83022
|
return cmd;
|
|
82965
83023
|
}
|
|
82966
83024
|
/**
|
|
83025
|
+
* The hole the legacy refusal's `cdkd deploy` prints for a name it cannot
|
|
83026
|
+
* name, and the key {@link withheldNameClause} reads the reason back by. ONE
|
|
83027
|
+
* spelling so the two cannot drift apart (m22 of the go-to-k/cdkd#3499 review).
|
|
83028
|
+
*/
|
|
83029
|
+
const STACK_HOLE = "stack";
|
|
83030
|
+
/**
|
|
83031
|
+
* The sentence for a name `pasteableCommand` would not print, rendered from the
|
|
83032
|
+
* REASON it gave rather than from a predicate of this site's own — M11 of the
|
|
83033
|
+
* go-to-k/cdkd#3499 review. Keying it on a second predicate got both directions
|
|
83034
|
+
* wrong at once: `Old;Stack` renders exactly, so a rendering-based clause called
|
|
83035
|
+
* a command that names it "not exact", and `--all` also renders exactly, so the
|
|
83036
|
+
* same clause said nothing above a hole the operator was then invited to fill
|
|
83037
|
+
* with the name printed beside it.
|
|
83038
|
+
*
|
|
83039
|
+
* THREE of the five reasons are reachable from this call site, and the other
|
|
83040
|
+
* two are bounded out by where the name comes from — an S3 key segment:
|
|
83041
|
+
*
|
|
83042
|
+
* - `altered`, `option-shaped` and `pattern-shaped` are all reachable: a
|
|
83043
|
+
* planted key can spell a name any of those ways in a handful of bytes.
|
|
83044
|
+
* - `empty` is not. `listStacks` drops a key whose stack segment is empty
|
|
83045
|
+
* (`s3-state-backend.ts`'s `if (!stackName) continue`), so `target.stackName`
|
|
83046
|
+
* is non-empty by the time the refusal is built.
|
|
83047
|
+
* - `too-long` is not either, and the bound is not obvious: the cap is
|
|
83048
|
+
* `STACK_REF_MAX_CODE_POINTS` (1152), while S3 caps the WHOLE key at 1024
|
|
83049
|
+
* bytes, leaving at most 1008 for the name inside `cdkd/<name>/state.json`.
|
|
83050
|
+
* A multi-byte character only lowers the code-point count further, so a name
|
|
83051
|
+
* arriving through `listStacks` can never reach the cap.
|
|
83052
|
+
*
|
|
83053
|
+
* Both unreachable arms stay, and are covered, because the REASON is the
|
|
83054
|
+
* gate's and not this site's: `pasteableCommand` can return either to a caller
|
|
83055
|
+
* whose value comes from somewhere else. A missing arm is no longer a silent
|
|
83056
|
+
* fall-through — the `switch` below is exhaustive over `WithholdReason`, so
|
|
83057
|
+
* dropping one is a compile error (M13). What the cases buy on top of that is
|
|
83058
|
+
* the SENTENCE: the type checker knows an arm exists, not that it says the
|
|
83059
|
+
* right thing, and substituting one arm's text for another's is exactly the
|
|
83060
|
+
* disagreement M11 closed.
|
|
83061
|
+
*/
|
|
83062
|
+
function withheldNameClause(built) {
|
|
83063
|
+
const reason = built.withheld.find((w) => w.hole === STACK_HOLE)?.reason;
|
|
83064
|
+
if (reason === void 0) return "";
|
|
83065
|
+
let why;
|
|
83066
|
+
switch (reason) {
|
|
83067
|
+
case "altered":
|
|
83068
|
+
why = `does NOT render exactly, so another record may render identically`;
|
|
83069
|
+
break;
|
|
83070
|
+
case "empty":
|
|
83071
|
+
why = `is empty`;
|
|
83072
|
+
break;
|
|
83073
|
+
case "too-long":
|
|
83074
|
+
why = `is too long to print`;
|
|
83075
|
+
break;
|
|
83076
|
+
case "option-shaped":
|
|
83077
|
+
why = "begins with a '-', so it is not safe to print as an argument to 'cdkd deploy' — a name like '--all' is parsed as the FLAG and targets every stack";
|
|
83078
|
+
break;
|
|
83079
|
+
case "pattern-shaped":
|
|
83080
|
+
why = `would be read as a PATTERN by 'cdkd deploy', which can match other stacks`;
|
|
83081
|
+
break;
|
|
83082
|
+
default: throw new Error(`withheldNameClause: unhandled WithholdReason ${String(reason)}`);
|
|
83083
|
+
}
|
|
83084
|
+
return ` This record's name ${why} — so it is not named in the command below; list the records as stored with 'cdkd state list --long' and act on the one whose key matches.`;
|
|
83085
|
+
}
|
|
83086
|
+
/**
|
|
82967
83087
|
* `cdkd state refresh-observed <stack>` command implementation.
|
|
82968
83088
|
*
|
|
82969
83089
|
* Walks every resource in the given stack(s) and refreshes its
|
|
@@ -83053,9 +83173,13 @@ async function stateRefreshObservedCommand(stackArgs, options) {
|
|
|
83053
83173
|
}
|
|
83054
83174
|
}
|
|
83055
83175
|
for (const target of targets) if (!target.region) {
|
|
83056
|
-
const stack =
|
|
83057
|
-
const
|
|
83058
|
-
|
|
83176
|
+
const stack = displayIdent(target.stackName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS });
|
|
83177
|
+
const migrate = pasteableCommand("cdkd deploy", [{
|
|
83178
|
+
value: target.stackName,
|
|
83179
|
+
hole: STACK_HOLE,
|
|
83180
|
+
opts: { patternMatched: true }
|
|
83181
|
+
}]);
|
|
83182
|
+
throw new Error(`Stack ${stack} has only a legacy state record without a region. Migrate it to the region-scoped layout with any cdkd write, then re-run refresh-observed.` + withheldNameClause(migrate) + `\nMigrate with: ${migrate.command}`);
|
|
83059
83183
|
}
|
|
83060
83184
|
let totalRefreshed = 0;
|
|
83061
83185
|
let totalUnsupported = 0;
|
|
@@ -83644,7 +83768,10 @@ async function rollbackCommand(stackArg, options) {
|
|
|
83644
83768
|
const fresh = await setup.stateBackend.getState(stackName, region);
|
|
83645
83769
|
currentEtag = await setup.stateBackend.saveState(stackName, region, next(), { ...fresh?.etag !== void 0 && { expectedEtag: fresh.etag } });
|
|
83646
83770
|
} catch (retryError) {
|
|
83647
|
-
logger.warn(`Failed to persist state after a rollback operation: ${displaySafe(retryError instanceof Error ? retryError.message : String(retryError))}. The resource was reverted in AWS; re-run
|
|
83771
|
+
logger.warn(`Failed to persist state after a rollback operation: ${displaySafe(retryError instanceof Error ? retryError.message : String(retryError))}. The resource was reverted in AWS; re-run the rollback to reconcile state.\nRe-run with: ${pasteableCommand("cdkd rollback", [{
|
|
83772
|
+
value: stackName,
|
|
83773
|
+
hole: "stack"
|
|
83774
|
+
}]).command}`);
|
|
83648
83775
|
}
|
|
83649
83776
|
}
|
|
83650
83777
|
};
|
|
@@ -83725,8 +83852,14 @@ async function rollbackCommand(stackArg, options) {
|
|
|
83725
83852
|
await setup.stateBackend.deleteState(stackName, region);
|
|
83726
83853
|
logger.info(`State for '${safeStack(stackName)}' (${safe(region)}) removed (stack fully rolled back).`);
|
|
83727
83854
|
}
|
|
83728
|
-
if (interrupted) throw new PartialFailureError(`Rollback interrupted. Journal preserved — re-run
|
|
83729
|
-
|
|
83855
|
+
if (interrupted) throw new PartialFailureError(`Rollback interrupted. Journal preserved — re-run the rollback to finish.\nRe-run with: ${pasteableCommand("cdkd rollback", [{
|
|
83856
|
+
value: stackName,
|
|
83857
|
+
hole: "stack"
|
|
83858
|
+
}]).command}`);
|
|
83859
|
+
if (totalFailures > 0) throw new PartialFailureError(`Rollback completed with ${totalFailures} failed operation(s). Journal preserved — re-run the rollback to retry.\nRe-run with: ${pasteableCommand("cdkd rollback", [{
|
|
83860
|
+
value: stackName,
|
|
83861
|
+
hole: "stack"
|
|
83862
|
+
}]).command}`);
|
|
83730
83863
|
if (totalWarnings > 0) throw new PartialFailureError(`Rollback completed with ${totalWarnings} skipped/unrecoverable operation(s) (see warnings above).`);
|
|
83731
83864
|
logger.info(`\nRollback of '${safeStack(stackName)}' (${safe(region)}) complete.`);
|
|
83732
83865
|
} finally {
|
|
@@ -84073,7 +84206,13 @@ function printRunList(stackName, region, runs) {
|
|
|
84073
84206
|
const resultColored = result === "SUCCEEDED" ? green(result) : result === "UNKNOWN" || result === "<unrenderable>" ? gray(result) : red(result);
|
|
84074
84207
|
logger.info(` ${cyan(safeId(run.runId) || "<unrenderable>")} ${safeId(run.command) || "<unrenderable>"} ${resultColored} ${gray(safeId(run.startedAt) || "?")} -> ${gray(safeId(run.finishedAt) || "?")} ${gray(`cdkd ${safeId(run.cdkdVersion) || "<unrenderable>"}`)} ${gray(`${safeCount(run.eventCount)} events`)}`);
|
|
84075
84208
|
}
|
|
84076
|
-
logger.info(gray(`\
|
|
84209
|
+
logger.info(gray(`\nRead one run's events with: ${pasteableCommand("cdkd events", [{
|
|
84210
|
+
value: stackName,
|
|
84211
|
+
hole: "stack"
|
|
84212
|
+
}, {
|
|
84213
|
+
flag: "--run",
|
|
84214
|
+
hole: "runId"
|
|
84215
|
+
}]).command}`));
|
|
84077
84216
|
}
|
|
84078
84217
|
/** Human-readable single-run event stream (in recorded order). */
|
|
84079
84218
|
/**
|
|
@@ -89366,7 +89505,9 @@ function resolveAssetCodeDirectory(opts) {
|
|
|
89366
89505
|
* `process.cwd()` survives only for a `StackInfo` carrying NEITHER field, where
|
|
89367
89506
|
* base and bound coincide again.
|
|
89368
89507
|
*
|
|
89369
|
-
* Exported for `local-start-api.ts`'s copy of the caller,
|
|
89508
|
+
* Exported for `local-start-api.ts`'s copy of the caller, for the Docker
|
|
89509
|
+
* context check the ECS emulator commands run over cdk-local's `StackInfo`
|
|
89510
|
+
* (which is why it takes only the two fields it reads), and for unit testing.
|
|
89370
89511
|
*/
|
|
89371
89512
|
function assetPathDirs(stack) {
|
|
89372
89513
|
const manifestDir = stack.assetManifestPath ? dirname(stack.assetManifestPath) : stack.assetOutdir ?? process.cwd();
|
|
@@ -90870,7 +91011,7 @@ async function runDetached(opts) {
|
|
|
90870
91011
|
args.push(opts.image, ...entryPointTail, ...opts.cmd);
|
|
90871
91012
|
const logger = getLogger().child("docker");
|
|
90872
91013
|
logger.debug(`${getDockerCmd()} ${redactDockerArgvValues(args).join(" ")}`);
|
|
90873
|
-
if (collisions.length > 0) logger.warn(`Env var(s) ${collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a
|
|
91014
|
+
if (collisions.length > 0) logger.warn(`Env var(s) ${collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a variable the container client reads (docker, or the CDK_DOCKER binary) or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack that client; a malformed name cannot form a valid environment variable). Rename the env var if the container needs it.`);
|
|
90874
91015
|
try {
|
|
90875
91016
|
const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
|
|
90876
91017
|
maxBuffer: 10485760,
|
|
@@ -91002,13 +91143,25 @@ const SENSITIVE_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
|
91002
91143
|
* wrapper translates cdk-local's thrown error back to cdkd's class at the shim
|
|
91003
91144
|
* boundary, so a local-invoke build failure still surfaces with cdkd's exit code
|
|
91004
91145
|
* / branding. `architectureToPlatform` is a pure helper (re-exported directly).
|
|
91146
|
+
*
|
|
91147
|
+
* It is also where the build context is CONTAINED: cdk-local joins
|
|
91148
|
+
* `source.directory` onto `cdkOutDir` with no check, so the shim refuses an
|
|
91149
|
+
* escaping value before delegating, against a REQUIRED `assetOutdir` bound
|
|
91150
|
+
* (issue [#3503](https://github.com/go-to-k/cdkd/issues/3503)).
|
|
91005
91151
|
* See cdk-local's `src/local/docker-image-builder.ts`.
|
|
91006
91152
|
*/
|
|
91007
|
-
async function buildContainerImage$1(
|
|
91008
|
-
const executable =
|
|
91153
|
+
async function buildContainerImage$1(asset, cdkOutDir, options) {
|
|
91154
|
+
const executable = asset?.source?.executable;
|
|
91009
91155
|
if (executable && executable.length > 0) warnManifestExecutable(executable);
|
|
91156
|
+
const { assetOutdir, ...implOptions } = options;
|
|
91157
|
+
assertCdkLocalDockerContextContained({
|
|
91158
|
+
manifestDir: cdkOutDir,
|
|
91159
|
+
source: asset.source,
|
|
91160
|
+
assetOutdir,
|
|
91161
|
+
wrapError: (message) => new LocalInvokeBuildError$1(`Refusing to build the container image: ${message}`)
|
|
91162
|
+
});
|
|
91010
91163
|
try {
|
|
91011
|
-
return await buildContainerImage(
|
|
91164
|
+
return await buildContainerImage(asset, cdkOutDir, implOptions);
|
|
91012
91165
|
} catch (e) {
|
|
91013
91166
|
if (e instanceof LocalInvokeBuildError) throw new LocalInvokeBuildError$1(e.message);
|
|
91014
91167
|
throw e;
|
|
@@ -93240,7 +93393,10 @@ async function buildContainerSpec(args) {
|
|
|
93240
93393
|
async function resolveContainerImageForStartApi(lambda, skipPull) {
|
|
93241
93394
|
const logger = getLogger();
|
|
93242
93395
|
const localBuild = await resolveLocalBuildPlan$1(lambda);
|
|
93243
|
-
if (localBuild) return { imageRef: await buildContainerImage$1(localBuild.asset, localBuild.cdkOutDir, {
|
|
93396
|
+
if (localBuild) return { imageRef: await buildContainerImage$1(localBuild.asset, localBuild.cdkOutDir, {
|
|
93397
|
+
assetOutdir: localBuild.assetOutdir,
|
|
93398
|
+
architecture: lambda.architecture
|
|
93399
|
+
}) };
|
|
93244
93400
|
if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI cdkd can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
|
|
93245
93401
|
logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull (uses your own credentials — this command has no --ecr-role-arn, so a cross-account image needs cross-account ECR permissions)...`);
|
|
93246
93402
|
return { imageRef: await pullEcrImage(lambda.imageUri, { skipPull }) };
|
|
@@ -93254,16 +93410,16 @@ async function resolveContainerImageForStartApi(lambda, skipPull) {
|
|
|
93254
93410
|
* the two commands evolve their asset-lookup heuristics independently.
|
|
93255
93411
|
*/
|
|
93256
93412
|
async function resolveLocalBuildPlan$1(lambda) {
|
|
93257
|
-
|
|
93258
|
-
|
|
93259
|
-
const cdkOutDir = path.dirname(manifestPath);
|
|
93413
|
+
if (!lambda.stack.assetManifestPath) return void 0;
|
|
93414
|
+
const { manifestDir: cdkOutDir, assetOutdir } = assetPathDirs(lambda.stack);
|
|
93260
93415
|
const manifest = await new AssetManifestLoader().loadManifest(cdkOutDir, lambda.stack.stackName);
|
|
93261
93416
|
if (!manifest) return void 0;
|
|
93262
93417
|
const entry = getDockerImageBySourceHash(manifest, lambda.imageUri);
|
|
93263
93418
|
if (!entry) return void 0;
|
|
93264
93419
|
return {
|
|
93265
93420
|
asset: entry.asset,
|
|
93266
|
-
cdkOutDir
|
|
93421
|
+
cdkOutDir,
|
|
93422
|
+
assetOutdir
|
|
93267
93423
|
};
|
|
93268
93424
|
}
|
|
93269
93425
|
/**
|
|
@@ -94123,7 +94279,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
94123
94279
|
if (cluster) sidecarEnv["CLUSTER"] = cluster;
|
|
94124
94280
|
const { flags: sidecarFlags, sensitiveEnv: sidecarSensitiveEnv, collisions: sidecarCollisions } = partitionSensitiveEnv(sidecarEnv, SENSITIVE_ENV_KEYS);
|
|
94125
94281
|
sidecarArgs.push(...sidecarFlags);
|
|
94126
|
-
if (sidecarCollisions.length > 0) logger.warn(`Sidecar env var(s) ${sidecarCollisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a
|
|
94282
|
+
if (sidecarCollisions.length > 0) logger.warn(`Sidecar env var(s) ${sidecarCollisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a variable the container client reads (docker, or the CDK_DOCKER binary) and were NOT passed to the sidecar at all (they would hijack that client).`);
|
|
94127
94283
|
sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
|
|
94128
94284
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
94129
94285
|
try {
|
|
@@ -94489,7 +94645,7 @@ async function runEcsTask(task, options, state) {
|
|
|
94489
94645
|
});
|
|
94490
94646
|
dockerCmds.set(container.name, built.args);
|
|
94491
94647
|
dockerEnvs.set(container.name, built.sensitiveEnv);
|
|
94492
|
-
if (built.collisions.length > 0) logger.warn(`Container '${container.name}': secret(s) ${built.collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a
|
|
94648
|
+
if (built.collisions.length > 0) logger.warn(`Container '${container.name}': secret(s) ${built.collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a variable the container client reads (docker, or the CDK_DOCKER binary) or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack that client; a malformed name cannot form a valid environment variable). Rename the secret if the container needs it.`);
|
|
94493
94649
|
}
|
|
94494
94650
|
const startedByName = /* @__PURE__ */ new Map();
|
|
94495
94651
|
for (const containerName of startOrder) {
|
|
@@ -95388,6 +95544,130 @@ function createLocalRunTaskCommand() {
|
|
|
95388
95544
|
return cmd;
|
|
95389
95545
|
}
|
|
95390
95546
|
|
|
95547
|
+
//#endregion
|
|
95548
|
+
//#region src/cli/commands/emulator-docker-context.ts
|
|
95549
|
+
/**
|
|
95550
|
+
* Decorate an `EmulatorStrategy` so every Docker image asset the bundled
|
|
95551
|
+
* engine could build for this run is containment-checked BEFORE it builds one
|
|
95552
|
+
* (issue [#3503](https://github.com/go-to-k/cdkd/issues/3503)).
|
|
95553
|
+
*
|
|
95554
|
+
* `cdkd local start-service` / `start-alb` hand the whole run to cdk-local's
|
|
95555
|
+
* `runEcsServiceEmulator`, which reads each stack's asset manifest itself and
|
|
95556
|
+
* joins `source.directory` onto the manifest directory raw — for an ECS
|
|
95557
|
+
* container image and for an ALB Lambda target's container image alike. No
|
|
95558
|
+
* argument reaches that join, so cdkd judges the manifests here instead.
|
|
95559
|
+
*
|
|
95560
|
+
* `resolveBoots` is the seam because the engine calls it after every synth —
|
|
95561
|
+
* at boot, and again on every `--watch` reload — and before any image is
|
|
95562
|
+
* built on either path. A refusal at boot fails the command; on a reload the
|
|
95563
|
+
* engine logs it and keeps the previous replicas serving, which is the
|
|
95564
|
+
* fail-closed outcome.
|
|
95565
|
+
*
|
|
95566
|
+
* Every stack with a manifest is checked, not only the booted targets' stacks:
|
|
95567
|
+
* which stack a target resolves to is decided inside the engine, and the ALB
|
|
95568
|
+
* front door builds Lambda images from stacks no ECS boot names. An escaping
|
|
95569
|
+
* `source.directory` is never something a real `cdk synth` writes, so
|
|
95570
|
+
* refusing one anywhere in the assembly costs a legitimate run nothing, and
|
|
95571
|
+
* `cdkd deploy` refuses the same assembly.
|
|
95572
|
+
*/
|
|
95573
|
+
function containEmulatorDockerContexts(strategy) {
|
|
95574
|
+
return {
|
|
95575
|
+
...strategy,
|
|
95576
|
+
resolveBoots: (stacks, chosenTargets) => {
|
|
95577
|
+
assertEmulatorDockerContextsContained(stacks);
|
|
95578
|
+
return strategy.resolveBoots(stacks, chosenTargets);
|
|
95579
|
+
}
|
|
95580
|
+
};
|
|
95581
|
+
}
|
|
95582
|
+
/**
|
|
95583
|
+
* The check itself, over the manifests the engine will read. Exported for
|
|
95584
|
+
* unit testing.
|
|
95585
|
+
*
|
|
95586
|
+
* It reads the SAME file the engine does — `<manifest dir>/<stackName>.assets.json`,
|
|
95587
|
+
* not `assetManifestPath` verbatim — and refuses a `stackName` that would
|
|
95588
|
+
* carry that filename out of the manifest directory, as cdkd's own
|
|
95589
|
+
* `AssetManifestLoader.loadManifest` does.
|
|
95590
|
+
*
|
|
95591
|
+
* A manifest that is absent, unreadable or not JSON is skipped: the engine
|
|
95592
|
+
* reads it again and reports that failure itself, before building anything.
|
|
95593
|
+
*/
|
|
95594
|
+
function assertEmulatorDockerContextsContained(stacks) {
|
|
95595
|
+
for (const stack of stacks) {
|
|
95596
|
+
if (!stack.assetManifestPath) continue;
|
|
95597
|
+
const dirs = assetPathDirs(stack);
|
|
95598
|
+
const { manifestDir } = dirs;
|
|
95599
|
+
const assetOutdir = stack.assetOutdir === void 0 || stack.assetOutdir === "" ? dirs.assetOutdir : engineAssemblyRoot(stack.assetOutdir);
|
|
95600
|
+
const manifestFile = resolveAssemblyPath(manifestDir, `${stack.stackName}.assets.json`);
|
|
95601
|
+
if (!manifestFile.contained) throw new LocalInvokeBuildError$1(`Refusing to build container images: the asset manifest for stack '${displaySafe(stack.stackName)}' ${renderAssemblyPathEscape(manifestFile, manifestDir)}`);
|
|
95602
|
+
const dockerImages = readDockerImages(manifestFile.path);
|
|
95603
|
+
for (const [assetId, asset] of Object.entries(dockerImages)) {
|
|
95604
|
+
const source = asset?.source;
|
|
95605
|
+
if (source === null || typeof source !== "object") continue;
|
|
95606
|
+
const { directory } = source;
|
|
95607
|
+
if (typeof directory !== "string") continue;
|
|
95608
|
+
assertCdkLocalDockerContextContained({
|
|
95609
|
+
manifestDir,
|
|
95610
|
+
source,
|
|
95611
|
+
assetOutdir,
|
|
95612
|
+
wrapError: (message) => new LocalInvokeBuildError$1(`Refusing to build container image asset '${displaySafe(assetId)}' of stack '${displaySafe(stack.stackName)}': ${message}`)
|
|
95613
|
+
});
|
|
95614
|
+
}
|
|
95615
|
+
}
|
|
95616
|
+
}
|
|
95617
|
+
/**
|
|
95618
|
+
* The containment bound the ENGINE itself uses for a stack it synthesized: its
|
|
95619
|
+
* `assetOutdir`, climbed to the parent assembly when `--app` names a
|
|
95620
|
+
* `cdk.Stage` sub-assembly. cdk-local enumerates no Stage stacks from an app
|
|
95621
|
+
* root, so `--app cdk.out/assembly-<Stage>` is the only way these commands run
|
|
95622
|
+
* a Stage, and its assets are staged one level up as `../asset.<hash>`.
|
|
95623
|
+
*
|
|
95624
|
+
* A copy of cdk-local's `assemblyRootOf` (not exported by the package): climb
|
|
95625
|
+
* only while the directory is named `assembly-*` AND its parent's
|
|
95626
|
+
* `manifest.json` declares it as a `cdk:cloud-assembly` artifact, and WARN
|
|
95627
|
+
* whenever a climb happens, since the predicate is read from the tree being
|
|
95628
|
+
* judged. Replace with the upstream export once one exists (go-to-k/cdkd#3597).
|
|
95629
|
+
*/
|
|
95630
|
+
function engineAssemblyRoot(outdir) {
|
|
95631
|
+
let dir = resolve(outdir);
|
|
95632
|
+
let climbed = false;
|
|
95633
|
+
for (;;) {
|
|
95634
|
+
if (!basename(dir).startsWith("assembly-")) break;
|
|
95635
|
+
const parent = dirname(dir);
|
|
95636
|
+
if (parent === dir || !parentDeclaresNestedAssembly(parent, basename(dir))) break;
|
|
95637
|
+
dir = parent;
|
|
95638
|
+
climbed = true;
|
|
95639
|
+
}
|
|
95640
|
+
if (!climbed) return outdir;
|
|
95641
|
+
if (!warnedDerivedRoots.has(dir)) {
|
|
95642
|
+
warnedDerivedRoots.add(dir);
|
|
95643
|
+
getLogger().warn(`'${displaySafe(outdir)}' is a cdk.Stage sub-assembly, so cdkd is treating its parent '${displaySafe(dir)}' as the assembly root when it checks Docker build contexts — that is where cdk synth stages a Stage's assets. Everything under that parent is now inside the containment bound, including siblings of the directory you named.`);
|
|
95644
|
+
}
|
|
95645
|
+
return dir;
|
|
95646
|
+
}
|
|
95647
|
+
const warnedDerivedRoots = /* @__PURE__ */ new Set();
|
|
95648
|
+
function parentDeclaresNestedAssembly(parent, child) {
|
|
95649
|
+
try {
|
|
95650
|
+
const artifacts = JSON.parse(readFileSync(join(parent, "manifest.json"), "utf-8")).artifacts;
|
|
95651
|
+
if (artifacts === null || typeof artifacts !== "object") return false;
|
|
95652
|
+
return Object.values(artifacts).some((a) => {
|
|
95653
|
+
const art = a;
|
|
95654
|
+
return art?.type === "cdk:cloud-assembly" && art.properties?.directoryName === child;
|
|
95655
|
+
});
|
|
95656
|
+
} catch {
|
|
95657
|
+
return false;
|
|
95658
|
+
}
|
|
95659
|
+
}
|
|
95660
|
+
function readDockerImages(manifestPath) {
|
|
95661
|
+
let parsed;
|
|
95662
|
+
try {
|
|
95663
|
+
parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
95664
|
+
} catch {
|
|
95665
|
+
return {};
|
|
95666
|
+
}
|
|
95667
|
+
const dockerImages = parsed?.dockerImages;
|
|
95668
|
+
return dockerImages !== null && typeof dockerImages === "object" ? dockerImages : {};
|
|
95669
|
+
}
|
|
95670
|
+
|
|
95391
95671
|
//#endregion
|
|
95392
95672
|
//#region src/cli/commands/local-start-service.ts
|
|
95393
95673
|
/**
|
|
@@ -95415,6 +95695,17 @@ function serviceStrategy() {
|
|
|
95415
95695
|
};
|
|
95416
95696
|
}
|
|
95417
95697
|
/**
|
|
95698
|
+
* The `EmulatorStrategy` `cdkd local start-service` runs with:
|
|
95699
|
+
* {@link serviceStrategy}, decorated by `containEmulatorDockerContexts` so the
|
|
95700
|
+
* engine's own Docker builds are containment-checked (issue
|
|
95701
|
+
* [#3503](https://github.com/go-to-k/cdkd/issues/3503)). A NAMED seam for the
|
|
95702
|
+
* same reason `buildAlbEmulatorStrategy` is one: inline in the action, the
|
|
95703
|
+
* decorator was deletable at its only call site.
|
|
95704
|
+
*/
|
|
95705
|
+
function buildServiceEmulatorStrategy() {
|
|
95706
|
+
return containEmulatorDockerContexts(serviceStrategy());
|
|
95707
|
+
}
|
|
95708
|
+
/**
|
|
95418
95709
|
* `cdkl start-service <Stack/Service>...` — run one or more `AWS::ECS::Service`
|
|
95419
95710
|
* resources locally as a long-running emulator. Spins up DesiredCount task
|
|
95420
95711
|
* replicas per service (clamped by --max-tasks) using the same per-task
|
|
@@ -95427,7 +95718,7 @@ function serviceStrategy() {
|
|
|
95427
95718
|
*/
|
|
95428
95719
|
function createLocalStartServiceCommand() {
|
|
95429
95720
|
const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkd local run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay (Issue #460). Omit <targets> in an interactive terminal to multi-select the ECS services from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run (omit to multi-select interactively in a TTY)").addOption(new Option("--from-state", "Read cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--state-bucket <bucket>", "S3 bucket for --from-state. Falls back to CDKD_STATE_BUCKET env or cdk.json context.cdkd.stateBucket.")).addOption(new Option("--state-prefix <prefix>", "S3 key prefix for --from-state state files.").default("cdkd")).action(withErrorHandling(async (targets, options) => {
|
|
95430
|
-
await runEcsServiceEmulator(targets, options,
|
|
95721
|
+
await runEcsServiceEmulator(targets, options, buildServiceEmulatorStrategy(), cdkdExtraStateProviders);
|
|
95431
95722
|
}));
|
|
95432
95723
|
addStartServiceSpecificOptions(cmd);
|
|
95433
95724
|
return adoptDeprecatedRegionFlag(addCommonEcsServiceOptions(cmd));
|
|
@@ -96323,6 +96614,7 @@ async function resolveAgentCoreImage(resolved, options, assemblyDir, loaded) {
|
|
|
96323
96614
|
if (manifest) {
|
|
96324
96615
|
const entry = getDockerImageBySourceHash(manifest, containerUri);
|
|
96325
96616
|
if (entry) return buildContainerImage$1(entry.asset, cdkOutDir, {
|
|
96617
|
+
assetOutdir: assemblyDir,
|
|
96326
96618
|
architecture,
|
|
96327
96619
|
noBuild: options.build === false
|
|
96328
96620
|
});
|
|
@@ -97115,7 +97407,8 @@ function warnUnresolvedLambdaTargetEnv(strategy, options) {
|
|
|
97115
97407
|
}
|
|
97116
97408
|
/**
|
|
97117
97409
|
* The `EmulatorStrategy` `cdkd local start-alb` runs with: cdk-local's ALB
|
|
97118
|
-
* strategy, decorated by {@link warnUnresolvedLambdaTargetEnv}
|
|
97410
|
+
* strategy, decorated by {@link warnUnresolvedLambdaTargetEnv} and then by
|
|
97411
|
+
* `containEmulatorDockerContexts` (issue go-to-k/cdkd#3503).
|
|
97119
97412
|
*
|
|
97120
97413
|
* Extracted from the command's action for ONE reason, and it is the finding
|
|
97121
97414
|
* that produced it: with the composition written inline in the `.action(...)`
|
|
@@ -97135,7 +97428,7 @@ function warnUnresolvedLambdaTargetEnv(strategy, options) {
|
|
|
97135
97428
|
* can satisfy reads as coverage while providing none.
|
|
97136
97429
|
*/
|
|
97137
97430
|
function buildAlbEmulatorStrategy(options) {
|
|
97138
|
-
return warnUnresolvedLambdaTargetEnv(albStrategy(options), options);
|
|
97431
|
+
return containEmulatorDockerContexts(warnUnresolvedLambdaTargetEnv(albStrategy(options), options));
|
|
97139
97432
|
}
|
|
97140
97433
|
/**
|
|
97141
97434
|
* `cdkl start-alb <Stack/Alb>` — Issue #86 v1. Names an
|
|
@@ -97693,6 +97986,7 @@ async function resolveContainerImagePlan(lambda, options) {
|
|
|
97693
97986
|
const localBuild = await resolveLocalBuildPlan(lambda);
|
|
97694
97987
|
let imageRef;
|
|
97695
97988
|
if (localBuild) imageRef = await buildContainerImage$1(localBuild.asset, localBuild.cdkOutDir, {
|
|
97989
|
+
assetOutdir: localBuild.assetOutdir,
|
|
97696
97990
|
architecture: lambda.architecture,
|
|
97697
97991
|
noBuild: options.build === false
|
|
97698
97992
|
});
|
|
@@ -97724,16 +98018,16 @@ async function resolveContainerImagePlan(lambda, options) {
|
|
|
97724
98018
|
* apply either) — the caller falls back to the ECR-pull path.
|
|
97725
98019
|
*/
|
|
97726
98020
|
async function resolveLocalBuildPlan(lambda) {
|
|
97727
|
-
|
|
97728
|
-
|
|
97729
|
-
const cdkOutDir = dirname(manifestPath);
|
|
98021
|
+
if (!lambda.stack.assetManifestPath) return void 0;
|
|
98022
|
+
const { manifestDir: cdkOutDir, assetOutdir } = assetPathDirs(lambda.stack);
|
|
97730
98023
|
const manifest = await new AssetManifestLoader().loadManifest(cdkOutDir, lambda.stack.stackName);
|
|
97731
98024
|
if (!manifest) return void 0;
|
|
97732
98025
|
const entry = getDockerImageBySourceHash(manifest, lambda.imageUri);
|
|
97733
98026
|
if (!entry) return void 0;
|
|
97734
98027
|
return {
|
|
97735
98028
|
asset: entry.asset,
|
|
97736
|
-
cdkOutDir
|
|
98029
|
+
cdkOutDir,
|
|
98030
|
+
assetOutdir
|
|
97737
98031
|
};
|
|
97738
98032
|
}
|
|
97739
98033
|
/**
|
|
@@ -98272,4 +98566,4 @@ function buildProgram() {
|
|
|
98272
98566
|
|
|
98273
98567
|
//#endregion
|
|
98274
98568
|
export { buildProgram };
|
|
98275
|
-
//# sourceMappingURL=program-
|
|
98569
|
+
//# sourceMappingURL=program-BfCsjuyU.js.map
|