@go-to-k/cdkd 0.231.12 → 0.233.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/dist/cli.js +333 -123
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-BkPZar1R.js → deploy-engine-Dj9mXUQa.js} +743 -7
- package/dist/deploy-engine-Dj9mXUQa.js.map +1 -0
- package/dist/index.d.ts +65 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BkPZar1R.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { A as withErrorHandling, B as generateResourceName, C as StackHasActiveImportsError, F as getLiveRenderer, H as withSkipPrefix, I as PATTERN_B_NAME_OPTIONS, L as PATTERN_B_NAME_PROPERTIES, M as getLogger, P as runStackBuffered, R as PATTERN_B_RESOURCE_TYPES, S as ResourceUpdateNotSupportedError, U as withStackName, V as generateResourceNameWithFallback, _ as MissingCdkCliError, a as normalizeAwsTagsToCfn, b as ProvisioningError, f as LocalInvokeBuildError$1, i as matchesCdkPath, k as normalizeAwsError, l as CdkdError, m as LocalStartServiceError, n as isTerminationProtectionPropagationError, o as resolveExplicitPhysicalId, p as LocalMigrateError, r as CDK_PATH_TAG, s as assertRegionMatch, t as disableInstanceApiTermination, v as NestedStackChildDirectDestroyError, w as StackTerminationProtectionError, x as ResourceTimeoutError, y as PartialFailureError } from "./ec2-termination-protection-uvVnteGl.js";
|
|
3
3
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-pjPwZz1r.js";
|
|
4
|
-
import { $ as
|
|
4
|
+
import { $ as AssetManifestLoader, A as DiffCalculator, B as buildAssetRedirectMap, C as findActionableSilentDrops, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as BOOTSTRAP_MARKER_PREFIX, H as loadPublishableAssetManifest, I as shouldRetainResource, J as buildDockerImage, K as ensureAssetStorage, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as runDockerStreaming, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as rewriteTemplateAssetReferences, V as createAssetRedirectResolver, W as AssetModeResolver, X as getDockerCmd, Y as formatDockerLoginError, Z as runDockerForeground, _ as green, _t as AssemblyReader, a as withRetry, at as resolveApp, b as IAMRoleProvider, c as computeImplicitDeleteEdges, ct as resolveStateBucketWithDefault, d as isStatefulRecreateTargetSync, dt as warnDeprecatedNoPrefixCliFlag, et as getDockerImageBySourceHash, f as renderStatefulReason, ft as CFN_TEMPLATE_BODY_LIMIT, g as gray, gt as uploadCfnTemplate, h as cyan, ht as findLargeInlineResources, i as withResourceDeadline, it as getLegacyStateBucketName, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, lt as resolveStateBucketWithDefaultAndSource, m as bold, mt as MIGRATE_TMP_PREFIX, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as synthesisStatusMessage, o as isRetryableTransientError, ot as resolveCaptureObservedState, p as formatResourceLine, pt as CFN_TEMPLATE_URL_LIMIT, q as parseBootstrapMarker, r as DeployEngine, rt as getDefaultStateBucketName, s as IMPLICIT_DELETE_DEPENDENCIES, st as resolveSkipPrefix, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as Synthesizer, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveUseCdkBootstrapAssets, v as red, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, y as yellow, yt as resolveBucketRegion, z as WorkGraph } from "./deploy-engine-Dj9mXUQa.js";
|
|
5
5
|
import { t as ASGProvider } from "./asg-provider-Dd9Xze-T.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -492,6 +492,14 @@ const recreateViaSdkProviderOption = new Option("--recreate-via-sdk-provider <lo
|
|
|
492
492
|
*/
|
|
493
493
|
const forceStatefulRecreationOption = new Option("--force-stateful-recreation", "Bypass the stateful-resource guard for --recreate-via-cc-api targets. Required when ANY named target is a stateful type (RDS / DynamoDB / EFS / S3 with data / Logs with retention / Cognito / Secrets / SSM / Glue / ECR / CloudFront / Kinesis / OpenSearch). Destroy + recreate loses ALL data in the resource — no automatic data migration. Triple-opt-in for CI use: --recreate-via-cc-api <id> --force-stateful-recreation --yes.").default(false);
|
|
494
494
|
const replaceOption = new Option("--replace", "Replace (DELETE + CREATE) a resource whose in-place update is rejected because an immutable property changed and AWS exposes no update API for it (e.g. Lambda LayerVersion content, EFS AccessPoint, ECS TaskDefinition, ApiGatewayV2 immutable fields). Without this flag such a change fails the deploy. Stateful types (RDS / DynamoDB / EFS / S3 with data / Logs with retention / etc.) ALSO require --force-stateful-recreation since the replacement is a data-losing DELETE + CREATE.").default(false);
|
|
495
|
+
/**
|
|
496
|
+
* Issue #1002 PR 2 — per-invocation legacy pin for asset destinations
|
|
497
|
+
* (design §4.2). Shared by `deploy`, `diff`, `import`, and `publish-assets`
|
|
498
|
+
* (every command the redirection / rewrite affects) so the CLI surface stays
|
|
499
|
+
* consistent; the per-app pin is `cdk.json context.cdkd.useCdkBootstrapAssets`
|
|
500
|
+
* (see `resolveUseCdkBootstrapAssets` in config-loader.ts).
|
|
501
|
+
*/
|
|
502
|
+
const useCdkBootstrapAssetsOption = new Option("--use-cdk-bootstrap-assets", "Publish assets to the CDK bootstrap bucket/repo named by the asset manifest verbatim, skipping the cdkd asset-storage redirection even when this region is opted in via 'cdkd bootstrap'. For apps deployed via both CloudFormation and cdkd during a migration window. Per-app pin: cdk.json context.cdkd.useCdkBootstrapAssets. Also suppresses the legacy-mode 'cdk gc' notice.").default(false);
|
|
495
503
|
const deployOptions = [
|
|
496
504
|
new Option("--concurrency <number>", "Maximum concurrent resource operations").default(10).argParser((value) => parseInt(value, 10)),
|
|
497
505
|
new Option("--stack-concurrency <number>", "Maximum concurrent stack deployments").default(4).argParser((value) => parseInt(value, 10)),
|
|
@@ -511,6 +519,7 @@ const deployOptions = [
|
|
|
511
519
|
recreateViaSdkProviderOption,
|
|
512
520
|
forceStatefulRecreationOption,
|
|
513
521
|
replaceOption,
|
|
522
|
+
useCdkBootstrapAssetsOption,
|
|
514
523
|
...resourceTimeoutOptions
|
|
515
524
|
];
|
|
516
525
|
/**
|
|
@@ -603,7 +612,11 @@ const destroyOptions = [
|
|
|
603
612
|
/**
|
|
604
613
|
* Bootstrap command implementation
|
|
605
614
|
*
|
|
606
|
-
* Creates S3 bucket for state management
|
|
615
|
+
* Creates the S3 bucket for state management and (unless --no-assets) the
|
|
616
|
+
* cdkd-owned asset storage for the region: asset bucket + container-asset
|
|
617
|
+
* ECR repo + the per-region bootstrap marker that flips deploys in this
|
|
618
|
+
* region from legacy (CDK bootstrap destinations) to cdkd-assets mode
|
|
619
|
+
* (issue #1002, design at docs/design/1002-cdkd-asset-storage.md).
|
|
607
620
|
*/
|
|
608
621
|
async function bootstrapCommand(options) {
|
|
609
622
|
const logger = getLogger();
|
|
@@ -646,54 +659,96 @@ async function bootstrapCommand(options) {
|
|
|
646
659
|
operation: "HeadBucket"
|
|
647
660
|
});
|
|
648
661
|
}
|
|
649
|
-
|
|
650
|
-
|
|
662
|
+
let skipStateBucketConfig = false;
|
|
663
|
+
if (bucketExists) if (!options.force) {
|
|
664
|
+
if (!options.assets) {
|
|
651
665
|
logger.warn(`Bucket ${bucketName} already exists. Use --force to reconfigure (this will not delete existing state)`);
|
|
652
666
|
return;
|
|
653
667
|
}
|
|
654
|
-
logger.info(
|
|
655
|
-
|
|
668
|
+
logger.info(`State bucket ${bucketName} already exists — skipping reconfiguration (use --force to reconfigure)`);
|
|
669
|
+
skipStateBucketConfig = true;
|
|
670
|
+
} else logger.info("--force specified, continuing with existing bucket");
|
|
671
|
+
else {
|
|
656
672
|
logger.info(`Creating S3 bucket: ${bucketName} in region ${region}`);
|
|
657
673
|
const createBucketParams = { Bucket: bucketName };
|
|
658
674
|
if (region !== "us-east-1") createBucketParams.CreateBucketConfiguration = { LocationConstraint: region };
|
|
659
675
|
await s3Client.send(new CreateBucketCommand(createBucketParams));
|
|
660
676
|
logger.info(`✓ Created S3 bucket: ${bucketName}`);
|
|
661
677
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
678
|
+
if (!skipStateBucketConfig) {
|
|
679
|
+
logger.debug("Enabling bucket versioning...");
|
|
680
|
+
await s3Client.send(new PutBucketVersioningCommand({
|
|
681
|
+
Bucket: bucketName,
|
|
682
|
+
VersioningConfiguration: { Status: "Enabled" }
|
|
683
|
+
}));
|
|
684
|
+
logger.info("✓ Enabled bucket versioning");
|
|
685
|
+
logger.debug("Enabling bucket encryption...");
|
|
686
|
+
await s3Client.send(new PutBucketEncryptionCommand({
|
|
687
|
+
Bucket: bucketName,
|
|
688
|
+
ServerSideEncryptionConfiguration: { Rules: [{
|
|
689
|
+
ApplyServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" },
|
|
690
|
+
BucketKeyEnabled: true
|
|
691
|
+
}] }
|
|
692
|
+
}));
|
|
693
|
+
logger.info("✓ Enabled bucket encryption (AES-256)");
|
|
694
|
+
logger.debug("Setting bucket policy...");
|
|
695
|
+
const bucketPolicy = {
|
|
696
|
+
Version: "2012-10-17",
|
|
697
|
+
Statement: [{
|
|
698
|
+
Sid: "DenyExternalAccess",
|
|
699
|
+
Effect: "Deny",
|
|
700
|
+
Principal: "*",
|
|
701
|
+
Action: "s3:*",
|
|
702
|
+
Resource: [`arn:aws:s3:::${bucketName}`, `arn:aws:s3:::${bucketName}/*`],
|
|
703
|
+
Condition: { StringNotEquals: { "aws:PrincipalAccount": accountId } }
|
|
704
|
+
}]
|
|
705
|
+
};
|
|
706
|
+
await s3Client.send(new PutBucketPolicyCommand({
|
|
707
|
+
Bucket: bucketName,
|
|
708
|
+
Policy: JSON.stringify(bucketPolicy)
|
|
709
|
+
}));
|
|
710
|
+
logger.info("✓ Set bucket policy (deny external access)");
|
|
711
|
+
}
|
|
712
|
+
let assetStorage;
|
|
713
|
+
if (options.assets) {
|
|
714
|
+
logger.info("\nSetting up cdkd asset storage...");
|
|
715
|
+
const ecrClient = new ECRClient({
|
|
716
|
+
region,
|
|
717
|
+
...options.profile && { profile: options.profile }
|
|
718
|
+
});
|
|
719
|
+
const markerS3Client = new S3Client({
|
|
720
|
+
region,
|
|
721
|
+
...options.profile && { profile: options.profile }
|
|
722
|
+
});
|
|
723
|
+
const stateBackend = new S3StateBackend(markerS3Client, {
|
|
724
|
+
bucket: bucketName,
|
|
725
|
+
prefix: "cdkd"
|
|
726
|
+
}, {
|
|
727
|
+
region,
|
|
728
|
+
...options.profile && { profile: options.profile }
|
|
729
|
+
});
|
|
730
|
+
try {
|
|
731
|
+
assetStorage = await ensureAssetStorage({
|
|
732
|
+
s3Client,
|
|
733
|
+
ecrClient,
|
|
734
|
+
stateBackend,
|
|
735
|
+
accountId,
|
|
736
|
+
region,
|
|
737
|
+
force: options.force
|
|
738
|
+
});
|
|
739
|
+
} finally {
|
|
740
|
+
ecrClient.destroy();
|
|
741
|
+
markerS3Client.destroy();
|
|
742
|
+
}
|
|
743
|
+
} else logger.info("\n--no-assets specified — skipping cdkd asset storage. Deploys in this region keep publishing assets to the CDK bootstrap bucket/repo.");
|
|
694
744
|
logger.info("\n✓ Bootstrap completed successfully");
|
|
695
745
|
logger.info(`\nState bucket: ${bucketName}`);
|
|
746
|
+
if (assetStorage) {
|
|
747
|
+
logger.info(`Asset bucket: ${assetStorage.assetBucket}`);
|
|
748
|
+
logger.info(`Container-asset repository: ${assetStorage.containerRepo}`);
|
|
749
|
+
}
|
|
696
750
|
logger.info(`Region: ${region}`);
|
|
751
|
+
if (assetStorage) logger.info(`\ncdkd asset storage is now ON for region ${region}: deploys in this region publish assets to the cdkd-owned bucket/repo (out of 'cdk gc' reach) instead of the CDK bootstrap storage. The first deploy of each existing stack with assets will show a one-time UPDATE re-pointing asset references — content is identical, no replacement.`);
|
|
697
752
|
logger.info("\nYou can now use cdkd deploy with:");
|
|
698
753
|
logger.info(` --state-bucket ${bucketName}`);
|
|
699
754
|
logger.info(` --region ${region}`);
|
|
@@ -705,7 +760,7 @@ async function bootstrapCommand(options) {
|
|
|
705
760
|
* Create bootstrap command
|
|
706
761
|
*/
|
|
707
762
|
function createBootstrapCommand() {
|
|
708
|
-
const cmd = new Command("bootstrap").description("Bootstrap cdkd by creating
|
|
763
|
+
const cmd = new Command("bootstrap").description("Bootstrap cdkd by creating the S3 state bucket plus cdkd-owned asset storage (asset bucket + container-asset ECR repo) for the region").option("--state-bucket <bucket>", "Name of S3 bucket to create for state storage (default: cdkd-state-{accountId})").option("--force", "Force reconfiguration of existing bucket", false).option("--no-assets", "Skip cdkd asset storage (asset bucket / ECR repo / marker) — deploys keep publishing assets to the CDK bootstrap bucket/repo").addOption(new Option("--region <region>", "AWS region in which to create the state bucket (defaults to AWS_REGION env or us-east-1)")).action(withErrorHandling(bootstrapCommand));
|
|
709
764
|
commonOptions.forEach((opt) => cmd.addOption(opt));
|
|
710
765
|
return cmd;
|
|
711
766
|
}
|
|
@@ -1445,7 +1500,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
1445
1500
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
1446
1501
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
1447
1502
|
function getCdkdVersion() {
|
|
1448
|
-
return "0.
|
|
1503
|
+
return "0.233.0";
|
|
1449
1504
|
}
|
|
1450
1505
|
/**
|
|
1451
1506
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -36769,7 +36824,7 @@ var NestedStackProvider = class {
|
|
|
36769
36824
|
this.requireDeployContext(ctx, "create");
|
|
36770
36825
|
const childTemplatePath = ctx.nestedTemplates[logicalId];
|
|
36771
36826
|
if (!childTemplatePath) throw new Error(`Nested template file not found for AWS::CloudFormation::Stack '${logicalId}' under parent '${ctx.parentStackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default).`);
|
|
36772
|
-
const childTemplate = this.readChildTemplate(childTemplatePath);
|
|
36827
|
+
const childTemplate = this.readChildTemplate(childTemplatePath, ctx.assetRedirect);
|
|
36773
36828
|
const childStackName = this.deriveChildStackName(ctx.parentStackName, logicalId);
|
|
36774
36829
|
const childRegion = ctx.parentRegion;
|
|
36775
36830
|
const childParameters = this.extractParameters(properties);
|
|
@@ -36788,7 +36843,7 @@ var NestedStackProvider = class {
|
|
|
36788
36843
|
this.requireDeployContext(ctx, "update");
|
|
36789
36844
|
const childTemplatePath = ctx.nestedTemplates[logicalId];
|
|
36790
36845
|
if (!childTemplatePath) throw new Error(`Nested template file not found for AWS::CloudFormation::Stack '${logicalId}' on update.`);
|
|
36791
|
-
const childTemplate = this.readChildTemplate(childTemplatePath);
|
|
36846
|
+
const childTemplate = this.readChildTemplate(childTemplatePath, ctx.assetRedirect);
|
|
36792
36847
|
const childStackName = this.deriveChildStackName(ctx.parentStackName, logicalId);
|
|
36793
36848
|
const childRegion = ctx.parentRegion;
|
|
36794
36849
|
const childParameters = this.extractParameters(properties);
|
|
@@ -36883,18 +36938,24 @@ var NestedStackProvider = class {
|
|
|
36883
36938
|
else throw new Error(`NestedStackProvider: child Parameter '${k}' resolved to a non-scalar value (type=${v === null ? "null" : typeof v}). Parameters must be scalars (string / number / boolean) by the time they reach the provider — an unresolved intrinsic here means IntrinsicFunctionResolver upstream did not handle the value, which is a bug. Surface the unresolved input rather than silently coercing to '[object Object]'.`);
|
|
36884
36939
|
return result;
|
|
36885
36940
|
}
|
|
36886
|
-
readChildTemplate(templatePath) {
|
|
36941
|
+
readChildTemplate(templatePath, assetRedirect) {
|
|
36887
36942
|
let raw;
|
|
36888
36943
|
try {
|
|
36889
36944
|
raw = fs.readFileSync(templatePath, "utf-8");
|
|
36890
36945
|
} catch (err) {
|
|
36891
36946
|
throw new Error(`Failed to read nested template at ${templatePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
36892
36947
|
}
|
|
36948
|
+
let template;
|
|
36893
36949
|
try {
|
|
36894
|
-
|
|
36950
|
+
template = JSON.parse(raw);
|
|
36895
36951
|
} catch (err) {
|
|
36896
36952
|
throw new Error(`Failed to parse nested template at ${templatePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
36897
36953
|
}
|
|
36954
|
+
if (assetRedirect) {
|
|
36955
|
+
const n = rewriteTemplateAssetReferences(template, assetRedirect);
|
|
36956
|
+
if (n > 0) this.logger.debug(`Rewrote ${n} asset reference(s) to cdkd asset storage in nested template ${templatePath}`);
|
|
36957
|
+
}
|
|
36958
|
+
return template;
|
|
36898
36959
|
}
|
|
36899
36960
|
indexGrandchildTemplates(childTemplate, childTemplatePath) {
|
|
36900
36961
|
const dir = path.dirname(childTemplatePath);
|
|
@@ -37644,6 +37705,11 @@ async function deployCommand(stacks, options) {
|
|
|
37644
37705
|
const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
|
|
37645
37706
|
stsClient.destroy();
|
|
37646
37707
|
const assetPublisher = new AssetPublisher();
|
|
37708
|
+
const useCdkBootstrapAssets = resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets);
|
|
37709
|
+
const assetModeResolver = new AssetModeResolver(preflightStateBackend, accountId, {
|
|
37710
|
+
...options.profile && { profile: options.profile },
|
|
37711
|
+
...useCdkBootstrapAssets && { useCdkBootstrapAssets: true }
|
|
37712
|
+
});
|
|
37647
37713
|
const stateConfig = {
|
|
37648
37714
|
bucket: stateBucket,
|
|
37649
37715
|
prefix: options.statePrefix
|
|
@@ -37657,20 +37723,36 @@ async function deployCommand(stacks, options) {
|
|
|
37657
37723
|
};
|
|
37658
37724
|
const workGraph = new WorkGraph();
|
|
37659
37725
|
const stackMap = new Map(targetStacks.map((s) => [s.stackName, s]));
|
|
37726
|
+
const stackRedirects = /* @__PURE__ */ new Map();
|
|
37660
37727
|
for (const stack of targetStacks) {
|
|
37661
37728
|
const stackNodeId = `stack:${stack.stackName}`;
|
|
37662
37729
|
const stackDeps = /* @__PURE__ */ new Set();
|
|
37663
|
-
if (
|
|
37730
|
+
if (stack.assetManifestPath) {
|
|
37664
37731
|
const assetRegion = stack.region || baseRegion;
|
|
37665
|
-
const
|
|
37666
|
-
|
|
37667
|
-
|
|
37668
|
-
|
|
37669
|
-
|
|
37670
|
-
|
|
37671
|
-
|
|
37672
|
-
|
|
37673
|
-
|
|
37732
|
+
const manifest = loadPublishableAssetManifest(stack.assetManifestPath);
|
|
37733
|
+
if (manifest) {
|
|
37734
|
+
const assetMode = await assetModeResolver.resolve(assetRegion);
|
|
37735
|
+
logger.debug(`Asset mode for region ${assetRegion}: ${assetMode.mode} (stack ${stack.stackName})`);
|
|
37736
|
+
let redirect;
|
|
37737
|
+
if (assetMode.mode === "cdkd-assets") {
|
|
37738
|
+
redirect = buildAssetRedirectMap(manifest, assetMode.marker, accountId, assetRegion);
|
|
37739
|
+
if (redirect.entries.length > 0) {
|
|
37740
|
+
stackRedirects.set(stack.stackName, redirect);
|
|
37741
|
+
const rewritten = rewriteTemplateAssetReferences(stack.template, redirect);
|
|
37742
|
+
logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage (${assetMode.marker.assetBucket} / ${assetMode.marker.containerRepo}) in template of stack ${stack.stackName}`);
|
|
37743
|
+
} else redirect = void 0;
|
|
37744
|
+
}
|
|
37745
|
+
if (!options.skipAssets) {
|
|
37746
|
+
const nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
|
|
37747
|
+
accountId,
|
|
37748
|
+
region: assetRegion,
|
|
37749
|
+
...options.profile && { profile: options.profile },
|
|
37750
|
+
nodePrefix: `${stack.stackName}:`,
|
|
37751
|
+
...redirect && { redirect }
|
|
37752
|
+
});
|
|
37753
|
+
for (const id of nodeIds) stackDeps.add(id);
|
|
37754
|
+
}
|
|
37755
|
+
}
|
|
37674
37756
|
}
|
|
37675
37757
|
for (const depName of effectiveStackDeps(stack.stackName, stack.dependencyNames)) if (stackMap.has(depName)) stackDeps.add(`stack:${depName}`);
|
|
37676
37758
|
workGraph.addNode({
|
|
@@ -37769,10 +37851,12 @@ async function deployCommand(stacks, options) {
|
|
|
37769
37851
|
})) return;
|
|
37770
37852
|
}
|
|
37771
37853
|
}
|
|
37854
|
+
const assetRedirect = stackRedirects.get(stackInfo.stackName);
|
|
37772
37855
|
const deployEngineOptions = {
|
|
37773
37856
|
concurrency: options.concurrency,
|
|
37774
37857
|
dryRun: options.dryRun,
|
|
37775
37858
|
noRollback: !options.rollback,
|
|
37859
|
+
...assetRedirect && { assetRedirect },
|
|
37776
37860
|
...eventRecorder && { eventRecorder },
|
|
37777
37861
|
...recreateViaCcApiTargets && recreateViaCcApiTargets.size > 0 && { recreateViaCcApiTargets },
|
|
37778
37862
|
...recreateViaSdkProviderTargets && recreateViaSdkProviderTargets.size > 0 && { recreateViaSdkProviderTargets },
|
|
@@ -37798,7 +37882,8 @@ async function deployCommand(stacks, options) {
|
|
|
37798
37882
|
nestedTemplates: stackInfo.nestedTemplates ?? {},
|
|
37799
37883
|
dagBuilder,
|
|
37800
37884
|
diffCalculator,
|
|
37801
|
-
options: deployEngineOptions
|
|
37885
|
+
options: deployEngineOptions,
|
|
37886
|
+
...assetRedirect && { assetRedirect }
|
|
37802
37887
|
}, () => stackDeployEngine.deploy(stackInfo.stackName, stackInfo.template));
|
|
37803
37888
|
logger.info(`\n${bold("Deployment Summary:")}`);
|
|
37804
37889
|
logger.info(` Stack: ${bold(cyan(deployResult.stackName))}`);
|
|
@@ -38836,7 +38921,7 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
|
|
|
38836
38921
|
* fail at deploy time.
|
|
38837
38922
|
*/
|
|
38838
38923
|
async function buildDiffTree(args) {
|
|
38839
|
-
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters } = args;
|
|
38924
|
+
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters, assetRedirect } = args;
|
|
38840
38925
|
const state = await loadStateOrEmpty(stackName, region, stateBackend);
|
|
38841
38926
|
const node = {
|
|
38842
38927
|
stackName,
|
|
@@ -38855,6 +38940,7 @@ async function buildDiffTree(args) {
|
|
|
38855
38940
|
if (!childTemplatePath) throw new Error(`Nested template file not found for ${NESTED_STACK_RESOURCE_TYPE} '${logicalId}' under stack '${stackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default), then re-run synth.`);
|
|
38856
38941
|
const childStackName = `${stackName}~${logicalId}`;
|
|
38857
38942
|
const childTemplate = readNestedTemplate(childTemplatePath);
|
|
38943
|
+
if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
|
|
38858
38944
|
const grandchildTemplates = indexNestedChildTemplates(childTemplate, childTemplatePath);
|
|
38859
38945
|
const childParameters = await resolveChildStackParameters(resource, template, state, region, stackName, stateBackend, parameters);
|
|
38860
38946
|
node.children.push(await buildDiffTree({
|
|
@@ -38866,7 +38952,8 @@ async function buildDiffTree(args) {
|
|
|
38866
38952
|
recursive: true,
|
|
38867
38953
|
stateBackend,
|
|
38868
38954
|
diffCalculator,
|
|
38869
|
-
parameters: childParameters
|
|
38955
|
+
parameters: childParameters,
|
|
38956
|
+
...assetRedirect && { assetRedirect }
|
|
38870
38957
|
}));
|
|
38871
38958
|
}
|
|
38872
38959
|
for (const [logicalId, resource] of Object.entries(state.resources)) {
|
|
@@ -39176,10 +39263,22 @@ async function diffCommand(stacks, options) {
|
|
|
39176
39263
|
});
|
|
39177
39264
|
const diffCalculator = new DiffCalculator();
|
|
39178
39265
|
const recursive = options.recursive ?? false;
|
|
39266
|
+
const resolveAssetRedirect = createAssetRedirectResolver({
|
|
39267
|
+
stateBackend,
|
|
39268
|
+
stsRegion: region,
|
|
39269
|
+
...options.profile && { profile: options.profile },
|
|
39270
|
+
useCdkBootstrapAssets: resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets),
|
|
39271
|
+
suppressLegacyNotice: true
|
|
39272
|
+
});
|
|
39179
39273
|
const trees = [];
|
|
39180
39274
|
for (const stackInfo of targetStacks) {
|
|
39181
39275
|
logger.info(`\nCalculating diff for stack: ${stackInfo.stackName}`);
|
|
39182
39276
|
const stackRegion = stackInfo.region || region;
|
|
39277
|
+
const assetRedirect = await resolveAssetRedirect(stackInfo.assetManifestPath, stackRegion);
|
|
39278
|
+
if (assetRedirect) {
|
|
39279
|
+
const rewritten = rewriteTemplateAssetReferences(stackInfo.template, assetRedirect);
|
|
39280
|
+
logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage in template of stack ${stackInfo.stackName}`);
|
|
39281
|
+
}
|
|
39183
39282
|
trees.push(await buildDiffTree({
|
|
39184
39283
|
stackName: stackInfo.stackName,
|
|
39185
39284
|
displayName: stackInfo.stackName,
|
|
@@ -39188,7 +39287,8 @@ async function diffCommand(stacks, options) {
|
|
|
39188
39287
|
nestedTemplates: stackInfo.nestedTemplates ?? {},
|
|
39189
39288
|
recursive,
|
|
39190
39289
|
stateBackend,
|
|
39191
|
-
diffCalculator
|
|
39290
|
+
diffCalculator,
|
|
39291
|
+
...assetRedirect && { assetRedirect }
|
|
39192
39292
|
}));
|
|
39193
39293
|
}
|
|
39194
39294
|
if (options.json) process.stdout.write(`${JSON.stringify(trees.map(diffTreeToJson), null, 2)}\n`);
|
|
@@ -39208,7 +39308,7 @@ async function diffCommand(stacks, options) {
|
|
|
39208
39308
|
* Create diff command
|
|
39209
39309
|
*/
|
|
39210
39310
|
function createDiffCommand() {
|
|
39211
|
-
const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).action(withErrorHandling(diffCommand));
|
|
39311
|
+
const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(diffCommand));
|
|
39212
39312
|
[
|
|
39213
39313
|
...commonOptions,
|
|
39214
39314
|
...appOptions,
|
|
@@ -41501,36 +41601,45 @@ async function publishAssetsCommand(stacks, options) {
|
|
|
41501
41601
|
const stsClient = new STSClient({ region: baseRegion });
|
|
41502
41602
|
const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
|
|
41503
41603
|
stsClient.destroy();
|
|
41604
|
+
const useCdkBootstrapAssets = resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets);
|
|
41605
|
+
let markerAccess;
|
|
41606
|
+
const getModeResolver = async () => {
|
|
41607
|
+
if (markerAccess !== void 0) return markerAccess?.resolver ?? null;
|
|
41608
|
+
let bucket;
|
|
41609
|
+
try {
|
|
41610
|
+
bucket = await resolveStateBucketWithDefault(options.stateBucket, baseRegion);
|
|
41611
|
+
} catch {
|
|
41612
|
+
logger.info("No cdkd state bucket found — publishing to the asset-manifest destinations (CDK bootstrap storage) verbatim. Run 'cdkd bootstrap' to opt into cdkd-owned asset storage that 'cdk gc' never touches.");
|
|
41613
|
+
markerAccess = null;
|
|
41614
|
+
return null;
|
|
41615
|
+
}
|
|
41616
|
+
const markerClients = new AwsClients({
|
|
41617
|
+
region: baseRegion,
|
|
41618
|
+
...options.profile && { profile: options.profile }
|
|
41619
|
+
});
|
|
41620
|
+
const resolver = new AssetModeResolver(new S3StateBackend(markerClients.s3, {
|
|
41621
|
+
bucket,
|
|
41622
|
+
prefix: options.statePrefix
|
|
41623
|
+
}, {
|
|
41624
|
+
region: baseRegion,
|
|
41625
|
+
...options.profile && { profile: options.profile }
|
|
41626
|
+
}), accountId, { ...options.profile && { profile: options.profile } });
|
|
41627
|
+
markerAccess = {
|
|
41628
|
+
resolver,
|
|
41629
|
+
dispose: () => markerClients.destroy()
|
|
41630
|
+
};
|
|
41631
|
+
return resolver;
|
|
41632
|
+
};
|
|
41504
41633
|
const assetPublisher = new AssetPublisher();
|
|
41505
41634
|
const results = [];
|
|
41506
|
-
|
|
41507
|
-
const
|
|
41508
|
-
|
|
41509
|
-
|
|
41510
|
-
|
|
41511
|
-
if (!stack.assetManifestPath) {
|
|
41512
|
-
logger.debug(`Stack ${stack.stackName} has no asset manifest; nothing to publish`);
|
|
41513
|
-
results.push({
|
|
41514
|
-
stackName: stack.stackName,
|
|
41515
|
-
displayName: stack.displayName,
|
|
41516
|
-
assetCount: 0,
|
|
41517
|
-
durationMs: Date.now() - startedAt
|
|
41518
|
-
});
|
|
41519
|
-
continue;
|
|
41520
|
-
}
|
|
41521
|
-
logger.info(`\nPublishing assets for stack: ${describeStack(stack)}`);
|
|
41522
|
-
const workGraph = new WorkGraph();
|
|
41523
|
-
let nodeIds = [];
|
|
41635
|
+
try {
|
|
41636
|
+
for (const stack of targetStacks) {
|
|
41637
|
+
const startedAt = Date.now();
|
|
41638
|
+
let assetCount = 0;
|
|
41639
|
+
let error;
|
|
41524
41640
|
try {
|
|
41525
|
-
|
|
41526
|
-
|
|
41527
|
-
region: stack.region || baseRegion,
|
|
41528
|
-
...options.profile && { profile: options.profile },
|
|
41529
|
-
nodePrefix: `${stack.stackName}:`
|
|
41530
|
-
});
|
|
41531
|
-
} catch (err) {
|
|
41532
|
-
if (err.code === "ENOENT") {
|
|
41533
|
-
logger.debug(`Asset manifest not found for ${stack.stackName} (${stack.assetManifestPath}); skipping`);
|
|
41641
|
+
if (!stack.assetManifestPath) {
|
|
41642
|
+
logger.debug(`Stack ${stack.stackName} has no asset manifest; nothing to publish`);
|
|
41534
41643
|
results.push({
|
|
41535
41644
|
stackName: stack.stackName,
|
|
41536
41645
|
displayName: stack.displayName,
|
|
@@ -41539,36 +41648,76 @@ async function publishAssetsCommand(stacks, options) {
|
|
|
41539
41648
|
});
|
|
41540
41649
|
continue;
|
|
41541
41650
|
}
|
|
41542
|
-
|
|
41543
|
-
|
|
41544
|
-
|
|
41545
|
-
|
|
41546
|
-
|
|
41547
|
-
|
|
41548
|
-
|
|
41549
|
-
|
|
41550
|
-
|
|
41551
|
-
|
|
41552
|
-
|
|
41553
|
-
|
|
41651
|
+
logger.info(`\nPublishing assets for stack: ${describeStack(stack)}`);
|
|
41652
|
+
let redirect;
|
|
41653
|
+
if (!useCdkBootstrapAssets) {
|
|
41654
|
+
const manifest = loadPublishableAssetManifest(stack.assetManifestPath);
|
|
41655
|
+
if (manifest) {
|
|
41656
|
+
const modeResolver = await getModeResolver();
|
|
41657
|
+
if (modeResolver) {
|
|
41658
|
+
const stackRegion = stack.region || baseRegion;
|
|
41659
|
+
const assetMode = await modeResolver.resolve(stackRegion);
|
|
41660
|
+
if (assetMode.mode === "cdkd-assets") {
|
|
41661
|
+
const map = buildAssetRedirectMap(manifest, assetMode.marker, accountId, stackRegion);
|
|
41662
|
+
if (map.entries.length > 0) redirect = map;
|
|
41663
|
+
}
|
|
41664
|
+
}
|
|
41665
|
+
}
|
|
41666
|
+
}
|
|
41667
|
+
const workGraph = new WorkGraph();
|
|
41668
|
+
let nodeIds = [];
|
|
41669
|
+
try {
|
|
41670
|
+
nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
|
|
41671
|
+
accountId,
|
|
41672
|
+
region: stack.region || baseRegion,
|
|
41673
|
+
...options.profile && { profile: options.profile },
|
|
41674
|
+
nodePrefix: `${stack.stackName}:`,
|
|
41675
|
+
...redirect && { redirect }
|
|
41676
|
+
});
|
|
41677
|
+
} catch (err) {
|
|
41678
|
+
if (err.code === "ENOENT") {
|
|
41679
|
+
logger.debug(`Asset manifest not found for ${stack.stackName} (${stack.assetManifestPath}); skipping`);
|
|
41680
|
+
results.push({
|
|
41681
|
+
stackName: stack.stackName,
|
|
41682
|
+
displayName: stack.displayName,
|
|
41683
|
+
assetCount: 0,
|
|
41684
|
+
durationMs: Date.now() - startedAt
|
|
41685
|
+
});
|
|
41686
|
+
continue;
|
|
41687
|
+
}
|
|
41688
|
+
throw err;
|
|
41689
|
+
}
|
|
41690
|
+
assetCount = nodeIds.filter((id) => id.startsWith("asset-publish:")).length;
|
|
41691
|
+
if (assetCount === 0) {
|
|
41692
|
+
logger.info(" (no assets to publish)");
|
|
41693
|
+
results.push({
|
|
41694
|
+
stackName: stack.stackName,
|
|
41695
|
+
displayName: stack.displayName,
|
|
41696
|
+
assetCount: 0,
|
|
41697
|
+
durationMs: Date.now() - startedAt
|
|
41698
|
+
});
|
|
41699
|
+
continue;
|
|
41700
|
+
}
|
|
41701
|
+
await workGraph.execute({
|
|
41702
|
+
"asset-build": options.imageBuildConcurrency,
|
|
41703
|
+
"asset-publish": options.assetPublishConcurrency,
|
|
41704
|
+
stack: 0
|
|
41705
|
+
}, (node) => assetPublisher.executeNode(node));
|
|
41706
|
+
logger.info(` ✓ Published ${assetCount} asset(s) in ${((Date.now() - startedAt) / 1e3).toFixed(2)}s`);
|
|
41707
|
+
} catch (err) {
|
|
41708
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
41709
|
+
logger.error(` ✗ ${stack.stackName}: ${error.message}`);
|
|
41554
41710
|
}
|
|
41555
|
-
|
|
41556
|
-
|
|
41557
|
-
|
|
41558
|
-
|
|
41559
|
-
|
|
41560
|
-
|
|
41561
|
-
|
|
41562
|
-
|
|
41563
|
-
|
|
41564
|
-
|
|
41565
|
-
results.push({
|
|
41566
|
-
stackName: stack.stackName,
|
|
41567
|
-
displayName: stack.displayName,
|
|
41568
|
-
assetCount,
|
|
41569
|
-
durationMs: Date.now() - startedAt,
|
|
41570
|
-
...error && { error }
|
|
41571
|
-
});
|
|
41711
|
+
results.push({
|
|
41712
|
+
stackName: stack.stackName,
|
|
41713
|
+
displayName: stack.displayName,
|
|
41714
|
+
assetCount,
|
|
41715
|
+
durationMs: Date.now() - startedAt,
|
|
41716
|
+
...error && { error }
|
|
41717
|
+
});
|
|
41718
|
+
}
|
|
41719
|
+
} finally {
|
|
41720
|
+
markerAccess?.dispose();
|
|
41572
41721
|
}
|
|
41573
41722
|
const failed = results.filter((r) => r.error);
|
|
41574
41723
|
const totalAssets = results.reduce((sum, r) => sum + r.assetCount, 0);
|
|
@@ -41591,11 +41740,12 @@ async function publishAssetsCommand(stacks, options) {
|
|
|
41591
41740
|
* `cdkd deploy --no-asset-publish` (which doesn't exist).
|
|
41592
41741
|
*/
|
|
41593
41742
|
function createPublishAssetsCommand() {
|
|
41594
|
-
const cmd = new Command("publish-assets").description("Synthesize the CDK app and publish assets to S3/ECR for the selected stack(s) without deploying").argument("[stacks...]", "Stack name(s) to publish assets for. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards.").option("--all", "Publish assets for all stacks", false).addOption(new Option("--asset-publish-concurrency <number>", "Maximum concurrent asset publish operations").default(8).argParser((value) => parseInt(value, 10))).addOption(new Option("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))).action(withErrorHandling(publishAssetsCommand));
|
|
41743
|
+
const cmd = new Command("publish-assets").description("Synthesize the CDK app and publish assets to S3/ECR for the selected stack(s) without deploying").argument("[stacks...]", "Stack name(s) to publish assets for. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards.").option("--all", "Publish assets for all stacks", false).addOption(new Option("--asset-publish-concurrency <number>", "Maximum concurrent asset publish operations").default(8).argParser((value) => parseInt(value, 10))).addOption(new Option("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))).addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(publishAssetsCommand));
|
|
41595
41744
|
[
|
|
41596
41745
|
...commonOptions,
|
|
41597
41746
|
...appOptions,
|
|
41598
|
-
...contextOptions
|
|
41747
|
+
...contextOptions,
|
|
41748
|
+
...stateOptions
|
|
41599
41749
|
].forEach((opt) => cmd.addOption(opt));
|
|
41600
41750
|
cmd.addOption(new Option("--stack <name>", "Stack name to publish (alternative to positional)"));
|
|
41601
41751
|
cmd.addOption(deprecatedRegionOption);
|
|
@@ -45188,6 +45338,45 @@ async function readSchemaVersion(awsClients, bucket, keys) {
|
|
|
45188
45338
|
}
|
|
45189
45339
|
}
|
|
45190
45340
|
/**
|
|
45341
|
+
* List every region's bootstrap marker under `cdkd-bootstrap/` in the state
|
|
45342
|
+
* bucket. Malformed markers are skipped with a warning — `state info` is a
|
|
45343
|
+
* cosmetic command and should not crash on an unexpected payload (deploy
|
|
45344
|
+
* hard-errors on the same marker instead).
|
|
45345
|
+
*/
|
|
45346
|
+
async function listAssetStorageMarkers(awsClients, bucket) {
|
|
45347
|
+
const logger = getLogger();
|
|
45348
|
+
const entries = [];
|
|
45349
|
+
let continuationToken;
|
|
45350
|
+
const keys = [];
|
|
45351
|
+
do {
|
|
45352
|
+
const resp = await awsClients.s3.send(new ListObjectsV2Command({
|
|
45353
|
+
Bucket: bucket,
|
|
45354
|
+
Prefix: BOOTSTRAP_MARKER_PREFIX,
|
|
45355
|
+
...continuationToken && { ContinuationToken: continuationToken }
|
|
45356
|
+
}));
|
|
45357
|
+
for (const obj of resp.Contents ?? []) if (typeof obj.Key === "string" && obj.Key.startsWith("cdkd-bootstrap/") && obj.Key.endsWith(".json")) keys.push(obj.Key);
|
|
45358
|
+
continuationToken = resp.NextContinuationToken;
|
|
45359
|
+
} while (continuationToken);
|
|
45360
|
+
for (const key of keys) {
|
|
45361
|
+
const region = key.slice(BOOTSTRAP_MARKER_PREFIX.length, -5);
|
|
45362
|
+
try {
|
|
45363
|
+
const marker = parseBootstrapMarker(await (await awsClients.s3.send(new GetObjectCommand({
|
|
45364
|
+
Bucket: bucket,
|
|
45365
|
+
Key: key
|
|
45366
|
+
}))).Body?.transformToString() ?? "", key);
|
|
45367
|
+
entries.push({
|
|
45368
|
+
region,
|
|
45369
|
+
assetBucket: marker.assetBucket,
|
|
45370
|
+
containerRepo: marker.containerRepo,
|
|
45371
|
+
createdAt: marker.createdAt
|
|
45372
|
+
});
|
|
45373
|
+
} catch (error) {
|
|
45374
|
+
logger.warn(`Skipping malformed/unreadable bootstrap marker '${key}': ${error.message}`);
|
|
45375
|
+
}
|
|
45376
|
+
}
|
|
45377
|
+
return entries.sort((a, b) => a.region.localeCompare(b.region));
|
|
45378
|
+
}
|
|
45379
|
+
/**
|
|
45191
45380
|
* `cdkd state info` command implementation.
|
|
45192
45381
|
*
|
|
45193
45382
|
* Prints the state-bucket information that used to appear as a banner on
|
|
@@ -45229,6 +45418,7 @@ async function stateInfoCommand(options) {
|
|
|
45229
45418
|
const detectedRegion = await detectBucketRegion(awsClients, bucket);
|
|
45230
45419
|
const stateFileKeys = await listStateFileKeys(awsClients, bucket, prefix);
|
|
45231
45420
|
const schemaVersion = await readSchemaVersion(awsClients, bucket, stateFileKeys);
|
|
45421
|
+
const assetStorage = await listAssetStorageMarkers(awsClients, bucket);
|
|
45232
45422
|
if (options.json) {
|
|
45233
45423
|
const json = {
|
|
45234
45424
|
bucket,
|
|
@@ -45236,7 +45426,8 @@ async function stateInfoCommand(options) {
|
|
|
45236
45426
|
regionSource: detectedRegion ? "auto-detected" : "unknown",
|
|
45237
45427
|
bucketSource: resolved.source,
|
|
45238
45428
|
schemaVersion,
|
|
45239
|
-
stackCount: stateFileKeys.length
|
|
45429
|
+
stackCount: stateFileKeys.length,
|
|
45430
|
+
assetStorage
|
|
45240
45431
|
};
|
|
45241
45432
|
process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
|
|
45242
45433
|
return;
|
|
@@ -45248,6 +45439,11 @@ async function stateInfoCommand(options) {
|
|
|
45248
45439
|
lines.push(`Source: ${formatBucketSource(resolved.source)}`);
|
|
45249
45440
|
lines.push(`Schema version: ${schemaVersion}`);
|
|
45250
45441
|
lines.push(`Stacks: ${stateFileKeys.length}`);
|
|
45442
|
+
if (assetStorage.length === 0) lines.push("Asset storage: legacy (CDK bootstrap) — run cdkd bootstrap to opt in");
|
|
45443
|
+
else {
|
|
45444
|
+
lines.push(`Asset storage: cdkd-assets mode in ${assetStorage.length} region(s)`);
|
|
45445
|
+
for (const entry of assetStorage) lines.push(` ${entry.region}: ${entry.assetBucket} / ${entry.containerRepo}`);
|
|
45446
|
+
}
|
|
45251
45447
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
45252
45448
|
} finally {
|
|
45253
45449
|
awsClients.destroy();
|
|
@@ -45573,6 +45769,17 @@ async function importCommand(stackArg, options) {
|
|
|
45573
45769
|
else throw new Error(`Multiple stacks found: ${result.stacks.map((s) => s.stackName).join(", ")}. Specify the stack name as a positional argument.`);
|
|
45574
45770
|
const targetRegion = stackInfo.region || region;
|
|
45575
45771
|
logger.info(`Target stack: ${stackInfo.stackName} (${targetRegion})`);
|
|
45772
|
+
const assetRedirect = await createAssetRedirectResolver({
|
|
45773
|
+
stateBackend,
|
|
45774
|
+
stsRegion: region,
|
|
45775
|
+
...options.profile && { profile: options.profile },
|
|
45776
|
+
useCdkBootstrapAssets: resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets),
|
|
45777
|
+
suppressLegacyNotice: true
|
|
45778
|
+
})(stackInfo.assetManifestPath, targetRegion);
|
|
45779
|
+
if (assetRedirect) {
|
|
45780
|
+
const rewritten = rewriteTemplateAssetReferences(stackInfo.template, assetRedirect);
|
|
45781
|
+
logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage in template of stack ${stackInfo.stackName}`);
|
|
45782
|
+
}
|
|
45576
45783
|
const overrides = parseResourceOverrides(options.resource, options.resourceMapping, options.resourceMappingInline);
|
|
45577
45784
|
if (overrides.size > 0) logger.debug(`User-supplied physical IDs: ${[...overrides.keys()].join(", ")}`);
|
|
45578
45785
|
const migrationCfnStackName = options.migrateFromCloudformation ? typeof options.migrateFromCloudformation === "string" && options.migrateFromCloudformation.length > 0 ? options.migrateFromCloudformation : stackInfo.stackName : void 0;
|
|
@@ -45709,7 +45916,8 @@ async function importCommand(stackArg, options) {
|
|
|
45709
45916
|
templateParser,
|
|
45710
45917
|
lockOwner: owner,
|
|
45711
45918
|
accountId: accountIdForNestedSynth,
|
|
45712
|
-
logger
|
|
45919
|
+
logger,
|
|
45920
|
+
assetRedirect
|
|
45713
45921
|
});
|
|
45714
45922
|
if (migrationCfnStackName) {
|
|
45715
45923
|
const orphaned = resources.length - importedRows.length;
|
|
@@ -46124,7 +46332,7 @@ async function confirmPrompt(prompt) {
|
|
|
46124
46332
|
* default behavior, now opt-in.
|
|
46125
46333
|
*/
|
|
46126
46334
|
function createImportCommand() {
|
|
46127
|
-
const cmd = new Command("import").description("Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest.").argument("[stack]", "Stack to import. Optional when the synthesized app contains exactly one stack.").option("--resource <id=physical>", "Explicit physical-id override for one logical ID. Repeatable. When at least one --resource is given, only listed resources are imported (CDK CLI parity). Pass --auto to also tag-import everything else.", collectMultiple, []).option("--resource-mapping <file>", "Path to a JSON file of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping` compatible). Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping-inline.").option("--resource-mapping-inline <json>", "Inline JSON object of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping-inline` compatible). Same shape as --resource-mapping but supplied as a string — useful for non-TTY CI scripts that do not want a separate file. Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping.").option("--record-resource-mapping <file>", "After cdkd resolves every logical ID (via --resource / --resource-mapping / tag-based auto-lookup), write the resulting {logicalId: physicalId} map to <file> as JSON. Useful in auto / hybrid mode for capturing the tag-resolved mapping and feeding it back as --resource-mapping in non-interactive CI re-runs. Written before the confirmation prompt (so the user can review the file before saying \"yes\") and even when the user says \"no\". Mirrors `cdk import --record-resource-mapping`.").option("--auto", "Hybrid mode: when explicit overrides are supplied, ALSO tag-import every other resource in the template. Without this flag, --resource / --resource-mapping behave as a whitelist (CDK CLI parity).", false).option("--dry-run", "Show planned imports without writing state", false).option("--force", "Confirm a destructive write to existing state. Required for auto / whole-stack import when state already exists (rebuilds the entire resource map). Also required in selective mode if a listed override would overwrite a resource already in state. Not needed for a pure selective merge (adding new resources without touching unlisted entries).", false).option("--migrate-from-cloudformation [cfn-stack-name]", "After cdkd state is written, retire the named CloudFormation stack (deletes the CFn stack record; AWS resources are NOT deleted): inject DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource via UpdateStack, then DeleteStack. cdkd takes over management. Pass without a value to use the cdkd stack name as the CFn stack name (the typical case for a CDK app that was previously deployed via `cdk deploy`); pass an explicit value when the CFn stack name differs.").action(withErrorHandling(importCommand));
|
|
46335
|
+
const cmd = new Command("import").description("Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest.").argument("[stack]", "Stack to import. Optional when the synthesized app contains exactly one stack.").option("--resource <id=physical>", "Explicit physical-id override for one logical ID. Repeatable. When at least one --resource is given, only listed resources are imported (CDK CLI parity). Pass --auto to also tag-import everything else.", collectMultiple, []).option("--resource-mapping <file>", "Path to a JSON file of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping` compatible). Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping-inline.").option("--resource-mapping-inline <json>", "Inline JSON object of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping-inline` compatible). Same shape as --resource-mapping but supplied as a string — useful for non-TTY CI scripts that do not want a separate file. Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping.").option("--record-resource-mapping <file>", "After cdkd resolves every logical ID (via --resource / --resource-mapping / tag-based auto-lookup), write the resulting {logicalId: physicalId} map to <file> as JSON. Useful in auto / hybrid mode for capturing the tag-resolved mapping and feeding it back as --resource-mapping in non-interactive CI re-runs. Written before the confirmation prompt (so the user can review the file before saying \"yes\") and even when the user says \"no\". Mirrors `cdk import --record-resource-mapping`.").option("--auto", "Hybrid mode: when explicit overrides are supplied, ALSO tag-import every other resource in the template. Without this flag, --resource / --resource-mapping behave as a whitelist (CDK CLI parity).", false).option("--dry-run", "Show planned imports without writing state", false).option("--force", "Confirm a destructive write to existing state. Required for auto / whole-stack import when state already exists (rebuilds the entire resource map). Also required in selective mode if a listed override would overwrite a resource already in state. Not needed for a pure selective merge (adding new resources without touching unlisted entries).", false).option("--migrate-from-cloudformation [cfn-stack-name]", "After cdkd state is written, retire the named CloudFormation stack (deletes the CFn stack record; AWS resources are NOT deleted): inject DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource via UpdateStack, then DeleteStack. cdkd takes over management. Pass without a value to use the cdkd stack name as the CFn stack name (the typical case for a CDK app that was previously deployed via `cdk deploy`); pass an explicit value when the CFn stack name differs.").addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(importCommand));
|
|
46128
46336
|
[
|
|
46129
46337
|
...commonOptions,
|
|
46130
46338
|
...appOptions,
|
|
@@ -46262,7 +46470,7 @@ function validateNestedStackShape(template, tree, parentStackName, nestedTemplat
|
|
|
46262
46470
|
* is verified by unit test.
|
|
46263
46471
|
*/
|
|
46264
46472
|
async function importNestedStackChildrenRecursive(args) {
|
|
46265
|
-
const { parentStackName, parentRegion, parentNestedTemplates, parentTree, stateBackend, lockManager, providerRegistry, templateParser, lockOwner, accountId, logger } = args;
|
|
46473
|
+
const { parentStackName, parentRegion, parentNestedTemplates, parentTree, stateBackend, lockManager, providerRegistry, templateParser, lockOwner, accountId, logger, assetRedirect } = args;
|
|
46266
46474
|
for (const [childLogicalId, childTreeNode] of parentTree.nested) {
|
|
46267
46475
|
const childStackName = `${parentStackName}~${childLogicalId}`;
|
|
46268
46476
|
const childRegion = parentRegion;
|
|
@@ -46270,6 +46478,7 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
46270
46478
|
if (!childTemplatePath) throw new Error(`cdkd import --migrate-from-cloudformation: missing nested-template path for '${childLogicalId}' under parent '${parentStackName}' — validateNestedStackShape should have rejected this; please file a bug.`);
|
|
46271
46479
|
logger.info(`Adopting nested stack '${childLogicalId}' as cdkd stack '${childStackName}' (${childRegion})...`);
|
|
46272
46480
|
const childTemplate = readNestedChildTemplate(childTemplatePath, childLogicalId);
|
|
46481
|
+
if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
|
|
46273
46482
|
await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import");
|
|
46274
46483
|
try {
|
|
46275
46484
|
const childResources = collectImportableResources(childTemplate);
|
|
@@ -46321,7 +46530,8 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
46321
46530
|
templateParser,
|
|
46322
46531
|
lockOwner,
|
|
46323
46532
|
accountId,
|
|
46324
|
-
logger
|
|
46533
|
+
logger,
|
|
46534
|
+
assetRedirect
|
|
46325
46535
|
});
|
|
46326
46536
|
} finally {
|
|
46327
46537
|
await lockManager.releaseLock(childStackName, childRegion).catch((err) => {
|
|
@@ -55779,7 +55989,7 @@ function reorderArgs(argv) {
|
|
|
55779
55989
|
async function main() {
|
|
55780
55990
|
installPipeCloseHandler();
|
|
55781
55991
|
const program = new Command();
|
|
55782
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
55992
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.233.0");
|
|
55783
55993
|
program.addCommand(createBootstrapCommand());
|
|
55784
55994
|
program.addCommand(createSynthCommand());
|
|
55785
55995
|
program.addCommand(createListCommand());
|