@go-to-k/cdkd 0.207.1 → 0.207.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import { DeleteObjectCommand, GetBucketLocationCommand, GetObjectCommand, HeadBu
5
5
  import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
6
6
  import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand, ListRolesCommand, NoSuchEntityException, PutRolePermissionsBoundaryCommand, PutRolePolicyCommand, RemoveRoleFromInstanceProfileCommand, TagRoleCommand, UntagRoleCommand, UpdateAssumeRolePolicyCommand, UpdateRoleCommand } from "@aws-sdk/client-iam";
7
7
  import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
8
- import { GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
8
+ import { GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctionConfigurationCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
9
9
  import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
10
10
  import { DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client } from "@aws-sdk/client-ec2";
11
11
  import { DescribeTableCommand } from "@aws-sdk/client-dynamodb";
@@ -8454,6 +8454,22 @@ function parseLambdaPayload(payloadBytes) {
8454
8454
  return parsed;
8455
8455
  }
8456
8456
  /**
8457
+ * IAM-authorization-propagation signals in a custom resource FAILED reason that
8458
+ * indicate the backing Lambda's freshly-attached execution-role policy has not
8459
+ * yet taken effect for its assumed-role session (so a recycle + retry will
8460
+ * succeed once IAM settles). Lowercase substrings. Intentionally narrow — these
8461
+ * are the IAM-permission-not-yet-effective phrases only, NOT generic transient
8462
+ * errors (throttling / timeouts), which must not trigger a CR re-invoke.
8463
+ */
8464
+ const CR_TRANSIENT_AUTHZ_SIGNALS = [
8465
+ "not authorized to perform",
8466
+ "no identity-based policy allows",
8467
+ "is not in the state functionactive",
8468
+ "not in the state functionactive",
8469
+ "cannot be assumed",
8470
+ "is unable to assume"
8471
+ ];
8472
+ /**
8457
8473
  * Custom Resource Provider
8458
8474
  *
8459
8475
  * Implements Lambda-backed custom resources by invoking the Lambda function
@@ -8517,6 +8533,28 @@ var CustomResourceProvider = class CustomResourceProvider {
8517
8533
  INITIAL_POLL_INTERVAL_MS = 2e3;
8518
8534
  /** Max poll interval for async polling with exponential backoff (30 seconds) */
8519
8535
  MAX_POLL_INTERVAL_MS = 3e4;
8536
+ /**
8537
+ * How many extra times to re-invoke a custom resource whose handler returned
8538
+ * FAILED with a *transient IAM-authorization* reason (e.g. the CDK Provider
8539
+ * framework's `lambda:GetFunction` / "not in the state functionActive" 403
8540
+ * when the framework role's freshly-attached inline policy has not yet
8541
+ * propagated to the assumed-role session). cdkd's fast SDK path invokes the
8542
+ * backing Lambda ~1s after `PutRolePolicy`, so the first cold-start can cache
8543
+ * stale credentials; CloudFormation never hits this because its deployment
8544
+ * latency gives IAM time to settle. This is the CR-path analogue of the
8545
+ * IAM-propagation retry cdkd's `withRetry` already applies to every other
8546
+ * resource (the CR provider opts out of that outer retry via
8547
+ * `disableOuterRetry` to avoid stranding a pre-signed response URL — so we
8548
+ * retry HERE instead, deriving a fresh response URL + RequestId per attempt
8549
+ * and recycling the backing function's execution environment between tries).
8550
+ * Override via `CDKD_CR_AUTHZ_MAX_RETRIES` (0 disables).
8551
+ */
8552
+ transientAuthzMaxRetries = (() => {
8553
+ const raw = process.env["CDKD_CR_AUTHZ_MAX_RETRIES"];
8554
+ if (raw === void 0 || raw === "") return 2;
8555
+ const n = Number(raw);
8556
+ return Number.isFinite(n) && n >= 0 ? n : 2;
8557
+ })();
8520
8558
  constructor(config) {
8521
8559
  const awsClients = getAwsClients();
8522
8560
  this.lambdaClient = awsClients.lambda;
@@ -8559,8 +8597,7 @@ var CustomResourceProvider = class CustomResourceProvider {
8559
8597
  if (!serviceToken) throw new ProvisioningError(`ServiceToken is required for custom resource ${logicalId}`, resourceType, logicalId);
8560
8598
  if (typeof serviceToken !== "string") throw new ProvisioningError(`Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). This usually indicates state was written by a pre-fix cdkd import; re-run \`cdkd import\` or \`cdkd state orphan <stack>\` to recover.`, resourceType, logicalId);
8561
8599
  try {
8562
- const invocation = await this.prepareInvocation();
8563
- const request = {
8600
+ const cfnResponse = await this.invokeCustomResourceWithRetry(serviceToken, logicalId, "Create", (invocation) => ({
8564
8601
  RequestType: "Create",
8565
8602
  RequestId: invocation.requestId,
8566
8603
  ResponseURL: invocation.responseURL,
@@ -8568,9 +8605,7 @@ var CustomResourceProvider = class CustomResourceProvider {
8568
8605
  LogicalResourceId: logicalId,
8569
8606
  StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,
8570
8607
  ResourceProperties: this.stringifyProperties(properties)
8571
- };
8572
- this.logger.debug(`Sending custom resource create request: ${serviceToken}`);
8573
- const cfnResponse = await this.sendRequest(serviceToken, request, invocation.responseKey, logicalId, "Create");
8608
+ }));
8574
8609
  if (cfnResponse.Status === "FAILED") throw new Error(`Custom resource handler returned FAILED: ${cfnResponse.Reason || "Unknown reason"}`);
8575
8610
  const physicalId = cfnResponse.PhysicalResourceId || logicalId;
8576
8611
  const attributes = cfnResponse.Data || {};
@@ -8593,8 +8628,7 @@ var CustomResourceProvider = class CustomResourceProvider {
8593
8628
  if (!serviceToken) throw new ProvisioningError(`ServiceToken is required for custom resource ${logicalId}`, resourceType, logicalId, physicalId);
8594
8629
  if (typeof serviceToken !== "string") throw new ProvisioningError(`Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). This usually indicates state was written by a pre-fix cdkd import; re-run \`cdkd import\` or \`cdkd state orphan <stack>\` to recover.`, resourceType, logicalId, physicalId);
8595
8630
  try {
8596
- const invocation = await this.prepareInvocation();
8597
- const request = {
8631
+ const cfnResponse = await this.invokeCustomResourceWithRetry(serviceToken, logicalId, "Update", (invocation) => ({
8598
8632
  RequestType: "Update",
8599
8633
  RequestId: invocation.requestId,
8600
8634
  ResponseURL: invocation.responseURL,
@@ -8604,9 +8638,7 @@ var CustomResourceProvider = class CustomResourceProvider {
8604
8638
  StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,
8605
8639
  ResourceProperties: this.stringifyProperties(properties),
8606
8640
  OldResourceProperties: this.stringifyProperties(previousProperties)
8607
- };
8608
- this.logger.debug(`Sending custom resource update request: ${serviceToken}`);
8609
- const cfnResponse = await this.sendRequest(serviceToken, request, invocation.responseKey, logicalId, "Update");
8641
+ }));
8610
8642
  if (cfnResponse.Status === "FAILED") throw new Error(`Custom resource handler returned FAILED: ${cfnResponse.Reason || "Unknown reason"}`);
8611
8643
  const newPhysicalId = cfnResponse.PhysicalResourceId || physicalId;
8612
8644
  const wasReplaced = newPhysicalId !== physicalId;
@@ -8638,8 +8670,7 @@ var CustomResourceProvider = class CustomResourceProvider {
8638
8670
  }
8639
8671
  if (typeof serviceToken !== "string") throw new ProvisioningError(`Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). This usually indicates state was written by a pre-fix cdkd import; re-run \`cdkd import\` or \`cdkd state orphan <stack>\` to recover.`, resourceType, logicalId, physicalId);
8640
8672
  try {
8641
- const invocation = await this.prepareInvocation();
8642
- const request = {
8673
+ const cfnResponse = await this.invokeCustomResourceWithRetry(serviceToken, logicalId, "Delete", (invocation) => ({
8643
8674
  RequestType: "Delete",
8644
8675
  RequestId: invocation.requestId,
8645
8676
  ResponseURL: invocation.responseURL,
@@ -8648,9 +8679,7 @@ var CustomResourceProvider = class CustomResourceProvider {
8648
8679
  PhysicalResourceId: physicalId,
8649
8680
  StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,
8650
8681
  ResourceProperties: this.stringifyProperties(properties)
8651
- };
8652
- this.logger.debug(`Sending custom resource delete request: ${serviceToken}`);
8653
- const cfnResponse = await this.sendRequest(serviceToken, request, invocation.responseKey, logicalId, "Delete");
8682
+ }));
8654
8683
  if (cfnResponse.Status === "FAILED") this.logger.warn(`Custom resource delete handler returned FAILED for ${logicalId}: ${cfnResponse.Reason || "Unknown reason"}`);
8655
8684
  else this.logger.debug(`Successfully deleted custom resource ${logicalId}`);
8656
8685
  } catch (error) {
@@ -8664,6 +8693,98 @@ var CustomResourceProvider = class CustomResourceProvider {
8664
8693
  return serviceToken.startsWith("arn:aws:sns:");
8665
8694
  }
8666
8695
  /**
8696
+ * Invoke a custom resource, retrying on a *transient IAM-authorization*
8697
+ * FAILED response.
8698
+ *
8699
+ * Why this exists: cdkd's fast SDK path attaches a backing Lambda's
8700
+ * execution-role inline policy and invokes the function ~1s later. If IAM has
8701
+ * not propagated the policy to the assumed-role session by the function's
8702
+ * first cold start, the session caches stale (policy-less) credentials for
8703
+ * the warm container's whole life — so the CDK Provider framework's
8704
+ * `lambda:GetFunction` / initial invoke 403s ("not authorized to perform" /
8705
+ * "not in the state functionActive") and the custom resource FAILS.
8706
+ * CloudFormation never hits this because its deployment latency lets IAM
8707
+ * settle first. This is the CR-path analogue of the IAM-propagation retry
8708
+ * cdkd's `withRetry` already applies to every other resource type — the CR
8709
+ * provider opts out of that outer retry (`disableOuterRetry`) to avoid
8710
+ * stranding a pre-signed response URL at an S3 key nobody polls, so we retry
8711
+ * HERE, deriving a FRESH response URL + RequestId per attempt (via
8712
+ * `prepareInvocation()`) and recycling the backing function's execution
8713
+ * environment between tries so its next cold start re-assumes the role.
8714
+ *
8715
+ * `buildRequest` is called once per attempt with the fresh invocation so the
8716
+ * CFn request body always carries the matching ResponseURL / RequestId.
8717
+ * Returns the final response; the caller decides what a terminal FAILED means
8718
+ * (create/update throw, delete warns-and-continues).
8719
+ */
8720
+ async invokeCustomResourceWithRetry(serviceToken, logicalId, operation, buildRequest) {
8721
+ for (let attempt = 0;; attempt++) {
8722
+ const invocation = await this.prepareInvocation();
8723
+ const request = buildRequest(invocation);
8724
+ this.logger.debug(`Sending custom resource ${operation.toLowerCase()} request: ${serviceToken}`);
8725
+ const cfnResponse = await this.sendRequest(serviceToken, request, invocation.responseKey, logicalId, operation);
8726
+ if (cfnResponse.Status === "FAILED" && attempt < this.transientAuthzMaxRetries && this.isTransientAuthzFailure(cfnResponse.Reason)) {
8727
+ this.logger.warn(`Custom resource ${operation} for ${logicalId} returned a transient IAM-authorization FAILED (attempt ${attempt + 1}/${this.transientAuthzMaxRetries + 1}): ${this.truncateReason(cfnResponse.Reason)}. Recycling the backing function's execution environment and retrying so its next cold start picks up the propagated policy.`);
8728
+ await this.recycleBackingFunctionExecEnv(serviceToken, logicalId);
8729
+ continue;
8730
+ }
8731
+ return cfnResponse;
8732
+ }
8733
+ }
8734
+ /**
8735
+ * Classify a custom resource FAILED reason as a transient IAM-authorization
8736
+ * race (worth retrying).
8737
+ *
8738
+ * Deliberately NARROW — only the IAM-permission-not-yet-effective signals,
8739
+ * NOT cdkd's broad transient classifier (`isRetryableTransientError`, which
8740
+ * also matches throttling / generic timeouts). A custom resource that FAILED
8741
+ * for an unrelated reason (user handler bug, a real timeout, a downstream API
8742
+ * error) must NOT be re-invoked — that would mask genuine failures and waste
8743
+ * the framework's ~minutes-long waiter per attempt. These phrases are the
8744
+ * IAM-authz subset of cdkd's `RETRYABLE_ERROR_MESSAGE_PATTERNS`, plus the CDK
8745
+ * Provider framework's `waitUntilFunctionActive` state phrasing.
8746
+ */
8747
+ isTransientAuthzFailure(reason) {
8748
+ if (!reason) return false;
8749
+ const lower = reason.toLowerCase();
8750
+ return CR_TRANSIENT_AUTHZ_SIGNALS.some((p) => lower.includes(p));
8751
+ }
8752
+ /** Truncate a CR FAILED reason for log readability. */
8753
+ truncateReason(reason, max = 200) {
8754
+ const r = reason ?? "Unknown reason";
8755
+ return r.length > max ? `${r.slice(0, max)}...` : r;
8756
+ }
8757
+ /**
8758
+ * Force the backing Lambda to drop its warm execution environment(s) so the
8759
+ * next invoke cold-starts and re-assumes the execution role, picking up the
8760
+ * now-propagated inline policy. A plain re-invoke would otherwise reuse the
8761
+ * same warm container that cached the stale credentials. Best-effort: any
8762
+ * failure (e.g. cdkd's own creds lack `lambda:UpdateFunctionConfiguration`)
8763
+ * degrades to a debug log and we still retry the invoke.
8764
+ *
8765
+ * The no-op `Description` write is the least-intrusive way to invalidate warm
8766
+ * containers. It persists on the backing function, but cdkd never reconciles
8767
+ * the CDK Provider framework's backing Lambda against a template `Description`
8768
+ * (the synthesized template leaves it empty / CDK-default and cdkd's diff only
8769
+ * compares state-recorded properties), so it does not surface as drift on a
8770
+ * later deploy. Only the IAM-propagation retry path (rare) ever sets it.
8771
+ */
8772
+ async recycleBackingFunctionExecEnv(serviceToken, logicalId) {
8773
+ if (this.isSnsServiceToken(serviceToken)) return;
8774
+ try {
8775
+ await this.lambdaClient.send(new UpdateFunctionConfigurationCommand({
8776
+ FunctionName: serviceToken,
8777
+ Description: `cdkd: recycled for IAM-propagation retry (${logicalId})`
8778
+ }));
8779
+ await waitUntilFunctionUpdatedV2({
8780
+ client: this.lambdaClient,
8781
+ maxWaitTime: 120
8782
+ }, { FunctionName: serviceToken });
8783
+ } catch (error) {
8784
+ this.logger.debug(`Could not recycle backing function for ${logicalId} (${error instanceof Error ? error.message : String(error)}); retrying invoke without a forced cold start`);
8785
+ }
8786
+ }
8787
+ /**
8667
8788
  * Send custom resource request via the appropriate service (Lambda or SNS)
8668
8789
  * For Lambda: invokes synchronously and returns the response
8669
8790
  * For SNS: publishes to topic and polls S3 for response
@@ -12205,7 +12326,8 @@ const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
12205
12326
  "KMS key is invalid for CreateGrant",
12206
12327
  "Could not deliver test message",
12207
12328
  "wait 60 seconds",
12208
- "concurrent update operation"
12329
+ "concurrent update operation",
12330
+ "because it is in use"
12209
12331
  ];
12210
12332
  /**
12211
12333
  * HTTP status codes that always indicate a transient failure worth retrying.
@@ -13584,5 +13706,5 @@ var DeployEngine = class {
13584
13706
  };
13585
13707
 
13586
13708
  //#endregion
13587
- export { uploadCfnTemplate as $, S3StateBackend as A, PATTERN_B_NAME_PROPERTIES as At, Synthesizer as B, assertRegionMatch as C, normalizeAwsError as Ct, DagBuilder as D, setLogger as Dt, DiffCalculator as E, getLogger as Et, buildDockerImage as F, withStackName as Ft, resolveSkipPrefix as G, getLegacyStateBucketName as H, formatDockerLoginError as I, warnDeprecatedNoPrefixCliFlag as J, resolveStateBucketWithDefault as K, getDockerCmd as L, AssetPublisher as M, generateResourceName as Mt, stringifyValue as N, generateResourceNameWithFallback as Nt, TemplateParser as O, runStackBuffered as Ot, WorkGraph as P, withSkipPrefix as Pt, findLargeInlineResources as Q, runDockerForeground as R, CloudControlProvider as S, isCdkdError as St, applyRoleArnIfSet as T, ConsoleLogger as Tt, resolveApp as U, getDefaultStateBucketName as V, resolveCaptureObservedState as W, CFN_TEMPLATE_URL_LIMIT as X, CFN_TEMPLATE_BODY_LIMIT as Y, MIGRATE_TMP_PREFIX as Z, matchesCdkPath as _, StackHasActiveImportsError as _t, withRetry as a, ConfigError as at, ProviderRegistry as b, SynthesisError as bt, bold as c, LocalMigrateError as ct, green as d, MissingCdkCliError as dt, AssemblyReader as et, red as f, NestedStackChildDirectDestroyError as ft, CDK_PATH_TAG as g, ResourceUpdateNotSupportedError as gt, collectInlinePolicyNamesManagedBySiblings as h, ResourceTimeoutError as ht, withResourceDeadline as i, CdkdError as it, shouldRetainResource as j, PATTERN_B_RESOURCE_TYPES as jt, LockManager as k, getLiveRenderer as kt, cyan as l, LocalStartServiceError as lt, IAMRoleProvider as m, ProvisioningError as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, resolveBucketRegion as nt, IMPLICIT_DELETE_DEPENDENCIES as o, DependencyError as ot, yellow as p, PartialFailureError as pt, resolveStateBucketWithDefaultAndSource as q, DeployEngine as r, AssetError as rt, formatResourceLine as s, LocalInvokeBuildError as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, clearBucketRegionCache as tt, gray as u, LockError as ut, normalizeAwsTagsToCfn as v, StackTerminationProtectionError as vt, IntrinsicFunctionResolver as w, withErrorHandling as wt, findActionableSilentDrops as x, formatError as xt, resolveExplicitPhysicalId as y, StateError as yt, runDockerStreaming as z };
13588
- //# sourceMappingURL=deploy-engine-CuJDHhTs.js.map
13709
+ export { findLargeInlineResources as $, LockManager as A, getLiveRenderer as At, runDockerStreaming as B, CloudControlProvider as C, isCdkdError as Ct, DiffCalculator as D, getLogger as Dt, applyRoleArnIfSet as E, ConsoleLogger as Et, WorkGraph as F, withSkipPrefix as Ft, resolveCaptureObservedState as G, getDefaultStateBucketName as H, buildDockerImage as I, withStackName as It, resolveStateBucketWithDefaultAndSource as J, resolveSkipPrefix as K, formatDockerLoginError as L, shouldRetainResource as M, PATTERN_B_RESOURCE_TYPES as Mt, AssetPublisher as N, generateResourceName as Nt, DagBuilder as O, setLogger as Ot, stringifyValue as P, generateResourceNameWithFallback as Pt, MIGRATE_TMP_PREFIX as Q, getDockerCmd as R, findActionableSilentDrops as S, formatError as St, IntrinsicFunctionResolver as T, withErrorHandling as Tt, getLegacyStateBucketName as U, Synthesizer as V, resolveApp as W, CFN_TEMPLATE_BODY_LIMIT as X, warnDeprecatedNoPrefixCliFlag as Y, CFN_TEMPLATE_URL_LIMIT as Z, CDK_PATH_TAG as _, ResourceUpdateNotSupportedError as _t, withRetry as a, CdkdError as at, resolveExplicitPhysicalId as b, StateError as bt, formatResourceLine as c, LocalInvokeBuildError as ct, gray as d, LockError as dt, uploadCfnTemplate as et, green as f, MissingCdkCliError as ft, collectInlinePolicyNamesManagedBySiblings as g, ResourceTimeoutError as gt, IAMRoleProvider as h, ProvisioningError as ht, withResourceDeadline as i, AssetError as it, S3StateBackend as j, PATTERN_B_NAME_PROPERTIES as jt, TemplateParser as k, runStackBuffered as kt, bold as l, LocalMigrateError as lt, yellow as m, PartialFailureError as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, clearBucketRegionCache as nt, isRetryableTransientError as o, ConfigError as ot, red as p, NestedStackChildDirectDestroyError as pt, resolveStateBucketWithDefault as q, DeployEngine as r, resolveBucketRegion as rt, IMPLICIT_DELETE_DEPENDENCIES as s, DependencyError as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, AssemblyReader as tt, cyan as u, LocalStartServiceError as ut, matchesCdkPath as v, StackHasActiveImportsError as vt, assertRegionMatch as w, normalizeAwsError as wt, ProviderRegistry as x, SynthesisError as xt, normalizeAwsTagsToCfn as y, StackTerminationProtectionError as yt, runDockerForeground as z };
13710
+ //# sourceMappingURL=deploy-engine-ai3rix-L.js.map