@aws/nx-plugin 1.0.4 → 1.0.5

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,10 +7,12 @@ import {
7
7
  } from './utils<% if (esm) { %>.js<% } %>';
8
8
  import { CfnOutput, Stack } from 'aws-cdk-lib';
9
9
  import {
10
+ DomainMappingOptions,
10
11
  HttpApi as _HttpApi,
11
12
  HttpApiProps as _HttpApiProps,
12
13
  HttpMethod,
13
14
  HttpStage,
15
+ IDomainName,
14
16
  LogGroupLogDestination,
15
17
  ThrottleSettings,
16
18
  } from 'aws-cdk-lib/aws-apigatewayv2';
@@ -28,7 +30,7 @@ import { suppressRules } from '../checkov<% if (esm) { %>.js<% } %>';
28
30
  export interface HttpApiProps<
29
31
  TIntegrations extends ApiIntegrations<TOperation, HttpApiIntegration>,
30
32
  TOperation extends string,
31
- > extends _HttpApiProps {
33
+ > extends Omit<_HttpApiProps, 'defaultDomainMapping'> {
32
34
  /**
33
35
  * Unique name for the API, used in runtime configuration
34
36
  */
@@ -47,6 +49,12 @@ export interface HttpApiProps<
47
49
  * @default { rateLimit: 10000, burstLimit: 5000 }
48
50
  */
49
51
  readonly throttle?: ThrottleSettings;
52
+ /**
53
+ * Custom domain mapped to the API's default stage
54
+ */
55
+ readonly defaultDomainMapping?: DomainMappingOptions & {
56
+ readonly domainName: IDomainName;
57
+ };
50
58
  }
51
59
 
52
60
  /**
@@ -70,6 +78,9 @@ export class HttpApi<
70
78
  /** Default auto-deployed stage */
71
79
  public readonly defaultStage: HttpStage;
72
80
 
81
+ /** The URL clients call: the custom domain's if one is mapped, otherwise the stage's */
82
+ private readonly apiUrl: string;
83
+
73
84
  /** Map of operation names to their API Gateway integrations */
74
85
  public readonly integrations: TIntegrations;
75
86
 
@@ -81,6 +92,7 @@ export class HttpApi<
81
92
  operations,
82
93
  integrations,
83
94
  throttle = { rateLimit: 10000, burstLimit: 5000 },
95
+ defaultDomainMapping,
84
96
  ...props
85
97
  }: HttpApiProps<TIntegrations, TOperation>,
86
98
  ) {
@@ -113,12 +125,16 @@ export class HttpApi<
113
125
 
114
126
  this.defaultStage = new HttpStage(this, 'DefaultStage', {
115
127
  httpApi: this.api,
128
+ domainMapping: defaultDomainMapping,
116
129
  autoDeploy: true,
117
130
  throttle,
118
131
  accessLogSettings: {
119
132
  destination: new LogGroupLogDestination(accessLogGroup),
120
133
  },
121
134
  });
135
+ this.apiUrl = defaultDomainMapping
136
+ ? this.defaultStage.domainUrl
137
+ : this.defaultStage.url!;
122
138
 
123
139
  // Resolve the integration for a given operation.
124
140
  // If the operation has a dedicated integration, use it; otherwise fall back to $router.
@@ -151,21 +167,35 @@ export class HttpApi<
151
167
  );
152
168
 
153
169
  new CfnOutput(this, `${apiName}Url`, {
154
- value: this.defaultStage.url!,
170
+ value: this.apiUrl,
155
171
  });
156
172
 
157
173
  // Register the API URL in runtime configuration for client discovery
158
174
  const rc = RuntimeConfig.ensure(this);
159
175
  rc.set('connection', 'apis', {
160
176
  ...rc.get('connection').apis,
161
- [apiName]: this.defaultStage.url!,
177
+ [apiName]: this.apiUrl,
162
178
  });
