Can AWS Eventbridge rules match object inside an array - amazon-web-services

I'm attempting to match events using an eventbridge rule. However I need to match the event if it's array contains an object with some particular properties and I'm struggling with how to do that.
An example event:
{
"version": "0",
"id": "396bfea8-6311-c1ab-44cf-d44d93014a89",
"detail-type": "ExampleEvent",
"source": "example.com",
"account": "207772098559",
"time": "2020-05-31T19:44:55Z",
"region": "eu-west-1",
"resources": [],
"detail": {
"Id": "2fbf7f1b0b0f462ba16b6076812f1b77",
"Data": {
"entities": [
{
"entityType": "task",
"action": "update",
"entityId": "bbf74ec6-8762-48d6-b09f-23a97834fc2f"
},
{
"entityType": "note",
"action": "update",
"entityId": "bbf74ec6-8762-48d6-b09f-23a97834fc2f"
}
]
}
}
}
I would like the rule to match where the entities collection contains any items with both entityType task and action update. I'd imagined it would look like the below but this gets the error "Unrecognized match type entityType" as it's thinking that the object inside the array means I'm trying to use one of the supported match types.
{
"source": [
"example.com"
],
"detail-type": [
"ExampleEvent"
],
"detail": {
"Data": {
"entities": [
{
"entityType": [
"Task"
],
"action": [
"update"
]
}
]
}
}
}

Hopefully I am answering your question and you are not trying to pull a single entity out of the event, instead I think you are asking how to match this event.
This is a matched event for the object array. I removed the array is the matched event and I lowercased the Task entityType filter since it is case-sensitive.
{
"source": [
"example.com"
],
"detail-type": [
"ExampleEvent"
],
"detail": {
"Data": {
"entities": {
"entityType": [
"task"
],
"action": [
"update"
]
}
}
}
}
Example 2:
Here is another example with that is more nesting and mixture of array and object. As you can it treats arrays and objects the same. This is a Redshift alarm that is producing high disk space.
{
"version": "0",
"id": "c4c1c1c9-6542-e61b-6ef0-8c4d36933a92",
"detail-type": "CloudWatch Alarm State Change",
"source": "aws.cloudwatch",
"account": "123456789012",
"time": "2019-10-02T17:04:40Z",
"region": "us-east-1",
"resources": ["arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerMemoryTooHigh"],
"detail": {
"alarmName": "ServerDiskSpaceTooHigh",
"configuration": {
"description": "Goes into alarm when server Disk Space utilization is too high!",
"metrics": [{
"id": "30b6c6b2-a864-43a2-4877-c09a1afc3b87",
"metricStat": {
"metric": {
"dimensions": {
"InstanceId": "i-12345678901234567"
},
"name": "PercentageDiskSpaceUsed",
"namespace": "AWS/Redshift"
},
"period": 300,
"stat": "Average"
},
"returnData": true
}]
},
"previousState": {
"reason": "Threshold Crossed: 1 out of the last 1 datapoints [0.0666851903306472 (01/10/19 13:46:00)] was not greater than the threshold (50.0) (minimum 1 datapoint for ALARM -> OK transition).",
"reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2019-10-01T13:56:40.985+0000\",\"startDate\":\"2019-10-01T13:46:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.0666851903306472],\"threshold\":50.0}",
"timestamp": "2019-10-01T13:56:40.987+0000",
"value": "OK"
},
"state": {
"reason": "Threshold Crossed: 1 out of the last 1 datapoints [99.50160229693434 (02/10/19 16:59:00)] was greater than the threshold (50.0) (minimum 1 datapoint for OK -> ALARM transition).",
"reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2019-10-02T17:04:40.985+0000\",\"startDate\":\"2019-10-02T16:59:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[99.50160229693434],\"threshold\":50.0}",
"timestamp": "2019-10-02T17:04:40.989+0000",
"value": "ALARM"
}
}
}
Here is the matched event:
{
"detail-type": ["CloudWatch Alarm State Change"],
"source": ["aws.cloudwatch"],
"detail": {
"configuration": {
"metrics": {
"metricStat": {
"metric": {
"name": ["PercentageDiskSpaceUsed"],
"namespace": ["AWS/Redshift"]
}
}
}
},
"state": {
"value": ["ALARM"]
}
}
}

Related

AWS Step Functions: Input Parameter

