@go-to-k/cdkd 0.221.8 → 0.221.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.
@@ -7,7 +7,7 @@ import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRo
7
7
  import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
8
8
  import { GetFunctionCommand, GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctionConfigurationCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
9
9
  import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
10
- import { DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client } from "@aws-sdk/client-ec2";
10
+ import { DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeInstancesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client } from "@aws-sdk/client-ec2";
11
11
  import { DescribeTableCommand } from "@aws-sdk/client-dynamodb";
12
12
  import { CloudFormationClient, CreateChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeTypeCommand, GetTemplateCommand, waitUntilChangeSetCreateComplete } from "@aws-sdk/client-cloudformation";
13
13
  import { GetRestApiCommand } from "@aws-sdk/client-api-gateway";
@@ -5463,6 +5463,15 @@ const cachedAvailabilityZones = {};
5463
5463
  */
5464
5464
  const cachedDynamicReferences = {};
5465
5465
  /**
5466
+ * Cache for EC2 instance attributes that require a live DescribeInstances
5467
+ * lookup (PrivateIp / PublicIp / PrivateDnsName / PublicDnsName /
5468
+ * AvailabilityZone). Keyed by `${physicalId}#${attributeName}`. The IP /
5469
+ * DNS attributes are not derivable from the instance id, so they are read
5470
+ * back from AWS once per (instance, attribute) and memoized for the deploy
5471
+ * lifetime.
5472
+ */
5473
+ const cachedEc2InstanceAttributes = {};
5474
+ /**
5466
5475
  * Get AWS account information from STS
5467
5476
  */
5468
5477
  async function getAccountInfo(overrideRegion) {
@@ -5859,6 +5868,10 @@ var IntrinsicFunctionResolver = class {
5859
5868
  case "Arn": return `arn:${partition}:cloudwatch:${region}:${accountId}:alarm:${physicalId}`;
5860
5869
  default: return physicalId;
5861
5870
  }
5871
+ if (resourceType === "AWS::CloudWatch::CompositeAlarm") switch (attributeName) {
5872
+ case "Arn": return `arn:${partition}:cloudwatch:${region}:${accountId}:alarm:${physicalId}`;
5873
+ default: return physicalId;
5874
+ }
5862
5875
  if (resourceType === "AWS::RDS::DBInstance" || resourceType === "AWS::DocDB::DBInstance" || resourceType === "AWS::Neptune::DBInstance") switch (attributeName) {
5863
5876
  case "DBInstanceArn":
5864
5877
  case "Arn": return `arn:${partition}:rds:${region}:${accountId}:db:${physicalId}`;
@@ -5929,6 +5942,47 @@ var IntrinsicFunctionResolver = class {
5929
5942
  case "SubnetId": return physicalId;
5930
5943
  default: return physicalId;
5931
5944
  }
5945
+ if (resourceType === "AWS::EC2::Instance") switch (attributeName) {
5946
+ case "PrivateIp":
5947
+ case "PublicIp":
5948
+ case "PrivateDnsName":
5949
+ case "PublicDnsName":
5950
+ case "AvailabilityZone": {
5951
+ const cacheKey = `${physicalId}#${attributeName}`;
5952
+ const cached = cachedEc2InstanceAttributes[cacheKey];
5953
+ if (cached !== void 0) return cached;
5954
+ try {
5955
+ const instance = (await getAwsClients().ec2.send(new DescribeInstancesCommand({ InstanceIds: [physicalId] }))).Reservations?.[0]?.Instances?.[0];
5956
+ let value;
5957
+ switch (attributeName) {
5958
+ case "PrivateIp":
5959
+ value = instance?.PrivateIpAddress;
5960
+ break;
5961
+ case "PublicIp":
5962
+ value = instance?.PublicIpAddress;
5963
+ break;
5964
+ case "PrivateDnsName":
5965
+ value = instance?.PrivateDnsName;
5966
+ break;
5967
+ case "PublicDnsName":
5968
+ value = instance?.PublicDnsName;
5969
+ break;
5970
+ case "AvailabilityZone":
5971
+ value = instance?.Placement?.AvailabilityZone;
5972
+ break;
5973
+ }
5974
+ if (value !== void 0 && value !== null && value !== "") {
5975
+ cachedEc2InstanceAttributes[cacheKey] = value;
5976
+ return value;
5977
+ }
5978
+ this.logger.warn(`DescribeInstances(${physicalId}) returned no ${attributeName}; returning physical ID`);
5979
+ } catch (err) {
5980
+ this.logger.warn(`DescribeInstances(${physicalId}) failed for ${attributeName}: ${err instanceof Error ? err.message : String(err)}`);
5981
+ }
5982
+ return physicalId;
5983
+ }
5984
+ default: return physicalId;
5985
+ }
5932
5986
  if (resourceType === "AWS::EC2::LaunchTemplate") {
5933
5987
  if (attributeName === "LatestVersionNumber" || attributeName === "DefaultVersionNumber") {
5934
5988
  try {
@@ -6555,6 +6609,13 @@ var IntrinsicFunctionResolver = class {
6555
6609
  * SECRET_ID can be a simple name or an ARN (arn:aws:secretsmanager:REGION:ACCOUNT:secret:NAME)
6556
6610
  * which contains colons, so we cannot simply split on ':'.
6557
6611
  * Instead, we find ':SecretString:' or ':SecretBinary:' as the delimiter.
6612
+ *
6613
+ * The whole-secret form omits everything after the type segment and carries no trailing
6614
+ * colon: "secretsmanager:SECRET_ID:SecretString" (returns the full secret string). We detect
6615
+ * it with an end-anchored check so that, for the whole-secret form, a SECRET_ID that merely
6616
+ * contains ":SecretString" mid-name is not split incorrectly. (The end-anchored fallback only
6617
+ * runs when no mid-string ":SecretString:" delimiter is present, so the json-key / version
6618
+ * forms are unaffected.)
6558
6619
  */
6559
6620
  async resolveSecretsManagerReference(inner) {
6560
6621
  const afterService = inner.substring(15);
@@ -6562,10 +6623,20 @@ var IntrinsicFunctionResolver = class {
6562
6623
  let jsonKey = "";
6563
6624
  let versionStage = "";
6564
6625
  let versionId = "";
6565
- const secretStringIdx = afterService.indexOf(":SecretString:");
6566
- const secretBinaryIdx = afterService.indexOf(":SecretBinary:");
6626
+ let secretStringIdx = afterService.indexOf(":SecretString:");
6627
+ let secretBinaryIdx = afterService.indexOf(":SecretBinary:");
6628
+ let delimiterLenAtBinary = 14;
6629
+ let delimiterLenAtString = 14;
6630
+ if (secretStringIdx < 0 && afterService.endsWith(":SecretString")) {
6631
+ secretStringIdx = afterService.length - 13;
6632
+ delimiterLenAtString = 13;
6633
+ }
6634
+ if (secretBinaryIdx < 0 && afterService.endsWith(":SecretBinary")) {
6635
+ secretBinaryIdx = afterService.length - 13;
6636
+ delimiterLenAtBinary = 13;
6637
+ }
6567
6638
  const delimiterIdx = secretStringIdx >= 0 && secretBinaryIdx >= 0 ? Math.min(secretStringIdx, secretBinaryIdx) : secretStringIdx >= 0 ? secretStringIdx : secretBinaryIdx;
6568
- const delimiterLen = delimiterIdx >= 0 && delimiterIdx === secretBinaryIdx ? 14 : 14;
6639
+ const delimiterLen = delimiterIdx >= 0 && delimiterIdx === secretBinaryIdx ? delimiterLenAtBinary : delimiterLenAtString;
6569
6640
  if (delimiterIdx >= 0) {
6570
6641
  secretId = afterService.substring(0, delimiterIdx);
6571
6642
  const remainingParts = afterService.substring(delimiterIdx + delimiterLen).split(":");
@@ -11435,6 +11506,84 @@ const IMPLICIT_DELETE_DEPENDENCIES = {
11435
11506
  "AWS::Lambda::Function"
11436
11507
  ]
11437
11508
  };
11509
+ /**
11510
+ * Matches one alarm-state function token in a CompositeAlarm `AlarmRule`:
11511
+ * ALARM("name") | OK('name') | INSUFFICIENT_DATA(name)
11512
+ * The argument is captured raw (with any surrounding quotes) and trimmed /
11513
+ * unquoted by {@link extractReferencedAlarmNames}. CloudWatch also accepts the
11514
+ * boolean literals TRUE / FALSE which carry no argument, so they never match.
11515
+ */
11516
+ const ALARM_RULE_FUNCTION_REGEX = /\b(?:ALARM|OK|INSUFFICIENT_DATA)\s*\(\s*([^)]*?)\s*\)/gi;
11517
+ /**
11518
+ * Extract every alarm NAME (or ARN) referenced by a CompositeAlarm `AlarmRule`
11519
+ * string. The rule references its child alarms by NAME (or ARN) as a plain
11520
+ * string — there is no `Ref` / `Fn::GetAtt`, so cdkd's DAG sees no dependency
11521
+ * edge from these references. We parse them out so a delete-ordering edge can be
11522
+ * synthesized (CloudWatch refuses to delete a metric alarm while a composite
11523
+ * alarm still references it).
11524
+ *
11525
+ * Handles the three alarm-state functions (`ALARM` / `OK` / `INSUFFICIENT_DATA`)
11526
+ * and both the bare-name and quoted-name forms. An ARN argument
11527
+ * (`arn:aws:cloudwatch:...:alarm:<name>`) is reduced to its trailing `<name>`
11528
+ * so it can be matched against a referenced alarm's `AlarmName` / physical id
11529
+ * the same way a bare name is.
11530
+ */
11531
+ function extractReferencedAlarmNames(alarmRule) {
11532
+ const names = /* @__PURE__ */ new Set();
11533
+ for (const match of alarmRule.matchAll(ALARM_RULE_FUNCTION_REGEX)) {
11534
+ let arg = (match[1] ?? "").trim();
11535
+ if (arg.length === 0) continue;
11536
+ if (arg.startsWith("\"") && arg.endsWith("\"") || arg.startsWith("'") && arg.endsWith("'")) arg = arg.slice(1, -1);
11537
+ if (arg.length === 0) continue;
11538
+ const arnAlarmMatch = /:alarm:(.+)$/.exec(arg);
11539
+ if (arnAlarmMatch?.[1]) names.add(arnAlarmMatch[1]);
11540
+ else names.add(arg);
11541
+ }
11542
+ return [...names];
11543
+ }
11544
+ /**
11545
+ * Compute per-resource delete-ordering edges that cannot be inferred from
11546
+ * Ref / Fn::GetAtt edges or from the type-pair {@link IMPLICIT_DELETE_DEPENDENCIES}
11547
+ * table.
11548
+ *
11549
+ * Currently this synthesizes edges for `AWS::CloudWatch::CompositeAlarm`: a
11550
+ * composite alarm references its child alarms (metric `AWS::CloudWatch::Alarm`
11551
+ * or other composite alarms) by NAME inside its `AlarmRule` string. Because the
11552
+ * reference is a plain string (no `Ref` / `Fn::GetAtt`), cdkd's DAG sees no
11553
+ * dependency edge, so without this the metric alarm can be scheduled for
11554
+ * deletion while the composite still exists — and CloudWatch rejects that with
11555
+ * `Cannot delete <alarm> as there are composite alarm(s) depending on it.`
11556
+ * We therefore emit an edge making the composite alarm delete BEFORE every
11557
+ * alarm its `AlarmRule` references (handling composite-of-composite too).
11558
+ *
11559
+ * @param resources logical id -> resource (the subset of resources participating
11560
+ * in the delete). Only entries whose logical id is a key in this record are
11561
+ * considered as edge endpoints.
11562
+ */
11563
+ function computeImplicitDeleteEdges(resources) {
11564
+ const edges = [];
11565
+ const alarmTypes = new Set(["AWS::CloudWatch::Alarm", "AWS::CloudWatch::CompositeAlarm"]);
11566
+ const nameToLogicalId = /* @__PURE__ */ new Map();
11567
+ for (const [logicalId, resource] of Object.entries(resources)) {
11568
+ if (!alarmTypes.has(resource.resourceType)) continue;
11569
+ const alarmName = typeof resource.properties?.["AlarmName"] === "string" ? resource.properties["AlarmName"] : resource.physicalId;
11570
+ if (alarmName) nameToLogicalId.set(alarmName, logicalId);
11571
+ }
11572
+ for (const [logicalId, resource] of Object.entries(resources)) {
11573
+ if (resource.resourceType !== "AWS::CloudWatch::CompositeAlarm") continue;
11574
+ const alarmRule = resource.properties?.["AlarmRule"];
11575
+ if (typeof alarmRule !== "string") continue;
11576
+ for (const referencedName of extractReferencedAlarmNames(alarmRule)) {
11577
+ const referencedLogicalId = nameToLogicalId.get(referencedName);
11578
+ if (!referencedLogicalId || referencedLogicalId === logicalId) continue;
11579
+ edges.push({
11580
+ before: logicalId,
11581
+ after: referencedLogicalId
11582
+ });
11583
+ }
11584
+ }
11585
+ return edges;
11586
+ }
11438
11587
 
11439
11588
  //#endregion
11440
11589
  //#region src/deployment/retryable-errors.ts
@@ -12850,6 +12999,18 @@ var DeployEngine = class {
12850
12999
  }
12851
13000
  }
12852
13001
  }
13002
+ const scoped = {};
13003
+ for (const id of deleteIds) {
13004
+ const resource = state.resources[id];
13005
+ if (resource) scoped[id] = resource;
13006
+ }
13007
+ for (const { before, after } of computeImplicitDeleteEdges(scoped)) {
13008
+ if (!dependedBy.has(after)) dependedBy.set(after, /* @__PURE__ */ new Set());
13009
+ if (!dependedBy.get(after).has(before)) {
13010
+ dependedBy.get(after).add(before);
13011
+ this.logger.debug(`Implicit delete dependency: ${before} (${scoped[before]?.resourceType}) must be deleted before ${after} (${scoped[after]?.resourceType})`);
13012
+ }
13013
+ }
12853
13014
  }
12854
13015
  /**
12855
13016
  * Prepare a property map for a Cloud Control API call. When a Tier 1
@@ -12933,5 +13094,5 @@ var DeployEngine = class {
12933
13094
  };
12934
13095
 
12935
13096
  //#endregion
12936
- export { clearBucketRegionCache as $, AssetPublisher as A, getLegacyStateBucketName as B, DiffCalculator as C, S3StateBackend as D, LockManager as E, getDockerCmd as F, resolveStateBucketWithDefaultAndSource as G, resolveCaptureObservedState as H, runDockerForeground as I, CFN_TEMPLATE_URL_LIMIT as J, warnDeprecatedNoPrefixCliFlag as K, runDockerStreaming as L, WorkGraph as M, buildDockerImage as N, rebuildClientForBucketRegion as O, formatDockerLoginError as P, AssemblyReader as Q, Synthesizer as R, applyRoleArnIfSet as S, TemplateParser as T, resolveSkipPrefix as U, resolveApp as V, resolveStateBucketWithDefault as W, findLargeInlineResources as X, MIGRATE_TMP_PREFIX as Y, uploadCfnTemplate as Z, collectInlinePolicyNamesManagedBySiblings as _, withRetry as a, CloudControlProvider as b, extractDeploymentEventError as c, cyan as d, resolveBucketRegion as et, gray as f, IAMRoleProvider as g, yellow as h, withResourceDeadline as i, stringifyValue as j, shouldRetainResource as k, formatResourceLine as l, red as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isRetryableTransientError as o, green as p, CFN_TEMPLATE_BODY_LIMIT as q, DeployEngine as r, IMPLICIT_DELETE_DEPENDENCIES as s, DEFAULT_RESOURCE_TIMEOUT_MS as t, bold as u, ProviderRegistry as v, DagBuilder as w, IntrinsicFunctionResolver as x, findActionableSilentDrops as y, getDefaultStateBucketName as z };
12937
- //# sourceMappingURL=deploy-engine-DCkhw7i7.js.map
13097
+ export { AssemblyReader as $, shouldRetainResource as A, getDefaultStateBucketName as B, applyRoleArnIfSet as C, LockManager as D, TemplateParser as E, formatDockerLoginError as F, resolveStateBucketWithDefault as G, resolveApp as H, getDockerCmd as I, CFN_TEMPLATE_BODY_LIMIT as J, resolveStateBucketWithDefaultAndSource as K, runDockerForeground as L, stringifyValue as M, WorkGraph as N, S3StateBackend as O, buildDockerImage as P, uploadCfnTemplate as Q, runDockerStreaming as R, IntrinsicFunctionResolver as S, DagBuilder as T, resolveCaptureObservedState as U, getLegacyStateBucketName as V, resolveSkipPrefix as W, MIGRATE_TMP_PREFIX as X, CFN_TEMPLATE_URL_LIMIT as Y, findLargeInlineResources as Z, IAMRoleProvider as _, withRetry as a, findActionableSilentDrops as b, computeImplicitDeleteEdges as c, bold as d, clearBucketRegionCache as et, cyan as f, yellow as g, red as h, withResourceDeadline as i, AssetPublisher as j, rebuildClientForBucketRegion as k, extractDeploymentEventError as l, green as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isRetryableTransientError as o, gray as p, warnDeprecatedNoPrefixCliFlag as q, DeployEngine as r, IMPLICIT_DELETE_DEPENDENCIES as s, DEFAULT_RESOURCE_TIMEOUT_MS as t, resolveBucketRegion as tt, formatResourceLine as u, collectInlinePolicyNamesManagedBySiblings as v, DiffCalculator as w, CloudControlProvider as x, ProviderRegistry as y, Synthesizer as z };
13098
+ //# sourceMappingURL=deploy-engine-U63EpLKn.js.map