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,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();
@@ -1,101 +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-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),
39
+ const submitJob = new LambdaInvoke(stack, 'Invoke Handler', {
40
+ lambdaFunction: submitJobLambda,
41
+ payload: sfn.TaskInput.fromObject({
42
+ execId: sfn.JsonPath.executionId,
43
+ execInput: sfn.JsonPath.executionInput,
44
+ execName: sfn.JsonPath.executionName,
45
+ execRoleArn: sfn.JsonPath.executionRoleArn,
46
+ execStartTime: sfn.JsonPath.executionStartTime,
47
+ stateEnteredTime: sfn.JsonPath.stateEnteredTime,
48
+ stateName: sfn.JsonPath.stateName,
49
+ stateRetryCount: sfn.JsonPath.stateRetryCount,
50
+ stateMachineId: sfn.JsonPath.stateMachineId,
51
+ stateMachineName: sfn.JsonPath.stateMachineName,
52
+ }),
53
+ outputPath: '$.Payload',
36
54
  });
37
55
 
38
- const api = new apigateway.RestApi(stack, 'IntegRestApi');
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',
70
+ });
39
71
 
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
- },
55
- }),
56
- {
57
- authorizer,
58
- methodResponses: [
59
- {
60
- statusCode: '200',
61
- },
62
- ],
72
+ const checkJobState = new LambdaInvoke(stack, 'Check the job state', {
73
+ lambdaFunction: checkJobStateLambda,
74
+ resultSelector: {
75
+ status: sfn.JsonPath.stringAt('$.Payload.status'),
63
76
  },
64
- );
65
-
66
- const connection = new events.Connection(stack, 'Connection', {
67
- authorization: events.Authorization.basic(username, cdk.SecretValue.unsafePlainText(password)),
68
77
  });
69
78
 
70
- const httpInvokeTask = new tasks.HttpInvoke(stack, 'Invoke HTTP Endpoint', {
71
- apiRoot: api.urlForPath('/'),
72
- apiEndpoint: sfn.TaskInput.fromText('/test'),
73
- connection,
74
- method: sfn.TaskInput.fromText('GET'),
79
+ const isComplete = new sfn.Choice(stack, 'Job Complete?');
80
+ const jobFailed = new sfn.Fail(stack, 'Job Failed', {
81
+ cause: 'Job Failed',
82
+ error: 'Received a status that was not 200',
75
83
  });
84
+ const finalStatus = new sfn.Pass(stack, 'Final step');
85
+
86
+ const chain = sfn.Chain.start(submitJob)
87
+ .next(checkJobState)
88
+ .next(
89
+ isComplete
90
+ .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed)
91
+ .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus),
92
+ );
76
93
 
77
94
  const sm = new sfn.StateMachine(stack, 'StateMachine', {
78
- definition: httpInvokeTask,
95
+ definition: chain,
79
96
  timeout: cdk.Duration.seconds(30),
80
97
  });
81
98
 
82
- const testCase = new IntegTest(app, 'HttpInvokeTest', {
83
- testCases: [stack],
99
+ new cdk.CfnOutput(stack, 'stateMachineArn', {
100
+ value: sm.stateMachineArn,
84
101
  });
85
102
 
86
- // Start an execution
87
- const start = testCase.assertions.awsApiCall('StepFunctions', 'startExecution', {
88
- stateMachineArn: sm.stateMachineArn,
103
+ const integ = new IntegTest(app, 'IntegTest', {
104
+ testCases: [stack],
89
105
  });
90
-
91
- // describe the results of the execution
92
- const describe = testCase.assertions.awsApiCall('StepFunctions', 'describeExecution', {
93
- executionArn: start.getAttString('executionArn'),
106
+ const res = integ.assertions.awsApiCall('StepFunctions', 'startExecution', {
107
+ stateMachineArn: sm.stateMachineArn,
94
108
  });
95
-
96
- // assert the results
97
- describe.expect(ExpectedResult.objectLike({
109
+ const executionArn = res.getAttString('executionArn');
110
+ integ.assertions.awsApiCall('StepFunctions', 'describeExecution', {
111
+ executionArn,
112
+ }).expect(ExpectedResult.objectLike({
98
113
  status: 'SUCCEEDED',
99
- }));
114
+ })).waitForAssertions({
115
+ totalTimeout: cdk.Duration.seconds(10),
116
+ interval: cdk.Duration.seconds(3),
117
+ });
100
118
 
101
119
  app.synth();
@@ -1,69 +1,122 @@
1
- import * as path from 'path';
2
- import * as glue from 'aws-cdk-lib/aws-glue';
1
+ import * as ec2 from 'aws-cdk-lib/aws-ec2';
2
+ import * as eks from 'aws-cdk-lib/aws-eks';
3
+ import { AwsAuthMapping } from 'aws-cdk-lib/aws-eks';
3
4
  import * as iam from 'aws-cdk-lib/aws-iam';
4
- import * as assets from 'aws-cdk-lib/aws-s3-assets';
5
5
  import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
6
6
  import * as cdk from 'aws-cdk-lib';
7
- import { GlueStartJobRun } from 'aws-cdk-lib/aws-stepfunctions-tasks';
8
- import { IntegTest } from '@aws-cdk/integ-tests-alpha';
7
+ import { Aws } from 'aws-cdk-lib';
8
+ import * as integ from '@aws-cdk/integ-tests-alpha';
9
+ import { KubectlV31Layer } from '@aws-cdk/lambda-layer-kubectl-v31';
10
+ import { EmrContainersStartJobRun, ReleaseLabel, VirtualClusterInput } from 'aws-cdk-lib/aws-stepfunctions-tasks';
11
+ import { EC2_RESTRICT_DEFAULT_SECURITY_GROUP } from 'aws-cdk-lib/cx-api';
9
12
 
10
- /*
13
+ /**
11
14
  * Stack verification steps:
12
- * * aws stepfunctions start-execution --state-machine-arn <deployed state machine arn>
13
- * * aws stepfunctions describe-execution --execution-arn <execution arn created above>
14
- * The "describe-execution" call should eventually return status "SUCCEEDED".
15
- * NOTE: It will take up to 15 minutes for the step function to complete due to the cold start time
16
- * for AWS Glue, which as of 02/2020, is around 10-15 minutes.
15
+ * Everything in the link below must be setup before running the state machine.
16
+ * @see https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/setting-up-enable-IAM.html
17
+ * aws stepfunctions start-execution --state-machine-arn <deployed state machine arn> : should return execution arn
18
+ * aws stepfunctions describe-execution --execution-arn <exection-arn generated before> : should return status as SUCCEEDED
17
19
  */
18
20
 
19
- const app = new cdk.App();
20
- const stack = new cdk.Stack(app, 'aws-stepfunctions-integ');
21
+ const app = new cdk.App({
22
+ postCliContext: {
23
+ '@aws-cdk/aws-lambda:useCdkManagedLogGroup': false,
24
+ '@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy': false,
25
+ },
26
+ });
27
+ const stack = new cdk.Stack(app, 'aws-stepfunctions-tasks-emr-containers-start-job-run');
28
+ stack.node.setContext(EC2_RESTRICT_DEFAULT_SECURITY_GROUP, false);
29
+
30
+ const eksCluster = new eks.Cluster(stack, 'integration-test-eks-cluster', {
31
+ version: eks.KubernetesVersion.V1_30,
32
+ defaultCapacity: 3,
33
+ defaultCapacityInstance: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE),
34
+ kubectlLayer: new KubectlV31Layer(stack, 'KubectlLayer'),
35
+ });
21
36
 
22
- const codeAsset = new assets.Asset(stack, 'Glue Job Script', {
23
- path: path.join(__dirname, 'my-glue-script/job.py'),
37
+ const virtualCluster = new cdk.CfnResource(stack, 'Virtual Cluster', {
38
+ type: 'AWS::EMRContainers::VirtualCluster',
39
+ properties: {
40
+ ContainerProvider: {
41
+ Id: eksCluster.clusterName,
42
+ Info: {
43
+ EksInfo: {
44
+ Namespace: 'default',
45
+ },
46
+ },
47
+ Type: 'EKS',
48
+ },
49
+ Name: 'Virtual-Cluster-Name',
50
+ },
24
51
  });
25
52
 
26
- const jobRole = new iam.Role(stack, 'Glue Job Role', {
27
- assumedBy: new iam.ServicePrincipal('glue'),
28
- managedPolicies: [
29
- iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSGlueServiceRole'),
53
+ const emrRole = eksCluster.addManifest('emrRole', {
54
+ apiVersion: 'rbac.authorization.k8s.io/v1',
55
+ kind: 'Role',
56
+ metadata: { name: 'emr-containers', namespace: 'default' },
57
+ rules: [
58
+ { apiGroups: [''], resources: ['namespaces'], verbs: ['get'] },
59
+ { apiGroups: [''], resources: ['serviceaccounts', 'services', 'configmaps', 'events', 'pods', 'pods/log'], verbs: ['get', 'list', 'watch', 'describe', 'create', 'edit', 'delete', 'deletecollection', 'annotate', 'patch', 'label'] },
60
+ { apiGroups: [''], resources: ['secrets'], verbs: ['create', 'patch', 'delete', 'watch'] },
61
+ { apiGroups: ['apps'], resources: ['statefulsets', 'deployments'], verbs: ['get', 'list', 'watch', 'describe', 'create', 'edit', 'delete', 'annotate', 'patch', 'label'] },
62
+ { apiGroups: ['batch'], resources: ['jobs'], verbs: ['get', 'list', 'watch', 'describe', 'create', 'edit', 'delete', 'annotate', 'patch', 'label'] },
63
+ { apiGroups: ['extensions'], resources: ['ingresses'], verbs: ['get', 'list', 'watch', 'describe', 'create', 'edit', 'delete', 'annotate', 'patch', 'label'] },
64
+ { apiGroups: ['rbac.authorization.k8s.io'], resources: ['roles', 'rolebindings'], verbs: ['get', 'list', 'watch', 'describe', 'create', 'edit', 'delete', 'deletecollection', 'annotate', 'patch', 'label'] },
30
65
  ],
31
66
  });
32
- codeAsset.grantRead(jobRole);
33
67
 
34
- const job = new glue.CfnJob(stack, 'Glue Job', {
35
- name: 'My Glue Job',
36
- glueVersion: '1.0',
37
- command: {
38
- name: 'glueetl',
39
- pythonVersion: '3',
40
- scriptLocation: `s3://${codeAsset.s3BucketName}/${codeAsset.s3ObjectKey}`,
41
- },
42
- role: jobRole.roleArn,
68
+ const emrRoleBind = eksCluster.addManifest('emrRoleBind', {
69
+ apiVersion: 'rbac.authorization.k8s.io/v1',
70
+ kind: 'RoleBinding',
71
+ metadata: { name: 'emr-containers', namespace: 'default' },
72
+ subjects: [{ kind: 'User', name: 'emr-containers', apiGroup: 'rbac.authorization.k8s.io' }],
73
+ roleRef: { kind: 'Role', name: 'emr-containers', apiGroup: 'rbac.authorization.k8s.io' },
43
74
  });
44
75
 
45
- const jobTask = new GlueStartJobRun(stack, 'Glue Job Task', {
46
- glueJobName: job.name!,
47
- integrationPattern: sfn.IntegrationPattern.RUN_JOB,
48
- arguments: sfn.TaskInput.fromObject({
49
- '--enable-metrics': 'true',
50
- }),
76
+ emrRoleBind.node.addDependency(emrRole);
77
+
78
+ const emrServiceRole = iam.Role.fromRoleArn(stack, 'emrServiceRole', 'arn:aws:iam::'+Aws.ACCOUNT_ID+':role/AWSServiceRoleForAmazonEMRContainers');
79
+ const authMapping: AwsAuthMapping = { groups: [], username: 'emr-containers' };
80
+ eksCluster.awsAuth.addRoleMapping(emrServiceRole, authMapping);
81
+
82
+ virtualCluster.node.addDependency(emrRoleBind);
83
+ virtualCluster.node.addDependency(eksCluster.awsAuth);
84
+
85
+ const startJobRunJob = new EmrContainersStartJobRun(stack, 'Start a Job Run', {
86
+ virtualCluster: VirtualClusterInput.fromVirtualClusterId(virtualCluster.getAtt('Id').toString()),
87
+ releaseLabel: ReleaseLabel.EMR_6_2_0,
88
+ jobName: 'EMR-Containers-Job',
89
+ jobDriver: {
90
+ sparkSubmitJobDriver: {
91
+ entryPoint: sfn.TaskInput.fromText('local:///usr/lib/spark/examples/src/main/python/pi.py'),
92
+ entryPointArguments: sfn.TaskInput.fromObject(['2']),
93
+ sparkSubmitParameters: '--conf spark.driver.memory=512M --conf spark.kubernetes.driver.request.cores=0.2 --conf spark.kubernetes.executor.request.cores=0.2 --conf spark.sql.shuffle.partitions=60 --conf spark.dynamicAllocation.enabled=false',
94
+ },
95
+ },
51
96
  });
52
97
 
53
- const startTask = new sfn.Pass(stack, 'Start Task');
54
- const endTask = new sfn.Pass(stack, 'End Task');
98
+ const chain = sfn.Chain.start(startJobRunJob);
55
99
 
56
- const stateMachine = new sfn.StateMachine(stack, 'State Machine', {
57
- definition: sfn.Chain.start(startTask).next(jobTask).next(endTask),
100
+ const sm = new sfn.StateMachine(stack, 'StateMachine', {
101
+ definition: chain,
102
+ timeout: cdk.Duration.seconds(1000),
58
103
  });
59
104
 
60
- new cdk.CfnOutput(stack, 'State Machine ARN Output', {
61
- value: stateMachine.stateMachineArn,
105
+ new cdk.CfnOutput(stack, 'stateMachineArn', {
106
+ value: sm.stateMachineArn,
62
107
  });
63
108
 
64
- new IntegTest(app, 'AwsSfnIntegTest', {
109
+ new integ.IntegTest(app, 'aws-stepfunctions-tasks-emr-containers-start-job-run-integ', {
65
110
  testCases: [stack],
66
- diffAssets: true,
111
+ // Test includes assets that are updated weekly. If not disabled, the upgrade PR will fail.
112
+ diffAssets: false,
113
+ cdkCommandOptions: {
114
+ deploy: {
115
+ args: {
116
+ rollback: true,
117
+ },
118
+ },
119
+ },
67
120
  });
68
121
 
69
122
  app.synth();
@@ -87,6 +87,28 @@ const schedule = synthetics.Schedule.cron({
87
87
 
88
88
  If you want the canary to run just once upon deployment, you can use `Schedule.once()`.
89
89
 
90
+ ### Automatic Retries
91
+
92
+ You can configure the canary to automatically retry failed runs by using the `maxRetries` property.
93
+
94
+ This is only supported on the following runtimes or newer: `Runtime.SYNTHETICS_NODEJS_PUPPETEER_10_0`, `Runtime.SYNTHETICS_NODEJS_PLAYWRIGHT_2_0`, `Runtime.SYNTHETICS_PYTHON_SELENIUM_5_1`.
95
+
96
+ Max retries can be set between 0 and 2. Canaries which time out after 10 minutes are automatically limited to one retry.
97
+
98
+ For more information, see [Configuring your canary to retry automatically](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_autoretry.html).
99
+
100
+ ```ts
101
+ const canary = new synthetics.Canary(this, 'MyCanary', {
102
+ schedule: synthetics.Schedule.rate(Duration.minutes(5)),
103
+ test: synthetics.Test.custom({
104
+ handler: 'canary.handler',
105
+ code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
106
+ }),
107
+ runtime: synthetics.Runtime.SYNTHETICS_PYTHON_SELENIUM_5_1,
108
+ maxRetries: 2, // The canary run will retry up to 2 times on a failure
109
+ });
110
+ ```
111
+
90
112
  ### Active Tracing
91
113
 
92
114
  You can choose to enable active AWS X-Ray tracing on canaries that use the `syn-nodejs-2.0` or later runtime by setting `activeTracing` to `true`.
@@ -368,3 +390,21 @@ const canary = new synthetics.Canary(this, 'MyCanary', {
368
390
  });
369
391
  ```
370
392
 
393
+ ### Tag replication
394
+
395
+ You can configure a canary to replicate its tags to the underlying Lambda function. This is useful when you want the same tags that are applied to the canary to also be applied to the Lambda function that the canary uses.
396
+
397
+ ```ts
398
+ const canary = new synthetics.Canary(this, 'MyCanary', {
399
+ schedule: synthetics.Schedule.rate(Duration.minutes(5)),
400
+ test: synthetics.Test.custom({
401
+ code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')),
402
+ handler: 'index.handler',
403
+ }),
404
+ runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_7_0,
405
+ resourcesToReplicateTags: [synthetics.ResourceToReplicateTags.LAMBDA_FUNCTION],
406
+ });
407
+ ```
408
+
409
+ When you specify `ResourceToReplicateTags.LAMBDA_FUNCTION` in the `resourcesToReplicateTags` property, CloudWatch Synthetics will keep the tags of the canary and the Lambda function synchronized. Any future changes you make to the canary's tags will also be applied to the function.
410
+
@@ -0,0 +1,36 @@
1
+ import * as path from 'node:path';
2
+ import { App, Duration, Size, Stack, StackProps, Tags } from 'aws-cdk-lib/core';
3
+ import { IntegTest } from '@aws-cdk/integ-tests-alpha';
4
+ import { Construct } from 'constructs';
5
+ import * as synthetics from 'aws-cdk-lib/aws-synthetics';
6
+
7
+ class TestStack extends Stack {
8
+ public canary: synthetics.Canary;
9
+
10
+ constructor(scope: Construct, id: string, props?: StackProps) {
11
+ super(scope, id, props);
12
+
13
+ this.canary = new synthetics.Canary(this, 'TagReplicationCanary', {
14
+ canaryName: 'tag-replication',
15
+ runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_7_0,
16
+ test: synthetics.Test.custom({
17
+ handler: 'canary.handler',
18
+ code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')),
19
+ }),
20
+ memory: Size.mebibytes(1024),
21
+ timeout: Duration.minutes(3),
22
+ resourcesToReplicateTags: [synthetics.ResourceToReplicateTags.LAMBDA_FUNCTION],
23
+ });
24
+
25
+ Tags.of(this.canary).add('Environment', 'test');
26
+ Tags.of(this.canary).add('Owner', 'cdk-team');
27
+ Tags.of(this.canary).add('Project', 'synthetics-tag-replication');
28
+ }
29
+ }
30
+
31
+ const app = new App();
32
+ const testStack = new TestStack(app, 'SyntheticsCanaryResourcesToReplicateTagsStack');
33
+
34
+ new IntegTest(app, 'SyntheticsCanaryResourcesToReplicateTags', {
35
+ testCases: [testStack],
36
+ });