@go-to-k/cdkd 0.236.0 → 0.238.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.
@@ -2996,6 +2996,33 @@ function getBootstrapMarkerKey(region) {
2996
2996
  return `${BOOTSTRAP_MARKER_PREFIX}${region}.json`;
2997
2997
  }
2998
2998
  /**
2999
+ * Pragmatic S3 bucket-name check for `cdkd bootstrap --asset-bucket`
3000
+ * (issue #1011): 3-63 chars, lowercase letters / digits / dots / hyphens,
3001
+ * starting and ending with a letter or digit. Rejecting before any AWS call
3002
+ * gives a clearer error than S3's own `InvalidBucketName`.
3003
+ */
3004
+ const ASSET_BUCKET_NAME_PATTERN = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
3005
+ /**
3006
+ * Pragmatic ECR repository-name check for `cdkd bootstrap --container-repo`
3007
+ * (issue #1011): 2-256 chars of `[a-z0-9]` runs joined by single `.` / `_` /
3008
+ * `-` / `/` separators (no leading / trailing / doubled separators).
3009
+ */
3010
+ const CONTAINER_REPO_NAME_PATTERN = /^[a-z0-9]+(?:[._\-/][a-z0-9]+)*$/;
3011
+ /**
3012
+ * Validate a custom asset bucket name (`cdkd bootstrap --asset-bucket`).
3013
+ * Throws before any AWS call so a typo never reaches S3.
3014
+ */
3015
+ function validateAssetBucketName(name) {
3016
+ if (!ASSET_BUCKET_NAME_PATTERN.test(name)) throw new CdkdError(`--asset-bucket '${name}' is not a valid S3 bucket name. Bucket names must be 3-63 characters of lowercase letters, digits, dots, and hyphens, and must start and end with a letter or digit.`, "INVALID_ASSET_STORAGE_NAME");
3017
+ }
3018
+ /**
3019
+ * Validate a custom container-asset ECR repository name
3020
+ * (`cdkd bootstrap --container-repo`). Throws before any AWS call.
3021
+ */
3022
+ function validateContainerRepoName(name) {
3023
+ if (name.length < 2 || name.length > 256 || !CONTAINER_REPO_NAME_PATTERN.test(name)) throw new CdkdError(`--container-repo '${name}' is not a valid ECR repository name. Repository names must be 2-256 characters of lowercase letters and digits, optionally separated by single '.', '_', '-', or '/' characters (no leading, trailing, or doubled separators).`, "INVALID_ASSET_STORAGE_NAME");
3024
+ }
3025
+ /**
2999
3026
  * Parse and validate a bootstrap marker body.
3000
3027
  *
3001
3028
  * Throws on malformed JSON or missing required fields — a corrupt marker is
@@ -3011,8 +3038,8 @@ function parseBootstrapMarker(body, markerKey) {
3011
3038
  throw new CdkdError(`Bootstrap marker '${markerKey}' in the state bucket is not valid JSON. Re-run 'cdkd bootstrap' for this region to rewrite it.`, "INVALID_BOOTSTRAP_MARKER", error);
3012
3039
  }
3013
3040
  const marker = parsed;
3041
+ if (typeof marker.assetSupportVersion === "number" && marker.assetSupportVersion > 1) throw new CdkdError(`Bootstrap marker '${markerKey}' has assetSupportVersion ${marker.assetSupportVersion}, but this cdkd only understands up to ${1}. Upgrade cdkd to deploy in this region.`, "UNSUPPORTED_BOOTSTRAP_MARKER_VERSION");
3014
3042
  if (typeof marker.assetBucket !== "string" || marker.assetBucket.length === 0 || typeof marker.containerRepo !== "string" || marker.containerRepo.length === 0 || typeof marker.assetSupportVersion !== "number") throw new CdkdError(`Bootstrap marker '${markerKey}' in the state bucket is malformed (missing assetBucket / containerRepo / assetSupportVersion). Re-run 'cdkd bootstrap' for this region to rewrite it.`, "INVALID_BOOTSTRAP_MARKER");
3015
- if (marker.assetSupportVersion > 1) throw new CdkdError(`Bootstrap marker '${markerKey}' has assetSupportVersion ${marker.assetSupportVersion}, but this cdkd only understands up to ${1}. Upgrade cdkd to deploy in this region.`, "UNSUPPORTED_BOOTSTRAP_MARKER_VERSION");
3016
3043
  return {
3017
3044
  assetBucket: marker.assetBucket,
3018
3045
  containerRepo: marker.containerRepo,
@@ -3076,12 +3103,38 @@ async function verifyAssetStorageExists(marker, accountId, region, opts = {}) {
3076
3103
  * Bucket-squatting defense: the bucket name is predictable (same weakness as
3077
3104
  * CDK's bootstrap bucket), so this refuses to adopt a bucket this account
3078
3105
  * does not own, and the `HeadBucket` probe passes `ExpectedBucketOwner`.
3106
+ * A CUSTOM bucket name (issue #1011) goes through the exact same probe /
3107
+ * refusal path — custom names get the identical squatting defense.
3108
+ *
3109
+ * Name resolution (issue #1011): an explicit custom name
3110
+ * ({@link EnsureAssetStorageOptions.assetBucketName} /
3111
+ * {@link EnsureAssetStorageOptions.containerRepoName}) wins; otherwise an
3112
+ * existing marker's name is reused (so a plain re-bootstrap of a region
3113
+ * bootstrapped with custom names keeps them instead of creating a second,
3114
+ * conventional set); otherwise the conventional name. A custom name that
3115
+ * DIFFERS from an existing marker's name is a hard error — re-pointing a
3116
+ * region at different storage requires `cdkd bootstrap --destroy` first.
3079
3117
  */