179
+
180
+ // Target for the custom domain's DNS record
181
+ if (defaultDomainMapping) {
182
+ new CfnOutput(this, `${apiName}DomainNameAlias`, {
183
+ value: defaultDomainMapping.domainName.regionalDomainName,
184
+ description:
185
+ 'Regional domain name to point a CNAME record at for the custom domain, with any DNS provider',
186
+ });
187
+ new CfnOutput(this, `${apiName}DomainNameAliasHostedZoneId`, {
188
+ value: defaultDomainMapping.domainName.regionalHostedZoneId,
189
+ description:
190
+ 'Hosted zone ID of the regional domain name, needed only for a Route 53 alias record',
191
+ });
192
+ }
163
193
  }
164
194
 
165
195
  /**
166
196
  * Return the API url
167
197
  */
168
198
  public get url() {
169
- return this.defaultStage.url;
199
+ return this.apiUrl;
170
200
  }
171
201
  }
@@ -10,7 +10,7 @@ import {
10
10
  Stage,
11
11
  ThrottleSettings,
12
12
  } from 'aws-cdk-lib/aws-apigateway';
13
- import { IAspect, RemovalPolicy, Stack } from 'aws-cdk-lib';
13
+ import { CfnOutput, IAspect, RemovalPolicy, Stack } from 'aws-cdk-lib';
14
14
  import {
15
15
  CfnLoggingConfiguration,
16
16
  CfnWebACL,
@@ -99,6 +99,7 @@ export class RestApi<
99
99
  integrations,
100
100
  enableWaf = true,
101
101
  throttle = { rateLimit: 10000, burstLimit: 5000 },
102
+ domainName,
102
103
  ...props
103
104
  }: RestApiProps<TIntegrations, TOperation>,
