From 89802a95360d698921c81a152d11ab6e46b00de3 Mon Sep 17 00:00:00 2001 From: Ryota Yamada Date: Sat, 18 Feb 2023 16:13:11 +0900 Subject: [PATCH 1/2] fix(ecs): validate ecs healthcheck (#24197) ---- #22200 I add a feature to validate some contents for healthcheck. ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-ecs/lib/container-definition.ts | 17 ++ .../aws-ecs/test/container-definition.test.ts | 156 ++++++++++++++++++ .../aws-ecs-integ-exec-command.assets.json | 6 +- .../aws-ecs-integ-exec-command.template.json | 9 + .../integ.exec-command.js.snapshot/cdk.out | 2 +- ...efaultTestDeployAssert4F7706FE.assets.json | 19 +++ ...aultTestDeployAssert4F7706FE.template.json | 36 ++++ .../integ.exec-command.js.snapshot/integ.json | 20 ++- .../manifest.json | 68 +++++++- .../integ.exec-command.js.snapshot/tree.json | 127 +++++++++++--- .../test/fargate/integ.exec-command.ts | 19 ++- 11 files changed, 438 insertions(+), 41 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.assets.json create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.template.json diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 8c112a18a3a38..4eda8f884b813 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -858,6 +858,23 @@ function renderEnvironmentFiles(partition: string, environmentFiles: Environment } function renderHealthCheck(hc: HealthCheck): CfnTaskDefinition.HealthCheckProperty { + if (hc.interval?.toSeconds() !== undefined) { + if (5 > hc.interval?.toSeconds() || hc.interval?.toSeconds() > 300) { + throw new Error('Interval must be between 5 seconds and 300 seconds.'); + } + } + + if (hc.timeout?.toSeconds() !== undefined) { + if (2 > hc.timeout?.toSeconds() || hc.timeout?.toSeconds() > 120) { + throw new Error('Timeout must be between 2 seconds and 120 seconds.'); + } + } + if (hc.interval?.toSeconds() !== undefined && hc.timeout?.toSeconds() !== undefined) { + if (hc.interval?.toSeconds() < hc.timeout?.toSeconds()) { + throw new Error('Health check interval should be longer than timeout.'); + } + } + return { command: getHealthCheckCommand(hc), interval: hc.interval?.toSeconds() ?? 30, diff --git a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts index a0bfbf217ed2b..1db0b3e8815d0 100644 --- a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts @@ -7,6 +7,7 @@ import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as ecs from '../lib'; import { AppProtocol } from '../lib'; +import { Duration } from '@aws-cdk/core'; describe('container definition', () => { describe('When creating a Task Definition', () => { @@ -1691,6 +1692,161 @@ describe('container definition', () => { }).toThrow(/At least one argument must be supplied for health check command./); }); + test('throws when setting Health Check with invalid interval because of too short', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + healthCheck: { + command: ['CMD-SHELL', 'curl localhost:8000'], + interval: Duration.seconds(4), + timeout: Duration.seconds(30), + }, + }); + + // THEN + expect(() => { + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + HealthCheck: { + Command: ['CMD-SHELL', 'curl localhost:8000'], + Interval: 4, + }, + }, + ], + }); + }).toThrow(/Interval must be between 5 seconds and 300 seconds./); + }); + + test('throws when setting Health Check with invalid interval because of too long', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + healthCheck: { + command: ['CMD-SHELL', 'curl localhost:8000'], + interval: Duration.seconds(301), + timeout: Duration.seconds(30), + }, + }); + + // THEN + expect(() => { + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + HealthCheck: { + Command: ['CMD-SHELL', 'curl localhost:8000'], + Interval: 4, + }, + }, + ], + }); + }).toThrow(/Interval must be between 5 seconds and 300 seconds./); + }); + + test('throws when setting Health Check with invalid timeout because of too short', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + healthCheck: { + command: ['CMD-SHELL', 'curl localhost:8000'], + interval: Duration.seconds(40), + timeout: Duration.seconds(1), + }, + }); + + // THEN + expect(() => { + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + HealthCheck: { + Command: ['CMD-SHELL', 'curl localhost:8000'], + Interval: 4, + }, + }, + ], + }); + }).toThrow(/Timeout must be between 2 seconds and 120 seconds./); + }); + + test('throws when setting Health Check with invalid timeout because of too long', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + healthCheck: { + command: ['CMD-SHELL', 'curl localhost:8000'], + interval: Duration.seconds(150), + timeout: Duration.seconds(130), + }, + }); + + // THEN + expect(() => { + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + HealthCheck: { + Command: ['CMD-SHELL', 'curl localhost:8000'], + Interval: 4, + }, + }, + ], + }); + }).toThrow(/Timeout must be between 2 seconds and 120 seconds./); + }); + + test('throws when setting Health Check with invalid interval and timeout because timeout is longer than interval', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + healthCheck: { + command: ['CMD-SHELL', 'curl localhost:8000'], + interval: Duration.seconds(10), + timeout: Duration.seconds(30), + }, + }); + + // THEN + expect(() => { + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + HealthCheck: { + Command: ['CMD-SHELL', 'curl localhost:8000'], + Interval: 4, + }, + }, + ], + }); + }).toThrow(/Health check interval should be longer than timeout./); + }); + test('can specify Health Check values in shell form', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.assets.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.assets.json index a926baf83f30c..69056d07d817d 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.assets.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "30.0.0", "files": { - "4dba456b46dc53b954d12cf55bad7b455371f307d7b5df57b5fb2e6cafe4e9ba": { + "1a5bcacf8adc1fb93503daec527cf36ecf57f189012726acf8ad69d9f993d3cb": { "source": { "path": "aws-ecs-integ-exec-command.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "4dba456b46dc53b954d12cf55bad7b455371f307d7b5df57b5fb2e6cafe4e9ba.json", + "objectKey": "1a5bcacf8adc1fb93503daec527cf36ecf57f189012726acf8ad69d9f993d3cb.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.template.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.template.json index a6e57f5b88db6..377a73d413b6d 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.template.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/aws-ecs-integ-exec-command.template.json @@ -673,6 +673,15 @@ "ContainerDefinitions": [ { "Essential": true, + "HealthCheck": { + "Command": [ + "CMD-SHELL", + "curl localhost:8000" + ], + "Interval": 60, + "Retries": 3, + "Timeout": 40 + }, "Image": "amazon/amazon-ecs-sample", "Name": "web" } diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/cdk.out b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/cdk.out index 588d7b269d34f..ae4b03c54e770 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"30.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.assets.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.assets.json new file mode 100644 index 0000000000000..d3e14dafbed7e --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.assets.json @@ -0,0 +1,19 @@ +{ + "version": "30.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "execcommandintegtestDefaultTestDeployAssert4F7706FE.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.template.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/execcommandintegtestDefaultTestDeployAssert4F7706FE.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/integ.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/integ.json index 54e1790dd2707..c11a247f3a29b 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/integ.json @@ -1,14 +1,20 @@ { - "version": "20.0.0", + "version": "30.0.0", "testCases": { - "integ.exec-command": { + "exec-command-integ-test/DefaultTest": { "stacks": [ "aws-ecs-integ-exec-command" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "diffAssets": true, + "cdkCommandOptions": { + "deploy": { + "args": { + "rollback": true + } + } + }, + "assertionStack": "exec-command-integ-test/DefaultTest/DeployAssert", + "assertionStackName": "execcommandintegtestDefaultTestDeployAssert4F7706FE" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/manifest.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/manifest.json index f2551e7cdb262..e45fd352cef32 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/manifest.json @@ -1,12 +1,6 @@ { - "version": "20.0.0", + "version": "30.0.0", "artifacts": { - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } - }, "aws-ecs-integ-exec-command.assets": { "type": "cdk:asset-manifest", "properties": { @@ -23,7 +17,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4dba456b46dc53b954d12cf55bad7b455371f307d7b5df57b5fb2e6cafe4e9ba.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/1a5bcacf8adc1fb93503daec527cf36ecf57f189012726acf8ad69d9f993d3cb.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -216,7 +210,10 @@ "/aws-ecs-integ-exec-command/TaskDef/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TaskDef54694570" + "data": "TaskDef54694570", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" + ] } ], "/aws-ecs-integ-exec-command/FargateService/Service": [ @@ -245,6 +242,59 @@ ] }, "displayName": "aws-ecs-integ-exec-command" + }, + "execcommandintegtestDefaultTestDeployAssert4F7706FE.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "execcommandintegtestDefaultTestDeployAssert4F7706FE.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "execcommandintegtestDefaultTestDeployAssert4F7706FE": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "execcommandintegtestDefaultTestDeployAssert4F7706FE.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "execcommandintegtestDefaultTestDeployAssert4F7706FE.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "execcommandintegtestDefaultTestDeployAssert4F7706FE.assets" + ], + "metadata": { + "/exec-command-integ-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/exec-command-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "exec-command-integ-test/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/tree.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/tree.json index 0da31318a2e10..ccc7204857046 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.js.snapshot/tree.json @@ -4,14 +4,6 @@ "id": "App", "path": "", "children": { - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "aws-ecs-integ-exec-command": { "id": "aws-ecs-integ-exec-command", "path": "aws-ecs-integ-exec-command", @@ -91,8 +83,8 @@ "id": "Acl", "path": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -258,8 +250,8 @@ "id": "Acl", "path": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -425,8 +417,8 @@ "id": "Acl", "path": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet1/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -544,8 +536,8 @@ "id": "Acl", "path": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet2/Acl", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" } }, "RouteTable": { @@ -880,6 +872,14 @@ "id": "TaskRole", "path": "aws-ecs-integ-exec-command/TaskDef/TaskRole", "children": { + "ImportTaskRole": { + "id": "ImportTaskRole", + "path": "aws-ecs-integ-exec-command/TaskDef/TaskRole/ImportTaskRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", "path": "aws-ecs-integ-exec-command/TaskDef/TaskRole/Resource", @@ -1051,7 +1051,16 @@ { "essential": true, "image": "amazon/amazon-ecs-sample", - "name": "web" + "name": "web", + "healthCheck": { + "command": [ + "CMD-SHELL", + "curl localhost:8000" + ], + "interval": 60, + "retries": 3, + "timeout": 40 + } } ], "cpu": "256", @@ -1178,17 +1187,95 @@ "fqn": "@aws-cdk/aws-ecs.FargateService", "version": "0.0.0" } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-ecs-integ-exec-command/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-ecs-integ-exec-command/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } } }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "exec-command-integ-test": { + "id": "exec-command-integ-test", + "path": "exec-command-integ-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "exec-command-integ-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "exec-command-integ-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.249" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "exec-command-integ-test/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "exec-command-integ-test/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "exec-command-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.249" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts index 5bc89fb2432b7..0af518dfa6b11 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts @@ -3,6 +3,8 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; import * as ecs from '../../lib'; const app = new cdk.App(); @@ -39,6 +41,11 @@ const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + healthCheck: { + command: ['CMD-SHELL', 'curl localhost:8000'], + interval: Duration.seconds(60), + timeout: Duration.seconds(40), + }, }); new ecs.FargateService(stack, 'FargateService', { @@ -47,4 +54,14 @@ new ecs.FargateService(stack, 'FargateService', { enableExecuteCommand: true, }); -app.synth(); \ No newline at end of file +new integ.IntegTest(app, 'exec-command-integ-test', { + testCases: [stack], + diffAssets: true, + cdkCommandOptions: { + deploy: { + args: { + rollback: true, + }, + }, + }, +}); From 75eb9330194824cdf435ae64095813191fcd6e13 Mon Sep 17 00:00:00 2001 From: Michael White Date: Sat, 18 Feb 2023 02:53:15 -0500 Subject: [PATCH 2/2] feat(logs): Add support for multiple parse and filter statements in QueryString (#24022) Currently, `QueryString` is limited to only allow a single line/statement to be provided for each query command. For some commands this makes sense (e.g. `limit`), but for `parse` and `filter` this can be limiting. Adding multiple lines for these commands is possible in the AWS console, so it makes sense for it to be supported in CDK too. In this PR, I'm adding support for `filter` and `parse` to be provided as `string` or `string[]`, and adding/modifying various utility methods to handle this ambiguity. I left the existing tests the same to verify no breaking changes, and added a new test for the newly enabled behavior. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-logs/README.md | 8 ++ .../@aws-cdk/aws-logs/lib/query-definition.ts | 114 +++++++++++++----- ...efaultTestDeployAssert902BAAD5.assets.json | 2 +- ...insights-querydefinition-integ.assets.json | 6 +- ...sights-querydefinition-integ.template.json | 12 ++ .../cdk.out | 2 +- .../integ.json | 2 +- .../manifest.json | 10 +- .../tree.json | 34 +++++- ...teg.save-logs-insights-query-definition.ts | 21 ++++ .../aws-logs/test/query-definition.test.ts | 63 +++++++++- 11 files changed, 231 insertions(+), 43 deletions(-) diff --git a/packages/@aws-cdk/aws-logs/README.md b/packages/@aws-cdk/aws-logs/README.md index 784ab7e91760c..5a6a28f74547d 100644 --- a/packages/@aws-cdk/aws-logs/README.md +++ b/packages/@aws-cdk/aws-logs/README.md @@ -330,6 +330,14 @@ new logs.QueryDefinition(this, 'QueryDefinition', { queryDefinitionName: 'MyQuery', queryString: new logs.QueryString({ fields: ['@timestamp', '@message'], + parseStatements: [ + '@message "[*] *" as loggingType, loggingMessage', + '@message "<*>: *" as differentLoggingType, differentLoggingMessage', + ], + filterStatements: [ + 'loggingType = "ERROR"', + 'loggingMessage = "A very strange error occurred!"', + ], sort: '@timestamp desc', limit: 20, }), diff --git a/packages/@aws-cdk/aws-logs/lib/query-definition.ts b/packages/@aws-cdk/aws-logs/lib/query-definition.ts index 2dff1ad3d93e1..09594756d4cb2 100644 --- a/packages/@aws-cdk/aws-logs/lib/query-definition.ts +++ b/packages/@aws-cdk/aws-logs/lib/query-definition.ts @@ -16,19 +16,43 @@ export interface QueryStringProps { readonly fields?: string[]; /** - * Extracts data from a log field and creates one or more ephemeral fields that you can process further in the query. + * A single statement for parsing data from a log field and creating ephemeral fields that can + * be processed further in the query. * + * @deprecated Use `parseStatements` instead * @default - no parse in QueryString */ readonly parse?: string; /** - * Filters the results of a query that's based on one or more conditions. + * An array of one or more statements for parsing data from a log field and creating ephemeral + * fields that can be processed further in the query. Each provided statement generates a separate + * parse line in the query string. * + * Note: If provided, this property overrides any value provided for the `parse` property. + * + * @default - no parse in QueryString + */ + readonly parseStatements?: string[]; + + /** + * A single statement for filtering the results of a query based on a boolean expression. + * + * @deprecated Use `filterStatements` instead * @default - no filter in QueryString */ readonly filter?: string; + /** + * An array of one or more statements for filtering the results of a query based on a boolean + * expression. Each provided statement generates a separate filter line in the query string. + * + * Note: If provided, this property overrides any value provided for the `filter` property. + * + * @default - no filter in QueryString + */ + readonly filterStatements?: string[]; + /** * Uses log field values to calculate aggregate statistics. * @@ -58,23 +82,13 @@ export interface QueryStringProps { readonly display?: string; } -interface QueryStringMap { - readonly fields?: string, - readonly parse?: string, - readonly filter?: string, - readonly stats?: string, - readonly sort?: string, - readonly limit?: Number, - readonly display?: string, -} - /** * Define a QueryString */ export class QueryString { private readonly fields?: string[]; - private readonly parse?: string; - private readonly filter?: string; + private readonly parse: string[]; + private readonly filter: string[]; private readonly stats?: string; private readonly sort?: string; private readonly limit?: Number; @@ -82,38 +96,74 @@ export class QueryString { constructor(props: QueryStringProps = {}) { this.fields = props.fields; - this.parse = props.parse; - this.filter = props.filter; this.stats = props.stats; this.sort = props.sort; this.limit = props.limit; this.display = props.display; + + // Determine parsing by either the parseStatements or parse properties, or default to empty array + if (props.parseStatements) { + this.parse = props.parseStatements; + } else if (props.parse) { + this.parse = [props.parse]; + } else { + this.parse = []; + } + + // Determine filtering by either the filterStatements or filter properties, or default to empty array + if (props.filterStatements) { + this.filter = props.filterStatements; + } else if (props.filter) { + this.filter = [props.filter]; + } else { + this.filter = []; + } } /** * String representation of this QueryString. */ public toString(): string { - return noUndef({ - fields: this.fields !== undefined ? this.fields.join(', ') : this.fields, - parse: this.parse, - filter: this.filter, - stats: this.stats, - sort: this.sort, - limit: this.limit, - display: this.display, - }).join('\n| '); + return [ + this.buildQueryLine('fields', this.fields?.join(', ')), + ...this.buildQueryLines('parse', this.parse), + ...this.buildQueryLines('filter', this.filter), + this.buildQueryLine('stats', this.stats), + this.buildQueryLine('sort', this.sort), + this.buildQueryLine('limit', this.limit?.toString()), + this.buildQueryLine('display', this.display), + ].filter( + (queryLine) => queryLine !== undefined && queryLine.length > 0, + ).join('\n| '); } -} -function noUndef(x: QueryStringMap): string[] { - const ret: string[] = []; - for (const [key, value] of Object.entries(x)) { - if (value !== undefined) { - ret.push(`${key} ${value}`); + /** + * Build an array of query lines given a command and statement(s). + * + * @param command a query command + * @param statements one or more query statements for the specified command, or undefined + * @returns an array of the query string lines generated from the provided command and statements + */ + private buildQueryLines(command: string, statements?: string[]): string[] { + if (statements === undefined) { + return []; } + + return statements.map( + (statement: string): string => this.buildQueryLine(command, statement), + ); + } + + /** + * Build a single query line given a command and statement. + * + * @param command a query command + * @param statement a single query statement + * @returns a single query string line generated from the provided command and statement + */ + private buildQueryLine(command: string, statement?: string): string { + return statement ? `${command} ${statement}` : ''; } - return ret; } /** diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/LogsInsightsQueryDefinitionIntegTestDefaultTestDeployAssert902BAAD5.assets.json b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/LogsInsightsQueryDefinitionIntegTestDefaultTestDeployAssert902BAAD5.assets.json index faca19c9ec2b8..7e7b72e0995a4 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/LogsInsightsQueryDefinitionIntegTestDefaultTestDeployAssert902BAAD5.assets.json +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/LogsInsightsQueryDefinitionIntegTestDefaultTestDeployAssert902BAAD5.assets.json @@ -1,5 +1,5 @@ { - "version": "21.0.0", + "version": "29.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.assets.json b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.assets.json index 7388475903110..98998264c32c0 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.assets.json +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.assets.json @@ -1,7 +1,7 @@ { - "version": "21.0.0", + "version": "29.0.0", "files": { - "e6a0a51961925fbc37d9b81431449c256ed453f98089eb70f83850f237b4d722": { + "3546c78647ea20567832041c960a034787f9bfc0128226ea8fbc0894366a4dd0": { "source": { "path": "aws-cdk-logs-insights-querydefinition-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "e6a0a51961925fbc37d9b81431449c256ed453f98089eb70f83850f237b4d722.json", + "objectKey": "3546c78647ea20567832041c960a034787f9bfc0128226ea8fbc0894366a4dd0.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.template.json b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.template.json index d4df6bf7837ca..16808c0ca7ef0 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.template.json +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/aws-cdk-logs-insights-querydefinition-integ.template.json @@ -19,6 +19,18 @@ } ] } + }, + "QueryDefinitionWithMultipleStatements58A3EF74": { + "Type": "AWS::Logs::QueryDefinition", + "Properties": { + "Name": "QueryDefinitionWithMultipleStatements", + "QueryString": "fields @timestamp, @message\n| parse @message \"[*] *\" as loggingType, loggingMessage\n| parse @message \"<*>: *\" as differentLoggingType, differentLoggingMessage\n| filter loggingType = \"ERROR\"\n| filter loggingMessage = \"A very strange error occurred!\"\n| sort @timestamp desc\n| limit 20\n| display loggingMessage", + "LogGroupNames": [ + { + "Ref": "LogGroupF5B46931" + } + ] + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/cdk.out b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/cdk.out index 8ecc185e9dbee..d8b441d447f8a 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"21.0.0"} \ No newline at end of file +{"version":"29.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/integ.json b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/integ.json index 5c5e893333693..f445dff967844 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "21.0.0", + "version": "29.0.0", "testCases": { "LogsInsightsQueryDefinitionIntegTest/DefaultTest": { "stacks": [ diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/manifest.json b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/manifest.json index d6fb5b02dcca8..a975ff9d5fae9 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "21.0.0", + "version": "29.0.0", "artifacts": { "aws-cdk-logs-insights-querydefinition-integ.assets": { "type": "cdk:asset-manifest", @@ -17,7 +17,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/e6a0a51961925fbc37d9b81431449c256ed453f98089eb70f83850f237b4d722.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3546c78647ea20567832041c960a034787f9bfc0128226ea8fbc0894366a4dd0.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -45,6 +45,12 @@ "data": "QueryDefinition4190BC36" } ], + "/aws-cdk-logs-insights-querydefinition-integ/QueryDefinitionWithMultipleStatements/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "QueryDefinitionWithMultipleStatements58A3EF74" + } + ], "/aws-cdk-logs-insights-querydefinition-integ/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/tree.json b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/tree.json index 9e04ca4a9a7fa..1bbbc61ba797b 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.js.snapshot/tree.json @@ -62,6 +62,36 @@ "version": "0.0.0" } }, + "QueryDefinitionWithMultipleStatements": { + "id": "QueryDefinitionWithMultipleStatements", + "path": "aws-cdk-logs-insights-querydefinition-integ/QueryDefinitionWithMultipleStatements", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-logs-insights-querydefinition-integ/QueryDefinitionWithMultipleStatements/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::QueryDefinition", + "aws:cdk:cloudformation:props": { + "name": "QueryDefinitionWithMultipleStatements", + "queryString": "fields @timestamp, @message\n| parse @message \"[*] *\" as loggingType, loggingMessage\n| parse @message \"<*>: *\" as differentLoggingType, differentLoggingMessage\n| filter loggingType = \"ERROR\"\n| filter loggingMessage = \"A very strange error occurred!\"\n| sort @timestamp desc\n| limit 20\n| display loggingMessage", + "logGroupNames": [ + { + "Ref": "LogGroupF5B46931" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.CfnQueryDefinition", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-logs.QueryDefinition", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "aws-cdk-logs-insights-querydefinition-integ/BootstrapVersion", @@ -97,7 +127,7 @@ "path": "LogsInsightsQueryDefinitionIntegTest/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.161" + "version": "10.1.237" } }, "DeployAssert": { @@ -143,7 +173,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.161" + "version": "10.1.237" } } }, diff --git a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.ts b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.ts index b3f19a5940d11..ad022bcc486a7 100644 --- a/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.ts +++ b/packages/@aws-cdk/aws-logs/test/integ.save-logs-insights-query-definition.ts @@ -11,6 +11,7 @@ class LogsInsightsQueryDefinitionIntegStack extends Stack { removalPolicy: RemovalPolicy.DESTROY, }); + // Test query creation with single parse and filter statements new QueryDefinition(this, 'QueryDefinition', { queryDefinitionName: 'QueryDefinition', queryString: new QueryString({ @@ -23,6 +24,26 @@ class LogsInsightsQueryDefinitionIntegStack extends Stack { }), logGroups: [logGroup], }); + + // Test query creation with multiple parse and filter statements + new QueryDefinition(this, 'QueryDefinitionWithMultipleStatements', { + queryDefinitionName: 'QueryDefinitionWithMultipleStatements', + queryString: new QueryString({ + fields: ['@timestamp', '@message'], + parseStatements: [ + '@message "[*] *" as loggingType, loggingMessage', + '@message "<*>: *" as differentLoggingType, differentLoggingMessage', + ], + filterStatements: [ + 'loggingType = "ERROR"', + 'loggingMessage = "A very strange error occurred!"', + ], + sort: '@timestamp desc', + limit: 20, + display: 'loggingMessage', + }), + logGroups: [logGroup], + }); } } diff --git a/packages/@aws-cdk/aws-logs/test/query-definition.test.ts b/packages/@aws-cdk/aws-logs/test/query-definition.test.ts index 7b9fd5cb7d4ff..10593be3c35f6 100644 --- a/packages/@aws-cdk/aws-logs/test/query-definition.test.ts +++ b/packages/@aws-cdk/aws-logs/test/query-definition.test.ts @@ -1,4 +1,5 @@ import { Template } from '@aws-cdk/assertions'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import { LogGroup, QueryDefinition, QueryString } from '../lib'; @@ -49,7 +50,7 @@ describe('query definition', () => { }); }); - test('create a query definition with all commands', () => { + testDeprecated('create a query definition with all commands', () => { // GIVEN const stack = new Stack(); @@ -75,4 +76,64 @@ describe('query definition', () => { QueryString: 'fields @timestamp, @message\n| parse @message "[*] *" as loggingType, loggingMessage\n| filter loggingType = "ERROR"\n| sort @timestamp desc\n| limit 20\n| display loggingMessage', }); }); + + test('create a query definition with multiple statements for supported commands', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new QueryDefinition(stack, 'QueryDefinition', { + queryDefinitionName: 'MyQuery', + queryString: new QueryString({ + fields: ['@timestamp', '@message'], + parseStatements: [ + '@message "[*] *" as loggingType, loggingMessage', + '@message "<*>: *" as differentLoggingType, differentLoggingMessage', + ], + filterStatements: [ + 'loggingType = "ERROR"', + 'loggingMessage = "A very strange error occurred!"', + ], + sort: '@timestamp desc', + limit: 20, + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Logs::QueryDefinition', { + Name: 'MyQuery', + QueryString: 'fields @timestamp, @message\n| parse @message "[*] *" as loggingType, loggingMessage\n| parse @message "<*>: *" as differentLoggingType, differentLoggingMessage\n| filter loggingType = "ERROR"\n| filter loggingMessage = "A very strange error occurred!"\n| sort @timestamp desc\n| limit 20', + }); + }); + + testDeprecated('create a query with both single and multi statement properties for filtering and parsing', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new QueryDefinition(stack, 'QueryDefinition', { + queryDefinitionName: 'MyQuery', + queryString: new QueryString({ + fields: ['@timestamp', '@message'], + parse: '@message "[*] *" as loggingType, loggingMessage', + parseStatements: [ + '@message "[*] *" as loggingType, loggingMessage', + '@message "<*>: *" as differentLoggingType, differentLoggingMessage', + ], + filter: 'loggingType = "ERROR"', + filterStatements: [ + 'loggingType = "ERROR"', + 'loggingMessage = "A very strange error occurred!"', + ], + sort: '@timestamp desc', + limit: 20, + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Logs::QueryDefinition', { + Name: 'MyQuery', + QueryString: 'fields @timestamp, @message\n| parse @message "[*] *" as loggingType, loggingMessage\n| parse @message "<*>: *" as differentLoggingType, differentLoggingMessage\n| filter loggingType = "ERROR"\n| filter loggingMessage = "A very strange error occurred!"\n| sort @timestamp desc\n| limit 20', + }); + }); });