I have a very simple workflow using Fargate containers. The containers simply return inputs.
Workflow Input is:
{
"value": "FALSE"
}
Choice operates as expected. $.value is processed as FALSE and choice runs the ECS RunTask-FALSE container.
The container should receive and return the string "FALSE", however it returns the string "*.value". For some reason the Input value is not being parsed. When I look at the Task run in ECS portal I see:
How can I pass the starting parameter to this Task ?
{
"Comment": "A description of my state machine",
"StartAt": "Choice",
"States": {
"Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.value",
"StringEquals": "FALSE",
"Next": "ECS RunTask-FALSE"
}
],
"Default": "ECS RunTask-TRUE"
},
"ECS RunTask-FALSE": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:us-east-2:xxxxxxxxxxx:cluster/portal",
"TaskDefinition": "arn:aws:ecs:us-east-2:xxxxxxxxxxx:task-definition/simple:4",
"Overrides": {
"ContainerOverrides": [
{
"Name": "simple",
"Command": ["$.value"]
}
]
},
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-001f85595e8af43cf",
"subnet-05e742358ac59ae04"
],
"SecurityGroups": [
"sg-0948e5328861ae667"
],
"AssignPublicIp": "ENABLED"
}
}
},
"End": true
},
"ECS RunTask-TRUE": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:us-east-2:xxxxxxxxxxx:cluster/portal",
"TaskDefinition": "arn:aws:ecs:us-east-2:xxxxxxxxxxx:task-definition/simple:4",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-001f85595e8af43cf",
"subnet-05e742358ac59ae04"
],
"SecurityGroups": [
"sg-0948e5328861ae667"
],
"AssignPublicIp": "ENABLED"
}
}
},
"End": true
}
}
}
"Command.$": "States.Array($.value)"
Parameters: For key-value pairs where the value is selected using a path, the key name must end in .$.
States.Array Intrinsic Function: The interpreter returns a JSON array containing the values of the arguments in the order provided.

AWS EVENTBRIDGE: Add content filtering to ECS task state changes

I am trying to create an eventbridge rule whenever ECS task is deleted abnormally.
Normally ECS sends all events event the created or attached states too but I want to filter only DELETEDstate.
I am using CDK to create my event rule. I am trying to implement content filtering based on status which is present in attachment field which is again a part of detail field.
Sample event from ECS Task ->
{
"version": "0",
"id": "3317b2af-7005-947d-b652-f55e762e571a",
"detail-type": "ECS Task State Change",
"source": "aws.ecs",
"account": "111122223333",
"time": "2020-01-23T17:57:58Z",
"region": "us-west-2",
"resources": [
"arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad"
],
"detail": {
"attachments": [
{
"id": "1789bcae-ddfb-4d10-8ebe-8ac87ddba5b8",
"type": "eni",
"status": "ATTACHED",
"details": [
{
"name": "subnetId",
"value": "subnet-abcd1234"
},
{
"name": "networkInterfaceId",
"value": "eni-abcd1234"
},
{
"name": "macAddress",
"value": "0a:98:eb:a7:29:ba"
},
{
"name": "privateIPv4Address",
"value": "10.0.0.139"
}
]
}
],
"availabilityZone": "us-west-2c",
"clusterArn": "arn:aws:ecs:us-west-2:111122223333:cluster/FargateCluster",
"containers": [
{
"containerArn": "arn:aws:ecs:us-west-2:111122223333:container/cf159fd6-3e3f-4a9e-84f9-66cbe726af01",
"lastStatus": "RUNNING",
"name": "FargateApp",
"image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/hello-repository:latest",
"imageDigest": "sha256:74b2c688c700ec95a93e478cdb959737c148df3fbf5ea706abe0318726e885e6",
"runtimeId": "ad64cbc71c7fb31c55507ec24c9f77947132b03d48d9961115cf24f3b7307e1e",
"taskArn": "arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad",
"networkInterfaces": [
{
"attachmentId": "1789bcae-ddfb-4d10-8ebe-8ac87ddba5b8",
"privateIpv4Address": "10.0.0.139"
}
],
"cpu": "0"
}
],
"createdAt": "2020-01-23T17:57:34.402Z",
"launchType": "FARGATE",
"cpu": "256",
"memory": "512",
"desiredStatus": "RUNNING",
"group": "family:sample-fargate",
"lastStatus": "RUNNING",
"overrides": {
"containerOverrides": [
{
"name": "FargateApp"
}
]
},
"connectivity": "CONNECTED",
"connectivityAt": "2020-01-23T17:57:38.453Z",
"pullStartedAt": "2020-01-23T17:57:52.103Z",
"startedAt": "2020-01-23T17:57:58.103Z",
"pullStoppedAt": "2020-01-23T17:57:55.103Z",
"updatedAt": "2020-01-23T17:57:58.103Z",
"taskArn": "arn:aws:ecs:us-west-2:111122223333:task/FargateCluster/c13b4cb40f1f4fe4a2971f76ae5a47ad",
"taskDefinitionArn": "arn:aws:ecs:us-west-2:111122223333:task-definition/sample-fargate:1",
"version": 4,
"platformVersion": "1.3.0"
}
}
cdk code
{
eventPattern: {
source: ['aws.ecs'],
detailType: ['ECS Task State Change'],
detail: {
clusterArn: [cluster.clusterArn],
attachments: [{ status: [{ prefix: 'DELETED' }] }] // this is not working
},
},
}
Edit: It *is* possible to filter on objects within arrays
detail: { "attachments": {"status": ["DELETED"] } }
EventBridge can match scalars in an array, but not arbitrary objects in an array:
docs: If the value in the event is an array, then the event pattern matches if the intersection of the event pattern array and the event array is non-empty.
That means EventBridge cannot match only "status": "DELETED". What are your options?
Base your pattern on a correlated non-array key-value pair, e.g. "lastStatus": "STOPPED".
Match all patterns. Add logic to the event target to ignore uninteresting patterns.
Note: because you say the array reliably has only one element, you can transform the event detail before it gets sent to the target. This does not help with the matching problem, but can make downstream filtering easier. Here is a CDK example for a Lambda target:
rule.addTarget(
new targets.LambdaFunction(func, {
event: events.RuleTargetInput.fromObject({
status: events.EventField.fromPath('$.detail.attachments[0].status'),
original: events.EventField.fromPath('$'),
}),
})
);
The Lambda receives the reshaped event detail:
{
"status": "ATTACHED",
"original": <the original event>
}