104
105
  ) {
@@ -124,6 +125,7 @@ export class RestApi<
124
125
  // Create the API Gateway REST API
125
126
  this.api = new _RestApi(this, 'Api', {
126
127
  ...props,
128
+ domainName,
127
129
  deployOptions: {
128
130
  accessLogDestination: new LogGroupLogDestination(accessLogs),
129
131
  accessLogFormat: AccessLogFormat.jsonWithStandardFields(),
@@ -265,8 +267,24 @@ export class RestApi<
265
267
  const rc = RuntimeConfig.ensure(this);
266
268
  rc.set('connection', 'apis', {
267
269
  ...rc.get('connection').apis,
268
- [apiName]: this.api.url!,
270
+ [apiName]: domainName
271
+ ? `https://${domainName.domainName}/${domainName.basePath ? `${domainName.basePath}/` : ''}`
272
+ : this.api.url!,
269
273
  });
274
+
275
+ // Target for the custom domain's DNS record
276
+ if (domainName) {
277
+ new CfnOutput(this, `${apiName}DomainNameAlias`, {
278
+ value: this.api.domainName!.domainNameAliasDomainName,
279
+ description:
280
+ 'Domain name of the API Gateway endpoint (regional or edge) to point a CNAME record at for the custom domain, with any DNS provider',
281
+ });
282
+ new CfnOutput(this, `${apiName}DomainNameAliasHostedZoneId`, {
283
+ value: this.api.domainName!.domainNameAliasHostedZoneId,
284
+ description:
285
+ 'Hosted zone ID of that domain name, needed only for a Route 53 alias record',
286
+ });
287
+ }
270
288
  }
271
289
 
272
290
  /**
@@ -125,6 +125,19 @@ variable "tags" {
125
125
  default = {}
126
126
  }
127
127
 
128
+ # Custom domain configuration
129
+ variable "custom_domain_name" {
130
+ description = "Custom domain name for the API. Requires acm_certificate_arn."
131
+ type = string
132
+ default = null
133
+ }
134
+
135
+ variable "acm_certificate_arn" {
136
+ description = "ARN of an ACM certificate (in the API's region) for the custom domain name."
137
+ type = string
138
+ default = null
139
+ }
140
+
128
141
  # Get current AWS region and account ID
129
142
  data "aws_region" "current" {}
130
143
  data "aws_caller_identity" "current" {}
@@ -431,6 +444,10 @@ locals {
431
444
  <%_ } _%>
432
445
  }
433
446
 
447
+ locals {
448
+ api_url = var.custom_domain_name == null ? module.http_api.stage_invoke_url : "https://${var.custom_domain_name}/"
449
+ }
450
+
434
451
  # CloudWatch Log Group for Lambda
435
452
  resource "aws_cloudwatch_log_group" "lambda_logs" {
436
453
  #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
@@ -674,11 +691,41 @@ module "add_url_to_runtime_config" {
674
691
 
675
692
  namespace = "connection"
676
693
  key = "apis"
677
- value = { "<%- apiNameClassName %>" = module.http_api.stage_invoke_url }
694
+ value = { "<%- apiNameClassName %>" = local.api_url }
678
695
 
679
696
  depends_on = [module.http_api]
680
697
  }
681
698
 
699
+ # Custom domain for the API
700
+ resource "aws_apigatewayv2_domain_name" "custom_domain" {
701
+ count = var.custom_domain_name == null ? 0 : 1
702
+
703
+ domain_name = var.custom_domain_name
704
+
705
+ domain_name_configuration {
706
+ certificate_arn = var.acm_certificate_arn
707
+ endpoint_type = "REGIONAL"
708
+ security_policy = "TLS_1_2"
709
+ }
710
+
711
+ tags = var.tags
712
+
713
+ lifecycle {
714
+ precondition {
715
+ condition = var.acm_certificate_arn != null
716
+ error_message = "acm_certificate_arn is required when custom_domain_name is set."
717
+ }
718
+ }
719
+ }
720
+
721
+ resource "aws_apigatewayv2_api_mapping" "custom_domain" {
722
+ count = var.custom_domain_name == null ? 0 : 1
723
+
724
+ api_id = module.http_api.api_id
725
+ stage = module.http_api.stage_id
726
+ domain_name = aws_apigatewayv2_domain_name.custom_domain[0].domain_name
727
+ }
728
+
682
729
  # Lambda permission for API Gateway to invoke the function<% if (backend.type === 'fastapi') { %> via alias<% } %>
683
730
  resource "aws_lambda_permission" "api_gateway_invoke" {
684
731
  <%_ if (backend.integrationPattern === 'isolated') { _%>
@@ -738,6 +785,21 @@ output "stage_invoke_url" {
738
785
  value = module.http_api.stage_invoke_url
739
786
  }
740
787
 
788
+ output "api_url" {
789
+ description = "URL clients should call: the custom domain if configured, otherwise the stage invoke URL"
790
+ value = local.api_url
791
+ }
792
+
793
+ output "custom_domain_target_domain_name" {
794
+ description = "Target domain name to point a DNS alias or CNAME record at for the custom domain"
795
+ value = var.custom_domain_name == null ? null : aws_apigatewayv2_domain_name.custom_domain[0].domain_name_configuration[0].target_domain_name
796
+ }
797
+
798
+ output "custom_domain_hosted_zone_id" {
799
+ description = "Hosted zone ID to use for a Route 53 alias record for the custom domain"
800
+ value = var.custom_domain_name == null ? null : aws_apigatewayv2_domain_name.custom_domain[0].domain_name_configuration[0].hosted_zone_id
801
+ }
802
+
741
803
  output "stage_arn" {
742
804
  description = "ARN of the API Gateway stage"
743
805
  value = module.http_api.stage_arn
@@ -121,6 +121,19 @@ variable "tags" {
121
121
  default = {}
122
122
  }
123
123
 
124
+ # Custom domain configuration
125
+ variable "custom_domain_name" {
126
+ description = "Custom domain name for the API. Requires acm_certificate_arn."
127
+ type = string
128
+ default = null
129
+ }
130
+
131
+ variable "acm_certificate_arn" {
132
+ description = "ARN of an ACM certificate (in the API's region) for the custom domain name."
133
+ type = string
134
+ default = null
135
+ }
136
+
124
137
  # Get current AWS region and account ID
125
138
  data "aws_region" "current" {}
126
139
  data "aws_caller_identity" "current" {}
@@ -485,6 +498,10 @@ locals {
485
498
  <%_ } _%>
486
499
  }
487
500
 
501
+ locals {
502
+ api_url = var.custom_domain_name == null ? "${aws_api_gateway_stage.api_stage.invoke_url}/" : "https://${var.custom_domain_name}/"
503
+ }
504
+
488
505
  # CloudWatch Log Group for Lambda
489
506
  resource "aws_cloudwatch_log_group" "lambda_logs" {
490
507
  #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
@@ -1139,11 +1156,41 @@ module "add_url_to_runtime_config" {
1139
1156
 
1140
1157
  namespace = "connection"
1141
1158
  key = "apis"
1142
- value = { "<%- apiNameClassName %>" = "${aws_api_gateway_stage.api_stage.invoke_url}/" }
1159
+ value = { "<%- apiNameClassName %>" = local.api_url }
1143
1160
 
1144
1161
  depends_on = [aws_api_gateway_stage.api_stage]
1145
1162
  }
1146
1163
 
1164
+ # Custom domain for the API, served without the stage path prefix
1165
+ resource "aws_api_gateway_domain_name" "custom_domain" {
1166
+ count = var.custom_domain_name == null ? 0 : 1
1167
+
1168
+ domain_name = var.custom_domain_name
1169
+ regional_certificate_arn = var.acm_certificate_arn
1170
+ security_policy = "TLS_1_2"
1171
+
1172
+ endpoint_configuration {
1173
+ types = ["REGIONAL"]
1174
+ }
1175
+
1176
+ tags = var.tags
1177
+
1178
+ lifecycle {
1179
+ precondition {
1180
+ condition = var.acm_certificate_arn != null
1181
+ error_message = "acm_certificate_arn is required when custom_domain_name is set."
1182
+ }
1183
+ }
1184
+ }
1185
+
1186
+ resource "aws_api_gateway_base_path_mapping" "custom_domain" {
1187
+ count = var.custom_domain_name == null ? 0 : 1
1188
+
1189
+ api_id = module.rest_api.api_id
1190
+ stage_name = aws_api_gateway_stage.api_stage.stage_name
1191
+ domain_name = aws_api_gateway_domain_name.custom_domain[0].domain_name
1192
+ }
1193
+
1147
1194
  # Outputs
1148
1195
 
1149
1196
  # API Gateway Outputs (from core module)
@@ -1172,6 +1219,21 @@ output "stage_invoke_url" {
1172
1219
  value = aws_api_gateway_stage.api_stage.invoke_url
1173
1220
  }
1174
1221
 
1222
+ output "api_url" {
1223
+ description = "URL clients should call: the custom domain if configured, otherwise the stage invoke URL"
1224
+ value = local.api_url
1225
+ }
1226
+
1227
+ output "custom_domain_target_domain_name" {
1228
+ description = "Target domain name to point a DNS alias or CNAME record at for the custom domain"
1229
+ value = var.custom_domain_name == null ? null : aws_api_gateway_domain_name.custom_domain[0].regional_domain_name
1230
+ }
1231
+
1232
+ output "custom_domain_hosted_zone_id" {
1233
+ description = "Hosted zone ID to use for a Route 53 alias record for the custom domain"
1234
+ value = var.custom_domain_name == null ? null : aws_api_gateway_domain_name.custom_domain[0].regional_zone_id
1235
+ }
1236
+
1175
1237
  output "stage_arn" {
1176
1238
  description = "ARN of the API Gateway stage"
1177
1239
  value = aws_api_gateway_stage.api_stage.arn