3080
3118
  async function ensureAssetStorage(options) {
3081
3119
  const logger = getLogger();
3082
3120
  const { s3Client, ecrClient, stateBackend, accountId, region, force } = options;
3083
- const assetBucket = getCdkdAssetBucketName(accountId, region);
3084
- const containerRepo = getCdkdContainerRepoName(accountId, region);
3121
+ const markerKey = getBootstrapMarkerKey(region);
3122
+ let existingMarker = null;
3123
+ const existingBody = await stateBackend.getRawObject(markerKey);
3124
+ if (existingBody !== null) try {
3125
+ existingMarker = parseBootstrapMarker(existingBody, markerKey);
3126
+ } catch (error) {
3127
+ if (error instanceof CdkdError && error.code === "UNSUPPORTED_BOOTSTRAP_MARKER_VERSION") throw error;
3128
+ logger.warn(`Bootstrap marker '${markerKey}' is malformed — rewriting it as part of this bootstrap.`);
3129
+ }
3130
+ if (existingMarker) {
3131
+ const conflicts = [];
3132
+ if (options.assetBucketName && options.assetBucketName !== existingMarker.assetBucket) conflicts.push(`asset bucket '${existingMarker.assetBucket}' (requested '${options.assetBucketName}')`);
3133
+ if (options.containerRepoName && options.containerRepoName !== existingMarker.containerRepo) conflicts.push(`container repo '${existingMarker.containerRepo}' (requested '${options.containerRepoName}')`);
3134
+ if (conflicts.length > 0) throw new CdkdError(`Region '${region}' is already bootstrapped with ${conflicts.join(" and ")}. Changing asset storage names would strand the existing storage and every published asset in it — run 'cdkd bootstrap --destroy --region ${region}' to tear the region's asset storage down first, then re-run bootstrap with the new names.`, "ASSET_STORAGE_NAME_CONFLICT");
3135
+ }
3136
+ const assetBucket = options.assetBucketName ?? existingMarker?.assetBucket ?? getCdkdAssetBucketName(accountId, region);
3137
+ const containerRepo = options.containerRepoName ?? existingMarker?.containerRepo ?? getCdkdContainerRepoName(accountId, region);
3085
3138
  let bucketExists = false;
3086
3139
  try {
3087
3140
  await s3Client.send(new HeadBucketCommand({
@@ -3185,8 +3238,8 @@ async function ensureAssetStorage(options) {
3185
3238
  assetSupportVersion: 1,
3186
3239
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3187
3240
  };
3188
- await stateBackend.putRawObject(getBootstrapMarkerKey(region), JSON.stringify(marker, null, 2));
3189
- logger.info(`✓ Wrote bootstrap marker (${getBootstrapMarkerKey(region)})`);
3241
+ await stateBackend.putRawObject(markerKey, JSON.stringify(marker, null, 2));
3242
+ logger.info(`✓ Wrote bootstrap marker (${markerKey})`);
3190
3243
  return {
3191
3244
  assetBucket,
3192
3245
  containerRepo
@@ -15897,5 +15950,5 @@ var DeployEngine = class {
15897
15950
  };
15898
15951
 
15899
15952
  //#endregion
15900
- export { runDockerStreaming as $, DiffCalculator as A, buildAssetRedirectMap as B, findActionableSilentDrops as C, refStateLookupFromResource as D, cfnRefValueFromPhysicalId as E, rebuildClientForBucketRegion as F, BOOTSTRAP_MARKER_PREFIX as G, loadPublishableAssetManifest as H, shouldRetainResource as I, parseBootstrapMarker as J, ensureAssetStorage as K, AssetPublisher as L, TemplateParser as M, LockManager as N, WAFv2WebACLProvider as O, S3StateBackend as P, runDockerForeground as Q, stringifyValue as R, ProviderRegistry as S, IntrinsicFunctionResolver as T, rewriteTemplateAssetReferences as U, createAssetRedirectResolver as V, AssetModeResolver as W, formatDockerLoginError as X, buildDockerImage as Y, getDockerCmd as Z, green as _, findLargeInlineResources as _t, withRetry as a, getLegacyStateBucketName as at, IAMRoleProvider as b, clearBucketRegionCache as bt, computeImplicitDeleteEdges as c, resolveCaptureObservedState as ct, isStatefulRecreateTargetSync as d, resolveStateBucketWithDefaultAndSource as dt, AssetManifestLoader as et, renderStatefulReason as f, resolveUseCdkBootstrapAssets as ft, gray as g, MIGRATE_TMP_PREFIX as gt, cyan as h, CFN_TEMPLATE_URL_LIMIT as ht, withResourceDeadline as i, getDefaultStateBucketName as it, DagBuilder as j, applyRoleArnIfSet as k, extractDeploymentEventError as l, resolveSkipPrefix as lt, bold as m, CFN_TEMPLATE_BODY_LIMIT as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, Synthesizer as nt, isRetryableTransientError as o, resolveApp as ot, formatResourceLine as p, warnDeprecatedNoPrefixCliFlag as pt, getBootstrapMarkerKey as q, DeployEngine as r, synthesisStatusMessage as rt, IMPLICIT_DELETE_DEPENDENCIES as s, resolveAutoAssetStorage as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, getDockerImageBySourceHash as tt, MULTI_REGION_RECREATE_BLOCKED_TYPES as u, resolveStateBucketWithDefault as ut, red as v, uploadCfnTemplate as vt, CloudControlProvider as w, collectInlinePolicyNamesManagedBySiblings as x, resolveBucketRegion as xt, yellow as y, AssemblyReader as yt, WorkGraph as z };
15901
- //# sourceMappingURL=deploy-engine-DqAIXGA_.js.map
15953
+ export { getDockerCmd as $, DiffCalculator as A, buildAssetRedirectMap as B, findActionableSilentDrops as C, resolveBucketRegion as Ct, refStateLookupFromResource as D, cfnRefValueFromPhysicalId as E, rebuildClientForBucketRegion as F, BOOTSTRAP_MARKER_PREFIX as G, loadPublishableAssetManifest as H, shouldRetainResource as I, parseBootstrapMarker as J, ensureAssetStorage as K, AssetPublisher as L, TemplateParser as M, LockManager as N, WAFv2WebACLProvider as O, S3StateBackend as P, formatDockerLoginError as Q, stringifyValue as R, ProviderRegistry as S, clearBucketRegionCache as St, IntrinsicFunctionResolver as T, rewriteTemplateAssetReferences as U, createAssetRedirectResolver as V, AssetModeResolver as W, validateContainerRepoName as X, validateAssetBucketName as Y, buildDockerImage as Z, green as _, CFN_TEMPLATE_URL_LIMIT as _t, withRetry as a, synthesisStatusMessage as at, IAMRoleProvider as b, uploadCfnTemplate as bt, computeImplicitDeleteEdges as c, resolveApp as ct, isStatefulRecreateTargetSync as d, resolveSkipPrefix as dt, runDockerForeground as et, renderStatefulReason as f, resolveStateBucketWithDefault as ft, gray as g, CFN_TEMPLATE_BODY_LIMIT as gt, cyan as h, warnDeprecatedNoPrefixCliFlag as ht, withResourceDeadline as i, Synthesizer as it, DagBuilder as j, applyRoleArnIfSet as k, extractDeploymentEventError as l, resolveAutoAssetStorage as lt, bold as m, resolveUseCdkBootstrapAssets as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, AssetManifestLoader as nt, isRetryableTransientError as o, getDefaultStateBucketName as ot, formatResourceLine as p, resolveStateBucketWithDefaultAndSource as pt, getBootstrapMarkerKey as q, DeployEngine as r, getDockerImageBySourceHash as rt, IMPLICIT_DELETE_DEPENDENCIES as s, getLegacyStateBucketName as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, runDockerStreaming as tt, MULTI_REGION_RECREATE_BLOCKED_TYPES as u, resolveCaptureObservedState as ut, red as v, MIGRATE_TMP_PREFIX as vt, CloudControlProvider as w, collectInlinePolicyNamesManagedBySiblings as x, AssemblyReader as xt, yellow as y, findLargeInlineResources as yt, WorkGraph as z };
15954
+ //# sourceMappingURL=deploy-engine-CX6s1TcX.js.map