@fjall/components-infrastructure 0.95.0 → 0.99.1

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.
Files changed (234) hide show
  1. package/dist/lib/app.d.ts +90 -107
  2. package/dist/lib/app.js +149 -139
  3. package/dist/lib/config/aws/__t17fixture.d.ts +1 -0
  4. package/dist/lib/config/aws/__t17fixture.js +3 -0
  5. package/dist/lib/config/aws/__t17fixtureType.d.ts +2 -0
  6. package/dist/lib/config/aws/__t17fixtureType.js +1 -0
  7. package/dist/lib/config/aws/alarmTopic.js +8 -4
  8. package/dist/lib/config/aws/cloudTrail.js +1 -1
  9. package/dist/lib/config/aws/disasterRecovery.js +11 -16
  10. package/dist/lib/config/aws/ecrDefaultImage.d.ts +0 -1
  11. package/dist/lib/config/aws/ecrDefaultImage.js +13 -23
  12. package/dist/lib/config/aws/identityCenter.d.ts +10 -3
  13. package/dist/lib/config/aws/identityCenter.js +101 -37
  14. package/dist/lib/config/aws/identityCenterGroupMembership.js +8 -2
  15. package/dist/lib/config/aws/identityCenterMembership.d.ts +11 -0
  16. package/dist/lib/config/aws/identityCenterMembership.js +61 -0
  17. package/dist/lib/config/aws/index.d.ts +1 -1
  18. package/dist/lib/config/aws/index.js +1 -1
  19. package/dist/lib/config/aws/ipam.js +6 -11
  20. package/dist/lib/config/aws/oidcConnector.js +5 -1
  21. package/dist/lib/config/aws/scpPreset.js +4 -1
  22. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +1 -0
  23. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +4 -0
  24. package/dist/lib/patterns/aws/account.js +7 -8
  25. package/dist/lib/patterns/aws/apexDomainPattern.js +10 -10
  26. package/dist/lib/patterns/aws/bastionFactory.d.ts +10 -0
  27. package/dist/lib/patterns/aws/bastionFactory.js +29 -0
  28. package/dist/lib/patterns/aws/buildkite.d.ts +2 -2
  29. package/dist/lib/patterns/aws/buildkite.js +51 -97
  30. package/dist/lib/patterns/aws/cdn.js +1 -1
  31. package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +172 -0
  32. package/dist/lib/patterns/aws/clickhouseDatabase.js +600 -0
  33. package/dist/lib/patterns/aws/compute.d.ts +4 -6
  34. package/dist/lib/patterns/aws/compute.js +7 -13
  35. package/dist/lib/patterns/aws/computeEcs.d.ts +95 -396
  36. package/dist/lib/patterns/aws/computeEcs.js +880 -46
  37. package/dist/lib/patterns/aws/computeEcsTypes.d.ts +889 -0
  38. package/dist/lib/patterns/aws/computeEcsTypes.js +12 -0
  39. package/dist/lib/patterns/aws/computeLambda.d.ts +0 -5
  40. package/dist/lib/patterns/aws/computeLambda.js +1 -2
  41. package/dist/lib/patterns/aws/database.d.ts +50 -8
  42. package/dist/lib/patterns/aws/database.js +183 -27
  43. package/dist/lib/patterns/aws/domain.js +8 -7
  44. package/dist/lib/patterns/aws/index.d.ts +3 -0
  45. package/dist/lib/patterns/aws/index.js +3 -0
  46. package/dist/lib/patterns/aws/interfaces/compute.d.ts +13 -1
  47. package/dist/lib/patterns/aws/interfaces/connector.d.ts +1 -1
  48. package/dist/lib/patterns/aws/interfaces/connector.js +1 -1
  49. package/dist/lib/patterns/aws/interfaces/database.d.ts +187 -8
  50. package/dist/lib/patterns/aws/interfaces/database.js +17 -3
  51. package/dist/lib/patterns/aws/interfaces/index.d.ts +4 -2
  52. package/dist/lib/patterns/aws/interfaces/index.js +4 -2
  53. package/dist/lib/patterns/aws/interfaces/messaging.d.ts +7 -0
  54. package/dist/lib/patterns/aws/interfaces/migrationContributor.d.ts +47 -0
  55. package/dist/lib/patterns/aws/interfaces/migrationContributor.js +9 -0
  56. package/dist/lib/patterns/aws/interfaces/vpcPeer.d.ts +7 -0
  57. package/dist/lib/patterns/aws/interfaces/vpcPeer.js +1 -0
  58. package/dist/lib/patterns/aws/messaging.d.ts +66 -10
  59. package/dist/lib/patterns/aws/messaging.js +115 -20
  60. package/dist/lib/patterns/aws/network.js +16 -7
  61. package/dist/lib/patterns/aws/organisation.d.ts +4 -0
  62. package/dist/lib/patterns/aws/organisation.js +24 -5
  63. package/dist/lib/patterns/aws/storage.d.ts +1 -2
  64. package/dist/lib/patterns/aws/storage.js +3 -2
  65. package/dist/lib/patterns/aws/vpcPeer.d.ts +34 -0
  66. package/dist/lib/patterns/aws/vpcPeer.js +38 -0
  67. package/dist/lib/patterns/aws/vpcPeerAccepter.d.ts +29 -0
  68. package/dist/lib/patterns/aws/vpcPeerAccepter.js +196 -0
  69. package/dist/lib/resources/aws/analytics/clickhouse.js +25 -7
  70. package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +49 -0
  71. package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +140 -0
  72. package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +4 -4
  73. package/dist/lib/resources/aws/analytics/clickhouseConstants.js +6 -4
  74. package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +12 -0
  75. package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -0
  76. package/dist/lib/resources/aws/analytics/clickhouseUserData.js +56 -5
  77. package/dist/lib/resources/aws/analytics/index.d.ts +2 -0
  78. package/dist/lib/resources/aws/analytics/index.js +1 -0
  79. package/dist/lib/resources/aws/base/awsStack.js +4 -2
  80. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +2 -0
  81. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +11 -0
  82. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.d.ts +52 -0
  83. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.js +60 -0
  84. package/dist/lib/resources/aws/compute/blockDeviceVolume.d.ts +8 -0
  85. package/dist/lib/resources/aws/compute/blockDeviceVolume.js +10 -0
  86. package/dist/lib/resources/aws/compute/ec2.d.ts +132 -12
  87. package/dist/lib/resources/aws/compute/ec2.js +163 -23
  88. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.d.ts +41 -0
  89. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +194 -0
  90. package/dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs +458 -0
  91. package/dist/lib/resources/aws/compute/ecs.d.ts +27 -1
  92. package/dist/lib/resources/aws/compute/ecs.js +42 -2
  93. package/dist/lib/resources/aws/compute/ecsConstants.d.ts +9 -0
  94. package/dist/lib/resources/aws/compute/ecsConstants.js +16 -0
  95. package/dist/lib/resources/aws/compute/ecsImages.js +32 -20
  96. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.d.ts +96 -0
  97. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +113 -0
  98. package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +2 -1
  99. package/dist/lib/resources/aws/compute/ecsNetworking.js +18 -6
  100. package/dist/lib/resources/aws/compute/ecsRemoteConnections.d.ts +38 -0
  101. package/dist/lib/resources/aws/compute/ecsRemoteConnections.js +80 -0
  102. package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +13 -4
  103. package/dist/lib/resources/aws/compute/ecsServiceFactory.js +155 -33
  104. package/dist/lib/resources/aws/compute/ecsTaskDefinition.d.ts +31 -1
  105. package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +110 -6
  106. package/dist/lib/resources/aws/compute/ecsTypes.d.ts +180 -13
  107. package/dist/lib/resources/aws/compute/ecsValidation.d.ts +9 -0
  108. package/dist/lib/resources/aws/compute/ecsValidation.js +63 -0
  109. package/dist/lib/resources/aws/compute/index.d.ts +2 -0
  110. package/dist/lib/resources/aws/compute/index.js +2 -0
  111. package/dist/lib/resources/aws/compute/lambda.d.ts +7 -13
  112. package/dist/lib/resources/aws/compute/lambda.js +30 -38
  113. package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +192 -0
  114. package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +104 -0
  115. package/dist/lib/resources/aws/compute/persistentDataVolume.js +245 -0
  116. package/dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs +398 -0
  117. package/dist/lib/resources/aws/compute/samApplication.d.ts +15 -0
  118. package/dist/lib/resources/aws/compute/samApplication.js +27 -0
  119. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +159 -0
  120. package/dist/lib/resources/aws/database/clickhouseConstants.js +181 -0
  121. package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +71 -0
  122. package/dist/lib/resources/aws/database/clickhouseSchemas.js +157 -0
  123. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +14 -0
  124. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +23 -0
  125. package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +69 -0
  126. package/dist/lib/resources/aws/database/clickhouseUserData.js +371 -0
  127. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +56 -0
  128. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +112 -0
  129. package/dist/lib/resources/aws/database/rdsAurora.d.ts +8 -1
  130. package/dist/lib/resources/aws/database/rdsAurora.js +42 -32
  131. package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +15 -2
  132. package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +39 -43
  133. package/dist/lib/resources/aws/database/rdsDefaults.d.ts +6 -0
  134. package/dist/lib/resources/aws/database/rdsDefaults.js +7 -1
  135. package/dist/lib/resources/aws/database/rdsHelpers.d.ts +3 -3
  136. package/dist/lib/resources/aws/database/rdsHelpers.js +1 -0
  137. package/dist/lib/resources/aws/database/rdsInstance.d.ts +8 -1
  138. package/dist/lib/resources/aws/database/rdsInstance.js +51 -34
  139. package/dist/lib/resources/aws/database/rdsProxyOutput.d.ts +1 -1
  140. package/dist/lib/resources/aws/database/rdsProxyOutput.js +1 -1
  141. package/dist/lib/resources/aws/iam/delegationRole.js +12 -5
  142. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.d.ts +9 -0
  143. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.js +12 -0
  144. package/dist/lib/resources/aws/iam/identityCenter/index.d.ts +1 -0
  145. package/dist/lib/resources/aws/iam/identityCenter/index.js +1 -0
  146. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.d.ts +1 -0
  147. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -0
  148. package/dist/lib/resources/aws/logging/logGroup.d.ts +0 -8
  149. package/dist/lib/resources/aws/logging/logGroup.js +0 -11
  150. package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +7 -0
  151. package/dist/lib/resources/aws/messaging/defaultEventBus.js +21 -0
  152. package/dist/lib/resources/aws/messaging/eventBridgeRule.d.ts +96 -0
  153. package/dist/lib/resources/aws/messaging/eventBridgeRule.js +110 -0
  154. package/dist/lib/resources/aws/messaging/eventTargets.d.ts +84 -0
  155. package/dist/lib/resources/aws/messaging/eventTargets.js +152 -0
  156. package/dist/lib/resources/aws/messaging/eventbridge.d.ts +25 -2
  157. package/dist/lib/resources/aws/messaging/eventbridge.js +22 -10
  158. package/dist/lib/resources/aws/messaging/index.d.ts +5 -0
  159. package/dist/lib/resources/aws/messaging/index.js +2 -0
  160. package/dist/lib/resources/aws/messaging/schedule.d.ts +118 -0
  161. package/dist/lib/resources/aws/messaging/schedule.js +64 -0
  162. package/dist/lib/resources/aws/messaging/sns.d.ts +2 -1
  163. package/dist/lib/resources/aws/messaging/sqs.d.ts +2 -1
  164. package/dist/lib/resources/aws/messaging/subscription.d.ts +112 -0
  165. package/dist/lib/resources/aws/messaging/subscription.js +67 -0
  166. package/dist/lib/resources/aws/messaging/utils.d.ts +6 -0
  167. package/dist/lib/resources/aws/messaging/utils.js +10 -0
  168. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +60 -0
  169. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +139 -0
  170. package/dist/lib/resources/aws/monitoring/index.d.ts +2 -0
  171. package/dist/lib/resources/aws/monitoring/index.js +2 -0
  172. package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +47 -0
  173. package/dist/lib/resources/aws/monitoring/scheduleAlarms.js +106 -0
  174. package/dist/lib/resources/aws/networking/crossAccountDelegationRecord.js +6 -3
  175. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.d.ts +40 -0
  176. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +158 -0
  177. package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -4
  178. package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
  179. package/dist/lib/resources/aws/networking/domainCertificate.js +6 -3
  180. package/dist/lib/resources/aws/networking/hostedZone.js +6 -4
  181. package/dist/lib/resources/aws/networking/index.d.ts +3 -0
  182. package/dist/lib/resources/aws/networking/index.js +3 -0
  183. package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +96 -0
  184. package/dist/lib/resources/aws/networking/serviceDiscovery.js +96 -0
  185. package/dist/lib/resources/aws/networking/vpc.d.ts +4 -1
  186. package/dist/lib/resources/aws/networking/vpc.js +10 -3
  187. package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.d.ts +18 -0
  188. package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.js +61 -0
  189. package/dist/lib/resources/aws/networking/vpcPeeringConnection.d.ts +49 -0
  190. package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +106 -0
  191. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.d.ts +16 -5
  192. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +17 -3
  193. package/dist/lib/resources/aws/organisation/index.d.ts +1 -1
  194. package/dist/lib/resources/aws/organisation/organisationPolicy.d.ts +2 -0
  195. package/dist/lib/resources/aws/organisation/organisationPolicy.js +3 -2
  196. package/dist/lib/resources/aws/secrets/secret.d.ts +7 -0
  197. package/dist/lib/resources/aws/secrets/secret.js +4 -3
  198. package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +16 -0
  199. package/dist/lib/resources/aws/storage/bucketDeployment.js +17 -0
  200. package/dist/lib/resources/aws/storage/ecr.js +5 -5
  201. package/dist/lib/resources/aws/storage/index.d.ts +1 -0
  202. package/dist/lib/resources/aws/storage/index.js +1 -0
  203. package/dist/lib/resources/aws/storage/s3.js +10 -3
  204. package/dist/lib/resources/aws/utilities/customResource.js +18 -9
  205. package/dist/lib/synth_dump.d.ts +1 -0
  206. package/dist/lib/synth_dump.js +42 -0
  207. package/dist/lib/utils/bastionFactory.d.ts +10 -0
  208. package/dist/lib/utils/bastionFactory.js +29 -0
  209. package/dist/lib/utils/capitaliseString.d.ts +1 -1
  210. package/dist/lib/utils/capitaliseString.js +1 -1
  211. package/dist/lib/utils/cdkContext.d.ts +10 -0
  212. package/dist/lib/utils/cdkContext.js +13 -0
  213. package/dist/lib/utils/connections.d.ts +7 -1
  214. package/dist/lib/utils/connections.js +21 -0
  215. package/dist/lib/utils/connector.d.ts +30 -2
  216. package/dist/lib/utils/connector.js +6 -1
  217. package/dist/lib/utils/costAllocationTags.d.ts +15 -0
  218. package/dist/lib/utils/costAllocationTags.js +16 -0
  219. package/dist/lib/utils/databaseTypes.d.ts +14 -0
  220. package/dist/lib/utils/getConfig.d.ts +2 -0
  221. package/dist/lib/utils/getConfig.js +2 -0
  222. package/dist/lib/utils/index.d.ts +4 -0
  223. package/dist/lib/utils/index.js +4 -0
  224. package/dist/lib/utils/manifestWriter.d.ts +6 -89
  225. package/dist/lib/utils/manifestWriter.js +36 -23
  226. package/dist/lib/utils/migrationVersionResolvers.d.ts +2 -0
  227. package/dist/lib/utils/migrationVersionResolvers.js +2 -0
  228. package/dist/lib/utils/orgConfigParser.js +2 -1
  229. package/dist/lib/utils/resolveAlertsTopic.d.ts +14 -0
  230. package/dist/lib/utils/resolveAlertsTopic.js +30 -0
  231. package/dist/lib/utils/validationLogger.js +6 -3
  232. package/dist/lib/utils/vpcPeerInterface.d.ts +22 -0
  233. package/dist/lib/utils/vpcPeerInterface.js +1 -0
  234. package/package.json +22 -18
