konokenj.cdk-api-mcp-server 0.32.0__py3-none-any.whl → 0.33.0__py3-none-any.whl

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.

Potentially problematic release.


This version of konokenj.cdk-api-mcp-server might be problematic. Click here for more details.

Files changed (35) hide show
  1. cdk_api_mcp_server/__about__.py +1 -1
  2. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2/integ.api-dualstack.ts +3 -4
  3. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2/integ.api.ts +2 -4
  4. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2/integ.stage.ts +7 -20
  5. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2-authorizers/integ.iam.ts +34 -38
  6. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2-integrations/integ.sqs.ts +58 -71
  7. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-cloudfront/README.md +292 -1
  8. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-cloudwatch/README.md +13 -0
  9. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-cloudwatch/integ.alarm-and-dashboard.ts +12 -0
  10. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-codebuild/README.md +67 -3
  11. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-codebuild/integ.project-s3-cache.ts +71 -0
  12. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs/integ.availability-zone-rebalancing.ts +14 -4
  13. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs/integ.enable-execute-command.ts +35 -29
  14. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs/integ.exec-command.ts +16 -22
  15. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs/integ.lb-awsvpc-nw.ts +26 -16
  16. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs/integ.pseudo-terminal.ts +18 -8
  17. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-events/integ.api-destination.ts +42 -0
  18. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-events-targets/README.md +7 -2
  19. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-kinesisfirehose/README.md +3 -0
  20. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-kinesisfirehose/integ.s3-bucket.lit.ts +1 -0
  21. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-lambda/integ.lambda-policy-with-token-resolution.ts +46 -0
  22. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-route53-targets/integ.elastic-beanstalk-hostedzoneid.ts +1 -1
  23. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-stepfunctions-tasks/integ.invoke-jsonata.ts +87 -80
  24. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-stepfunctions-tasks/integ.invoke.ts +87 -69
  25. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-stepfunctions-tasks/integ.start-job-run.ts +96 -43
  26. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-synthetics/README.md +40 -0
  27. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-synthetics/integ.canary-resources-to-replicate-tags.ts +36 -0
  28. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-synthetics/integ.canary-retry.ts +32 -0
  29. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/cx-api/FEATURE_FLAGS.md +29 -0
  30. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/pipelines/integ.pipeline-with-customsynthesizer.ts +105 -0
  31. {konokenj_cdk_api_mcp_server-0.32.0.dist-info → konokenj_cdk_api_mcp_server-0.33.0.dist-info}/METADATA +2 -2
  32. {konokenj_cdk_api_mcp_server-0.32.0.dist-info → konokenj_cdk_api_mcp_server-0.33.0.dist-info}/RECORD +35 -29
  33. {konokenj_cdk_api_mcp_server-0.32.0.dist-info → konokenj_cdk_api_mcp_server-0.33.0.dist-info}/WHEEL +0 -0
  34. {konokenj_cdk_api_mcp_server-0.32.0.dist-info → konokenj_cdk_api_mcp_server-0.33.0.dist-info}/entry_points.txt +0 -0
  35. {konokenj_cdk_api_mcp_server-0.32.0.dist-info → konokenj_cdk_api_mcp_server-0.33.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Kenji Kono <konoken@amazon.co.jp>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.32.0"
4
+ __version__ = "0.33.0"
@@ -4,14 +4,13 @@ import * as cdk from 'aws-cdk-lib';
4
4
  import * as apigw from 'aws-cdk-lib/aws-apigatewayv2';
5
5
 
6
6
  const app = new cdk.App();
7
- const stack = new cdk.Stack(app, 'DualStackHttpApiStack');
7
+ const stack = new cdk.Stack(app, 'DualStackWebsocketApiStack');
8
8
 
9
- new apigw.HttpApi(stack, 'HttpApi', {
10
- routeSelectionExpression: true,
9
+ new apigw.WebSocketApi(stack, 'WebSocketApi', {
11
10
  ipAddressType: apigw.IpAddressType.DUAL_STACK,
12
11
  });
13
12
 
14
- new IntegTest(app, 'DualStackHttpApiInteg', {
13
+ new IntegTest(app, 'DualStackWebsocketApiInteg', {
15
14
  testCases: [stack],
16
15
  });
17
16
 
@@ -6,11 +6,9 @@ import * as apigw from 'aws-cdk-lib/aws-apigatewayv2';
6
6
  const app = new cdk.App();
7
7
  const stack = new cdk.Stack(app, 'aws-cdk-aws-apigatewayv2');
8
8
 
9
- new apigw.HttpApi(stack, 'HttpApi', {
10
- routeSelectionExpression: true,
11
- });
9
+ new apigw.WebSocketApi(stack, 'WebSocketApi');
12
10
 
13
- new IntegTest(app, 'http-api', {
11
+ new IntegTest(app, 'web-socket-api', {
14
12
  testCases: [stack],
15
13
  });
16
14
 
@@ -1,33 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { IntegTest } from '@aws-cdk/integ-tests-alpha';
3
2
  import * as cdk from 'aws-cdk-lib';
4
- import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
5
- import * as apigw from 'aws-cdk-lib/aws-apigateway';
6
- import * as logs from 'aws-cdk-lib/aws-logs';
3
+ import * as apigw from 'aws-cdk-lib/aws-apigatewayv2';
7
4
 
8
5
  const app = new cdk.App();
9
- const stack = new cdk.Stack(app, 'aws-cdk-aws-apigatewayv2-http-stage');
6
+ const stack = new cdk.Stack(app, 'aws-cdk-aws-apigatewayv2-websocket-stage');
10
7
 
11
- const testLogGroup = new logs.LogGroup(stack, 'MyLogGroup');
12
-
13
- const httpApi = new apigwv2.HttpApi(stack, 'HttpApi', { createDefaultStage: false });
14
- new apigwv2.HttpStage(stack, 'HttpStageWithProperties', {
15
- httpApi,
8
+ const webSocketApi = new apigw.WebSocketApi(stack, 'WebSocketApi');
9
+ new apigw.WebSocketStage(stack, 'WebSocketStage', {
10
+ webSocketApi,
11
+ stageName: 'dev',
16
12
  throttle: {
17
13
  rateLimit: 1000,
18
14
  burstLimit: 1000,
19
15
  },
20
16
  detailedMetricsEnabled: true,
21
17
  description: 'My Stage',
22
- accessLogSettings: {
23
- destination: new apigwv2.LogGroupLogDestination(testLogGroup),
24
- format: apigw.AccessLogFormat.custom(JSON.stringify({
25
- extendedRequestId: apigw.AccessLogField.contextExtendedRequestId(),
26
- requestTime: apigw.AccessLogField.contextRequestTime(),
27
- })),
28
- },
29
18
  });
30
19
 
31
- new IntegTest(app, 'aws-cdk-aws-apigatewayv2-http-stage-test', {
32
- testCases: [stack],
33
- });
20
+ app.synth();
@@ -1,46 +1,53 @@
1
1
  import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
2
+ import { WebSocketLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
2
3
  import * as iam from 'aws-cdk-lib/aws-iam';
4
+ import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda';
3
5
  import * as cdk from 'aws-cdk-lib';
4
- import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
6
+ import { Stack } from 'aws-cdk-lib';
7
+ import * as integ from '@aws-cdk/integ-tests-alpha';
8
+ import { WebSocketIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
5
9
 
6
- class ExampleComIntegration extends apigatewayv2.HttpRouteIntegration {
7
- public bind(): apigatewayv2.HttpRouteIntegrationConfig {
8
- return {
9
- type: apigatewayv2.HttpIntegrationType.HTTP_PROXY,
10
- payloadFormatVersion: apigatewayv2.PayloadFormatVersion.VERSION_1_0,
11
- method: apigatewayv2.HttpMethod.GET,
12
- uri: 'https://www.example.com/',
13
- };
14
- }
15
- }
16
-
17
- const app = new cdk.App();
10
+ const app = new cdk.App({
11
+ postCliContext: {
12
+ '@aws-cdk/aws-lambda:useCdkManagedLogGroup': false,
13
+ },
14
+ });
18
15
  const stack = new cdk.Stack(app, 'IntegApiGatewayV2Iam');
19
16
  const user = new iam.User(stack, 'User');
20
17
  const userAccessKey = new iam.AccessKey(stack, 'UserAccess', {
21
18
  user,
22
19
  });
23
20
 
24
- const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', {
25
- defaultAuthorizer: new HttpIamAuthorizer(),
21
+ const handler = new Function(stack, 'auth-function', {
22
+ runtime: Runtime.NODEJS_18_X,
23
+ code: Code.fromInline('exports.handler = () => {return true}'),
24
+ handler: 'index.handler',
26
25
  });
27
26
 
28
- const [fooRoute] = httpApi.addRoutes({
29
- integration: new ExampleComIntegration('examplecom'),
30
- path: '/foo',
27
+ const webSocketApi = new apigatewayv2.WebSocketApi(stack, 'WebSocketApi', {
28
+ connectRouteOptions: {
29
+ integration: new WebSocketLambdaIntegration('WebSocketLambdaIntegration', handler),
30
+ authorizer: new WebSocketIamAuthorizer(),
31
+ },
31
32
  });
32
33
 
33
- fooRoute.grantInvoke(user);
34
-
35
- const [booksRoute] = httpApi.addRoutes({
36
- integration: new ExampleComIntegration('examplecom'),
37
- path: '/books/{book}',
34
+ const arn = Stack.of(stack).formatArn({
35
+ service: 'execute-api',
36
+ resource: webSocketApi.apiId,
38
37
  });
39
38
 
40
- booksRoute.grantInvoke(user);
39
+ user.attachInlinePolicy(new iam.Policy(stack, 'AllowInvoke', {
40
+ statements: [
41
+ new iam.PolicyStatement({
42
+ actions: ['execute-api:Invoke'],
43
+ effect: iam.Effect.ALLOW,
44
+ resources: [arn],
45
+ }),
46
+ ],
47
+ }));
41
48
 
42
- new cdk.CfnOutput(stack, 'API', {
43
- value: httpApi.url!,
49
+ new integ.IntegTest(app, 'ApiGatewayV2WebSocketIamTest', {
50
+ testCases: [stack],
44
51
  });
45
52
 
46
53
  new cdk.CfnOutput(stack, 'TESTACCESSKEYID', {
@@ -55,15 +62,4 @@ new cdk.CfnOutput(stack, 'TESTREGION', {
55
62
  value: stack.region,
56
63
  });
57
64
 
58
- /*
59
- * Stack verification steps:
60
- * * Get cURL version 7.75.0 or later so you can use the --aws-sigv4 option
61
- * * Curl <url>/foo without sigv4 and expect a 403
62
- * * Curl <url>/books/something without sigv4 and expect a 403
63
- * * Curl <url>/foo with sigv4 from the authorized user and expect 200
64
- * * Curl <url>/books/something with sigv4 from the authorized user and expect 200
65
- *
66
- * Reference:
67
- * * Using cURL 7.75.0 or later via the official docker image: docker run --rm curlimages/curl -s -o/dev/null -w"%{http_code}" <url>
68
- * * Args to enable sigv4 with authorized credentials: --user "$TESTACCESSKEYID:$TESTSECRETACCESSKEY" --aws-sigv4 "aws:amz:$TESTREGION:execute-api"
69
- */
65
+ app.synth();
@@ -1,85 +1,72 @@
1
- import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
1
+ import { HttpMethod, PassthroughBehavior, WebSocketApi, WebSocketStage } from 'aws-cdk-lib/aws-apigatewayv2';
2
2
  import * as sqs from 'aws-cdk-lib/aws-sqs';
3
- import { App, Stack } from 'aws-cdk-lib';
4
- import { HttpSqsIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
5
- import * as integ from '@aws-cdk/integ-tests-alpha';
3
+ import * as iam from 'aws-cdk-lib/aws-iam';
4
+ import { App, Stack, Aws } from 'aws-cdk-lib';
5
+ import { WebSocketAwsIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
6
+ import { IntegTest } from '@aws-cdk/integ-tests-alpha';
6
7
 
7
- const app = new App();
8
- const stack = new Stack(app, 'sqs-integration');
8
+ /*
9
+ * Stack verification steps:
10
+ * 1. Verify manually that the integration has type "AWS"
11
+ */
9
12
 
10
- const queue = new sqs.Queue(stack, 'Queue');
13
+ const app = new App();
14
+ const stack = new Stack(app, 'integ-aws-websocket-sqs-integration');
11
15
 
12
- const httpApi = new apigwv2.HttpApi(stack, 'Api');
13
- httpApi.addRoutes({
14
- path: '/default',
15
- methods: [apigwv2.HttpMethod.POST],
16
- integration: new HttpSqsIntegration('defaultIntegration', {
17
- queue,
18
- }),
19
- });
20
- httpApi.addRoutes({
21
- path: '/send-message',
22
- methods: [apigwv2.HttpMethod.POST],
23
- integration: new HttpSqsIntegration('sendMessageIntegration', {
24
- queue,
25
- subtype: apigwv2.HttpIntegrationSubtype.SQS_SEND_MESSAGE,
26
- }),
27
- });
28
- httpApi.addRoutes({
29
- path: '/receive-message',
30
- methods: [apigwv2.HttpMethod.POST],
31
- integration: new HttpSqsIntegration('receiveMessageIntegration', {
32
- queue,
33
- subtype: apigwv2.HttpIntegrationSubtype.SQS_RECEIVE_MESSAGE,
34
- }),
35
- });
36
- httpApi.addRoutes({
37
- path: '/delete-message',
38
- methods: [apigwv2.HttpMethod.POST],
39
- integration: new HttpSqsIntegration('deleteMessageIntegration', {
40
- queue,
41
- subtype: apigwv2.HttpIntegrationSubtype.SQS_DELETE_MESSAGE,
42
- }),
16
+ const sqsMessageQueue = new sqs.Queue(stack, 'MessageSQSQueue', {
17
+ fifo: true,
18
+ queueName: 'MessageSQSQueue.fifo',
43
19
  });
44
- httpApi.addRoutes({
45
- path: '/purge-queue',
46
- methods: [apigwv2.HttpMethod.POST],
47
- integration: new HttpSqsIntegration('purgeQueueIntegration', {
48
- queue,
49
- subtype: apigwv2.HttpIntegrationSubtype.SQS_PURGE_QUEUE,
50
- }),
20
+
21
+ // API Gateway WebSocket API
22
+ const webSocketApi = new WebSocketApi(stack, 'webSocketApi', {
23
+ description: 'Send websocket data to SQS which is then processed by a Lambda 2',
24
+ routeSelectionExpression: '$request.body.action',
51
25
  });
52
26
 
53
- const integTest = new integ.IntegTest(app, 'SqsIntegrationIntegTest', {
54
- testCases: [stack],
27
+ // Optionally, create a WebSocket stage
28
+ new WebSocketStage(stack, 'DevStage', {
29
+ webSocketApi: webSocketApi,
30
+ stageName: 'dev',
31
+ autoDeploy: true,
55
32
  });
56
33
 
57
- const defaultAssertion = integTest.assertions.httpApiCall(
58
- `${httpApi.apiEndpoint}/default`, {
59
- body: JSON.stringify({ MessageBody: 'Hello World!' }),
60
- method: 'POST',
61
- },
62
- );
63
- defaultAssertion.expect(integ.ExpectedResult.objectLike({ status: 200, statusText: 'OK' }));
34
+ // IAM Role for API Gateway
35
+ const webSocketApiRole = new iam.Role(stack, 'webSocketApiRole', {
36
+ assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
37
+ });
64
38
 
65
- const sendMessageAssertion = integTest.assertions.httpApiCall(
66
- `${httpApi.apiEndpoint}/send-message`, {
67
- body: JSON.stringify({ MessageBody: 'Hello World!' }),
68
- method: 'POST',
69
- },
39
+ webSocketApiRole.addToPolicy(
40
+ new iam.PolicyStatement({
41
+ actions: ['sqs:SendMessage'],
42
+ effect: iam.Effect.ALLOW,
43
+ resources: [sqsMessageQueue.queueArn],
44
+ }),
70
45
  );
71
- sendMessageAssertion.expect(integ.ExpectedResult.objectLike({ status: 200, statusText: 'OK' }));
72
46
 
73
- const receiveMessageAssertion = integTest.assertions.httpApiCall(
74
- `${httpApi.apiEndpoint}/receive-message`, {
75
- method: 'POST',
76
- },
77
- );
78
- receiveMessageAssertion.expect(integ.ExpectedResult.objectLike({ status: 200, statusText: 'OK' }));
47
+ webSocketApi.addRoute('$default', {
48
+ integration: new WebSocketAwsIntegration('SQSSendMessage', {
49
+ integrationUri: `arn:aws:apigateway:${Aws.REGION}:sqs:path/${Aws.ACCOUNT_ID}/${sqsMessageQueue.queueName}`,
50
+ integrationMethod: HttpMethod.POST,
51
+ credentialsRole: webSocketApiRole,
52
+ passthroughBehavior: PassthroughBehavior.NEVER,
53
+ templateSelectionExpression: '\\$default',
54
+ requestTemplates: {
55
+ $default: 'Action=SendMessage&MessageGroupId=$input.path(\'$.MessageGroupId\')&MessageDeduplicationId=$context.requestId&MessageAttribute.1.Name=connectionId&MessageAttribute.1.Value.StringValue=$context.connectionId&MessageAttribute.1.Value.DataType=String&MessageAttribute.2.Name=requestId&MessageAttribute.2.Value.StringValue=$context.requestId&MessageAttribute.2.Value.DataType=String&MessageBody=$input.json(\'$\')',
56
+ },
57
+ requestParameters: {
58
+ 'integration.request.header.Content-Type': '\'application/x-www-form-urlencoded\'',
59
+ },
60
+ }),
61
+ });
79
62
 
80
- const purgeQueueAssertion = integTest.assertions.httpApiCall(
81
- `${httpApi.apiEndpoint}/purge-queue`, {
82
- method: 'POST',
63
+ new IntegTest(app, 'apigatewayv2-aws-integration-sqs-integ-test', {
64
+ testCases: [stack],
65
+ cdkCommandOptions: {
66
+ deploy: {
67
+ args: {
68
+ rollback: true,
69
+ },
70
+ },
83
71
  },
84
- );
85
- purgeQueueAssertion.expect(integ.ExpectedResult.objectLike({ status: 200, statusText: 'OK' }));
72
+ });
@@ -67,6 +67,297 @@ new cloudfront.Distribution(this, 'myDist', {
67
67
  defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') },
68
68
  });
69
69
  ```
70
+ ### CloudFront SaaS Manager resources
71
+
72
+ #### Multi-tenant distribution and tenant providing ACM certificates
73
+ You can use Cloudfront to build multi-tenant distributions to house applications.
74
+
75
+ To create a multi-tenant distribution w/parameters, create a Distribution construct, and then update DistributionConfig in the CfnDistribution to use connectionMode: "tenant-only"
76
+
77
+ Then create a tenant
78
+ ```ts
79
+ // Create the simple Origin
80
+ const myBucket = new s3.Bucket(this, 'myBucket');
81
+ const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, {
82
+ originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST],
83
+ });
84
+
85
+ // Create the Distribution construct
86
+ const myMultiTenantDistribution = new cloudfront.Distribution(this, 'distribution', {
87
+ defaultBehavior: {
88
+ origin: s3Origin,
89
+ },
90
+ defaultRootObject: 'index.html', // recommended to specify
91
+ });
92
+
93
+ // Access the underlying L1 CfnDistribution to configure SaaS Manager properties which are not yet available in the L2 Distribution construct
94
+ const cfnDistribution = myMultiTenantDistribution.node.defaultChild as cloudfront.CfnDistribution;
95
+
96
+ const defaultCacheBehavior: cloudfront.CfnDistribution.DefaultCacheBehaviorProperty = {
97
+ targetOriginId: myBucket.bucketArn,
98
+ viewerProtocolPolicy: 'allow-all',
99
+ compress: false,
100
+ allowedMethods: ['GET', 'HEAD'],
101
+ cachePolicyId: cloudfront.CachePolicy.CACHING_OPTIMIZED.cachePolicyId
102
+ };
103
+ // Create the updated distributionConfig
104
+ const distributionConfig: cloudfront.CfnDistribution.DistributionConfigProperty = {
105
+ defaultCacheBehavior: defaultCacheBehavior,
106
+ enabled: true,
107
+ // the properties below are optional
108
+ connectionMode: 'tenant-only',
109
+ origins: [
110
+ {
111
+ id: myBucket.bucketArn,
112
+ domainName: myBucket.bucketDomainName,
113
+ s3OriginConfig: {},
114
+ originPath: "/{{tenantName}}"
115
+ },
116
+ ],
117
+ tenantConfig: {
118
+ parameterDefinitions: [
119
+ {
120
+ definition: {
121
+ stringSchema: {
122
+ required: false,
123
+ // the properties below are optional
124
+ comment: 'tenantName',
125
+ defaultValue: 'root',
126
+ },
127
+ },
128
+ name: 'tenantName',
129
+ },
130
+ ],
131
+ },
132
+ };
133
+
134
+ // Override the distribution configuration to enable multi-tenancy.
135
+ cfnDistribution.distributionConfig = distributionConfig;
136
+
137
+ // Create a distribution tenant using an existing ACM certificate
138
+ const cfnDistributionTenant = new cloudfront.CfnDistributionTenant(this, 'distribution-tenant', {
139
+ distributionId: myMultiTenantDistribution.distributionId,
140
+ domains: ['my-tenant.my.domain.com'],
141
+ name: 'my-tenant',
142
+ enabled: true,
143
+ parameters: [ // Override the default 'tenantName' parameter (root) defined in the multi-tenant distribution.
144
+ {
145
+ name: 'tenantName',
146
+ value: 'app',
147
+ },
148
+ ],
149
+ customizations: {
150
+ certificate: {
151
+ arn: 'REPLACE_WITH_ARN', // Certificate must be in us-east-1 region and cover 'my-tenant.my.domain.com'
152
+ },
153
+ },
154
+ });
155
+ ```
156
+
157
+ #### Multi-tenant distribution and tenant with CloudFront-hosted certificate
158
+ A distribution tenant with CloudFront-hosted domain validation is useful if you don't currently have traffic to the domain.
159
+
160
+ Start by creating a parent multi-tenant distribution, then create the distribution tenant.
161
+ ```ts
162
+ import * as route53 from 'aws-cdk-lib/aws-route53';
163
+
164
+ // Create the simple Origin
165
+ const myBucket = new s3.Bucket(this, 'myBucket');
166
+ const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, {
167
+ originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST],
168
+ });
169
+
170
+ // Create the Distribution construct
171
+ const myMultiTenantDistribution = new cloudfront.Distribution(this, 'cf-hosted-distribution', {
172
+ defaultBehavior: {
173
+ origin: s3Origin,
174
+ },
175
+ defaultRootObject: 'index.html', // recommended to specify
176
+ });
177
+
178
+ // Access the underlying L1 CfnDistribution to configure SaaS Manager properties which are not yet available in the L2 Distribution construct
179
+ const cfnDistribution = myMultiTenantDistribution.node.defaultChild as cloudfront.CfnDistribution;
180
+
181
+ const defaultCacheBehavior: cloudfront.CfnDistribution.DefaultCacheBehaviorProperty = {
182
+ targetOriginId: myBucket.bucketArn,
183
+ viewerProtocolPolicy: 'allow-all',
184
+ compress: false,
185
+ allowedMethods: ['GET', 'HEAD'],
186
+ cachePolicyId: cloudfront.CachePolicy.CACHING_OPTIMIZED.cachePolicyId
187
+ };
188
+ // Create the updated distributionConfig
189
+ const distributionConfig: cloudfront.CfnDistribution.DistributionConfigProperty = {
190
+ defaultCacheBehavior: defaultCacheBehavior,
191
+ enabled: true,
192
+ // the properties below are optional
193
+ connectionMode: 'tenant-only',
194
+ origins: [
195
+ {
196
+ id: myBucket.bucketArn,
197
+ domainName: myBucket.bucketDomainName,
198
+ s3OriginConfig: {},
199
+ originPath: "/{{tenantName}}"
200
+ },
201
+ ],
202
+ tenantConfig: {
203
+ parameterDefinitions: [
204
+ {
205
+ definition: {
206
+ stringSchema: {
207
+ required: false,
208
+ // the properties below are optional
209
+ comment: 'tenantName',
210
+ defaultValue: 'root',
211
+ },
212
+ },
213
+ name: 'tenantName',
214
+ },
215
+ ],
216
+ },
217
+ };
218
+
219
+ // Override the distribution configuration to enable multi-tenancy.
220
+ cfnDistribution.distributionConfig = distributionConfig;
221
+
222
+ // Create a connection group and a cname record in an existing hosted zone to validate domain ownership
223
+ const connectionGroup = new cloudfront.CfnConnectionGroup(this, 'cf-hosted-connection-group', {
224
+ enabled: true,
225
+ ipv6Enabled: true,
226
+ name: 'my-connection-group',
227
+ });
228
+
229
+ // Import the existing hosted zone info, replacing with your hostedZoneId and zoneName
230
+ const hostedZoneId = 'YOUR_HOSTED_ZONE_ID';
231
+ const zoneName = 'my.domain.com';
232
+ const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, 'hosted-zone', {
233
+ hostedZoneId,
234
+ zoneName,
235
+ });
236
+
237
+ const record = new route53.CnameRecord(this, 'cname-record', {
238
+ domainName: connectionGroup.attrRoutingEndpoint,
239
+ zone: hostedZone,
240
+ recordName: 'cf-hosted-tenant.my.domain.com',
241
+ });
242
+
243
+ // Create the cloudfront-hosted tenant, passing in the previously created connection group
244
+ const cloudfrontHostedTenant = new cloudfront.CfnDistributionTenant(this, 'cf-hosted-tenant', {
245
+ distributionId: myMultiTenantDistribution.distributionId,
246
+ name: 'cf-hosted-tenant',
247
+ domains: ['cf-hosted-tenant.my.domain.com'],
248
+ connectionGroupId: connectionGroup.attrId,
249
+ enabled: true,
250
+ managedCertificateRequest: {
251
+ validationTokenHost: 'cloudfront'
252
+ },
253
+ });
254
+ ```
255
+
256
+ #### Multi-tenant distribution and tenant with self-hosted certificate
257
+ A tenant with self-hosted domain validation is useful if you already have traffic to the domain and can't tolerate downtime during migration to multi-tenant architecture.
258
+
259
+ The tenant will be created, and the managed certificate will be awaiting validation of domain ownership. You can then validate domain ownership via http redirect or token file upload. [More details here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/managed-cloudfront-certificates.html#complete-domain-ownership)
260
+
261
+ Traffic won't be migrated until you update your hosted zone to point the tenant domain to the CloudFront RoutingEndpoint.
262
+
263
+ Start by creating a parent multi-tenant distribution
264
+ ```ts
265
+ // Create the simple Origin
266
+ const myBucket = new s3.Bucket(this, 'myBucket');
267
+ const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, {
268
+ originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST],
269
+ });
270
+
271
+ // Create the Distribution construct
272
+ const myMultiTenantDistribution = new cloudfront.Distribution(this, 'cf-hosted-distribution', {
273
+ defaultBehavior: {
274
+ origin: s3Origin,
275
+ },
276
+ defaultRootObject: 'index.html', // recommended to specify
277
+ });
278
+
279
+ // Access the underlying L1 CfnDistribution to configure SaaS Manager properties which are not yet available in the L2 Distribution construct
280
+ const cfnDistribution = myMultiTenantDistribution.node.defaultChild as cloudfront.CfnDistribution;
281
+
282
+ const defaultCacheBehavior: cloudfront.CfnDistribution.DefaultCacheBehaviorProperty = {
283
+ targetOriginId: myBucket.bucketArn,
284
+ viewerProtocolPolicy: 'allow-all',
285
+ compress: false,
286
+ allowedMethods: ['GET', 'HEAD'],
287
+ cachePolicyId: cloudfront.CachePolicy.CACHING_OPTIMIZED.cachePolicyId
288
+ };
289
+ // Create the updated distributionConfig
290
+ const distributionConfig: cloudfront.CfnDistribution.DistributionConfigProperty = {
291
+ defaultCacheBehavior: defaultCacheBehavior,
292
+ enabled: true,
293
+ // the properties below are optional
294
+ connectionMode: 'tenant-only',
295
+ origins: [
296
+ {
297
+ id: myBucket.bucketArn,
298
+ domainName: myBucket.bucketDomainName,
299
+ s3OriginConfig: {},
300
+ originPath: "/{{tenantName}}"
301
+ },
302
+ ],
303
+ tenantConfig: {
304
+ parameterDefinitions: [
305
+ {
306
+ definition: {
307
+ stringSchema: {
308
+ required: false,
309
+ // the properties below are optional
310
+ comment: 'tenantName',
311
+ defaultValue: 'root',
312
+ },
313
+ },
314
+ name: 'tenantName',
315
+ },
316
+ ],
317
+ },
318
+ };
319
+
320
+ // Override the distribution configuration to enable multi-tenancy.
321
+ cfnDistribution.distributionConfig = distributionConfig;
322
+
323
+ // Create a connection group so we have access to the RoutingEndpoint associated with the tenant we are about to create
324
+ const connectionGroup = new cloudfront.CfnConnectionGroup(this, 'self-hosted-connection-group', {
325
+ enabled: true,
326
+ ipv6Enabled: true,
327
+ name: 'self-hosted-connection-group',
328
+ });
329
+
330
+ // Export the RoutingEndpoint, skip this step if you'd prefer to fetch it from the CloudFront console or via Cloudfront.ListConnectionGroups API
331
+ new CfnOutput(this, 'RoutingEndpoint', {
332
+ value: connectionGroup.attrRoutingEndpoint,
333
+ description: 'CloudFront Routing Endpoint to be added to my hosted zone CNAME records',
334
+ });
335
+
336
+ // Create a distribution tenant with a self-hosted domain.
337
+ const selfHostedTenant = new cloudfront.CfnDistributionTenant(this, 'self-hosted-tenant', {
338
+ distributionId: myMultiTenantDistribution.distributionId,
339
+ connectionGroupId: connectionGroup.attrId,
340
+ name: 'self-hosted-tenant',
341
+ domains: ['self-hosted-tenant.my.domain.com'],
342
+ enabled: true,
343
+ managedCertificateRequest: {
344
+ primaryDomainName: 'self-hosted-tenant.my.domain.com',
345
+ validationTokenHost: 'self-hosted',
346
+ },
347
+ });
348
+ ```
349
+ While CDK is deploying, it will attempt to validate domain ownership by confirming that a validation token is served directly from your domain, or via http redirect.
350
+
351
+ [follow the steps here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/managed-cloudfront-certificates.html#complete-domain-ownership) to complete domain setup before deploying this CDK stack, or while CDK is in the waiting state during tenant creation. Refer to the section "I have existing traffic"
352
+
353
+ A simple option for validating via http redirect, would be to add a rewrite rule like so to your server (Apache in this example)
354
+ ```
355
+ RewriteEngine On
356
+ RewriteCond %{REQUEST_URI} ^/\.well-known/pki-validation/(.+)$ [NC]
357
+ RewriteRule ^(.*)$ https://validation.us-east-1.acm-validations.aws/%{ENV:AWS_ACCOUNT_ID}/.well-known/pki-validation/%1 [R=301,L]
358
+ ```
359
+
360
+ Then, when you are ready to accept traffic, follow the steps [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/managed-cloudfront-certificates.html#point-domains-to-cloudfront) using the RoutingEndpoint from above to configure DNS to point to CloudFront.
70
361
 
71
362
  ### VPC origins
72
363
 
@@ -548,7 +839,7 @@ const functionVersion = lambda.Version.fromVersionArn(this, 'Version', 'arn:aws:
548
839
 
549
840
  new cloudfront.Distribution(this, 'distro', {
550
841
  defaultBehavior: {
551
- origin: new origins.S3Origin(s3Bucket),
842
+ origin: origins.S3BucketOrigin.withOriginAccessControl(s3Bucket),
552
843
  edgeLambdas: [
553
844
  {
554
845
  functionVersion,
@@ -848,6 +848,19 @@ dashboard.addWidgets(new cloudwatch.LogQueryWidget({
848
848
  }));
849
849
  ```
850
850
 
851
+ Log Insights QL is the default query language. You may specify an [alternate query language: OpenSearch PPL or SQL](https://aws.amazon.com/blogs/aws/new-amazon-cloudwatch-and-amazon-opensearch-service-launch-an-integrated-analytics-experience/), if desired:
852
+
853
+ ```ts
854
+ declare const dashboard: cloudwatch.Dashboard;
855
+
856
+ dashboard.addWidgets(new cloudwatch.LogQueryWidget({
857
+ logGroupNames: ['my-log-group'],
858
+ view: cloudwatch.LogQueryVisualizationType.TABLE,
859
+ queryString: "SELECT count(*) as count FROM 'my-log-group'",
860
+ queryLanguage: cloudwatch.LogQueryLanguage.SQL,
861
+ }));
862
+ ```
863
+
851
864
  ### Custom widget
852
865
 
853
866
  A `CustomWidget` shows the result of an AWS Lambda function:
@@ -94,6 +94,18 @@ dashboard.addWidgets(new cloudwatch.LogQueryWidget({
94
94
  | filter @message like /Error/`,
95
95
  accountId: '123456789012',
96
96
  }));
97
+ dashboard.addWidgets(new cloudwatch.LogQueryWidget({
98
+ title: 'Errors in my log group - SQL',
99
+ logGroupNames: ['my-log-group'],
100
+ queryString: "SELECT count(*) as count FROM 'my-log-group'",
101
+ queryLanguage: cloudwatch.LogQueryLanguage.SQL,
102
+ }));
103
+ dashboard.addWidgets(new cloudwatch.LogQueryWidget({
104
+ title: 'Errors in my log group - PPL',
105
+ logGroupNames: ['my-log-group'],
106
+ queryString: 'fields `@message`\ | sort - `@timestamp`',
107
+ queryLanguage: cloudwatch.LogQueryLanguage.PPL,
108
+ }));
97
109
  dashboard.addWidgets(new cloudwatch.SingleValueWidget({
98
110
  title: 'Sent message size',
99
111
  metrics: [sentMessageSizeMetric],