def __init__(self, metadata=None, template={}): self.metadata = metadata or {} self.template = template or {} self._template_raw = clone_safe(self.template) self.template_original = clone_safe(self.template) # initialize resources for resource_id, resource in self.template_resources.items(): resource['LogicalResourceId'] = self.template_original['Resources'][resource_id]['LogicalResourceId'] = ( resource.get('LogicalResourceId') or resource_id) # initialize stack template attributes self.template['StackId'] = self.metadata['StackId'] = (self.metadata.get('StackId') or aws_stack.cloudformation_stack_arn(self.stack_name, short_uid())) self.template['Parameters'] = self.template.get('Parameters') or {} self.template['Outputs'] = self.template.get('Outputs') or {} # initialize metadata self.metadata['Parameters'] = self.metadata.get('Parameters') or [] self.metadata['StackStatus'] = 'CREATE_IN_PROGRESS' self.metadata['CreationTime'] = self.metadata.get('CreationTime') or timestamp_millis() # maps resource id to resource state self.resource_states = {} # maps resource id to moto resource class instance (TODO: remove in the future) self.moto_resource_statuses = {} # list of stack events self.events = [] # list of stack change sets self.change_sets = [] # initialize parameters for i in range(1, 100): key = 'Parameters.member.%s.ParameterKey' % i value = 'Parameters.member.%s.ParameterValue' % i key = self.metadata.get(key) value = self.metadata.get(value) if not key: break self.metadata['Parameters'].append({'ParameterKey': key, 'ParameterValue': value})
def list_templates(self): email_templates = ses_backend.list_templates() for template in email_templates: if isinstance(template['Timestamp'], (date, datetime)): # Hack to change the last digits to Java SDKv2 compatible format template['Timestamp'] = timestamp_millis(template['Timestamp']) return email_responses_list_templates_orig(self)
def get_function_event_invoke_config(self): response = {} if self.max_retry_attempts is not None: response.update({"MaximumRetryAttempts": self.max_retry_attempts}) if self.max_event_age is not None: response.update({"MaximumEventAgeInSeconds": self.max_event_age}) if self.on_successful_invocation or self.on_failed_invocation: response.update({"DestinationConfig": {}}) if self.on_successful_invocation: response["DestinationConfig"].update({ "OnSuccess": { "Destination": self.on_successful_invocation } }) if self.on_failed_invocation: response["DestinationConfig"].update( {"OnFailure": { "Destination": self.on_failed_invocation }}) if not response: return None response.update({ "LastModified": timestamp_millis(self.last_modified), "FunctionArn": str(self.id), }) return response
def lambda_result_to_destination(func_details, event, result, is_async, error): if not func_details.destination_enabled(): return payload = { "version": "1.0", "timestamp": timestamp_millis(), "requestContext": { "requestId": long_uid(), "functionArn": func_details.arn(), "condition": "RetriesExhausted", "approximateInvokeCount": 1, }, "requestPayload": event, "responseContext": {"statusCode": 200, "executedVersion": "$LATEST"}, "responsePayload": {}, } if result and result.result: try: payload["requestContext"]["condition"] = "Success" payload["responsePayload"] = json.loads(result.result) except Exception: payload["responsePayload"] = result.result if error: payload["responseContext"]["functionError"] = "Unhandled" # add the result in the response payload if error.result is not None: payload["responsePayload"] = json.loads(error.result) send_event_to_target(func_details.on_failed_invocation, payload) return if func_details.on_successful_invocation is not None: send_event_to_target(func_details.on_successful_invocation, payload)
def get_function_event_invoke_config(self): response = {} if self.max_retry_attempts: response.update({'MaximumRetryAttempts': self.max_retry_attempts}) if self.max_event_age: response.update({'MaximumEventAgeInSeconds': self.max_event_age}) if self.on_successful_invocation or self.on_failed_invocation: response.update({'DestinationConfig': {}}) if self.on_successful_invocation: response['DestinationConfig'].update({ 'OnSuccess': { 'Destination': self.on_successful_invocation } }) if self.on_failed_invocation: response['DestinationConfig'].update({ 'OnFailure': { 'Destination': self.on_failed_invocation } }) if not response: return None response.update({ 'LastModified': timestamp_millis(self.last_modified), 'FunctionArn': str(self.id), }) return response
def set_resource_status(self, resource_id: str, status: str, physical_res_id: str = None): """Update the deployment status of the given resource ID and publish a corresponding stack event.""" self._set_resource_status_details(resource_id, physical_res_id=physical_res_id) state = self.resource_states.setdefault(resource_id, {}) state["PreviousResourceStatus"] = state.get("ResourceStatus") state["ResourceStatus"] = status state["LastUpdatedTimestamp"] = timestamp_millis() self.add_stack_event(resource_id, physical_res_id, status)
def create_sns_message_body(subscriber, req_data): message = req_data['Message'][0] subject = req_data.get('Subject', [None])[0] protocol = subscriber['Protocol'] if six.PY2 and type(message).__name__ == 'unicode': # fix non-ascii unicode characters under Python 2 message = message.encode('raw-unicode-escape') if is_raw_message_delivery(subscriber): return message if req_data.get('MessageStructure') == ['json']: message = json.loads(message) try: message = message.get(protocol, message['default']) except KeyError: raise Exception("Unable to find 'default' key in message payload") token = short_uid() external_url = external_service_url('sns') topic_arn = subscriber['TopicArn'] data = { 'Type': req_data.get('Type', ['Notification'])[0], 'MessageId': str(uuid.uuid4()), 'TopicArn': topic_arn, 'Message': message, 'Timestamp': timestamp_millis(), 'SignatureVersion': '1', # TODO Add a more sophisticated solution with an actual signature # Hardcoded 'Signature': 'EXAMPLEpH+..', 'SigningCertURL': 'https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem', 'UnsubscribeURL': '%s/?Action=Unsubscribe&TopicArn=%s&Token=%s' % (external_url, topic_arn, token) } if subject is not None: data['Subject'] = subject attributes = get_message_attributes(req_data) if attributes: data['MessageAttributes'] = attributes return json.dumps(data)
def add_stack_event(self, resource_id, physical_res_id, status): event = { 'EventId': long_uid(), 'Timestamp': timestamp_millis(), 'StackId': self.stack_id, 'StackName': self.stack_name, 'LogicalResourceId': resource_id, 'PhysicalResourceId': physical_res_id, 'ResourceStatus': status, 'ResourceType': 'AWS::CloudFormation::Stack' } self.events.insert(0, event)
def add_stack_event(self, resource_id, physical_res_id, status): event = { "EventId": long_uid(), "Timestamp": timestamp_millis(), "StackId": self.stack_id, "StackName": self.stack_name, "LogicalResourceId": resource_id, "PhysicalResourceId": physical_res_id, "ResourceStatus": status, "ResourceType": "AWS::CloudFormation::Stack", } self.events.insert(0, event)
def create_sns_message_body(subscriber, req_data, message_id=None): message = req_data["Message"][0] protocol = subscriber["Protocol"] if six.PY2 and type(message).__name__ == "unicode": # fix non-ascii unicode characters under Python 2 message = message.encode("raw-unicode-escape") if req_data.get("MessageStructure") == ["json"]: message = json.loads(message) try: message = message.get(protocol, message["default"]) except KeyError: raise Exception("Unable to find 'default' key in message payload") if is_raw_message_delivery(subscriber): return message data = { "Type": req_data.get("Type", ["Notification"])[0], "MessageId": message_id, "TopicArn": subscriber["TopicArn"], "Message": message, "Timestamp": timestamp_millis(), "SignatureVersion": "1", # TODO Add a more sophisticated solution with an actual signature # Hardcoded "Signature": "EXAMPLEpH+..", "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem", } for key in ["Subject", "SubscribeURL", "Token"]: if req_data.get(key): data[key] = req_data[key][0] for key in HTTP_SUBSCRIPTION_ATTRIBUTES: if key in subscriber: data[key] = subscriber[key] attributes = get_message_attributes(req_data) if attributes: data["MessageAttributes"] = attributes return json.dumps(data)
def create_sns_message_body(subscriber, req_data, message_id=None): message = req_data['Message'][0] protocol = subscriber['Protocol'] if six.PY2 and type(message).__name__ == 'unicode': # fix non-ascii unicode characters under Python 2 message = message.encode('raw-unicode-escape') if req_data.get('MessageStructure') == ['json']: message = json.loads(message) try: message = message.get(protocol, message['default']) except KeyError: raise Exception("Unable to find 'default' key in message payload") if is_raw_message_delivery(subscriber): return message data = { 'Type': req_data.get('Type', ['Notification'])[0], 'MessageId': message_id, 'TopicArn': subscriber['TopicArn'], 'Message': message, 'Timestamp': timestamp_millis(), 'SignatureVersion': '1', # TODO Add a more sophisticated solution with an actual signature # Hardcoded 'Signature': 'EXAMPLEpH+..', 'SigningCertURL': 'https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem' } for key in ['Subject', 'SubscribeURL', 'Token']: if req_data.get(key): data[key] = req_data[key][0] for key in HTTP_SUBSCRIPTION_ATTRIBUTES: if key in subscriber: data[key] = subscriber[key] attributes = get_message_attributes(req_data) if attributes: data['MessageAttributes'] = attributes return json.dumps(data)
def set_stack_status(self, status): self.metadata['StackStatus'] = status event = { 'EventId': long_uid(), 'Timestamp': timestamp_millis(), 'StackId': self.stack_id, 'StackName': self.stack_name, 'LogicalResourceId': self.stack_name, 'PhysicalResourceId': self.stack_id, 'ResourceStatus': status, 'ResourceType': 'AWS::CloudFormation::Stack' } self.events.insert(0, event)
def set_resource_status(self, resource_id, status, physical_res_id=None): resource = self.resources[resource_id] state = self.resource_states[resource_id] = self.resource_states.get(resource_id) or {} attr_defaults = (('LogicalResourceId', resource_id), ('PhysicalResourceId', physical_res_id)) for res in [resource, state]: for attr, default in attr_defaults: res[attr] = res.get(attr) or default state['ResourceStatus'] = status state['StackName'] = state.get('StackName') or self.stack_name state['StackId'] = state.get('StackId') or self.stack_id state['ResourceType'] = state.get('ResourceType') or self.resources[resource_id].get('Type') state['LastUpdatedTimestamp'] = timestamp_millis() self.add_stack_event(resource_id, physical_res_id, status)
def forward_request(self, method, path, data, headers): global STREAM_CONSUMERS data = json.loads(to_str(data or '{}')) action = headers.get('X-Amz-Target') if action == '%s.RegisterStreamConsumer' % ACTION_PREFIX: consumer = clone(data) consumer['ConsumerStatus'] = 'ACTIVE' consumer['ConsumerARN'] = '%s/consumer/%s' % (data['StreamARN'], data['ConsumerName']) consumer['ConsumerCreationTimestamp'] = timestamp_millis() consumer = json_safe(consumer) STREAM_CONSUMERS.append(consumer) return {'Consumer': consumer} elif action == '%s.DeregisterStreamConsumer' % ACTION_PREFIX: def consumer_matches(c): stream_arn = data.get('StreamARN') cons_name = data.get('ConsumerName') cons_arn = data.get('ConsumerARN') return (c.get('ConsumerARN') == cons_arn or (c.get('StreamARN') == stream_arn and c.get('ConsumerName') == cons_name)) STREAM_CONSUMERS = [c for c in STREAM_CONSUMERS if not consumer_matches(c)] return {} elif action == '%s.ListStreamConsumers' % ACTION_PREFIX: result = { 'Consumers': [c for c in STREAM_CONSUMERS if c.get('StreamARN') == data.get('StreamARN')] } return result elif action == '%s.DescribeStreamConsumer' % ACTION_PREFIX: consumer_arn = data.get('ConsumerARN') or data['ConsumerName'] consumer_name = data.get('ConsumerName') or data['ConsumerARN'] result = { 'ConsumerDescription': { 'ConsumerARN': consumer_arn, # 'ConsumerCreationTimestamp': number, 'ConsumerName': consumer_name, 'ConsumerStatus': 'ACTIVE', 'StreamARN': data.get('StreamARN') } } return result if random.random() < config.KINESIS_ERROR_PROBABILITY: action = headers.get('X-Amz-Target') if action in [ACTION_PUT_RECORD, ACTION_PUT_RECORDS]: return kinesis_error_response(data, action) return True
def get_event_message(event_name, bucket_name, file_name='testfile.txt', version_id=None, file_size=0): # Based on: http://docs.aws.amazon.com/AmazonS3/latest/dev/notification-content-structure.html bucket_name = normalize_bucket_name(bucket_name) return { 'Records': [{ 'eventVersion': '2.0', 'eventSource': 'aws:s3', 'awsRegion': aws_stack.get_region(), 'eventTime': timestamp_millis(), 'eventName': event_name, 'userIdentity': { 'principalId': 'AIDAJDPLRKLG7UEXAMPLE' }, 'requestParameters': { 'sourceIPAddress': '127.0.0.1' # TODO determine real source IP }, 'responseElements': { 'x-amz-request-id': short_uid(), 'x-amz-id-2': 'eftixk72aD6Ap51TnqcoF8eFidJG9Z/2' # Amazon S3 host that processed the request }, 's3': { 's3SchemaVersion': '1.0', 'configurationId': 'testConfigRule', 'bucket': { 'name': bucket_name, 'ownerIdentity': { 'principalId': 'A3NL1KOZZKExample' }, 'arn': 'arn:aws:s3:::%s' % bucket_name }, 'object': { 'key': file_name, 'size': file_size, 'eTag': 'd41d8cd98f00b204e9800998ecf8427e', 'versionId': version_id, 'sequencer': '0055AED6DCD90281E5' } } }] }
def __init__(self, metadata=None, template=None): if template is None: template = {} self.metadata = metadata or {} self.template = template or {} self._template_raw = clone_safe(self.template) self.template_original = clone_safe(self.template) # initialize resources for resource_id, resource in self.template_resources.items(): resource["LogicalResourceId"] = self.template_original[ "Resources"][resource_id]["LogicalResourceId"] = ( resource.get("LogicalResourceId") or resource_id) # initialize stack template attributes self.template["StackId"] = self.metadata[ "StackId"] = self.metadata.get( "StackId") or aws_stack.cloudformation_stack_arn( self.stack_name, short_uid()) self.template["Parameters"] = self.template.get("Parameters") or {} self.template["Outputs"] = self.template.get("Outputs") or {} self.template["Conditions"] = self.template.get("Conditions") or {} # initialize metadata self.metadata["Parameters"] = self.metadata.get("Parameters") or [] self.metadata["StackStatus"] = "CREATE_IN_PROGRESS" self.metadata["CreationTime"] = self.metadata.get( "CreationTime") or timestamp_millis() # maps resource id to resource state self._resource_states = {} # list of stack events self.events = [] # list of stack change sets self.change_sets = [] # initialize parameters for i in range(1, 100): key = "Parameters.member.%s.ParameterKey" % i value = "Parameters.member.%s.ParameterValue" % i key = self.metadata.get(key) value = self.metadata.get(value) if not key: break self.metadata["Parameters"].append({ "ParameterKey": key, "ParameterValue": value })
def set_resource_status(self, resource_id, status, physical_res_id=None): resource = self.resources[resource_id] state = self.resource_states[resource_id] = self.resource_states.get( resource_id) or {} attr_defaults = ( ("LogicalResourceId", resource_id), ("PhysicalResourceId", physical_res_id), ) for res in [resource, state]: for attr, default in attr_defaults: res[attr] = res.get(attr) or default state["PreviousResourceStatus"] = state.get("ResourceStatus") state["ResourceStatus"] = status state["StackName"] = state.get("StackName") or self.stack_name state["StackId"] = state.get("StackId") or self.stack_id state["ResourceType"] = state.get( "ResourceType") or self.resources[resource_id].get("Type") state["LastUpdatedTimestamp"] = timestamp_millis() self.add_stack_event(resource_id, physical_res_id, status)
def _send_to_failure_destination( self, shard_id, start_sequence_num, end_sequence_num, source_arn, func_arn, invoke_count, status_code, batch_size, first_record_arrival_time, last_record_arrival_time, destination, ): """ Creates a metadata payload relating to a failed Lambda invocation and delivers it to the given destination """ payload = { "version": "1.0", "timestamp": timestamp_millis(), "requestContext": { "requestId": long_uid(), "functionArn": func_arn, "condition": "RetryAttemptsExhausted", "approximateInvokeCount": invoke_count, }, "responseContext": { "statusCode": status_code, "executedVersion": "$LATEST", # TODO: don't hardcode these fields "functionError": "Unhandled", }, } details = { "shardId": shard_id, "startSequenceNumber": start_sequence_num, "endSequenceNumber": end_sequence_num, "approximateArrivalOfFirstRecord": first_record_arrival_time, "approximateArrivalOfLastRecord": last_record_arrival_time, "batchSize": batch_size, "streamArn": source_arn, } payload[self._FAILURE_PAYLOAD_DETAILS_FIELD_NAME] = details send_event_to_target(destination, payload)
def handle_associate_vpc_request(method, path, data): is_associate = path.endswith("/associatevpc") region_details = Route53Backend.get() zone_id = extract_zone_id(path) req_data = clone(xmltodict.parse(to_str(data))) zone_details = region_details.vpc_hosted_zone_associations.get(zone_id) or [] if is_associate: assoc_id = short_uid() zone_data = req_data.get("AssociateVPCWithHostedZoneRequest", {}) zone_data["Id"] = assoc_id zone_data["HostedZoneId"] = zone_id zone_details.append(zone_data) response_entry = { "ChangeInfo": { "Id": assoc_id, "Status": "INSYNC", "SubmittedAt": timestamp_millis(), } } # update VPC info in hosted zone moto object - fixes required after https://github.com/spulec/moto/pull/4786 hosted_zone = route53_backend.zones.get(zone_id) if not getattr(hosted_zone, "vpcid", None): hosted_zone.vpcid = zone_data["VPC"].get("VPCId") if not getattr(hosted_zone, "vpcregion", None): hosted_zone.vpcregion = aws_stack.get_region() else: def _match(z): return z["HostedZoneId"] == zone_id and z["VPC"]["VPCId"] == zone_data["VPC"]["VPCId"] zone_data = req_data.get("DisassociateVPCFromHostedZoneRequest", {}) response_entry = [z for z in zone_details if _match(z)] zone_details = [z for z in zone_details if not _match(z)] if not response_entry: return 404 response_entry = response_entry[0] region_details.vpc_hosted_zone_associations[zone_id] = zone_details response_tag = "%sVPCWithHostedZoneResponse" % ("Associate" if is_associate else "Disassociate") return {response_tag: response_entry}
def forward_request(self, method, path, data, headers): parsed_url = urlparse(path) action = parsed_url.path.split("/")[2] if method == "GET" and action == "change": resource_id = parsed_url.path.split("/")[-1] change_response = { "GetChangeResponse": { "ChangeInfo": { "Id": resource_id, "Status": "INSYNC", "SubmittedAt": timestamp_millis(), } } } body = xmltodict.unparse(change_response) response = requests_response(body) return response return True
def forward_request(self, method, path, data, headers): parsed_url = urlparse(path) action = parsed_url.path.split('/')[2] if action == 'change': if method == 'GET': resource_id = parsed_url.path.split('/')[-1] change_response = { 'GetChangeResponse': { 'ChangeInfo': { 'Id': resource_id, 'Status': 'INSYNC', 'SubmittedAt': timestamp_millis() } } } body = xmltodict.unparse(change_response) response = requests_response(body) return response return True
def handle_associate_vpc_request(method, path, data): is_associate = path.endswith("/associatevpc") region_details = Route53Backend.get() path_parts = path.lstrip("/").split("/") zone_id = path_parts[2] req_data = xmltodict.parse(to_str(data)) zone_details = region_details.vpc_hosted_zone_associations.get( zone_id) or [] if is_associate: assoc_id = short_uid() zone_data = req_data.get("AssociateVPCWithHostedZoneRequest", {}) zone_data["Id"] = assoc_id zone_data["HostedZoneId"] = zone_id zone_details.append(zone_data) response_entry = { "ChangeInfo": { "Id": assoc_id, "Status": "INSYNC", "SubmittedAt": timestamp_millis(), } } else: def _match(z): return z["HostedZoneId"] == zone_id and z["VPC"][ "VPCId"] == zone_data["VPC"]["VPCId"] zone_data = req_data.get("DisassociateVPCFromHostedZoneRequest", {}) response_entry = [z for z in zone_details if _match(z)] zone_details = [z for z in zone_details if not _match(z)] if not response_entry: return 404 response_entry = response_entry[0] region_details.vpc_hosted_zone_associations[zone_id] = zone_details response_tag = "%sVPCWithHostedZoneResponse" % ("Associate" if is_associate else "Disassociate") return {response_tag: response_entry}
def lambda_result_to_destination(func_details, event, result, is_async, error): if not func_details.destination_enabled(): return payload = { 'version': '1.0', 'timestamp': timestamp_millis(), 'requestContext': { 'requestId': long_uid(), 'functionArn': func_details.arn(), 'condition': 'RetriesExhausted', 'approximateInvokeCount': 1 }, 'requestPayload': event, 'responseContext': { 'statusCode': 200, 'executedVersion': '$LATEST' }, 'responsePayload': {} } if is_async: payload['responsePayload']['statusCode'] = 200 payload['responsePayload']['headers'] = None payload['responsePayload']['multiValueHeaders'] = None payload['responsePayload']['body'] = '' else: payload['responsePayload'] = result.result if error: payload['responseContext']['functionError'] = 'Unhandled' # add the result in the response payload if error.result is not None: payload['responsePayload'] = json.loads(error.result) send_event_to_target(func_details.on_failed_invocation, payload) return send_event_to_target(func_details.on_successful_invocation, payload)
def forward_request(self, method, path, data, headers): parsed_url = urlparse(path) action = parsed_url.path.split('/')[2] is_associate = path.endswith('/associatevpc') if is_associate or path.endswith('/disassociatevpc'): path_parts = path.split('/') zone_id = path_parts[3] req_data = xmltodict.parse(to_str(data)) region_details = Route53Backend.get() zone_details = region_details.vpc_hosted_zone_associations.get( zone_id) or [] if is_associate: assoc_id = short_uid() zone_data = req_data.get('AssociateVPCWithHostedZoneRequest', {}) zone_data['Id'] = assoc_id zone_data['HostedZoneId'] = zone_id zone_details.append(zone_data) response_entry = { 'ChangeInfo': { 'Id': assoc_id, 'Status': 'INSYNC', 'SubmittedAt': timestamp_millis() } } else: def _match(z): return z['HostedZoneId'] == zone_id and z['VPC'][ 'VPCId'] == zone_data['VPC']['VPCId'] zone_data = req_data.get( 'DisassociateVPCFromHostedZoneRequest', {}) response_entry = [z for z in zone_details if _match(z)] zone_details = [z for z in zone_details if not _match(z)] if not response_entry: return 404 response_entry = response_entry[0] region_details.vpc_hosted_zone_associations[zone_id] = zone_details response_tag = '%sVPCWithHostedZoneResponse' % ( 'Associate' if is_associate else 'Disassociate') response = {response_tag: response_entry} body = xmltodict.unparse(response) response = requests_response(body) return response if action == 'change': if method == 'GET': resource_id = parsed_url.path.split('/')[-1] change_response = { 'GetChangeResponse': { 'ChangeInfo': { 'Id': resource_id, 'Status': 'INSYNC', 'SubmittedAt': timestamp_millis() } } } body = xmltodict.unparse(change_response) response = requests_response(body) return response return True
def return_response(self, method, path, data, headers, response): action = headers.get('X-Amz-Target') data = json.loads(to_str(data or '{}')) records = [] if action in (ACTION_CREATE_STREAM, ACTION_DELETE_STREAM): event_type = (event_publisher.EVENT_KINESIS_CREATE_STREAM if action == ACTION_CREATE_STREAM else event_publisher.EVENT_KINESIS_DELETE_STREAM) payload = {'n': event_publisher.get_hash(data.get('StreamName'))} if action == ACTION_CREATE_STREAM: payload['s'] = data.get('ShardCount') event_publisher.fire_event(event_type, payload=payload) elif action == ACTION_PUT_RECORD: response_body = json.loads(to_str(response.content)) event_record = { 'approximateArrivalTimestamp': timestamp_millis(), 'data': data['Data'], 'encryptionType': 'NONE', 'partitionKey': data['PartitionKey'], 'sequenceNumber': response_body.get('SequenceNumber') } event_records = [event_record] stream_name = data['StreamName'] lambda_api.process_kinesis_records(event_records, stream_name) elif action == ACTION_PUT_RECORDS: event_records = [] response_body = json.loads(to_str(response.content)) if 'Records' in response_body: response_records = response_body['Records'] records = data['Records'] for index in range(0, len(records)): record = records[index] event_record = { 'approximateArrivalTimestamp': timestamp_millis(), 'data': record['Data'], 'encryptionType': 'NONE', 'partitionKey': record['PartitionKey'], 'sequenceNumber': response_records[index].get('SequenceNumber') } event_records.append(event_record) stream_name = data['StreamName'] lambda_api.process_kinesis_records(event_records, stream_name) elif action == ACTION_UPDATE_SHARD_COUNT: # Currently kinesalite, which backs the Kinesis implementation for localstack, does # not support UpdateShardCount: # https://github.com/mhart/kinesalite/issues/61 # # [Terraform](https://www.terraform.io) makes the call to UpdateShardCount when it # applies Kinesis resources. A Terraform run fails when this is not present. # # The code that follows just returns a successful response, bypassing the 400 # response that kinesalite returns. # response = Response() response.status_code = 200 content = { 'CurrentShardCount': 1, 'StreamName': data['StreamName'], 'TargetShardCount': data['TargetShardCount'] } response.encoding = 'UTF-8' response._content = json.dumps(content) return response
def return_response(self, method, path, data, headers, response): if response.status_code < 400 or response.status_code >= 500: return region_details = Route53Backend.get() is_associate = path.endswith('/associatevpc') if is_associate or path.endswith('/disassociatevpc'): path_parts = path.lstrip('/').split('/') zone_id = path_parts[2] req_data = xmltodict.parse(to_str(data)) zone_details = region_details.vpc_hosted_zone_associations.get( zone_id) or [] if is_associate: assoc_id = short_uid() zone_data = req_data.get('AssociateVPCWithHostedZoneRequest', {}) zone_data['Id'] = assoc_id zone_data['HostedZoneId'] = zone_id zone_details.append(zone_data) response_entry = { 'ChangeInfo': { 'Id': assoc_id, 'Status': 'INSYNC', 'SubmittedAt': timestamp_millis() } } else: def _match(z): return z['HostedZoneId'] == zone_id and z['VPC'][ 'VPCId'] == zone_data['VPC']['VPCId'] zone_data = req_data.get( 'DisassociateVPCFromHostedZoneRequest', {}) response_entry = [z for z in zone_details if _match(z)] zone_details = [z for z in zone_details if not _match(z)] if not response_entry: return 404 response_entry = response_entry[0] region_details.vpc_hosted_zone_associations[zone_id] = zone_details response_tag = '%sVPCWithHostedZoneResponse' % ( 'Associate' if is_associate else 'Disassociate') response = {response_tag: response_entry} body = xmltodict.unparse(response) response = requests_response(body) return response if '/hostedzonesbyvpc' in path and method == 'GET': req_data = parse_request_data(method, path, data) vpc_id = req_data.get('vpcid') zone_details = region_details.vpc_hosted_zone_associations result = [ z for z_list in zone_details.values() for z in z_list if z['VPC']['VPCId'] == vpc_id ] response = { 'ListHostedZonesByVPCResponse': { 'HostedZoneSummaries': { 'HostedZoneSummary': result } } } body = xmltodict.unparse(response) response = requests_response(body) return response
def fix_hardcoded_creation_date(response): # TODO: remove once this is fixed upstream search = r'<CreationTime>\s*(2011-05-23T15:47:44Z)?\s*</CreationTime>' replace = r'<CreationTime>%s</CreationTime>' % timestamp_millis() fix_in_response(search, replace, response)
def test_timestamp_millis(self): result = common.timestamp_millis(datetime.now()) assert "T" in result result = common.timestamp_millis(date.today()) assert "00:00:00" in result assert "T" in result
def test_timestamp_millis(self): result = common.timestamp_millis(datetime.now()) self.assertIn('T', result) result = common.timestamp_millis(date.today()) self.assertIn('00:00:00', result) self.assertIn('T', result)