Completed events

This commit is contained in:
Terry Cain 2017-10-28 20:17:34 +01:00
parent 6e28c58e26
commit a6e5ffb99b
6 changed files with 179 additions and 68 deletions

View File

@ -917,13 +917,13 @@
- [ ] unpeer_vpc - [ ] unpeer_vpc
- [ ] update_domain_entry - [ ] update_domain_entry
## xray - 0% implemented ## xray - 100% implemented
- [ ] batch_get_traces - [X] batch_get_traces
- [ ] get_service_graph - [X] get_service_graph
- [ ] get_trace_graph - [X] get_trace_graph
- [ ] get_trace_summaries - [X] get_trace_summaries
- [ ] put_telemetry_records - [X] put_telemetry_records
- [ ] put_trace_segments - [X] put_trace_segments
## ec2 - 41% implemented ## ec2 - 41% implemented
- [ ] accept_reserved_instances_exchange_quote - [ ] accept_reserved_instances_exchange_quote
@ -1700,16 +1700,16 @@
- [ ] stop_db_instance - [ ] stop_db_instance
## acm - 100% implemented ## acm - 100% implemented
- [x] add_tags_to_certificate - [X] add_tags_to_certificate
- [x] delete_certificate - [X] delete_certificate
- [x] describe_certificate - [X] describe_certificate
- [x] get_certificate - [X] get_certificate
- [x] import_certificate - [X] import_certificate
- [x] list_certificates - [X] list_certificates
- [x] list_tags_for_certificate - [X] list_tags_for_certificate
- [x] remove_tags_from_certificate - [X] remove_tags_from_certificate
- [x] request_certificate - [X] request_certificate
- [x] resend_validation_email - [X] resend_validation_email
## elasticache - 0% implemented ## elasticache - 0% implemented
- [ ] add_tags_to_resource - [ ] add_tags_to_resource
@ -3089,20 +3089,20 @@
- [ ] start_configuration_recorder - [ ] start_configuration_recorder
- [ ] stop_configuration_recorder - [ ] stop_configuration_recorder
## events - 73% implemented ## events - 100% implemented
- [X] delete_rule - [X] delete_rule
- [ ] describe_event_bus - [X] describe_event_bus
- [X] describe_rule - [X] describe_rule
- [X] disable_rule - [X] disable_rule
- [X] enable_rule - [X] enable_rule
- [X] list_rule_names_by_target - [X] list_rule_names_by_target
- [X] list_rules - [X] list_rules
- [X] list_targets_by_rule - [X] list_targets_by_rule
- [ ] put_events - [X] put_events
- [ ] put_permission - [X] put_permission
- [X] put_rule - [X] put_rule
- [X] put_targets - [X] put_targets
- [ ] remove_permission - [X] remove_permission
- [X] remove_targets - [X] remove_targets
- [X] test_event_pattern - [X] test_event_pattern

View File

@ -68,6 +68,8 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| Cloudwatch | @mock_cloudwatch | basic endpoints done | | Cloudwatch | @mock_cloudwatch | basic endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| CloudwatchEvents | @mock_events | all endpoints done |
|------------------------------------------------------------------------------|
| Data Pipeline | @mock_datapipeline| basic endpoints done | | Data Pipeline | @mock_datapipeline| basic endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| DynamoDB | @mock_dynamodb | core endpoints done | | DynamoDB | @mock_dynamodb | core endpoints done |
@ -127,7 +129,7 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| SWF | @mock_swf | basic endpoints done | | SWF | @mock_swf | basic endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| X-Ray | @mock_xray | core endpoints done | | X-Ray | @mock_xray | all endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
``` ```

View File