Alexa smart home skill development: Device discovery not working

I am currently developing a smart home skill for my blinds, however I am unable to discover device. Is there a way for me to validate my Discovery response message? I'm thinking this is some logical error in the JSON.
I'm using a Lambda function to perform the requests to my API using node-fetch and async/await, thus I have tagged all JS function as async, this could be another potential cause of this issue. I don't get any errors in CloudWatch either.
This is the response my Lambda function is sending:
{
"event": {
"header": {
"namespace": "Alexa.Discovery",
"name": "Discover.Response",
"payloadVersion": "3",
"messageId": "0a58ace0-e6ab-47de-b6af-b600b5ab8a7a"
},
"payload": {
"endpoints": [
{
"endpointId": "com-tobisoft-rollos-1",
"manufacturerName": "tobisoft",
"description": "Office Blinds",
"friendlyName": "Office Blinds",
"displayCategories": [
"INTERIOR_BLIND"
],
"capabilities": [
{
"type": "AlexaInterface",
"interface": "Alexa.RangeController",
"instance": "Blind.Lift",
"version": "3",
"properties": {
"supported": [
{
"name": "rangeValue"
}
],
"proactivelyReported": true,
"retrievable": true
},
"capabilityResources": {
"friendlyNames": [
{
"#type": "asset",
"value": {
"assetId": "Alexa.Setting.Opening"
}
}
]
},
"configuration": {
"supportedRange": {
"minimumValue": 0,
"maximumValue": 100,
"precision": 1
},
"unitOfMeasure": "Alexa.Unit.Percent"
},
"semantics": {
"actionMappings": [
{
"#type": "ActionsToDirective",
"actions": [
"Alexa.Actions.Close"
],
"directive": {
"name": "SetRangeValue",
"payload": {
"rangeValue": 100
}
}
},
{
"#type": "ActionsToDirective",
"actions": [
"Alexa.Actions.Open"
],
"directive": {
"name": "SetRangeValue",
"payload": {
"rangeValue": 1
}
}
},
{
"#type": "ActionsToDirective",
"actions": [
"Alexa.Actions.Lower"
],
"directive": {
"name": "AdjustRangeValue",
"payload": {
"rangeValueDelta": 10,
"rangeValueDeltaDefault": false
}
}
},
{
"#type": "ActionsToDirective",
"actions": [
"Alexa.Actions.Raise"
],
"directive": {
"name": "AdjustRangeValue",
"payload": {
"rangeValueDelta": -10,
"rangeValueDeltaDefault": false
}
}
}
],
"stateMappings": [
{
"#type": "StatesToValue",
"states": [
"Alexa.States.Closed"
],
"value": 100
},
{
"#type": "StatesToRange",
"states": [
"Alexa.States.Open"
],
"range": {
"value": 0
}
}
]
}
},
{
"type": "AlexaInterface",
"interface": "Alexa",
"version": "3"
}
]
}
]
}
}
}
Thanks for any help.
Is there a way for me to validate my Discovery response message?
Yes, you could use the Alexa Smart Home Message JSON Schema. This schema can be used for message validation during skill development, it validates Smart Home skills (except the Video Skills API).
This is the response my Lambda function is sending
I've validated your response following this steps, the result: no errors found, the JSON validates against the schema. So, there's probably another thing going on. I suggest getting in touch with Alexa Developer Contact Us

