konokenj.cdk-api-mcp-server 0.41.0__py3-none-any.whl → 0.42.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 (37) hide show
  1. cdk_api_mcp_server/__about__.py +1 -1
  2. cdk_api_mcp_server/resources/aws-cdk/constructs/@aws-cdk/aws-bedrock-alpha/README.md +540 -0
  3. cdk_api_mcp_server/resources/aws-cdk/constructs/@aws-cdk/aws-eks-v2-alpha/README.md +44 -46
  4. cdk_api_mcp_server/resources/aws-cdk/constructs/@aws-cdk/aws-lambda-python-alpha/README.md +6 -6
  5. cdk_api_mcp_server/resources/aws-cdk/constructs/@aws-cdk/aws-s3tables-alpha/README.md +28 -1
  6. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2/integ.api-dualstack.ts +3 -4
  7. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2/integ.api.ts +2 -4
  8. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2/integ.stage.ts +7 -20
  9. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2-authorizers/integ.iam.ts +34 -38
  10. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-apigatewayv2-integrations/integ.sqs.ts +58 -71
  11. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-codepipeline-actions/integ.pipeline-elastic-beanstalk-deploy.ts +1 -1
  12. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-cognito/README.md +11 -0
  13. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-cognito/integ.user-pool-client-explicit-props.ts +1 -0
  14. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-dynamodb/README.md +38 -13
  15. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-dynamodb/integ.dynamodb-v2.cci.ts +49 -0
  16. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-dynamodb/integ.dynamodb.cci.ts +27 -0
  17. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-dynamodb/integ.dynamodb.contirubtor-insights-for-gsi.ts +6 -2
  18. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-dynamodb/integ.table-v2-global.ts +9 -3
  19. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs/README.md +3 -0
  20. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs/integ.ebs-volume-initialization-rate.ts +80 -0
  21. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs-patterns/README.md +2 -0
  22. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-ecs-patterns/integ.alb-fargate-service-smart-defaults.ts +143 -0
  23. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-events/README.md +25 -3
  24. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-events/integ.archive-customer-managed-key.ts +23 -0
  25. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-s3-deployment/README.md +18 -0
  26. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-sns/README.md +2 -0
  27. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-sns-subscriptions/integ.sns-sqs-subscription-filter.ts +75 -0
  28. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-sns-subscriptions/integ.sns-sqs.ts +21 -40
  29. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-stepfunctions-tasks/integ.invoke-jsonata.ts +87 -80
  30. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-stepfunctions-tasks/integ.invoke.ts +87 -69
  31. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/aws-stepfunctions-tasks/integ.start-job-run.ts +102 -104
  32. cdk_api_mcp_server/resources/aws-cdk/constructs/aws-cdk-lib/cx-api/FEATURE_FLAGS.md +28 -1
  33. {konokenj_cdk_api_mcp_server-0.41.0.dist-info → konokenj_cdk_api_mcp_server-0.42.0.dist-info}/METADATA +2 -2
  34. {konokenj_cdk_api_mcp_server-0.41.0.dist-info → konokenj_cdk_api_mcp_server-0.42.0.dist-info}/RECORD +37 -31
  35. {konokenj_cdk_api_mcp_server-0.41.0.dist-info → konokenj_cdk_api_mcp_server-0.42.0.dist-info}/WHEEL +0 -0
  36. {konokenj_cdk_api_mcp_server-0.41.0.dist-info → konokenj_cdk_api_mcp_server-0.42.0.dist-info}/entry_points.txt +0 -0
  37. {konokenj_cdk_api_mcp_server-0.41.0.dist-info → konokenj_cdk_api_mcp_server-0.42.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Integration test for the conditional openListener behavior in ApplicationLoadBalancedFargateService.
3
+ *
4
+ * This test validates the security feature that automatically sets openListener to false when custom
5
+ * security groups are detected on the load balancer, preventing unintended internet exposure.
6
+ *
7
+ * Test scenarios:
8
+ * 1. DefaultService: No custom security groups provided
9
+ * - Expected: openListener defaults to true, creates 0.0.0.0/0 ingress rules
10
+ * - Validates: Default behavior when CDK manages all security groups
11
+ *
12
+ * 2. ExplicitOpenService: Explicit openListener: true
13
+ * - Expected: Creates 0.0.0.0/0 ingress rules regardless of other settings
14
+ * - Validates: Explicit override functionality works correctly
15
+ *
16
+ * 3. ExplicitClosedService: Explicit openListener: false
17
+ * - Expected: Does NOT create 0.0.0.0/0 ingress rules
18
+ * - Validates: Explicit closed listener prevents internet access
19
+ *
20
+ * 4. ConditionalWithCustomSG: Custom security groups + no explicit openListener
21
+ * - Expected: Conditional behavior kicks in, openListener defaults to false
22
+ * - Validates: Core feature - prevents 0.0.0.0/0 rules when custom SGs detected
23
+ *
24
+ * The test uses AWS SDK calls to verify actual security group configurations in deployed resources,
25
+ * ensuring the feature works correctly in real AWS environments.
26
+ */
27
+
28
+ import { Vpc, SecurityGroup, Port } from 'aws-cdk-lib/aws-ec2';
29
+ import { Cluster, ContainerImage } from 'aws-cdk-lib/aws-ecs';
30
+ import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
31
+ import { App, Stack, Duration } from 'aws-cdk-lib';
32
+ import * as integ from '@aws-cdk/integ-tests-alpha';
33
+ import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
34
+
35
+ const app = new App({
36
+ postCliContext: {
37
+ // Enable the feature flag for this test
38
+ '@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener': true,
39
+ },
40
+ });
41
+
42
+ const stack = new Stack(app, 'aws-ecs-integ-alb-fg-smart-defaults');
43
+ const vpc = new Vpc(stack, 'Vpc', { maxAzs: 3, natGateways: 1, restrictDefaultSecurityGroup: false });
44
+ const cluster = new Cluster(stack, 'Cluster', { vpc });
45
+
46
+ // Test case 1: Service with conditional default (no openListener specified)
47
+ // CDK creates load balancer, should default to openListener: true (no custom security groups)
48
+ new ApplicationLoadBalancedFargateService(stack, 'SmartDefaultService', {
49
+ cluster,
50
+ memoryLimitMiB: 512,
51
+ taskImageOptions: {
52
+ image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
53
+ },
54
+ // No openListener specified - should default to true since no custom security groups
55
+ });
56
+
57
+ // Test case 2: Service with explicit openListener: true
58
+ new ApplicationLoadBalancedFargateService(stack, 'ExplicitOpenService', {
59
+ cluster,
60
+ memoryLimitMiB: 512,
61
+ taskImageOptions: {
62
+ image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
63
+ },
64
+ openListener: true, // Should create 0.0.0.0/0 rules
65
+ listenerPort: 8080,
66
+ });
67
+
68
+ // Test case 3: Service with explicit openListener: false
69
+ new ApplicationLoadBalancedFargateService(stack, 'ExplicitClosedService', {
70
+ cluster,
71
+ memoryLimitMiB: 512,
72
+ taskImageOptions: {
73
+ image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
74
+ },
75
+ openListener: false, // Should NOT create 0.0.0.0/0 rules
76
+ listenerPort: 9090,
77
+ });
78
+
79
+ // Test case 4: Service with custom security groups (conditional default should apply)
80
+ const customSecurityGroup = new SecurityGroup(stack, 'CustomSecurityGroup', {
81
+ vpc,
82
+ description: 'Custom security group for load balancer',
83
+ });
84
+
85
+ // Add a custom rule to the security group
86
+ customSecurityGroup.addIngressRule(
87
+ customSecurityGroup,
88
+ Port.tcp(80),
89
+ 'Allow HTTP from custom security group',
90
+ );
91
+
92
+ const customLoadBalancer = new ApplicationLoadBalancer(stack, 'CustomLoadBalancer', {
93
+ vpc,
94
+ internetFacing: true,
95
+ securityGroup: customSecurityGroup,
96
+ });
97
+
98
+ // This should use conditional default (openListener: false) because custom security groups are detected
99
+ new ApplicationLoadBalancedFargateService(stack, 'SmartDefaultWithCustomSG', {
100
+ cluster,
101
+ memoryLimitMiB: 512,
102
+ taskImageOptions: {
103
+ image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
104
+ },
105
+ loadBalancer: customLoadBalancer,
106
+ // No openListener specified - should default to false due to custom security groups
107
+ });
108
+
109
+ const integTest = new integ.IntegTest(app, 'albFargateServiceSmartDefaultsTest', {
110
+ testCases: [stack],
111
+ });
112
+
113
+ // Validate the core conditional behavior by checking the custom security group
114
+ // This confirms that when custom security groups are provided, the conditional default prevents
115
+ // creating overly permissive 0.0.0.0/0 ingress rules
116
+ // Assert that the custom security group only contains self-referencing rules (no 0.0.0.0/0)
117
+ // This validates the feature prevents unintended internet exposure
118
+ integTest.assertions.awsApiCall('EC2', 'describeSecurityGroups', {
119
+ GroupIds: [customSecurityGroup.securityGroupId],
120
+ }).expect(integ.ExpectedResult.objectLike({
121
+ SecurityGroups: [
122
+ {
123
+ IpPermissions: integ.Match.arrayWith([
124
+ integ.Match.objectLike({
125
+ FromPort: 80,
126
+ ToPort: 80,
127
+ // Verify only security group references exist, no public internet access (0.0.0.0/0)
128
+ UserIdGroupPairs: integ.Match.arrayWith([
129
+ integ.Match.objectLike({
130
+ GroupId: customSecurityGroup.securityGroupId,
131
+ }),
132
+ ]),
133
+ // Ensure no IpRanges with 0.0.0.0/0 are present
134
+ IpRanges: [],
135
+ }),
136
+ ]),
137
+ },
138
+ ],
139
+ })).waitForAssertions({
140
+ totalTimeout: Duration.minutes(5),
141
+ });
142
+
143
+ app.synth();
@@ -332,6 +332,28 @@ new events.EventBus(this, 'Bus', {
332
332
  });
333
333
  ```
334
334
 
335
- **Note**: Archives and schema discovery are not supported for event buses encrypted using a customer managed key.
336
- To enable archives or schema discovery on an event bus, choose to use an AWS owned key.
337
- For more information, see [KMS key options for event bus encryption](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-encryption-at-rest-key-options.html).
335
+ To use a customer managed key for an archive, use the `kmsKey` attribute.
336
+
337
+ Note: When you attach a customer managed key to either an EventBus or an Archive, a policy that allows EventBridge to interact with your resource will be added.
338
+
339
+ ```ts
340
+ import * as kms from 'aws-cdk-lib/aws-kms';
341
+ import { Archive, EventBus } from 'aws-cdk-lib/aws-events';
342
+
343
+ const stack = new Stack();
344
+
345
+ declare const kmsKey: kms.IKey;
346
+
347
+ const eventBus = new EventBus(stack, 'Bus');
348
+
349
+ const archive = new Archive(stack, 'Archive', {
350
+ kmsKey: kmsKey,
351
+ sourceEventBus: eventBus,
352
+ eventPattern: {
353
+ source: ['aws.ec2']
354
+ },
355
+ });
356
+ ```
357
+
358
+ To enable archives or schema discovery on an event bus, customers has the choice of using either an AWS owned key or a customer managed key.
359
+ For more information, see [KMS key options for event bus encryption](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-encryption-at-rest-key-options.html).
@@ -0,0 +1,23 @@
1
+ import * as kms from 'aws-cdk-lib/aws-kms';
2
+ import { App, RemovalPolicy, Stack } from 'aws-cdk-lib';
3
+ import { IntegTest } from '@aws-cdk/integ-tests-alpha';
4
+ import { Archive, EventBus } from 'aws-cdk-lib/aws-events';
5
+
6
+ const app = new App();
7
+ const stack = new Stack(app, 'archive-customer-managed-key');
8
+
9
+ const kmsKey = new kms.Key(stack, 'KmsKey', {
10
+ removalPolicy: RemovalPolicy.DESTROY,
11
+ });
12
+
13
+ new Archive(stack, 'Archive', {
14
+ kmsKey: kmsKey,
15
+ sourceEventBus: EventBus.fromEventBusName(stack, 'DefaultEventBus', 'default'),
16
+ eventPattern: {
17
+ source: ['test'],
18
+ },
19
+ });
20
+
21
+ new IntegTest(app, 'archive-customer-managed-key-test', {
22
+ testCases: [stack],
23
+ });
@@ -325,6 +325,24 @@ new s3deploy.BucketDeployment(this, 'DeployWithInvalidation', {
325
325
  });
326
326
  ```
327
327
 
328
+ By default, the deployment will wait for invalidation to succeed to complete. This will poll Cloudfront for a maximum of 13 minutes to check for a successful invalidation. The drawback to this is that the deployment will fail if invalidation fails or if it takes longer than 13 minutes. As a workaround, there is the option `waitForDistributionInvalidation`, which can be set to false to skip waiting for the invalidation, but this can be risky as invalidation errors will not be reported.
329
+
330
+ ```ts
331
+ import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
332
+
333
+ declare const bucket: s3.IBucket;
334
+ declare const distribution: cloudfront.IDistribution;
335
+
336
+ new s3deploy.BucketDeployment(this, 'DeployWithInvalidation', {
337
+ sources: [s3deploy.Source.asset('./website-dist')],
338
+ destinationBucket: bucket,
339
+ distribution,
340
+ distributionPaths: ['/images/*.png'],
341
+ // Invalidate cache but don't wait or verify that invalidation has completed successfully.
342
+ waitForDistributionInvalidation: false
343
+ });
344
+ ```
345
+
328
346
  ## Signed Content Payloads
329
347
 
330
348
  By default, deployment uses streaming uploads which set the `x-amz-content-sha256`
@@ -121,6 +121,7 @@ declare const fn: lambda.Function;
121
121
 
122
122
  // Lambda should receive only message matching the following conditions on message body:
123
123
  // color: 'red' or 'orange'
124
+ // store: property must not be present
124
125
  myTopic.addSubscription(new subscriptions.LambdaSubscription(fn, {
125
126
  filterPolicyWithMessageBody: {
126
127
  background: sns.FilterOrPolicy.policy({
@@ -128,6 +129,7 @@ myTopic.addSubscription(new subscriptions.LambdaSubscription(fn, {
128
129
  allowlist: ['red', 'orange'],
129
130
  })),
130
131
  }),
132
+ store: sns.FilterOrPolicy.filter(sns.SubscriptionFilter.notExistsFilter()),
131
133
  },
132
134
  }));
133
135
  ```
@@ -0,0 +1,75 @@
1
+ import * as sns from 'aws-cdk-lib/aws-sns';
2
+ import * as sqs from 'aws-cdk-lib/aws-sqs';
3
+ import * as cdk from 'aws-cdk-lib';
4
+ import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests-alpha';
5
+ import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
6
+
7
+ const app = new cdk.App();
8
+
9
+ const stack = new cdk.Stack(app, 'sns-sqs-subscription-filter');
10
+
11
+ const topic = new sns.Topic(stack, 'MyTopic');
12
+
13
+ const queue1 = new sqs.Queue(stack, 'MyQueue1');
14
+ const queue2 = new sqs.Queue(stack, 'MyQueue2');
15
+
16
+ topic.addSubscription(new subs.SqsSubscription(queue1, {
17
+ filterPolicyWithMessageBody: {
18
+ background: sns.Policy.policy({
19
+ color: sns.Filter.filter(sns.SubscriptionFilter.stringFilter({
20
+ allowlist: ['red', 'green'],
21
+ })),
22
+ }),
23
+ price: sns.Filter.filter(sns.SubscriptionFilter.numericFilter({
24
+ allowlist: [100, 200],
25
+ })),
26
+ store: sns.Filter.filter(sns.SubscriptionFilter.existsFilter()),
27
+ },
28
+ rawMessageDelivery: true,
29
+ }));
30
+
31
+ topic.addSubscription(new subs.SqsSubscription(queue2, {
32
+ filterPolicyWithMessageBody: {
33
+ background: sns.Policy.policy({
34
+ color: sns.Filter.filter(sns.SubscriptionFilter.stringFilter({
35
+ denylist: ['red', 'green'],
36
+ })),
37
+ }),
38
+ price: sns.Filter.filter(sns.SubscriptionFilter.numericFilter({
39
+ betweenStrict: { start: 100, stop: 200 },
40
+ })),
41
+ store: sns.Filter.filter(sns.SubscriptionFilter.notExistsFilter()),
42
+ },
43
+ rawMessageDelivery: true,
44
+ }));
45
+
46
+ const integTest = new IntegTest(app, 'SNS Subscription filters', {
47
+ testCases: [stack],
48
+ });
49
+
50
+ const message1 = JSON.stringify({ background: { color: 'green' }, price: 200, store: 'fans' }); // matches queue1 subscription filter
51
+ const message2 = JSON.stringify({ background: { color: 'blue' }, price: 150 }); // matches queue2 subscription filter
52
+
53
+ // publish messages to SNS topic
54
+ integTest.assertions.awsApiCall('SNS', 'publish', {
55
+ Message: message1,
56
+ TopicArn: topic.topicArn,
57
+ });
58
+ integTest.assertions.awsApiCall('SNS', 'publish', {
59
+ Message: message2,
60
+ TopicArn: topic.topicArn,
61
+ });
62
+
63
+ // check messages arrived at expected destination queue
64
+ integTest.assertions.awsApiCall('SQS', 'receiveMessage', {
65
+ QueueUrl: queue1.queueUrl,
66
+ WaitTimeSeconds: 20,
67
+ }).expect(ExpectedResult.objectLike({
68
+ Messages: [{ Body: message1 }],
69
+ }));
70
+ integTest.assertions.awsApiCall('SQS', 'receiveMessage', {
71
+ QueueUrl: queue2.queueUrl,
72
+ WaitTimeSeconds: 20,
73
+ }).expect(ExpectedResult.objectLike({
74
+ Messages: [{ Body: message2 }],
75
+ }));
@@ -3,50 +3,31 @@ import * as sqs from 'aws-cdk-lib/aws-sqs';
3
3
  import * as cdk from 'aws-cdk-lib';
4
4
  import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests-alpha';
5
5
  import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
6
- class SnsToSqsStack extends cdk.Stack {
7
- topic: sns.Topic;
8
- queue: sqs.Queue;
9
- constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
10
- super(scope, id, props);
11
- this.topic = new sns.Topic(this, 'MyTopic');
12
- const queueStack = new cdk.Stack(app, 'QueueStack');
13
- this.queue = new sqs.Queue(queueStack, 'MyQueue');
14
- this.topic.addSubscription(new subs.SqsSubscription(this.queue, {
15
- filterPolicyWithMessageBody: {
16
- background: sns.Policy.policy({
17
- color: sns.Filter.filter(sns.SubscriptionFilter.stringFilter({
18
- allowlist: ['red', 'green'],
19
- denylist: ['white', 'orange'],
20
- })),
21
- }),
22
- price: sns.Filter.filter(sns.SubscriptionFilter.numericFilter({
23
- allowlist: [100, 200],
24
- between: { start: 300, stop: 350 },
25
- greaterThan: 500,
26
- lessThan: 1000,
27
- betweenStrict: { start: 2000, stop: 3000 },
28
- })),
29
- },
30
- }));
31
- }
32
- }
33
- // Beginning of the test suite
6
+
34
7
  const app = new cdk.App();
35
- const stack = new SnsToSqsStack(app, 'SnsToSqsStack');
8
+
9
+ const stack = new cdk.Stack(app, 'SnsToSqsStack');
10
+
11
+ const topic = new sns.Topic(stack, 'MyTopic');
12
+
13
+ const queue = new sqs.Queue(stack, 'MyQueue');
14
+
15
+ topic.addSubscription(new subs.SqsSubscription(queue, { rawMessageDelivery: true }));
16
+
36
17
  const integTest = new IntegTest(app, 'SNS Subscriptions', {
37
- testCases: [
38
- stack,
39
- ],
18
+ testCases: [stack],
40
19
  });
20
+
21
+ const message = JSON.stringify({ color: 'green', price: 200 });
22
+
41
23
  integTest.assertions.awsApiCall('SNS', 'publish', {
42
- Message: '{ background: { color: \'green\' }, price: 200 }',
43
- TopicArn: stack.topic.topicArn,
24
+ Message: message,
25
+ TopicArn: topic.topicArn,
44
26
  });
45
- const message = integTest.assertions.awsApiCall('SQS', 'receiveMessage', {
46
- QueueUrl: stack.queue.queueUrl,
27
+
28
+ integTest.assertions.awsApiCall('SQS', 'receiveMessage', {
29
+ QueueUrl: queue.queueUrl,
47
30
  WaitTimeSeconds: 20,
48
- });
49
- message.expect(ExpectedResult.objectLike({
50
- Messages: [{ Body: '{color: "green", price: 200}' }],
31
+ }).expect(ExpectedResult.objectLike({
32
+ Messages: [{ Body: message }],
51
33
  }));
52
- app.synth();
@@ -1,112 +1,119 @@
1
- import * as path from 'path';
2
- import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests-alpha';
3
- import * as cdk from 'aws-cdk-lib';
4
- import * as apigateway from 'aws-cdk-lib/aws-apigateway';
5
- import * as events from 'aws-cdk-lib/aws-events';
1
+ import { Code, Function } from 'aws-cdk-lib/aws-lambda';
6
2
  import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
7
- import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
8
- import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks';
9
- import { password, username } from './my-lambda-handler';
3
+ import * as cdk from 'aws-cdk-lib';
4
+ import { LambdaInvoke } from 'aws-cdk-lib/aws-stepfunctions-tasks';
5
+ import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests-alpha';
6
+ import { STANDARD_NODEJS_RUNTIME } from '../../../config';
10
7
 
11
8
  /*
12
- * Creates an API Gateway instance with a GET method and mock integration,
13
- * secured with basic auth. It then creates a matching Connection and uses it
14
- * in a state machine with a task state to invoke the endpoint.
9
+ * Creates a state machine with a task state to invoke a Lambda function
10
+ * The state machine creates a couple of Lambdas that pass results forward
11
+ * and into a Choice state that validates the output.
12
+ *
13
+ * Stack verification steps:
14
+ * The generated State Machine can be executed from the CLI (or Step Functions console)
15
+ * and runs with an execution status of `Succeeded`.
15
16
  *
16
- * Stack verification steps :
17
- * * aws stepfunctions start-execution --state-machine-arn <deployed state machine arn> : should return execution arn
18
- * * aws stepfunctions describe-execution --execution-arn <execution-arn generated before> : should return status as SUCCEEDED
17
+ * -- aws stepfunctions start-execution --state-machine-arn <state-machine-arn-from-output> provides execution arn
18
+ * -- aws stepfunctions describe-execution --execution-arn <state-machine-arn-from-output> returns a status of `Succeeded`
19
19
  */
20
20
  const app = new cdk.App({
21
21
  postCliContext: {
22
22
  '@aws-cdk/aws-lambda:useCdkManagedLogGroup': false,
23
23
  },
24
24
  });
25
- const stack = new cdk.Stack(app, 'aws-stepfunctions-tasks-http-invoke-integ');
25
+ const stack = new cdk.Stack(app, 'aws-stepfunctions-tasks-lambda-invoke-jsonata-integ');
26
26
 
27
- const authorizerHandler = new lambda.NodejsFunction(stack, 'AuthorizerHandler', {
28
- entry: path.resolve(__dirname, 'my-lambda-handler', 'index.ts'),
29
- handler: 'handler',
27
+ const submitJobLambda = new Function(stack, 'submitJobLambda', {
28
+ code: Code.fromInline(`exports.handler = async (event, context) => {
29
+ return {
30
+ statusCode: '200',
31
+ body: 'hello, world!',
32
+ ...event,
33
+ };
34
+ };`),
35
+ runtime: STANDARD_NODEJS_RUNTIME,
36
+ handler: 'index.handler',
30
37
  });
31
38
 
32
- const authorizer = new apigateway.TokenAuthorizer(stack, 'Authorizer', {
33
- handler: authorizerHandler,
34
- identitySource: 'method.request.header.Authorization',
35
- resultsCacheTtl: cdk.Duration.seconds(0),
36
- });
37
-
38
- const api = new apigateway.RestApi(stack, 'IntegRestApi');
39
-
40
- api.root.addResource('test').addMethod(
41
- 'GET',
42
- new apigateway.MockIntegration({
43
- integrationResponses: [
44
- {
45
- statusCode: '200',
46
- responseTemplates: {
47
- 'application/json': JSON.stringify({ message: 'Hello, tester!' }),
48
- },
49
- },
50
- ],
51
- passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
52
- requestTemplates: {
53
- 'application/json': '{ "statusCode": 200 }',
54
- },
39
+ const submitJob = LambdaInvoke.jsonata(stack, 'Invoke Handler', {
40
+ lambdaFunction: submitJobLambda,
41
+ payload: sfn.TaskInput.fromObject({
42
+ execId: '{% $states.context.Execution.Id %}',
43
+ execInput: '{% $states.context.Execution.Input %}',
44
+ execName: '{% $states.context.Execution.Name %}',
45
+ execRoleArn: '{% $states.context.Execution.RoleArn %}',
46
+ execStartTime: '{% $states.context.Execution.StartTime %}',
47
+ stateEnteredTime: '{% $states.context.State.EnteredTime %}',
48
+ stateName: '{% $states.context.State.Name %}',
49
+ stateRetryCount: '{% $states.context.State.RetryCount %}',
50
+ stateMachineId: '{% $states.context.StateMachine.Id %}',
51
+ stateMachineName: '{% $states.context.StateMachine.Name %}',
55
52
  }),
56
- {
57
- authorizer,
58
- methodResponses: [
59
- {
60
- statusCode: '200',
61
- },
62
- ],
63
- },
64
- );
53
+ outputs: '{% $states.result.Payload %}',
54
+ });
65
55
 
66
- const connection = new events.Connection(stack, 'Connection', {
67
- authorization: events.Authorization.basic(username, cdk.SecretValue.unsafePlainText(password)),
56
+ const checkJobStateLambda = new Function(stack, 'checkJobStateLambda', {
57
+ code: Code.fromInline(`exports.handler = async function(event, context) {
58
+ const expectedFields = [
59
+ 'execId', 'execInput', 'execName', 'execRoleArn',
60
+ 'execStartTime', 'stateEnteredTime', 'stateName',
61
+ 'stateRetryCount', 'stateMachineId', 'stateMachineName',
62
+ ];
63
+ const fieldsAreSet = expectedFields.every(field => event[field] !== undefined);
64
+ return {
65
+ status: event.statusCode === '200' && fieldsAreSet ? 'SUCCEEDED' : 'FAILED'
66
+ };
67
+ };`),
68
+ runtime: STANDARD_NODEJS_RUNTIME,
69
+ handler: 'index.handler',
68
70
  });
69
71
 
70
- const httpInvokeTask = tasks.HttpInvoke.jsonata(stack, 'Invoke HTTP Endpoint', {
71
- apiRoot: api.urlForPath('/'),
72
- apiEndpoint: sfn.TaskInput.fromText('{% $states.input.apiEndpoint %}'),
73
- connection,
74
- method: sfn.TaskInput.fromText('GET'),
72
+ const checkJobState = LambdaInvoke.jsonata(stack, 'Check the job state', {
73
+ lambdaFunction: checkJobStateLambda,
75
74
  outputs: {
76
- ResponseBody: '{% $states.result.ResponseBody %}',
75
+ status: '{% $states.result.Payload.status %}',
77
76
  },
78
77
  });
79
78
 
79
+ const isComplete = sfn.Choice.jsonata(stack, 'Job Complete?');
80
+ const jobFailed = sfn.Fail.jsonata(stack, 'Job Failed', {
81
+ cause: 'Job Failed',
82
+ error: 'Received a status that was not 200',
83
+ });
84
+ const finalStatus = sfn.Pass.jsonata(stack, 'Final step');
85
+
86
+ const chain = sfn.Chain.start(submitJob)
87
+ .next(checkJobState)
88
+ .next(
89
+ isComplete
90
+ .when(sfn.Condition.jsonata("{% $states.input.status = 'FAILED' %}"), jobFailed)
91
+ .when(sfn.Condition.jsonata("{% $states.input.status = 'SUCCEEDED' %}"), finalStatus),
92
+ );
93
+
80
94
  const sm = new sfn.StateMachine(stack, 'StateMachine', {
81
- definition: httpInvokeTask,
95
+ definition: chain,
82
96
  timeout: cdk.Duration.seconds(30),
83
97
  });
84
98
 
85
- const testCase = new IntegTest(app, 'HttpInvokeTest', {
86
- testCases: [stack],
99
+ new cdk.CfnOutput(stack, 'stateMachineArn', {
100
+ value: sm.stateMachineArn,
87
101
  });
88
102
 
89
- // Start an execution
90
- const start = testCase.assertions.awsApiCall('@aws-sdk/client-sfn', 'StartExecution', {
91
- stateMachineArn: sm.stateMachineArn,
92
- input: JSON.stringify({
93
- apiEndpoint: '/test',
94
- }),
103
+ const integ = new IntegTest(app, 'IntegTest', {
104
+ testCases: [stack],
95
105
  });
96
-
97
- // describe the results of the execution
98
- const describe = testCase.assertions.awsApiCall('@aws-sdk/client-sfn', 'DescribeExecution', {
99
- executionArn: start.getAttString('executionArn'),
106
+ const res = integ.assertions.awsApiCall('@aws-sdk/client-sfn', 'StartExecution', {
107
+ stateMachineArn: sm.stateMachineArn,
100
108
  });
101
-
102
- // assert the results
103
- describe.expect(ExpectedResult.objectLike({
109
+ const executionArn = res.getAttString('executionArn');
110
+ integ.assertions.awsApiCall('@aws-sdk/client-sfn', 'DescribeExecution', {
111
+ executionArn,
112
+ }).expect(ExpectedResult.objectLike({
104
113
  status: 'SUCCEEDED',
105
- output: JSON.stringify({
106
- ResponseBody: {
107
- message: 'Hello, tester!',
108
- },
109
- }),
110
- }));
114
+ })).waitForAssertions({
115
+ totalTimeout: cdk.Duration.seconds(10),
116
+ interval: cdk.Duration.seconds(3),
117
+ });
111
118
 
112
119
  app.synth();