@ -34,6 +34,8 @@ ERROR_JSON_RESPONSE = u"""{
class RESTError(HTTPException): class RESTError(HTTPException):
code = 400
templates = { templates = {
'single_error': SINGLE_ERROR_RESPONSE, 'single_error': SINGLE_ERROR_RESPONSE,
'error': ERROR_RESPONSE, 'error': ERROR_RESPONSE,
@ -54,7 +56,6 @@ class DryRunClientError(RESTError):
class JsonRESTError(RESTError): class JsonRESTError(RESTError):
def __init__(self, error_type, message, template='error_json', **kwargs): def __init__(self, error_type, message, template='error_json', **kwargs):
super(JsonRESTError, self).__init__( super(JsonRESTError, self).__init__(
error_type, message, template, **kwargs) error_type, message, template, **kwargs)

View File

@ -1,6 +1,7 @@
import os import os
import re import re
from moto.core.exceptions import JsonRESTError
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
@ -50,6 +51,8 @@ class Rule(BaseModel):
class EventsBackend(BaseBackend): class EventsBackend(BaseBackend):
ACCOUNT_ID = re.compile(r'^(\d{1,12}|\*)$')
STATEMENT_ID = re.compile(r'^[a-zA-Z0-9-_]{1,64}$')
def __init__(self): def __init__(self):
self.rules = {} self.rules = {}
@ -58,6 +61,8 @@ class EventsBackend(BaseBackend):
self.rules_order = [] self.rules_order = []
self.next_tokens = {} self.next_tokens = {}
self.permissions = {}
def _get_rule_by_index(self, i): def _get_rule_by_index(self, i):
return self.rules.get(self.rules_order[i]) return self.rules.get(self.rules_order[i])
@ -181,6 +186,17 @@ class EventsBackend(BaseBackend):
return False return False
def put_events(self, events):
num_events = len(events)
if num_events < 1:
raise JsonRESTError('ValidationError', 'Need at least 1 event')
elif num_events > 10:
raise JsonRESTError('ValidationError', 'Can only submit 10 events at once')
# We dont really need to store the events yet
return []
def remove_targets(self, name, ids): def remove_targets(self, name, ids):
rule = self.rules.get(name) rule = self.rules.get(name)
@ -193,5 +209,40 @@ class EventsBackend(BaseBackend):
def test_event_pattern(self): def test_event_pattern(self):
raise NotImplementedError() raise NotImplementedError()
def put_permission(self, action, principal, statement_id):
if action is None or action != 'PutEvents':
raise JsonRESTError('InvalidParameterValue', 'Action must be PutEvents')
if principal is None or self.ACCOUNT_ID.match(principal) is None:
raise JsonRESTError('InvalidParameterValue', 'Principal must match ^(\d{1,12}|\*)$')
if statement_id is None or self.STATEMENT_ID.match(statement_id) is None:
raise JsonRESTError('InvalidParameterValue', 'StatementId must match ^[a-zA-Z0-9-_]{1,64}$')
self.permissions[statement_id] = {'action': action, 'principal': principal}
def remove_permission(self, statement_id):
try:
del self.permissions[statement_id]
except KeyError:
raise JsonRESTError('ResourceNotFoundException', 'StatementId not found')
def describe_event_bus(self):
arn = "arn:aws:events:us-east-1:000000000000:event-bus/default"
statements = []
for statement_id, data in self.permissions.items():
statements.append({
'Sid': statement_id,
'Effect': 'Allow',
'Principal': {'AWS': 'arn:aws:iam::{0}:root'.format(data['principal'])},
'Action': 'events:{0}'.format(data['action']),
'Resource': arn
})
return {
'Policy': {'Version': '2012-10-17', 'Statement': statements},
'Name': 'default',
'Arn': arn
}
events_backend = EventsBackend() events_backend = EventsBackend()

View File

@ -18,9 +18,17 @@ class EventsHandler(BaseResponse):
'RoleArn': rule.role_arn 'RoleArn': rule.role_arn
} }
def load_body(self): @property
decoded_body = self.body def request_params(self):
return json.loads(decoded_body or '{}') if not hasattr(self, '_json_body'):
try:
self._json_body = json.loads(self.body)
except ValueError:
self._json_body = {}
return self._json_body
def _get_param(self, param, if_none=None):
return self.request_params.get(param, if_none)
def error(self, type_, message='', status=400): def error(self, type_, message='', status=400):
headers = self.response_headers headers = self.response_headers
@ -28,8 +36,7 @@ class EventsHandler(BaseResponse):
return json.dumps({'__type': type_, 'message': message}), headers, return json.dumps({'__type': type_, 'message': message}), headers,
def delete_rule(self): def delete_rule(self):
body = self.load_body() name = self._get_param('Name')
name = body.get('Name')
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') return self.error('ValidationException', 'Parameter Name is required.')
@ -38,8 +45,7 @@ class EventsHandler(BaseResponse):
return '', self.response_headers return '', self.response_headers
def describe_rule(self): def describe_rule(self):
body = self.load_body() name = self._get_param('Name')
name = body.get('Name')
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') return self.error('ValidationException', 'Parameter Name is required.')
@ -53,8 +59,7 @@ class EventsHandler(BaseResponse):
return json.dumps(rule_dict), self.response_headers return json.dumps(rule_dict), self.response_headers
def disable_rule(self): def disable_rule(self):
body = self.load_body() name = self._get_param('Name')
name = body.get('Name')
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') return self.error('ValidationException', 'Parameter Name is required.')
@ -65,8 +70,7 @@ class EventsHandler(BaseResponse):
return '', self.response_headers return '', self.response_headers
def enable_rule(self): def enable_rule(self):
body = self.load_body() name = self._get_param('Name')
name = body.get('Name')
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') return self.error('ValidationException', 'Parameter Name is required.')
@ -80,10 +84,9 @@ class EventsHandler(BaseResponse):
pass pass
def list_rule_names_by_target(self): def list_rule_names_by_target(self):
body = self.load_body() target_arn = self._get_param('TargetArn')
target_arn = body.get('TargetArn') next_token = self._get_param('NextToken')
next_token = body.get('NextToken') limit = self._get_param('Limit')
limit = body.get('Limit')
if not target_arn: if not target_arn:
return self.error('ValidationException', 'Parameter TargetArn is required.') return self.error('ValidationException', 'Parameter TargetArn is required.')
@ -94,10 +97,9 @@ class EventsHandler(BaseResponse):
return json.dumps(rule_names), self.response_headers return json.dumps(rule_names), self.response_headers
def list_rules(self): def list_rules(self):
body = self.load_body() prefix = self._get_param('NamePrefix')
prefix = body.get('NamePrefix') next_token = self._get_param('NextToken')
next_token = body.get('NextToken') limit = self._get_param('Limit')
limit = body.get('Limit')
rules = events_backend.list_rules(prefix, next_token, limit) rules = events_backend.list_rules(prefix, next_token, limit)
rules_obj = {'Rules': []} rules_obj = {'Rules': []}
@ -111,10 +113,9 @@ class EventsHandler(BaseResponse):
return json.dumps(rules_obj), self.response_headers return json.dumps(rules_obj), self.response_headers
def list_targets_by_rule(self): def list_targets_by_rule(self):
body = self.load_body() rule_name = self._get_param('Rule')
rule_name = body.get('Rule') next_token = self._get_param('NextToken')
next_token = body.get('NextToken') limit = self._get_param('Limit')
limit = body.get('Limit')
if not rule_name: if not rule_name:
return self.error('ValidationException', 'Parameter Rule is required.') return self.error('ValidationException', 'Parameter Rule is required.')
@ -128,13 +129,25 @@ class EventsHandler(BaseResponse):
return json.dumps(targets), self.response_headers return json.dumps(targets), self.response_headers
def put_events(self): def put_events(self):
events = self._get_param('Entries')
failed_entries = events_backend.put_events(events)
if failed_entries:
return json.dumps({
'FailedEntryCount': len(failed_entries),
'Entries': failed_entries
})
return '', self.response_headers return '', self.response_headers
def put_rule(self): def put_rule(self):
body = self.load_body() name = self._get_param('Name')
name = body.get('Name') event_pattern = self._get_param('EventPattern')
event_pattern = body.get('EventPattern') sched_exp = self._get_param('ScheduleExpression')
sched_exp = body.get('ScheduleExpression') state = self._get_param('State')
desc = self._get_param('Description')
role_arn = self._get_param('RoleArn')
if not name: if not name:
return self.error('ValidationException', 'Parameter Name is required.') return self.error('ValidationException', 'Parameter Name is required.')
@ -156,17 +169,16 @@ class EventsHandler(BaseResponse):
name, name,
ScheduleExpression=sched_exp, ScheduleExpression=sched_exp,
EventPattern=event_pattern, EventPattern=event_pattern,
State=body.get('State'), State=state,
Description=body.get('Description'), Description=desc,
RoleArn=body.get('RoleArn') RoleArn=role_arn
) )
return json.dumps({'RuleArn': rule_arn}), self.response_headers return json.dumps({'RuleArn': rule_arn}), self.response_headers
def put_targets(self): def put_targets(self):
body = self.load_body() rule_name = self._get_param('Rule')
rule_name = body.get('Rule') targets = self._get_param('Targets')
targets = body.get('Targets')
if not rule_name: if not rule_name:
return self.error('ValidationException', 'Parameter Rule is required.') return self.error('ValidationException', 'Parameter Rule is required.')
@ -180,9 +192,8 @@ class EventsHandler(BaseResponse):
return '', self.response_headers return '', self.response_headers
def remove_targets(self): def remove_targets(self):
body = self.load_body() rule_name = self._get_param('Rule')
rule_name = body.get('Rule') ids = self._get_param('Ids')
ids = body.get('Ids')
if not rule_name: if not rule_name:
return self.error('ValidationException', 'Parameter Rule is required.') return self.error('ValidationException', 'Parameter Rule is required.')
@ -197,3 +208,22 @@ class EventsHandler(BaseResponse):
def test_event_pattern(self): def test_event_pattern(self):
pass pass
def put_permission(self):
action = self._get_param('Action')
principal = self._get_param('Principal')
statement_id = self._get_param('StatementId')
events_backend.put_permission(action, principal, statement_id)
return ''
def remove_permission(self):
statement_id = self._get_param('StatementId')
events_backend.remove_permission(statement_id)
return ''
def describe_event_bus(self):
return json.dumps(events_backend.describe_event_bus())

View File

@ -3,6 +3,8 @@ import random
import boto3 import boto3
from moto.events import mock_events from moto.events import mock_events
from botocore.exceptions import ClientError
from nose.tools import assert_raises
RULES = [ RULES = [
@ -171,11 +173,36 @@ def test_remove_targets():
assert(targets_before - 1 == targets_after) assert(targets_before - 1 == targets_after)
if __name__ == '__main__': @mock_events
test_list_rules() def test_permissions():
test_describe_rule() client = boto3.client('events', 'eu-central-1')
test_enable_disable_rule()
test_list_rule_names_by_target() client.put_permission(Action='PutEvents', Principal='111111111111', StatementId='Account1')
test_list_rules() client.put_permission(Action='PutEvents', Principal='222222222222', StatementId='Account2')
test_list_targets_by_rule()
test_remove_targets() resp = client.describe_event_bus()
assert len(resp['Policy']['Statement']) == 2
client.remove_permission(StatementId='Account2')
resp = client.describe_event_bus()
assert len(resp['Policy']['Statement']) == 1
assert resp['Policy']['Statement'][0]['Sid'] == 'Account1'
@mock_events
def test_put_events():
client = boto3.client('events', 'eu-central-1')
event = {
"Source": "com.mycompany.myapp",
"Detail": '{"key1": "value3", "key2": "value4"}',
"Resources": ["resource1", "resource2"],
"DetailType": "myDetailType"
}
client.put_events(Entries=[event])
# Boto3 would error if it didn't return 200 OK
with assert_raises(ClientError):
client.put_events(Entries=[event]*20)