Configure the LoadBalancer in AWS CloudWatch Alarm

I have a web application on AWS and I am trying to configure my autoscaling based on the requests.
My AppLoadBalancer resource is as below:
"AppLoadBalancer": {
"Properties": {
"LoadBalancerAttributes": [
{
"Key": "idle_timeout.timeout_seconds",
"Value": "60"
}
],
"Name": "sample-app-v1",
"Scheme": "internet-facing",
"SecurityGroups": [
"sg-1abcd234"
],
"Subnets": {
"Fn::FindInMap": [
"LoadBalancerSubnets",
{
"Ref": "AWS::Region"
},
"Subnets"
]
},
"Tags": [
{
"Key": "Name",
"Value": "sample-app-v1"
},
{
"Key": "StackName",
"Value": "sample-app"
},
{
"Key": "StackVersion",
"Value": "v1"
}
]
},
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer"
}
I am trying to configure a CloudWatch Alarm like this:
"RequestCountTooHighAlarm": {
"Properties": {
"AlarmActions": [
{
"Ref": "ScaleUp"
}
],
"AlarmDescription": "Scale-up if request count >= 8000 for last 5 minute",
"ComparisonOperator": "GreaterThanOrEqualToThreshold",
"Dimensions": [
{
"Name": "LoadBalancer",
"Value": [
{
"Fn::GetAtt": [
"AppLoadBalancer",
"LoadBalancerFullName"
]
}
]
}
],
"EvaluationPeriods": 1,
"MetricName": "RequestCount",
"Namespace": "AWS/ApplicationELB",
"OKActions": [
{
"Ref": "ScaleDown"
}
],
"Period": 300,
"Statistic": "SampleCount",
"Threshold": 8000
},
"Type": "AWS::CloudWatch::Alarm"
}
However, my stack continues to fail and I don't know what is wrong here. Here is the error which I am getting.
ERROR: RequestCountTooHighAlarm CREATE_FAILED: Value of property Value must be of type String
ERROR: sample-app-v1 CREATE_FAILED: The following resource(s) failed to create: [RequestCountTooHighAlarm].
Can somebody suggest?
The property mentioned requires a string. You have it defined as a list:
"Value": [
{
"Fn::GetAtt": [
"AppLoadBalancer",
"LoadBalancerFullName"
]
} ]
The [] brackets defines a list in JSON. Remove the outside brackets in the Value value, and use only the Fn::GetAt portion. That call will return a string.

Setup Lambda to trigger from CloudWatch using CloudFormation