@@ -1,4 +1,4 @@
1
- import { Cluster as CdkCluster, type FargateService, type Ec2Service } from "aws-cdk-lib/aws-ecs";
1
+ import { Cluster as CdkCluster, type FargateService, type Ec2Service, type TaskDefinition } from "aws-cdk-lib/aws-ecs";
2
2
  import { Connections, type IConnectable } from "aws-cdk-lib/aws-ec2";
3
3
  import { Construct } from "constructs";
4
4
  import type { StackBuilder } from "../base/awsStack.js";
@@ -56,6 +56,7 @@ export default class EcsCluster extends Construct implements IConnectable {
56
56
  private certificate?;
57
57
  private asgState;
58
58
  private services;
59
+ private scheduledTaskDefinitions;
59
60
  private scope;
60
61
  private props;
61
62
  private outputName;
@@ -72,8 +73,33 @@ export default class EcsCluster extends Construct implements IConnectable {
72
73
  getService(name: string): FargateService | Ec2Service | undefined;
73
74
  /** Get all services in this cluster. */
74
75
  getServices(): Map<string, FargateService | Ec2Service>;
76
+ /**
77
+ * Get the task definition for a service or a registered scheduled task.
78
+ * Used by Schedule / Subscription targets to construct the EcsTask adapter
79
+ * for EventBridge invocation. Returns undefined for unknown names — callers
80
+ * MUST validate before passing to `resolveTarget(...)`.
81
+ */
82
+ getTaskDefinition(serviceName: string): TaskDefinition | undefined;
83
+ /**
84
+ * Register a `Ec2TaskDefinition` under a synthetic name so it can be
85
+ * resolved by `app.addSchedule(...)` against the existing
86
+ * `EcsScheduleTarget` shape (AC34). Throws if `name` collides with any
87
+ * steady-state service name OR a previously-registered scheduled task.
88
+ */
89
+ registerScheduledTaskDefinition(name: string, taskDefinition: TaskDefinition): void;
75
90
  /** Get the ECS cluster construct. */
76
91
  getCluster(): CdkCluster;
92
+ /**
93
+ * Get the EC2 instance role for the cluster's ASG (D10). Returns undefined
94
+ * for Fargate-only clusters. Pulled from the shared ASG capacity state.
95
+ */
96
+ getInstanceRole(): import("aws-cdk-lib/aws-iam").IRole | undefined;
97
+ /**
98
+ * Get the underlying ASG's `autoScalingGroupName` token (string-only,
99
+ * not the ASG construct itself — D10 forbids ASG-typed accessors).
100
+ * Used by alarm helpers that need CloudWatch dimension keys.
101
+ */
102
+ getAutoScalingGroupName(): string | undefined;
77
103
  /** Get the ALB URL (http:// or https://). */
78
104
  getUrl(): string | undefined;
79
105
  /**
@@ -69,8 +69,8 @@ export default class EcsCluster extends Construct {
69
69
  autoScalingGroup: undefined,
70
70
  asgSecurityGroup: undefined
71
71
  };
72
- // Per-service tracking
73
72
  services = new Map();
73
+ scheduledTaskDefinitions = new Map();
74
74
  // Configuration
75
75
  scope;
76
76
  props;
@@ -147,10 +147,50 @@ export default class EcsCluster extends Construct {
147
147
  }
148
148
  return result;
149
149
  }
150
+ /**
151
+ * Get the task definition for a service or a registered scheduled task.
152
+ * Used by Schedule / Subscription targets to construct the EcsTask adapter
153
+ * for EventBridge invocation. Returns undefined for unknown names — callers
154
+ * MUST validate before passing to `resolveTarget(...)`.
155
+ */
156
+ getTaskDefinition(serviceName) {
157
+ return (this.services.get(serviceName)?.taskDefinition ??
158
+ this.scheduledTaskDefinitions.get(serviceName));
159
+ }
160
+ /**
161
+ * Register a `Ec2TaskDefinition` under a synthetic name so it can be
162
+ * resolved by `app.addSchedule(...)` against the existing
163
+ * `EcsScheduleTarget` shape (AC34). Throws if `name` collides with any
164
+ * steady-state service name OR a previously-registered scheduled task.
165
+ */
166
+ registerScheduledTaskDefinition(name, taskDefinition) {
167
+ if (this.services.has(name)) {
168
+ throw new Error(`Scheduled task name '${name}' collides with a steady-state service in cluster '${this.props.clusterName}'.`);
169
+ }
170
+ if (this.scheduledTaskDefinitions.has(name)) {
171
+ throw new Error(`Scheduled task name '${name}' is already registered in cluster '${this.props.clusterName}'.`);
172
+ }
173
+ this.scheduledTaskDefinitions.set(name, taskDefinition);
174
+ }
150
175
  /** Get the ECS cluster construct. */
151
176
  getCluster() {
152
177
  return this.cluster;
153
178
  }
179
+ /**
180
+ * Get the EC2 instance role for the cluster's ASG (D10). Returns undefined
181
+ * for Fargate-only clusters. Pulled from the shared ASG capacity state.
182
+ */
183
+ getInstanceRole() {
184
+ return this.asgState.autoScalingGroup?.role;
185
+ }
186
+ /**
187
+ * Get the underlying ASG's `autoScalingGroupName` token (string-only,
188
+ * not the ASG construct itself — D10 forbids ASG-typed accessors).
189
+ * Used by alarm helpers that need CloudWatch dimension keys.
190
+ */
191
+ getAutoScalingGroupName() {
192
+ return this.asgState.autoScalingGroup?.autoScalingGroupName;
193
+ }
154
194
  /** Get the ALB URL (http:// or https://). */
155
195
  getUrl() {
156
196
  if (!this.loadBalancer)
@@ -199,7 +239,7 @@ export default class EcsCluster extends Construct {
199
239
  );
200
240
  }
201
241
  catch (error) {
202
- throw new Error(`Failed to process connections for ECS service '${serviceName}': ${error instanceof Error ? error.message : String(error)}`);
242
+ throw new Error(`Failed to process connections for ECS service '${serviceName}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
203
243
  }
204
244
  }
205
245
  // Per-service alarm wiring (shared topic on cluster, thresholds per service)
@@ -3,6 +3,15 @@ export declare const DEFAULT_EC2_INSTANCE_TYPE = "t4g.micro";
3
3
  export declare const DEFAULT_WARM_POOL_MIN_SIZE = 1;
4
4
  export declare const DEFAULT_WARM_POOL_REUSE_ON_SCALE_IN = true;
5
5
  export declare const DEFAULT_LOG_RETENTION_DAYS = 14;
6
+ export declare const DEFAULT_FARGATE_CPU = 256;
7
+ export declare const DEFAULT_FARGATE_MEMORY_MIB = 512;
8
+ export declare const DEFAULT_EC2_CONTAINER_MEMORY_MIB = 1024;
9
+ export declare const DEFAULT_ECS_FALLBACK_IMAGE = "amazon/amazon-ecs-sample";
10
+ export declare const DEFAULT_CUSTOM_RESOURCE_TIMEOUT_SECONDS = 300;
11
+ export declare const DEFAULT_HEALTH_CHECK_GRACE_SECONDS = 120;
12
+ export declare const DEFAULT_MIN_HEALTHY_PERCENT = 100;
13
+ export declare const DEFAULT_MAX_HEALTHY_PERCENT = 200;
14
+ export declare const DEFAULT_DESIRED_COUNT = 2;
6
15
  /**
7
16
  * Instance type prefixes that use ARM64 architecture (Graviton processors).
8
17
  * All other prefixes are assumed to be x86-64 (STANDARD).
@@ -5,6 +5,22 @@ export const DEFAULT_WARM_POOL_MIN_SIZE = 1;
5
5
  export const DEFAULT_WARM_POOL_REUSE_ON_SCALE_IN = true;
6
6
  // 14 days balances cost against retaining enough history for post-mortem debugging
7
7
  export const DEFAULT_LOG_RETENTION_DAYS = 14;
8
+ // Smallest valid (cpu, memory) pair on the Fargate matrix — must move together.
9
+ export const DEFAULT_FARGATE_CPU = 256;
10
+ export const DEFAULT_FARGATE_MEMORY_MIB = 512;
11
+ export const DEFAULT_EC2_CONTAINER_MEMORY_MIB = 1024;
12
+ // AWS sample image used when no ECR repository is provided. Consumed by both
13
+ // the resources/ image resolver and the patterns/ defaults block — keep them
14
+ // in lockstep via this single export.
15
+ export const DEFAULT_ECS_FALLBACK_IMAGE = "amazon/amazon-ecs-sample";
16
+ // 5 minutes matches the Lambda service default; CDK uses it for sync providers.
17
+ export const DEFAULT_CUSTOM_RESOURCE_TIMEOUT_SECONDS = 300;
18
+ // 100/200 must move together — the rolling-deploy window relies on the gap.
19
+ export const DEFAULT_HEALTH_CHECK_GRACE_SECONDS = 120;
20
+ export const DEFAULT_MIN_HEALTHY_PERCENT = 100;
21
+ export const DEFAULT_MAX_HEALTHY_PERCENT = 200;
22
+ // Read at two sites (createService initial count + addServiceScaling derive-base) that must agree.
23
+ export const DEFAULT_DESIRED_COUNT = 2;
8
24
  /**
9
25
  * Instance type prefixes that use ARM64 architecture (Graviton processors).
10
26
  * All other prefixes are assumed to be x86-64 (STANDARD).
@@ -1,35 +1,47 @@
1
+ import { CfnParameter, Stack } from "aws-cdk-lib";
1
2
  import { ContainerImage } from "aws-cdk-lib/aws-ecs";
2
3
  import { Repository } from "aws-cdk-lib/aws-ecr";
4
+ import { DEFAULT_ECS_FALLBACK_IMAGE } from "./ecsConstants.js";
5
+ import { toPascalCase } from "../../../utils/capitaliseString.js";
6
+ function buildImageTagDescription(serviceName) {
7
+ return `Image tag for ECS service ${serviceName}. Set by fjall deploy to the content-hash tag.`;
8
+ }
9
+ function getOrCreateImageTagParameter(ctx, serviceName) {
10
+ const stack = Stack.of(ctx.scope);
11
+ const paramLogicalId = `${toPascalCase(serviceName)}ImageTag`;
12
+ const description = buildImageTagDescription(serviceName);
13
+ const existing = stack.node.tryFindChild(paramLogicalId);
14
+ if (existing instanceof CfnParameter) {
15
+ if (existing.description !== description) {
16
+ throw new Error(`CfnParameter logical-ID collision: "${paramLogicalId}" is already registered for a different service. ` +
17
+ `Distinct service names cannot share a PascalCase identifier — rename one of the conflicting services.`);
18
+ }
19
+ return existing;
20
+ }
21
+ return new CfnParameter(stack, paramLogicalId, {
22
+ type: "String",
23
+ default: "latest",
24
+ description
25
+ });
26
+ }
3
27
  export function getContainerImage(ctx, serviceName, containerConfig, serviceProps) {
4
28
  const imageSource = containerConfig.image || serviceProps.image || ctx.props.ecrRepository;
5
29
  if (!imageSource) {
6
- return ContainerImage.fromRegistry("amazon/amazon-ecs-sample");
30
+ return ContainerImage.fromRegistry(DEFAULT_ECS_FALLBACK_IMAGE);
7
31
  }
8
- // Build image tag with optional dockerTarget suffix
9
- // Format: <service>-[<target>-]<version>
10
- // imageVersion comes from CDK context (git SHA) to ensure CloudFormation
11
- // detects template changes when new code is deployed. Falls back to 'latest'
12
- // for apps without Dockerfiles (welcome image) or local dev.
13
- const rawVersion = ctx.scope.node.tryGetContext("imageVersion");
14
- const imageVersion = (typeof rawVersion === "string" ? rawVersion : undefined) || "latest";
15
- const targetSuffix = serviceProps.dockerTarget
16
- ? `-${serviceProps.dockerTarget.toLowerCase()}`
17
- : "";
18
- const imageTag = `${serviceName.toLowerCase()}${targetSuffix}-${imageVersion}`;
19
32
  if (typeof imageSource === "string") {
20
- // Detect full registry URLs vs bare ECR repository names.
21
- // Bare names (with or without namespace slashes) are treated as ECR repos.
22
- // Docker Hub requires an explicit registry prefix (docker.io/).
23
- const isFullRegistryUrl = /^(docker\.io|registry\.hub\.docker\.com|ghcr\.io)\//i.test(imageSource) || // Docker Hub / GHCR URLs
24
- imageSource.startsWith("public.ecr.aws/") || // Public ECR: public.ecr.aws/fjall/welcome
25
- imageSource.includes(".dkr.ecr."); // Private ECR full URL: 123456789012.dkr.ecr.us-east-2.amazonaws.com/repo:tag
33
+ const isFullRegistryUrl = /^(docker\.io|registry\.hub\.docker\.com|ghcr\.io)\//i.test(imageSource) ||
34
+ imageSource.startsWith("public.ecr.aws/") ||
35
+ imageSource.includes(".dkr.ecr.");
26
36
  if (isFullRegistryUrl) {
27
37
  return ContainerImage.fromRegistry(imageSource);
28
38
  }
29
- return ContainerImage.fromEcrRepository(Repository.fromRepositoryName(ctx.scope, `${serviceName}${containerConfig.name}EcrRepo`, imageSource), imageTag);
39
+ const param = getOrCreateImageTagParameter(ctx, serviceName);
40
+ return ContainerImage.fromEcrRepository(Repository.fromRepositoryName(ctx.scope, `${serviceName}${containerConfig.name}EcrRepo`, imageSource), param.valueAsString);
30
41
  }
31
42
  if (imageSource instanceof Repository) {
32
- return ContainerImage.fromEcrRepository(imageSource, imageTag);
43
+ const param = getOrCreateImageTagParameter(ctx, serviceName);
44
+ return ContainerImage.fromEcrRepository(imageSource, param.valueAsString);
33
45
  }
34
46
  return imageSource;
35
47
  }
@@ -0,0 +1,96 @@
1
+ import { type BaseService, DeploymentLifecycleStage } from "aws-cdk-lib/aws-ecs";
2
+ import { Construct } from "constructs";
3
+ import { LambdaFunction } from "./lambda.js";
4
+ /**
5
+ * Awsvpc network configuration consumed by the runner's `RunTask` call.
6
+ */
7
+ export interface LifecycleHookNetworkConfiguration {
8
+ subnetIds: string[];
9
+ securityGroupIds: string[];
10
+ assignPublicIp: boolean;
11
+ }
12
+ /**
13
+ * Optional nested block describing a separate migration task definition.
14
+ *
15
+ * Three lockstep fields enforced by the type system — present-as-a-group or
16
+ * absent-as-a-group. This shape is the resources-layer mirror of
17
+ * `EcsLifecycleHookMigrationsSeparateTaskDef` at the patterns layer.
18
+ */
19
+ export interface MigrationTaskDef {
20
+ /** ARN of the migration task definition (different from the service task def). */
21
+ definitionArn: string;
22
+ /** Family of the migration task definition — used to scope `ecs:RunTask`. */
23
+ family: string;
24
+ /** Task role ARN for the migration task. */
25
+ taskRoleArn: string;
26
+ /** Execution role ARN for the migration task. */
27
+ executionRoleArn: string;
28
+ }
29
+ export interface EcsLifecycleHookMigrationProps {
30
+ /**
31
+ * The ECS service this hook fires for.
32
+ */
33
+ service: BaseService;
34
+ /** Cluster ARN — used for both `ecs:RunTask`'s cluster condition and ListTasks scoping. */
35
+ clusterArn: string;
36
+ /** ARN of the task definition the migrate task launches from. */
37
+ taskDefinitionArn: string;
38
+ /** Family of the task definition — used in the `ecs:RunTask` resource ARN. */
39
+ taskDefinitionFamily: string;
40
+ /** Task execution role ARN (the role pulling the image, writing logs). */
41
+ taskExecutionRoleArn: string;
42
+ /** Task role ARN (the role the running container assumes). */
43
+ taskRoleArn: string;
44
+ /** Migration command, passed through to the runner. */
45
+ command: string[];
46
+ /** Synthetic container name used in `RunTask` overrides. */
47
+ containerName: string;
48
+ /** Optional image override (else the task definition's image is used). */
49
+ image?: string;
50
+ /**
51
+ * Optional container entrypoint override (else inherited from the image).
52
+ * Lets the patterns layer carry `["/usr/bin/tini", "--"]` shapes through to
53
+ * the runner's `ContainerOverrides` block.
54
+ */
55
+ entryPoint?: string[];
56
+ /** Optional environment override. */
57
+ environment?: Record<string, string>;
58
+ /**
59
+ * Lambda timeout in seconds. Defaults to 300, capped at 900 (Lambda max).
60
+ * Should comfortably exceed the migration command's expected runtime
61
+ * including a few re-invocation cycles.
62
+ */
63
+ timeoutSeconds?: number;
64
+ /** Awsvpc network configuration for the migrate task. */
65
+ networkConfiguration: LifecycleHookNetworkConfiguration;
66
+ /**
67
+ * Optional separate migration task definition. When present, `RunTask`
68
+ * targets THIS task def instead of the service's, and the Lambda's IAM
69
+ * policy expands to cover both task-def families + both role pairs.
70
+ *
71
+ * When absent, behaviour is unchanged — the migration runs in the service
72
+ * task def via `ContainerOverrides`.
73
+ */
74
+ migrationTaskDef?: MigrationTaskDef;
75
+ /**
76
+ * Which deployment lifecycle stage to fire at. Defaults to
77
+ * `PRE_SCALE_UP` (migration runs BEFORE the new revision scales up;
78
+ * service rollout blocks until the hook returns SUCCEEDED).
79
+ *
80
+ * Set to `POST_SCALE_UP` for post-deploy work (backfills, smoke tests)
81
+ * that should run AFTER new tasks are healthy; a FAILED return triggers
82
+ * deployment circuit-breaker rollback.
83
+ */
84
+ lifecycleStage?: DeploymentLifecycleStage;
85
+ }
86
+ /**
87
+ * ECS deployment lifecycle hook (PRE_SCALE_UP) that runs migrations as a
88
+ * one-off `RunTask` before the new revision scales up. Self-wires:
89
+ * synthesises the Lambda + IAM + LogGroup, then attaches the hook to the
90
+ * service via the typed CDK API. CDK auto-generates the hook-invocation
91
+ * role.
92
+ */
93
+ export declare class EcsLifecycleHookMigration extends Construct {
94
+ readonly lambda: LambdaFunction;
95
+ constructor(scope: Construct, id: string, props: EcsLifecycleHookMigrationProps);
96
+ }
@@ -0,0 +1,113 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Stack } from "aws-cdk-lib";
5
+ import { Code, Runtime } from "aws-cdk-lib/aws-lambda";
6
+ import { PolicyStatement, Effect } from "aws-cdk-lib/aws-iam";
7
+ import { RetentionDays } from "aws-cdk-lib/aws-logs";
8
+ import { DeploymentLifecycleLambdaTarget, DeploymentLifecycleStage } from "aws-cdk-lib/aws-ecs";
9
+ import { Construct } from "constructs";
10
+ import { LambdaFunction } from "./lambda.js";
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const LAMBDA_SOURCE_FILE = "lifecycleHookLambda.source.cjs";
13
+ const LAMBDA_TIMEOUT_DEFAULT_SECONDS = 300;
14
+ const LAMBDA_TIMEOUT_MAX_SECONDS = 900;
15
+ /**
16
+ * ECS deployment lifecycle hook (PRE_SCALE_UP) that runs migrations as a
17
+ * one-off `RunTask` before the new revision scales up. Self-wires:
18
+ * synthesises the Lambda + IAM + LogGroup, then attaches the hook to the
19
+ * service via the typed CDK API. CDK auto-generates the hook-invocation
20
+ * role.
21
+ */
22
+ export class EcsLifecycleHookMigration extends Construct {
23
+ lambda;
24
+ constructor(scope, id, props) {
25
+ super(scope, id);
26
+ const stack = Stack.of(scope);
27
+ const timeoutSeconds = Math.min(props.timeoutSeconds ?? LAMBDA_TIMEOUT_DEFAULT_SECONDS, LAMBDA_TIMEOUT_MAX_SECONDS);
28
+ const sourcePath = path.resolve(__dirname, LAMBDA_SOURCE_FILE);
29
+ const source = readFileSync(sourcePath, "utf-8");
30
+ const migrationTaskDef = props.migrationTaskDef;
31
+ const targetTaskDefinitionArn = migrationTaskDef?.definitionArn ?? props.taskDefinitionArn;
32
+ const runTaskResources = [
33
+ `arn:aws:ecs:${stack.region}:${stack.account}:task-definition/${props.taskDefinitionFamily}:*`
34
+ ];
35
+ if (migrationTaskDef !== undefined) {
36
+ runTaskResources.push(`arn:aws:ecs:${stack.region}:${stack.account}:task-definition/${migrationTaskDef.family}:*`);
37
+ }
38
+ const passRoleResources = [props.taskExecutionRoleArn, props.taskRoleArn];
39
+ if (migrationTaskDef !== undefined) {
40
+ passRoleResources.push(migrationTaskDef.taskRoleArn, migrationTaskDef.executionRoleArn);
41
+ }
42
+ // ECS RunTask awsvpcConfiguration uses subnets/securityGroups and
43
+ // assignPublicIp as "ENABLED"/"DISABLED" string, not boolean.
44
+ const awsvpcConfiguration = {
45
+ subnets: props.networkConfiguration.subnetIds,
46
+ securityGroups: props.networkConfiguration.securityGroupIds,
47
+ assignPublicIp: props.networkConfiguration.assignPublicIp
48
+ ? "ENABLED"
49
+ : "DISABLED"
50
+ };
51
+ this.lambda = new LambdaFunction(this, `${id}Fn`, {
52
+ runtime: Runtime.NODEJS_22_X,
53
+ handler: "index.handler",
54
+ code: Code.fromInline(source),
55
+ lambdaDescription: `${id} ECS deployment lifecycle hook for migration`,
56
+ roleDescription: `Execution role for ${id} migration lifecycle-hook runner`,
57
+ timeout: timeoutSeconds,
58
+ memorySize: 256,
59
+ logGroupRetention: RetentionDays.ONE_MONTH,
60
+ environment: {
61
+ MIGRATE_CONFIG: JSON.stringify({
62
+ clusterArn: props.clusterArn,
63
+ taskDefinitionArn: targetTaskDefinitionArn,
64
+ name: props.containerName,
65
+ command: props.command,
66
+ ...(props.image !== undefined ? { image: props.image } : {}),
67
+ ...(props.entryPoint !== undefined
68
+ ? { entryPoint: props.entryPoint }
69
+ : {}),
70
+ ...(props.environment !== undefined
71
+ ? { environment: props.environment }
72
+ : {}),
73
+ networkConfiguration: awsvpcConfiguration,
74
+ ...(migrationTaskDef !== undefined
75
+ ? { hasSeparateMigrationTaskDef: true }
76
+ : {})
77
+ })
78
+ },
79
+ inlinePolicy: [
80
+ new PolicyStatement({
81
+ effect: Effect.ALLOW,
82
+ actions: ["ecs:RunTask"],
83
+ resources: runTaskResources,
84
+ conditions: {
85
+ ArnEquals: { "ecs:cluster": props.clusterArn }
86
+ }
87
+ }),
88
+ // ListTasks/DescribeTasks do not support resource-level ARN scoping;
89
+ // restrict via the `ecs:cluster` condition key instead.
90
+ new PolicyStatement({
91
+ effect: Effect.ALLOW,
92
+ actions: ["ecs:ListTasks", "ecs:DescribeTasks"],
93
+ resources: ["*"],
94
+ conditions: {
95
+ ArnEquals: { "ecs:cluster": props.clusterArn }
96
+ }
97
+ }),
98
+ new PolicyStatement({
99
+ effect: Effect.ALLOW,
100
+ actions: ["iam:PassRole"],
101
+ resources: passRoleResources,
102
+ conditions: {
103
+ StringEquals: { "iam:PassedToService": "ecs-tasks.amazonaws.com" }
104
+ }
105
+ })
106
+ ]
107
+ });
108
+ const lifecycleStage = props.lifecycleStage ?? DeploymentLifecycleStage.PRE_SCALE_UP;
109
+ props.service.addLifecycleHook(new DeploymentLifecycleLambdaTarget(this.lambda, id, {
110
+ lifecycleStages: [lifecycleStage]
111
+ }));
112
+ }
113
+ }
@@ -1,4 +1,5 @@
1
1
  import { type ApplicationListener, ApplicationLoadBalancer, type IApplicationTargetGroup, ListenerCondition } from "aws-cdk-lib/aws-elasticloadbalancingv2";
2
+ import { type ISecurityGroup } from "aws-cdk-lib/aws-ec2";
2
3
  import { type ICertificate } from "aws-cdk-lib/aws-certificatemanager";
3
4
  import { ARecord, type IHostedZone } from "aws-cdk-lib/aws-route53";
4
5
  import type { AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
@@ -14,7 +15,7 @@ export interface PriorityState {
14
15
  /** Returns the next unused auto-incremented ALB priority, skipping any manually assigned values. */
15
16
  export declare function getNextPriority(state: PriorityState): number;
16
17
  export declare function buildRoutingConditions(rule: EcsRoutingConfig | undefined): ListenerCondition[];
17
- export declare function addLoadBalancer(ctx: EcsConstructContext, anyServiceUsesEc2: boolean, asgSecurityGroup?: SecurityGroup): {
18
+ export declare function addLoadBalancer(ctx: EcsConstructContext, anyServiceUsesEc2: boolean, asgSecurityGroup?: ISecurityGroup): {
18
19
  loadBalancer: ApplicationLoadBalancer;
19
20
  loadBalancerSecurityGroup?: SecurityGroup;
20
21
  };
@@ -100,21 +100,33 @@ export function addLoadBalancer(ctx, anyServiceUsesEc2, asgSecurityGroup) {
100
100
  }
101
101
  export function addLoadBalancerListener(ctx, loadBalancer, certificate) {
102
102
  const port = certificate ? 443 : 80;
103
- const defaultAction = ListenerAction.fixedResponse(404, {
104
- contentType: "text/plain",
105
- messageBody: "Not Found"
106
- });
103
+ const servicesWithPorts = ctx.props.services.filter((s) => s.containers.some((c) => c.port !== undefined));
104
+ const willHaveMultipleRoutes = servicesWithPorts.length > 1 ||
105
+ servicesWithPorts.some((s) => {
106
+ const rules = Array.isArray(s.routing)
107
+ ? s.routing
108
+ : s.routing
109
+ ? [s.routing]
110
+ : [];
111
+ return rules.length > 1;
112
+ });
113
+ const defaultAction = willHaveMultipleRoutes
114
+ ? ListenerAction.fixedResponse(404, {
115
+ contentType: "text/plain",
116
+ messageBody: "Not Found"
117
+ })
118
+ : undefined;
107
119
  if (certificate) {
108
120
  return loadBalancer.addListener(`${ctx.props.clusterName}Listener`, {
109
121
  port,
110
122
  certificates: [certificate],
111
- defaultAction
123
+ ...(defaultAction !== undefined && { defaultAction })
112
124
  });
113
125
  }
114
126
  else {
115
127
  return loadBalancer.addListener(`${ctx.props.clusterName}Listener`, {
116
128
  port,
117
- defaultAction
129
+ ...(defaultAction !== undefined && { defaultAction })
118
130
  });
119
131
  }
120
132
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Synth-time resolver for ECS `remoteConnections` — turns each declared
3
+ * cross-app resource into a pair of `${PREFIX}_HOST` / `${PREFIX}_PORT` env
4
+ * vars resolved from SSM (`StringParameter.valueForStringParameter`) and
5
+ * indexed by service name for downstream merge into container environments.
6
+ *
7
+ * SSM path layout (matches the accepter side's publishing layout in
8
+ * `vpcPeerAccepter.ts`):
9
+ * /fjall/{orgId ?? "default"}/{peer.peerAppName}/resources/{resource}/endpoint
10
+ * /fjall/{orgId ?? "default"}/{peer.peerAppName}/resources/{resource}/port
11
+ *
12
+ * Default env-var prefix: `${toScreamingSnake(peer.peerAppName)}_${toScreamingSnake(resource)}`.
13
+ * Override via `RemoteConnectionSpec.envPrefix`.
14
+ */
15
+ import { type Construct } from "constructs";
16
+ import { type IVpcPeer } from "../../../utils/vpcPeerInterface.js";
17
+ export interface RemoteConnectionSpec {
18
+ peer: IVpcPeer;
19
+ resource: string;
20
+ envPrefix?: string;
21
+ }
22
+ interface ServiceWithRemoteConnections {
23
+ name: string;
24
+ remoteConnections?: RemoteConnectionSpec[];
25
+ }
26
+ /**
27
+ * Resolve `remoteConnections` for every service into a per-service env-var
28
+ * bag. Returns `Record<serviceName, Record<envVarName, value>>`.
29
+ *
30
+ * Validates each `RemoteConnectionSpec` at synth time:
31
+ * - `resource` matches `RESOURCE_NAME_PATTERN` (PascalCase).
32
+ * - `envPrefix` (when supplied) matches `ENV_PREFIX_PATTERN`.
33
+ * - `peer.peerAppName` is defined and is not a CFN token — token-derived app
34
+ * names produce `${Token[…]}` literals in env-var names, breaking ECS task
35
+ * definitions silently.
36
+ */
37
+ export declare function resolveRemoteConnections(services: ServiceWithRemoteConnections[], scope: Construct, orgId: string | undefined): Record<string, Record<string, string>>;
38
+ export {};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Synth-time resolver for ECS `remoteConnections` — turns each declared
3
+ * cross-app resource into a pair of `${PREFIX}_HOST` / `${PREFIX}_PORT` env
4
+ * vars resolved from SSM (`StringParameter.valueForStringParameter`) and
5
+ * indexed by service name for downstream merge into container environments.
6
+ *
7
+ * SSM path layout (matches the accepter side's publishing layout in
8
+ * `vpcPeerAccepter.ts`):
9
+ * /fjall/{orgId ?? "default"}/{peer.peerAppName}/resources/{resource}/endpoint
10
+ * /fjall/{orgId ?? "default"}/{peer.peerAppName}/resources/{resource}/port
11
+ *
12
+ * Default env-var prefix: `${toScreamingSnake(peer.peerAppName)}_${toScreamingSnake(resource)}`.
13
+ * Override via `RemoteConnectionSpec.envPrefix`.
14
+ */
15
+ import { Token } from "aws-cdk-lib";
16
+ import { StringParameter } from "aws-cdk-lib/aws-ssm";
17
+ import { VALIDATION_PATTERNS } from "@fjall/generator";
18
+ import { buildSsmPrefix, DEFAULT_ORG_ID } from "../../../utils/cdkContext.js";
19
+ import { toScreamingSnake } from "../../../utils/capitaliseString.js";
20
+ const RESOURCE_NAME_PATTERN = VALIDATION_PATTERNS.RESOURCE_NAME;
21
+ /** SCREAMING_SNAKE_CASE env-var prefix (digits allowed, must start with letter). */
22
+ const ENV_PREFIX_PATTERN = /^[A-Z][A-Z0-9_]*$/;
23
+ /**
24
+ * Resolve `remoteConnections` for every service into a per-service env-var
25
+ * bag. Returns `Record<serviceName, Record<envVarName, value>>`.
26
+ *
27
+ * Validates each `RemoteConnectionSpec` at synth time:
28
+ * - `resource` matches `RESOURCE_NAME_PATTERN` (PascalCase).
29
+ * - `envPrefix` (when supplied) matches `ENV_PREFIX_PATTERN`.
30
+ * - `peer.peerAppName` is defined and is not a CFN token — token-derived app
31
+ * names produce `${Token[…]}` literals in env-var names, breaking ECS task
32
+ * definitions silently.
33
+ */
34
+ export function resolveRemoteConnections(services, scope, orgId) {
35
+ const byService = {};
36
+ for (const service of services) {
37
+ const specs = service.remoteConnections ?? [];
38
+ if (specs.length === 0)
39
+ continue;
40
+ const envVars = {};
41
+ const seenPrefixes = new Set();
42
+ for (const spec of specs) {
43
+ const peerAppName = validateSpec(service.name, spec);
44
+ const peerOrgId = spec.peer.peerOrgId ?? orgId ?? DEFAULT_ORG_ID;
45
+ const ssmPrefix = `${buildSsmPrefix(peerOrgId, peerAppName)}/resources/${spec.resource}`;
46
+ const endpoint = StringParameter.valueForStringParameter(scope, `${ssmPrefix}/endpoint`);
47
+ const port = StringParameter.valueForStringParameter(scope, `${ssmPrefix}/port`);
48
+ const prefix = spec.envPrefix ??
49
+ `${toScreamingSnake(peerAppName)}_${toScreamingSnake(spec.resource)}`;
50
+ if (seenPrefixes.has(prefix)) {
51
+ throw new Error(`remoteConnections on service '${service.name}': duplicate env-var prefix '${prefix}' — set distinct envPrefix on each spec to avoid silently clobbering ${prefix}_HOST and ${prefix}_PORT.`);
52
+ }
53
+ seenPrefixes.add(prefix);
54
+ envVars[`${prefix}_HOST`] = endpoint;
55
+ envVars[`${prefix}_PORT`] = port;
56
+ }
57
+ byService[service.name] = envVars;
58
+ }
59
+ return byService;
60
+ }
61
+ function validateSpec(serviceName, spec) {
62
+ if (!RESOURCE_NAME_PATTERN.test(spec.resource)) {
63
+ throw new Error(`remoteConnections on service '${serviceName}': resource '${spec.resource}' is not a valid PascalCase resource name (must match ${RESOURCE_NAME_PATTERN.source}).`);
64
+ }
65
+ if (spec.envPrefix !== undefined &&
66
+ !ENV_PREFIX_PATTERN.test(spec.envPrefix)) {
67
+ throw new Error(`remoteConnections on service '${serviceName}': envPrefix '${spec.envPrefix}' is not SCREAMING_SNAKE_CASE (must match ${ENV_PREFIX_PATTERN.source}).`);
68
+ }
69
+ const peerAppName = spec.peer.peerAppName;
70
+ if (peerAppName === undefined) {
71
+ throw new Error(`remoteConnections on service '${serviceName}': peer.peerAppName is undefined — pass a peer built via VpcPeerFactory.build() so the SSM path can be resolved at synth.`);
72
+ }
73
+ if (typeof peerAppName === "string" && peerAppName.length === 0) {
74
+ throw new Error(`remoteConnections on service '${serviceName}': peer.peerAppName is an empty string, which would produce malformed env-var names and an invalid SSM path. Pass a literal app name (typically the Fjall app's name string).`);
75
+ }
76
+ if (Token.isUnresolved(peerAppName)) {
77
+ throw new Error(`remoteConnections on service '${serviceName}': peer.peerAppName is a CFN token, which would corrupt env-var names. Pass a literal app name (typically the Fjall app's name string).`);
78
+ }
79
+ return peerAppName;
80
+ }
@@ -1,15 +1,24 @@
1
- import { FargateService, Ec2Service, AsgCapacityProvider } from "aws-cdk-lib/aws-ecs";
1
+ import { FargateService, Ec2Service, AsgCapacityProvider, type DeploymentCircuitBreaker } from "aws-cdk-lib/aws-ecs";
2
2
  import type { FargateTaskDefinition, Ec2TaskDefinition } from "aws-cdk-lib/aws-ecs";
3
+ import { type ISecurityGroup } from "aws-cdk-lib/aws-ec2";
3
4
  import { TargetTrackingScalingPolicy } from "aws-cdk-lib/aws-applicationautoscaling";
4
- import { AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
5
- import { SecurityGroup } from "../networking/securityGroup.js";
5
+ import { type AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
6
6
  import { type EcsServiceProps, type Ec2CapacityConfig } from "./ecsTypes.js";
7
7
  import type { EcsConstructContext } from "./ecsContext.js";
8
+ /**
9
+ * Resolves the user's `circuitBreaker` config to the CDK
10
+ * `DeploymentCircuitBreaker` shape. `undefined` resolves to the safe default
11
+ * `{ enable: true, rollback: true }`; `false` resolves to `undefined` (CDK
12
+ * omits the breaker block from CFN output entirely).
13
+ */
14
+ export declare function resolveCircuitBreaker(config: false | {
15
+ rollback?: boolean;
16
+ } | undefined): DeploymentCircuitBreaker | undefined;
8
17
  /** Mutable state for ASG capacity provider deduplication. */
9
18
  export interface AsgCapacityState {
10
19
  providers: Map<string, AsgCapacityProvider>;
11
20
  autoScalingGroup?: AutoScalingGroup;
12
- asgSecurityGroup?: SecurityGroup;
21
+ asgSecurityGroup?: ISecurityGroup;
13
22
  }
14
23
  /**
15
24
  * Generates a unique key for EC2 config so services with matching