Skip to content

Commit

Permalink
Add MVP rule for AWS::Events::Rule ScheduleExpression
Browse files Browse the repository at this point in the history
  • Loading branch information
fatbasstard committed Jul 16, 2019
1 parent 29019eb commit 02d5bba
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ To include these rules, use the `-e/include-experimental` argument when running


## Rules
The following **119** rules are applied by this linter:
The following **120** rules are applied by this linter:
(_This documentation is generated from the Rules, do not alter this manually_)

| Rule ID | Title | Description | Config<br />(Name:Type:Default) | Source | Tags |
Expand Down Expand Up @@ -117,6 +117,7 @@ The following **119** rules are applied by this linter:
| E3024<a name="E3024"></a> | Validate that ProvisionedThroughput is not specified with BillingMode PAY_PER_REQUEST | When using ProvisionedThroughput with BillingMode PAY_PER_REQUEST will result in BillingMode being changed to PROVISIONED | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) | `resources`,`dynamodb`,`provisioned_throughput`,`billing_mode` |
| E3025<a name="E3025"></a> | RDS instance type is compatible with the RDS type | Check the RDS instance types are supported by the type of RDS engine. Only if the values are strings will this be checked. | | [Source](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html) | `resources`,`rds` |
| E3026<a name="E3026"></a> | Check Elastic Cache Redis Cluster settings | Evaluate Redis Cluster groups to make sure automatic failover is enabled when cluster mode is enabled | | [Source](https://github.com/awslabs/cfn-python-lint) | `resources`,`elasticcache` |
| E3027<a name="E3027"></a> | Validate AWS Event ScheduleExpression format | Validate the formation of the AWS::Event ScheduleExpression | | [Source](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html) | `resources`,`events` |
| E3030<a name="E3030"></a> | Check if properties have a valid value | Check if properties have a valid value in case of an enumator | | [Source](https://github.com/aws-cloudformation/cfn-python-lint/blob/master/docs/cfn-resource-specification.md#allowedvalue) | `resources`,`property`,`allowed value` |
| E3031<a name="E3031"></a> | Check if property values adhere to a specific pattern | Check if properties have a valid value in case of a pattern (Regular Expression) | | [Source](https://github.com/awslabs/cfn-python-lint/blob/master/docs/cfn-resource-specification.md#allowedpattern) | `resources`,`property`,`allowed pattern`,`regex` |
| E3032<a name="E3032"></a> | Check if a list has between min and max number of values specified | Check lists for the number of items in the list to validate they are between the minimum and maximum | | [Source](https://github.com/awslabs/cfn-python-lint/blob/master/docs/cfn-resource-specification.md#allowedpattern) | `resources`,`property`,`list`,`size` |
Expand Down
98 changes: 98 additions & 0 deletions src/cfnlint/rules/resources/events/RuleScheduleExpression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
from cfnlint import CloudFormationLintRule
from cfnlint import RuleMatch


class RuleScheduleExpression(CloudFormationLintRule):
"""Validate AWS Events Schedule expression format"""
id = 'E3027'
shortdesc = 'Validate AWS Event ScheduleExpression format'
description = 'Validate the formation of the AWS::Event ScheduleExpression'
source_url = 'https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html'
tags = ['resources', 'events']

def initialize(self, cfn):
"""Initialize the rule"""
self.resource_property_types = ['AWS::Events::Rule']

def check_rate(self, value, path):
"""Check Rate configuration"""
matches = []
# Extract the expression from rate(XXX)
rate_expression = value[value.find('(')+1:value.find(')')]

if not rate_expression:
matches.append(RuleMatch(path, 'Rate value of ScheduleExpression cannot be empty'))
else:
# Rate format: rate(Value Unit)
items = rate_expression.split(' ')

if len(items) != 2:
message = 'Rate expression must contain 2 elements (Value Unit), rate contains {} elements'
matches.append(RuleMatch(path, message.format(len(items))))
else:
# Check the Value
if not items[0].isdigit():
message = 'Rate Value ({}) should be of type Integer.'
matches.append(RuleMatch(path, message.format(items[0])))

return matches

def check_cron(self, value, path):
"""Check Cron configuration"""
matches = []
# Extract the expression from cron(XXX)
cron_expression = value[value.find('(')+1:value.find(')')]

if not cron_expression:
matches.append(RuleMatch(path, 'Cron value of ScheduleExpression cannot be empty'))
else:
# Rate format: cron(Minutes Hours Day-of-month Month Day-of-week Year)
items = cron_expression.split(' ')

if len(items) != 6:
message = 'Cron expression must contain 6 elements (Minutes Hours Day-of-month Month Day-of-week Year), cron contains {} elements'
matches.append(RuleMatch(path, message.format(len(items))))

return matches

def check_value(self, value, path):
"""Count ScheduledExpression value"""
matches = []

# Value is either "cron()" or "rate()"
if value.startswith('rate(') and value.endswith(')'):
matches.extend(self.check_rate(value, path))
elif value.startswith('cron(') and value.endswith(')'):
matches.extend(self.check_cron(value, path))
else:
message = 'Invalid ScheduledExpression specified ({}). Value has to be either cron() or rate()'
matches.append(RuleMatch(path, message.format(value)))

return matches

def match_resource_properties(self, properties, _, path, cfn):
"""Check CloudFormation Properties"""
matches = []

matches.extend(
cfn.check_value(
obj=properties, key='ScheduleExpression',
path=path[:],
check_value=self.check_value
))

return matches
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyScheduledRule1:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "daily" # has to be cron() or rate()
MyScheduledRule2:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron()" # Empty cron
MyScheduledRule3:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "rate()" # Empty rate
MyScheduledRule4:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "rate(5)" # Not enough values
MyScheduledRule5:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "rate(5 minutes daily)" # Too many values
MyScheduledRule6:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "rate(five minutes)" # Value has to be a digit
MyScheduledRule7:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0 */1 * * WED)" # Not enough values
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
AWSTemplateFormatVersion: "2010-09-09"
Description: >
Valid scheduled expression accordig to the examples in the documentation
(https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html)
Resources:
MyCronRule1:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(15 10 * * ? *)" # 10:15 AM (UTC) every day
MyCronRule2:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0 18 ? * MON-FRI *)" # 6:00 PM Monday through Friday
MyCronRule3:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0 8 1 * ? *)" # 8:00 AM on the first day of the month
MyCronRule4:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0/10 * ? * MON-FRI *)" # Every 10 min on weekdays
MyCronRule5:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0/5 8-17 ? * MON-FRI *)" # Every 5 minutes between 8:00 AM and 5:55 PM weekdays
MyCronRule6:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0 9 ? * 2#1 *)" # 9:00 AM on the first Monday of each month
MyRateRule1:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: rate(5 minutes) # Every 5 minutes
MyRateRule2:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: rate(1 hour) # Every hour
MyRateRule3:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: rate(7 days) # Every seven days
37 changes: 37 additions & 0 deletions test/rules/resources/events/test_rule_schedule_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
from cfnlint.rules.resources.events.RuleScheduleExpression import RuleScheduleExpression # pylint: disable=E0401
from ... import BaseRuleTestCase


class TestRuleScheduleExpression(BaseRuleTestCase):
"""Test Event Rules ScheduledExpression format"""
def setUp(self):
"""Setup"""
super(TestRuleTargetsLimit, self).setUp()
self.collection.register(RuleScheduleExpression())
self.success_templates = [
'test/fixtures/templates/good/resources/events/rule_schedule_expression.yaml'
]

def test_file_positive(self):
"""Test Positive"""
self.helper_file_positive()

def test_file_negative_alias(self):
"""Test failure"""
self.helper_file_negative('test/fixtures/templates/bad/resources/events/rule_schedule_expression.yaml', 7)

0 comments on commit 02d5bba

Please sign in to comment.