I want to use CloudFormation to trigger Lambda when my CloudWatch function is called. I have the below, but it does not work.
CloudWatch rule created fine
"CloudWatchNewEc2": {
"Type": "AWS::Events::Rule",
"DependsOn": ["LambdaNewEc2"],
"Properties": {
"Description": "Triggered on new EC2 instances",
"EventPattern": {
"source": [
"aws.ec2"
],
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"ec2.amazonaws.com"
],
"eventName": [
"RunInstances"
]
}
},
"Targets": [
{
"Arn": {
"Fn::GetAtt": ["LambdaNewEc2", "Arn"]
},
"Id": "NewEc2AutoTag"
}
]
}
},
Lambda created but is not triggered
"LambdaNewEc2": {
"Type": "AWS::Lambda::Function",
"DependsOn": ["S3Lambda", "IAMRoleLambda"],
"Properties": {
"Code": {
"S3Bucket": {"Ref": "LambdaBucketName"},
"S3Key": "skynet-lambda.zip"
},
"Description": "When new EC2 instances are created, auto tag them",
"FunctionName": "newEc2AutoTag",
"Handler": "index.newEc2_autoTag",
"Role": {"Fn::GetAtt": ["IAMRoleLambda", "Arn"]},
"Runtime": "nodejs6.10",
"Timeout": "30"
}
}
},
It seems like CloudWatch Target is not sufficient?
UPDATE (Full CloudFormation template)
{
"Parameters": {
"Environment": {
"Type": "String",
"Default": "Staging",
"AllowedValues": [
"Testing",
"Staging",
"Production"
],
"Description": "Environment name"
},
"BucketName": {
"Type": "String",
"Default": "skynet-staging",
"Description": "Bucket Name"
},
"LambdaBucketName": {
"Type": "String",
"Default": "skynet-lambda",
"Description": "Lambda Bucket Name"
},
"Owner": {
"Type": "String",
"Description": "Owner"
}
},
"Resources": {
"S3Web": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": {
"Ref": "BucketName"
},
"WebsiteConfiguration": {
"IndexDocument": "index.html",
"RoutingRules": [
{
"RedirectRule": {
"ReplaceKeyPrefixWith": "#"
},
"RoutingRuleCondition": {
"HttpErrorCodeReturnedEquals": "404"
}
}
]
},
"AccessControl": "PublicRead",
"Tags": [
{
"Key": "Cost Center",
"Value": "Skynet"
},
{
"Key": "Environment",
"Value": {
"Ref": "Environment"
}
},
{
"Key": "Owner",
"Value": {
"Ref": "Owner"
}
}
]
}
},
"S3Lambda": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": {
"Ref": "LambdaBucketName"
},
"VersioningConfiguration": {
"Status": "Enabled"
},
"Tags": [
{
"Key": "Cost Center",
"Value": "Skynet"
},
{
"Key": "Owner",
"Value": {
"Ref": "Owner"
}
}
]
}
},
"CloudWatchNewEc2": {
"Type": "AWS::Events::Rule",
"DependsOn": ["LambdaNewEc2"],
"Properties": {
"Description": "Triggered on new EC2 instances",
"EventPattern": {
"source": [
"aws.ec2"
],
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"ec2.amazonaws.com"
],
"eventName": [
"RunInstances"
]
}
},
"Targets": [
{
"Arn": {
"Fn::GetAtt": ["LambdaNewEc2", "Arn"]
},
"Id": "NewEc2AutoTag"
}
]
}
},
"IAMRoleLambda": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "skynet-lambda-role",
"AssumeRolePolicyDocument": {
"Version" : "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [ "lambda.amazonaws.com" ]
},
"Action": [ "sts:AssumeRole" ]
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/AmazonEC2FullAccess",
"arn:aws:iam::aws:policy/AWSLambdaFullAccess",
"arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess",
"arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
]
}
},
"LambdaNewEc2": {
"Type": "AWS::Lambda::Function",
"DependsOn": ["S3Lambda", "IAMRoleLambda"],
"Properties": {
"Code": {
"S3Bucket": {"Ref": "LambdaBucketName"},
"S3Key": "skynet-lambda.zip"
},
"Description": "When new EC2 instances are created, auto tag them",
"FunctionName": "newEc2AutoTag",
"Handler": "index.newEc2_autoTag",
"Role": {"Fn::GetAtt": ["IAMRoleLambda", "Arn"]},
"Runtime": "nodejs6.10",
"Timeout": "30"
}
}
},
"Outputs": {
"WebUrl": {
"Value": {
"Fn::GetAtt": [
"S3Web",
"WebsiteURL"
]
},
"Description": "S3 bucket for web files"
}
}
}
I managed to deploy your template into a CloudFormation stack (by removing the LambdaBucket and pointing to my own zip file). It seems to create all resource correctly.
It took about 10 minutes for the RunInstances event to appear in CloudTrail. It then successfully triggered the Rule, but the CloudWatch metrics for my rule showed a failed invocation because I faked a Lambda function for your template.
Once I edited the rule to point to a better function and re-tested, it worked fine.
Bottom line: Seems to work!