diff --git a/unicorn_contracts/integration/event-schemas.yaml b/unicorn_contracts/integration/event-schemas.yaml index 05b3399..9ac7084 100644 --- a/unicorn_contracts/integration/event-schemas.yaml +++ b/unicorn_contracts/integration/event-schemas.yaml @@ -1,7 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 -AWSTemplateFormatVersion: '2010-09-09' -Description: | +AWSTemplateFormatVersion: "2010-09-09" +Description: > Defines the event bus policies that determine who can create rules on the event bus to subscribe to events published by Unicorn Contracts Service. @@ -19,12 +19,14 @@ Resources: Type: AWS::EventSchemas::Registry Properties: Description: 'Event schemas for Unicorn Contracts' - RegistryName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" + RegistryName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" EventRegistryPolicy: Type: AWS::EventSchemas::RegistryPolicy - Properties: - RegistryName: !GetAtt EventRegistry.RegistryName + Properties: + RegistryName: + Fn::GetAtt: EventRegistry.RegistryName Policy: Version: '2012-10-17' Statement: @@ -32,7 +34,7 @@ Resources: Effect: Allow Principal: AWS: - - !Ref AWS::AccountId + - Ref: AWS::AccountId Action: - schemas:DescribeCodeBinding - schemas:DescribeRegistry @@ -42,99 +44,102 @@ Resources: - schemas:ListSchemaVersions - schemas:SearchSchemas Resource: - - !GetAtt EventRegistry.RegistryArn - - !Sub "arn:${AWS::Partition}:schemas:${AWS::Region}:${AWS::AccountId}:schema/${EventRegistry.RegistryName}*" + - Fn::GetAtt: EventRegistry.RegistryArn + - Fn::Sub: "arn:${AWS::Partition}:schemas:${AWS::Region}:${AWS::AccountId}:schema/${EventRegistry.RegistryName}*" ContractApprovedEventSchema: Type: AWS::EventSchemas::Schema Properties: Type: 'OpenApi3' - RegistryName: !GetAtt EventRegistry.RegistryName - SchemaName: !Sub '${EventRegistry.RegistryName}@ContractStatusChanged' + RegistryName: + Fn::GetAtt: EventRegistry.RegistryName + SchemaName: + Fn::Sub: "${EventRegistry.RegistryName}@ContractStatusChanged" Description: 'The schema for a request to publish a property' - Content: !Sub | - { - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "ContractStatusChanged" - }, - "paths": {}, - "components": { - "schemas": { - "AWSEvent": { - "type": "object", - "required": [ - "detail-type", - "resources", - "detail", - "id", - "source", - "time", - "region", - "version", - "account" - ], - "x-amazon-events-detail-type": "ContractStatusChanged", - "x-amazon-events-source": "${EventRegistry.RegistryName}", - "properties": { - "detail": { - "$ref": "#/components/schemas/ContractStatusChanged" - }, - "account": { - "type": "string" - }, - "detail-type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "region": { - "type": "string" - }, - "resources": { - "type": "array", - "items": { - "type": "object" + Content: + Fn::Sub: | + { + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "ContractStatusChanged" + }, + "paths": {}, + "components": { + "schemas": { + "AWSEvent": { + "type": "object", + "required": [ + "detail-type", + "resources", + "detail", + "id", + "source", + "time", + "region", + "version", + "account" + ], + "x-amazon-events-detail-type": "ContractStatusChanged", + "x-amazon-events-source": "${EventRegistry.RegistryName}", + "properties": { + "detail": { + "$ref": "#/components/schemas/ContractStatusChanged" + }, + "account": { + "type": "string" + }, + "detail-type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "object" + } + }, + "source": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "string" } - }, - "source": { - "type": "string" - }, - "time": { - "type": "string", - "format": "date-time" - }, - "version": { - "type": "string" } - } - }, - "ContractStatusChanged": { - "type": "object", - "required": [ - "contract_last_modified_on", - "contract_id", - "contract_status", - "property_id" - ], - "properties": { - "contract_id": { - "type": "string" - }, - "contract_last_modified_on": { - "type": "string", - "format": "date-time" - }, - "contract_status": { - "type": "string" - }, - "property_id": { - "type": "string" + }, + "ContractStatusChanged": { + "type": "object", + "required": [ + "contract_last_modified_on", + "contract_id", + "contract_status", + "property_id" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "contract_last_modified_on": { + "type": "string", + "format": "date-time" + }, + "contract_status": { + "type": "string" + }, + "property_id": { + "type": "string" + } } } } } } - } diff --git a/unicorn_contracts/integration/subscriber-policies.yaml b/unicorn_contracts/integration/subscriber-policies.yaml index 929d627..4f5e210 100644 --- a/unicorn_contracts/integration/subscriber-policies.yaml +++ b/unicorn_contracts/integration/subscriber-policies.yaml @@ -20,12 +20,15 @@ Resources: CrossServiceCreateRulePolicy: Type: AWS::Events::EventBusPolicy Properties: - EventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBus}}" - StatementId: !Sub "OnlyRulesForContractServiceEvents-${Stage}" + EventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBus}}" + StatementId: + Fn::Sub: "OnlyRulesForContractServiceEvents-${Stage}" Statement: Effect: Allow Principal: - AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" + AWS: + Fn::Sub: "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" Action: - events:PutRule - events:DeleteRule @@ -35,14 +38,15 @@ Resources: - events:PutTargets - events:RemoveTargets Resource: - - !Sub + - Fn::Sub: - arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/${eventBusName}/* - - eventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBus}}" + - eventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBus}}" Condition: StringEqualsIfExists: "events:creatorAccount": "${aws:PrincipalAccount}" StringEquals: "events:source": - - !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" + - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" "Null": "events:source": "false" diff --git a/unicorn_contracts/samconfig.yaml b/unicorn_contracts/samconfig.yaml index 8c67cf3..e4a47b9 100644 --- a/unicorn_contracts/samconfig.yaml +++ b/unicorn_contracts/samconfig.yaml @@ -16,7 +16,9 @@ default: disable_rollback: true confirm_changeset: false fail_on_empty_changeset: false - capabilities: CAPABILITY_IAM + capabilities: + - CAPABILITY_IAM + - CAPABILITY_AUTO_EXPAND parameter_overrides: - "Stage=local" validate: @@ -31,34 +33,3 @@ default: local_start_lambda: parameters: warm_containers: EAGER - -schemas: - global: - parameters: - stack_name: uni-prop-local-contracts-schemas - s3_prefix: uni-prop-local-contracts-schemas - resolve_s3: true - deploy: - parameters: - template_file: integration/event-schemas.yaml - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - parameter_overrides: - - "Stage=local" - -subscriber-policies: - global: - parameters: - stack_name: uni-prop-local-contracts-subscriber-policies - s3_prefix: uni-prop-local-contracts-subscriber-policies - resolve_s3: true - deploy: - parameters: - template_file: ./integration/subscriber-policies.yaml - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - capabilities: CAPABILITY_IAM - parameter_overrides: - - "Stage=local" diff --git a/unicorn_contracts/template.yaml b/unicorn_contracts/template.yaml index 1cb7085..a264ee4 100644 --- a/unicorn_contracts/template.yaml +++ b/unicorn_contracts/template.yaml @@ -11,10 +11,11 @@ Metadata: cfn-lint: config: ignore_checks: - - ES4000 # Rule disabled because the CatchAll Rule doesn't need a DLQ - - ES6000 # Rule disabled because SQS DLOs don't need a RedrivePolicy - - WS2001 # Rule disabled because check does not support !ToJsonString transform - - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure + - ES4000 # Rule disabled because the CatchAll Rule doesn't need a DLQ + - ES6000 # Rule disabled because SQS DLOs don't need a RedrivePolicy + - WS2001 # Rule disabled because check does not support !ToJsonString transform + - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure + - W3002 Parameters: Stage: @@ -83,7 +84,6 @@ Resources: Name: !Sub /uni-prop/${Stage}/UnicornContractsEventBusArn Value: !GetAtt UnicornContractsEventBus.Arn - #### LAMBDA FUNCTIONS # Processes customer API requests from SQS queue UnicornContractsIngestQueue ContractEventHandlerFunction: @@ -195,7 +195,6 @@ Resources: ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs - #### INGEST QUEUES # Queue API Gateway requests to be processed by ContractEventHandlerFunction UnicornContractsIngestQueue: @@ -217,7 +216,7 @@ Resources: Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace Value: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" - + #### DEAD LETTER QUEUES # DeadLetterQueue for UnicornContractsIngestQueue. Contains messages that failed to be processed UnicornContractsIngestDLQ: @@ -260,7 +259,6 @@ Resources: - Key: namespace Value: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" - #### EVENT BUS # Event bus for Unicorn Contract Service used to publish and consume events UnicornContractsEventBus: @@ -349,7 +347,6 @@ Resources: ] } - #### EVENT BRIDGE PIPES # Pipe changed on DynamoDB Table to UnicornContractsEventBus DdbStreamToEventPipe: @@ -413,6 +410,22 @@ Resources: - events:PutEvents Resource: !GetAtt UnicornContractsEventBus.Arn + #### CLOUDFORMATION NESTED STACKS + EventSchemasStack: + Type: AWS::Serverless::Application + Properties: + Location: "integration/event-schemas.yaml" + Parameters: + Stage: !Ref Stage + SubscriberPoliciesStack: + Type: AWS::Serverless::Application + DependsOn: + - UnicornContractsEventBusNameParam + Properties: + Location: "integration/subscriber-policies.yaml" + Parameters: + Stage: !Ref Stage + Outputs: #### API GATEWAY OUTPUTS BaseUrl: @@ -440,6 +453,11 @@ Outputs: Description: ContractEventHandler function ARN Value: !GetAtt ContractEventHandlerFunction.Arn + #### EVENT BRIDGE OUTPUTS + UnicornContractsEventBusName: + Value: !GetAtt UnicornContractsEventBus.Name + #### CLOUDWATCH LOGS OUTPUTS UnicornContractsCatchAllLogGroupArn: + Description: Log all events on the service's EventBridge Bus Value: !GetAtt UnicornContractsCatchAllLogGroup.Arn diff --git a/unicorn_properties/integration/event-schemas.yaml b/unicorn_properties/integration/event-schemas.yaml index c12cae8..47a9789 100644 --- a/unicorn_properties/integration/event-schemas.yaml +++ b/unicorn_properties/integration/event-schemas.yaml @@ -17,12 +17,14 @@ Resources: Type: AWS::EventSchemas::Registry Properties: Description: 'Event schemas for Unicorn Properties' - RegistryName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" + RegistryName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" EventRegistryPolicy: Type: AWS::EventSchemas::RegistryPolicy Properties: - RegistryName: !GetAtt EventRegistry.RegistryName + RegistryName: + Fn::GetAtt: EventRegistry.RegistryName Policy: Version: '2012-10-17' Statement: @@ -30,7 +32,7 @@ Resources: Effect: Allow Principal: AWS: - - !Ref AWS::AccountId + - Ref: AWS::AccountId Action: - schemas:DescribeCodeBinding - schemas:DescribeRegistry @@ -40,90 +42,93 @@ Resources: - schemas:ListSchemaVersions - schemas:SearchSchemas Resource: - - !GetAtt EventRegistry.RegistryArn - - !Sub "arn:${AWS::Partition}:schemas:${AWS::Region}:${AWS::AccountId}:schema/${EventRegistry.RegistryName}*" + - Fn::GetAtt: EventRegistry.RegistryArn + - Fn::Sub: "arn:${AWS::Partition}:schemas:${AWS::Region}:${AWS::AccountId}:schema/${EventRegistry.RegistryName}*" PublicationEvaluationCompleted: Type: AWS::EventSchemas::Schema Properties: Type: 'OpenApi3' - RegistryName: !GetAtt EventRegistry.RegistryName - SchemaName: !Sub '${EventRegistry.RegistryName}@PublicationEvaluationCompleted' + RegistryName: + Fn::GetAtt: EventRegistry.RegistryName + SchemaName: + Fn::Sub: '${EventRegistry.RegistryName}@PublicationEvaluationCompleted' Description: 'The schema for when a property evaluation is completed' - Content: !Sub | - { - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "PublicationEvaluationCompleted" - }, - "paths": {}, - "components": { - "schemas": { - "AWSEvent": { - "type": "object", - "required": [ - "detail-type", - "resources", - "detail", - "id", - "source", - "time", - "region", - "version", - "account" - ], - "x-amazon-events-detail-type": "PublicationEvaluationCompleted", - "x-amazon-events-source": "${EventRegistry.RegistryName}", - "properties": { - "detail": { - "$ref": "#/components/schemas/PublicationEvaluationCompleted" - }, - "account": { - "type": "string" - }, - "detail-type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "region": { - "type": "string" - }, - "resources": { - "type": "array", - "items": { + Content: + Fn::Sub: | + { + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "PublicationEvaluationCompleted" + }, + "paths": {}, + "components": { + "schemas": { + "AWSEvent": { + "type": "object", + "required": [ + "detail-type", + "resources", + "detail", + "id", + "source", + "time", + "region", + "version", + "account" + ], + "x-amazon-events-detail-type": "PublicationEvaluationCompleted", + "x-amazon-events-source": "${EventRegistry.RegistryName}", + "properties": { + "detail": { + "$ref": "#/components/schemas/PublicationEvaluationCompleted" + }, + "account": { + "type": "string" + }, + "detail-type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "version": { "type": "string" } - }, - "source": { - "type": "string" - }, - "time": { - "type": "string", - "format": "date-time" - }, - "version": { - "type": "string" } - } - }, - "PublicationEvaluationCompleted": { - "type": "object", - "required": [ - "property_id", - "evaluation_result" - ], - "properties": { - "property_id": { - "type": "string" - }, - "evaluation_result": { - "type": "string" + }, + "PublicationEvaluationCompleted": { + "type": "object", + "required": [ + "property_id", + "evaluation_result" + ], + "properties": { + "property_id": { + "type": "string" + }, + "evaluation_result": { + "type": "string" + } } } } } } - } diff --git a/unicorn_properties/integration/subscriber-policies.yaml b/unicorn_properties/integration/subscriber-policies.yaml index 32d3441..2bbe786 100644 --- a/unicorn_properties/integration/subscriber-policies.yaml +++ b/unicorn_properties/integration/subscriber-policies.yaml @@ -19,12 +19,15 @@ Resources: CrossServiceCreateRulePolicy: Type: AWS::Events::EventBusPolicy Properties: - EventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" - StatementId: !Sub "OnlyRulesForPropertiesServiceEvents-${Stage}" + EventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" + StatementId: + Fn::Sub: "OnlyRulesForPropertiesServiceEvents-${Stage}" Statement: Effect: Allow Principal: - AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" + AWS: + Fn::Sub: "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" Action: - events:PutRule - events:DeleteRule @@ -34,14 +37,15 @@ Resources: - events:PutTargets - events:RemoveTargets Resource: - - !Sub + - Fn::Sub: - arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/${eventBusName}/* - - eventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" + - eventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" Condition: StringEqualsIfExists: "events:creatorAccount": "${aws:PrincipalAccount}" StringEquals: "events:source": - - !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" + - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" "Null": "events:source": "false" diff --git a/unicorn_properties/integration/subscriptions.yaml b/unicorn_properties/integration/subscriptions.yaml index 0536820..871e0fa 100644 --- a/unicorn_properties/integration/subscriptions.yaml +++ b/unicorn_properties/integration/subscriptions.yaml @@ -20,17 +20,20 @@ Resources: Properties: Name: unicorn.properties-ContractStatusChanged Description: Contract Status Changed subscription - EventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBusArn}}" + EventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBusArn}}" EventPattern: source: - - !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" + - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsNamespace}}" detail-type: - ContractStatusChanged State: ENABLED Targets: - Id: SendEventTo - Arn: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" - RoleArn: !GetAtt UnicornPropertiesSubscriptionRole.Arn + Arn: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + RoleArn: + Fn::GetAtt: UnicornPropertiesSubscriptionRole.Arn #### UNICORN WEB EVENT SUBSCRIPTIONS PublicationApprovalRequestedSubscriptionRule: @@ -38,17 +41,20 @@ Resources: Properties: Name: unicorn.properties-PublicationApprovalRequested Description: Publication evaluation completed subscription - EventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" + EventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" EventPattern: source: - - !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebNamespace}}" + - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebNamespace}}" detail-type: - PublicationApprovalRequested State: ENABLED Targets: - Id: SendEventTo - Arn: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" - RoleArn: !GetAtt UnicornPropertiesSubscriptionRole.Arn + Arn: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + RoleArn: + Fn::GetAtt: UnicornPropertiesSubscriptionRole.Arn # This IAM role allows EventBridge to assume the permissions necessary to send events @@ -68,13 +74,16 @@ Resources: Statement: - Effect: Allow Action: events:PutEvents - Resource: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Resource: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" Outputs: ContractStatusChangedSubscription: Description: Rule ARN for Contract service event subscription - Value: !GetAtt ContractStatusChangedSubscriptionRule.Arn + Value: + Fn::GetAtt: ContractStatusChangedSubscriptionRule.Arn PublicationApprovalRequestedSubscription: Description: Rule ARN for Web service event subscription - Value: !GetAtt PublicationApprovalRequestedSubscriptionRule.Arn + Value: + Fn::GetAtt: PublicationApprovalRequestedSubscriptionRule.Arn diff --git a/unicorn_properties/samconfig.yaml b/unicorn_properties/samconfig.yaml index 94eac2b..3829e33 100644 --- a/unicorn_properties/samconfig.yaml +++ b/unicorn_properties/samconfig.yaml @@ -16,7 +16,9 @@ default: disable_rollback: true confirm_changeset: false fail_on_empty_changeset: false - capabilities: CAPABILITY_IAM + capabilities: + - CAPABILITY_IAM + - CAPABILITY_AUTO_EXPAND parameter_overrides: - "Stage=local" validate: @@ -31,48 +33,3 @@ default: local_start_lambda: parameters: warm_containers: EAGER - -schemas: - deploy: - parameters: - stack_name: uni-prop-local-properties-schemas - s3_prefix: uni-prop-local-properties-schemas - template_file: integration/event-schemas.yaml - resolve_s3: true - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - parameter_overrides: - - "Stage=local" - -subscriber-policies: - global: - parameters: - stack_name: uni-prop-local-properties-subscriber-policies - s3_prefix: uni-prop-local-properties-subscriber-policies - resolve_s3: true - deploy: - parameters: - template_file: ./integration/subscriber-policies.yaml - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - capabilities: CAPABILITY_IAM - parameter_overrides: - - "Stage=local" - -subscriptions: - global: - parameters: - stack_name: uni-prop-local-properties-subscriptions - s3_prefix: uni-prop-local-properties-subscriptions - resolve_s3: true - deploy: - parameters: - template_file: ./integration/subscriptions.yaml - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - capabilities: CAPABILITY_IAM - parameter_overrides: - - "Stage=local" diff --git a/unicorn_properties/template.yaml b/unicorn_properties/template.yaml index 2e8100f..2722823 100644 --- a/unicorn_properties/template.yaml +++ b/unicorn_properties/template.yaml @@ -11,8 +11,9 @@ Metadata: cfn-lint: config: ignore_checks: - - ES4000 # Rule disabled because the CatchAll Rule doesn't need a DLQ - - ES6000 # Rule disabled because SQS DLOs don't need a RedrivePolicy + - ES4000 # Rule disabled because the CatchAll Rule doesn't need a DLQ + - ES6000 # Rule disabled because SQS DLOs don't need a RedrivePolicy + - E0001 # Rule disabled because cfn-lint cannot parse SAM Policy templates without arguments (ComprehendBasicAccessPolicy, RekognitionDetectOnlyPolicy) Parameters: Stage: @@ -36,9 +37,7 @@ Mappings: Value: "AWS Serverless Developer Experience" Conditions: - IsProd: !Equals - - !Ref Stage - - Prod + IsProd: !Equals [!Ref Stage, Prod] Globals: Function: @@ -61,7 +60,7 @@ Globals: LOG_LEVEL: INFO # Log level for Logger (INFO, DEBUG, etc.), default Tags: stage: !Ref Stage - project: !FindInMap [ Constants, ProjectName, Value ] + project: !FindInMap [Constants, ProjectName, Value] namespace: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" Resources: @@ -73,7 +72,7 @@ Resources: Type: String Name: !Sub /uni-prop/${Stage}/UnicornPropertiesEventBus Value: !GetAtt UnicornPropertiesEventBus.Name - + UnicornPropertiesEventBusArnParam: Type: AWS::SSM::Parameter Properties: @@ -81,7 +80,6 @@ Resources: Name: !Sub /uni-prop/${Stage}/UnicornPropertiesEventBusArn Value: !GetAtt UnicornPropertiesEventBus.Arn - #### LAMBDA FUNCTIONS # Listens to ContractStatusChanged events from EventBridge ContractStatusChangedHandlerFunction: @@ -182,7 +180,7 @@ Resources: - DynamoDBCrudPolicy: TableName: !Ref ContractStatusTable - # Log group for the ContractExistsCheckerFunction + # Log group for the ContractExistsCheckerFunction ContractExistsCheckerFunctionLogGroup: Type: AWS::Logs::LogGroup UpdateReplacePolicy: Delete @@ -236,8 +234,8 @@ Resources: Enabled: true Policies: - AWSXRayDaemonWriteAccess - - ComprehendBasicAccessPolicy - - RekognitionDetectOnlyPolicy + - ComprehendBasicAccessPolicy: {} + - RekognitionDetectOnlyPolicy: {} - LambdaInvokePolicy: FunctionName: !Ref WaitForContractApprovalFunction - LambdaInvokePolicy: @@ -312,7 +310,7 @@ Resources: MessageRetentionPeriod: 1209600 # Maximum value, 1,209,600 (14days) Tags: - Key: project - Value: !FindInMap [ Constants, ProjectName, Value ] + Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace Value: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" - Key: stage @@ -328,7 +326,7 @@ Resources: MessageRetentionPeriod: 1209600 # Maximum value, 1,209,600 (14days) Tags: - Key: project - Value: !FindInMap [ Constants, ProjectName, Value ] + Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace Value: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" - Key: stage @@ -351,13 +349,12 @@ Resources: BillingMode: PAY_PER_REQUEST Tags: - Key: project - Value: !FindInMap [ Constants, ProjectName, Value ] + Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace Value: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesNamespace}}" - Key: stage Value: !Ref Stage - #### EVENT BUS # Event bus for Unicorn Properties Service, used to publish and consume events UnicornPropertiesEventBus: @@ -443,6 +440,30 @@ Resources: ] } + #### CLOUDFORMATION NESTED STACKS + EventSchemasStack: + Type: AWS::Serverless::Application + Properties: + Location: "integration/event-schemas.yaml" + Parameters: + Stage: !Ref Stage + SubscriberPoliciesStack: + Type: AWS::Serverless::Application + DependsOn: + - UnicornPropertiesEventBusNameParam + Properties: + Location: "integration/subscriber-policies.yaml" + Parameters: + Stage: !Ref Stage + SubscriptionsStack: + Type: AWS::Serverless::Application + DependsOn: + - UnicornPropertiesEventBusArnParam + Properties: + Location: "integration/subscriptions.yaml" + Parameters: + Stage: !Ref Stage + Outputs: #### DYNAMODB OUTPUTS ContractStatusTableName: @@ -475,6 +496,20 @@ Outputs: WaitForContractApprovalFunctionArn: Value: !GetAtt WaitForContractApprovalFunction.Arn + #### STEPFUNCTIONS OUTPUTS + ApprovalStateMachineName: + Value: !GetAtt ApprovalStateMachine.Name + ApprovalStateMachineArn: + Value: !Ref ApprovalStateMachine + + #### EVENT BRIDGE OUTPUTS + UnicornPropertiesEventBusName: + Value: !GetAtt UnicornPropertiesEventBus.Name + #### CLOUDWATCH LOGS OUTPUTS UnicornPropertiesCatchAllLogGroupArn: + Description: Log all events on the service's EventBridge Bus Value: !GetAtt UnicornPropertiesCatchAllLogGroup.Arn + + ApprovalStateMachineLogGroupName: + Value: !Ref ApprovalStateMachineLogGroup diff --git a/unicorn_properties/tests/unit/conftest.py b/unicorn_properties/tests/unit/conftest.py index 477e05d..1e09dc4 100644 --- a/unicorn_properties/tests/unit/conftest.py +++ b/unicorn_properties/tests/unit/conftest.py @@ -4,6 +4,7 @@ import os import boto3 +from aws_lambda_powertools.utilities.typing import LambdaContext import pytest from moto import mock_dynamodb, mock_events, mock_stepfunctions @@ -34,3 +35,18 @@ def eventbridge(aws_credentials): def stepfunction(aws_credentials): with mock_stepfunctions(): yield boto3.client("stepfunctions", region_name='ap-southeast-2') + + +@pytest.fixture(scope='function') +def lambda_context(): + context: LambdaContext = LambdaContext() + context._function_name="propertiesService-LambdaFunction-HJsvdah2ubi2" + context._function_version="$LATEST" + context._invoked_function_arn="arn:aws:lambda:ap-southeast-2:424490683636:function:propertiesService-LambdaFunction-HJsvdah2ubi2" + context._memory_limit_in_mb=128 + context._aws_request_id="6f970d26-71d6-4c87-a196-9375f85c7b07" + context._log_group_name="/aws/lambda/propertiesService-LambdaFunction-HJsvdah2ubi2" + context._log_stream_name="2022/07/14/[$LATEST]7c71ca59882b4c569dd007c7e41c81e8" + # context._identity=CognitoIdentity([cognito_identity_id=None,cognito_identity_pool_id=None])]) + # context._client_context=None + return context diff --git a/unicorn_properties/tests/events/dbb_stream_events/contract_status_changed_draft.json b/unicorn_properties/tests/unit/events/ddb_stream_events/contract_status_changed_draft.json similarity index 100% rename from unicorn_properties/tests/events/dbb_stream_events/contract_status_changed_draft.json rename to unicorn_properties/tests/unit/events/ddb_stream_events/contract_status_changed_draft.json diff --git a/unicorn_properties/tests/events/dbb_stream_events/sfn_check_exists.json b/unicorn_properties/tests/unit/events/ddb_stream_events/sfn_check_exists.json similarity index 100% rename from unicorn_properties/tests/events/dbb_stream_events/sfn_check_exists.json rename to unicorn_properties/tests/unit/events/ddb_stream_events/sfn_check_exists.json diff --git a/unicorn_properties/tests/events/dbb_stream_events/sfn_wait_approval.json b/unicorn_properties/tests/unit/events/ddb_stream_events/sfn_wait_approval.json similarity index 100% rename from unicorn_properties/tests/events/dbb_stream_events/sfn_wait_approval.json rename to unicorn_properties/tests/unit/events/ddb_stream_events/sfn_wait_approval.json diff --git a/unicorn_properties/tests/events/dbb_stream_events/status_approved_waiting_for_approval.json b/unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_waiting_for_approval.json similarity index 100% rename from unicorn_properties/tests/events/dbb_stream_events/status_approved_waiting_for_approval.json rename to unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_waiting_for_approval.json diff --git a/unicorn_properties/tests/events/dbb_stream_events/status_approved_with_no_workflow.json b/unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_with_no_workflow.json similarity index 100% rename from unicorn_properties/tests/events/dbb_stream_events/status_approved_with_no_workflow.json rename to unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_with_no_workflow.json diff --git a/unicorn_properties/tests/events/lambda/contract_status_changed.json b/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed.json similarity index 100% rename from unicorn_properties/tests/events/lambda/contract_status_changed.json rename to unicorn_properties/tests/unit/events/eventbridge/contract_status_changed.json diff --git a/unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_1_approved.json b/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_approved.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_1_approved.json rename to unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_approved.json diff --git a/unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_1_draft.json b/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_draft.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_1_draft.json rename to unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_draft.json diff --git a/unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_2_approved.json b/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_approved.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_2_approved.json rename to unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_approved.json diff --git a/unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_2_draft.json b/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_draft.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/contract_status_changed_event_contract_2_draft.json rename to unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_draft.json diff --git a/unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_all_good.json b/unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_all_good.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_all_good.json rename to unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_all_good.json diff --git a/unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_inappropriate_description.json b/unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_inappropriate_description.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_inappropriate_description.json rename to unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_inappropriate_description.json diff --git a/unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_inappropriate_images.json b/unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_inappropriate_images.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_inappropriate_images.json rename to unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_inappropriate_images.json diff --git a/unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_non_existing_contract.json b/unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_non_existing_contract.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_non_existing_contract.json rename to unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_non_existing_contract.json diff --git a/unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_pause_workflow.json b/unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_pause_workflow.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/publicaction_approval_requested_event_pause_workflow.json rename to unicorn_properties/tests/unit/events/eventbridge/publicaction_approval_requested_event_pause_workflow.json diff --git a/unicorn_properties/tests/events/eventbridge/publication_approval_requested_event.json b/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/publication_approval_requested_event.json rename to unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event.json diff --git a/unicorn_properties/tests/events/eventbridge/publication_evaluation_completed_event.json b/unicorn_properties/tests/unit/events/eventbridge/publication_evaluation_completed_event.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/publication_evaluation_completed_event.json rename to unicorn_properties/tests/unit/events/eventbridge/publication_evaluation_completed_event.json diff --git a/unicorn_properties/tests/events/eventbridge/put_event_property_approval_requested.json b/unicorn_properties/tests/unit/events/eventbridge/put_event_property_approval_requested.json similarity index 100% rename from unicorn_properties/tests/events/eventbridge/put_event_property_approval_requested.json rename to unicorn_properties/tests/unit/events/eventbridge/put_event_property_approval_requested.json diff --git a/unicorn_properties/tests/events/lambda/content_integrity_validator_function_success.json b/unicorn_properties/tests/unit/events/lambda/content_integrity_validator_function_success.json similarity index 100% rename from unicorn_properties/tests/events/lambda/content_integrity_validator_function_success.json rename to unicorn_properties/tests/unit/events/lambda/content_integrity_validator_function_success.json diff --git a/unicorn_properties/tests/events/lambda/contract_status_checker.json b/unicorn_properties/tests/unit/events/lambda/contract_status_checker.json similarity index 100% rename from unicorn_properties/tests/events/lambda/contract_status_checker.json rename to unicorn_properties/tests/unit/events/lambda/contract_status_checker.json diff --git a/unicorn_properties/tests/events/lambda/wait_for_contract_approval_function.json b/unicorn_properties/tests/unit/events/lambda/wait_for_contract_approval_function.json similarity index 100% rename from unicorn_properties/tests/events/lambda/wait_for_contract_approval_function.json rename to unicorn_properties/tests/unit/events/lambda/wait_for_contract_approval_function.json diff --git a/unicorn_properties/tests/unit/helper.py b/unicorn_properties/tests/unit/helper.py index ab2336b..7188309 100644 --- a/unicorn_properties/tests/unit/helper.py +++ b/unicorn_properties/tests/unit/helper.py @@ -1,28 +1,30 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 - import json +from pathlib import Path + TABLE_NAME = 'table1' +EVENTBUS_NAME = 'test-eventbridge' +EVENTS_DIR = Path(__file__).parent / 'events' def load_event(filename): - with open(filename) as f: - data = json.load(f) - return data + return json.load(open(EVENTS_DIR / f'{filename}.json', 'r')) def return_env_vars_dict(k={}): d = { "AWS_DEFAULT_REGION": "ap-southeast-2", "CONTRACT_STATUS_TABLE": TABLE_NAME, - "EVENT_BUS": "test-eventbridge", + "EVENT_BUS": EVENTBUS_NAME, + "SERVICE_NAMESPACE": "unicorn.properties", + "POWERTOOLS_SERVICE_NAME":"unicorn.properties", + "POWERTOOLS_TRACE_DISABLED":"true", "POWERTOOLS_LOGGER_LOG_EVENT":"true", "POWERTOOLS_LOGGER_SAMPLE_RATE":"0.1", - "POWERTOOLS_METRICS_NAMESPACE":"unicorn.contracts", - "POWERTOOLS_SERVICE_NAME":"unicorn.contracts", - "POWERTOOLS_TRACE_DISABLED":"true", - "SERVICE_NAMESPACE": "unicorn.properties", + "POWERTOOLS_METRICS_NAMESPACE":"unicorn.properties", + "LOG_LEVEL": "INFO", } d.update(k) return d @@ -89,3 +91,8 @@ def create_ddb_table_contracts_with_entry(dynamodb): } table.put_item(Item=contract) return table + + +def create_test_eventbridge_bus(eventbridge): + bus = eventbridge.create_event_bus(Name=EVENTBUS_NAME) + return bus diff --git a/unicorn_properties/tests/unit/lambda_context.py b/unicorn_properties/tests/unit/lambda_context.py deleted file mode 100644 index a260817..0000000 --- a/unicorn_properties/tests/unit/lambda_context.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -""" -Simple Lambda Context class to be passed to the lambda handler when test is invoked -""" - -class LambdaContext: - aws_request_id="6f970d26-71d6-4c87-a196-9375f85c7b07" - log_group_name="/aws/lambda/propertiesService-ContractStatusChanged-IWaQgsTEtLtX" - log_stream_name="2022/07/14/[$LATEST]7c71ca59882b4c569dd007c7e41c81e8" - function_name="propertiesService-ContractStatusChanged-IWaQgsTEtLtX" - memory_limit_in_mb=128 - function_version="$LATEST" - invoked_function_arn="arn:aws:lambda:ap-southeast-2:424490683636:function:propertiesService-ContractStatusChanged-IWaQgsTEtLtX" - client_context=None - #identity=CognitoIdentity([cognito_identity_id=None,cognito_identity_pool_id=None])]) diff --git a/unicorn_properties/tests/unit/test_content_integrity_validator_function.py b/unicorn_properties/tests/unit/test_content_integrity_validator_function.py index dc8cc00..b87f6e7 100644 --- a/unicorn_properties/tests/unit/test_content_integrity_validator_function.py +++ b/unicorn_properties/tests/unit/test_content_integrity_validator_function.py @@ -1,18 +1,16 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 - import os import pytest from unittest import mock -from .lambda_context import LambdaContext from .helper import load_event, return_env_vars_dict @pytest.fixture def stepfunctions_event(): - return load_event('tests/events/lambda/content_integrity_validator_function_success.json') + return load_event('lambda/content_integrity_validator_function_success') @pytest.fixture @@ -49,9 +47,9 @@ def invalid_content_sentiment(stepfunctions_event): @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_valid_image_and_valid_sentiment(stepfunctions_event): +def test_valid_image_and_valid_sentiment(stepfunctions_event, lambda_context): from properties_service import content_integrity_validator_function - ret = content_integrity_validator_function.lambda_handler(stepfunctions_event, LambdaContext()) + ret = content_integrity_validator_function.lambda_handler(stepfunctions_event, lambda_context) assert ret['validation_result'] == "PASS" assert ret['imageModerations'] == stepfunctions_event['imageModerations'] @@ -59,11 +57,11 @@ def test_valid_image_and_valid_sentiment(stepfunctions_event): @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_valid_image_and_invalid_sentiment(invalid_content_sentiment): +def test_valid_image_and_invalid_sentiment(invalid_content_sentiment, lambda_context): event = invalid_content_sentiment from properties_service import content_integrity_validator_function - ret = content_integrity_validator_function.lambda_handler(event, LambdaContext()) + ret = content_integrity_validator_function.lambda_handler(event, lambda_context) assert ret['validation_result'] == "FAIL" assert ret['imageModerations'] == event['imageModerations'] @@ -71,11 +69,11 @@ def test_valid_image_and_invalid_sentiment(invalid_content_sentiment): @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_invalid_image_and_valid_sentiment(invalid_image_moderation): +def test_invalid_image_and_valid_sentiment(invalid_image_moderation, lambda_context): event = invalid_image_moderation from properties_service import content_integrity_validator_function - ret = content_integrity_validator_function.lambda_handler(event, LambdaContext()) + ret = content_integrity_validator_function.lambda_handler(event, lambda_context) assert ret['validation_result'] == "FAIL" assert ret['imageModerations'] == event['imageModerations'] @@ -83,11 +81,11 @@ def test_invalid_image_and_valid_sentiment(invalid_image_moderation): @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_invalid_image_and_invalid_sentiment(invalid_image_moderation, invalid_content_sentiment): +def test_invalid_image_and_invalid_sentiment(invalid_image_moderation, invalid_content_sentiment, lambda_context): event = {**invalid_image_moderation, **invalid_content_sentiment} from properties_service import content_integrity_validator_function - ret = content_integrity_validator_function.lambda_handler(event, LambdaContext()) + ret = content_integrity_validator_function.lambda_handler(event, lambda_context) assert ret['validation_result'] == "FAIL" assert ret['imageModerations'] == event['imageModerations'] diff --git a/unicorn_properties/tests/unit/test_contract_exists_checker_function.py b/unicorn_properties/tests/unit/test_contract_exists_checker_function.py index 277c5f0..ec91718 100644 --- a/unicorn_properties/tests/unit/test_contract_exists_checker_function.py +++ b/unicorn_properties/tests/unit/test_contract_exists_checker_function.py @@ -1,34 +1,31 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 - import os from importlib import reload import pytest from unittest import mock -from .lambda_context import LambdaContext from .helper import load_event, return_env_vars_dict, create_ddb_table_contracts_with_entry @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_existing_contract_exists_checker_function(dynamodb, mocker): - stepfunctions_event = load_event('tests/events/lambda/contract_status_checker.json') - +def test_existing_contract_exists_checker_function(dynamodb, lambda_context): + stepfunctions_event = load_event('lambda/contract_status_checker') from properties_service import contract_exists_checker_function reload(contract_exists_checker_function) create_ddb_table_contracts_with_entry(dynamodb) - ret = contract_exists_checker_function.lambda_handler(stepfunctions_event, LambdaContext()) + ret = contract_exists_checker_function.lambda_handler(stepfunctions_event, lambda_context) assert ret['property_id'] == stepfunctions_event['Input']['property_id'] assert ret['address']['country'] == stepfunctions_event['Input']['country'] @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_missing_contract_exists_checker_function(dynamodb, mocker): - stepfunctions_event = load_event('tests/events/lambda/contract_status_checker.json') +def test_missing_contract_exists_checker_function(dynamodb, lambda_context): + stepfunctions_event = load_event('lambda/contract_status_checker') stepfunctions_event['Input']['property_id'] = 'NOT/a/valid/CONTRACT' from properties_service import contract_exists_checker_function @@ -38,6 +35,6 @@ def test_missing_contract_exists_checker_function(dynamodb, mocker): create_ddb_table_contracts_with_entry(dynamodb) with pytest.raises(ContractStatusNotFoundException) as errinfo: - contract_exists_checker_function.lambda_handler(stepfunctions_event, LambdaContext()) + contract_exists_checker_function.lambda_handler(stepfunctions_event, lambda_context) assert errinfo.value.message == 'No contract found for specified Property ID' diff --git a/unicorn_properties/tests/unit/test_contract_status_changed_event_handler.py b/unicorn_properties/tests/unit/test_contract_status_changed_event_handler.py index cb13c52..12e5d59 100644 --- a/unicorn_properties/tests/unit/test_contract_status_changed_event_handler.py +++ b/unicorn_properties/tests/unit/test_contract_status_changed_event_handler.py @@ -1,6 +1,5 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 - import os from importlib import reload @@ -8,13 +7,12 @@ from unittest import mock from botocore.exceptions import ClientError -from .lambda_context import LambdaContext from .helper import load_event, return_env_vars_dict, create_ddb_table_properties @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_contract_status_changed_event_handler(dynamodb, mocker): - eventbridge_event = load_event('tests/events/lambda/contract_status_changed.json') +def test_contract_status_changed_event_handler(dynamodb, lambda_context): + eventbridge_event = load_event('eventbridge/contract_status_changed') from properties_service import contract_status_changed_event_handler # Reload is required to prevent function setup reuse from another test @@ -22,13 +20,13 @@ def test_contract_status_changed_event_handler(dynamodb, mocker): create_ddb_table_properties(dynamodb) - ret = contract_status_changed_event_handler.lambda_handler(eventbridge_event, LambdaContext()) + ret = contract_status_changed_event_handler.lambda_handler(eventbridge_event, lambda_context) assert ret["statusCode"] == 200 @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_missing_property_id(dynamodb, mocker): +def test_missing_property_id(dynamodb, lambda_context): eventbridge_event = {'detail': {}} from properties_service import contract_status_changed_event_handler @@ -38,6 +36,6 @@ def test_missing_property_id(dynamodb, mocker): create_ddb_table_properties(dynamodb) with pytest.raises(ClientError) as e: - contract_status_changed_event_handler.lambda_handler(eventbridge_event, LambdaContext()) + contract_status_changed_event_handler.lambda_handler(eventbridge_event, lambda_context) assert 'ValidationException' in str(e.value) diff --git a/unicorn_properties/tests/unit/test_properties_approval_sync_function.py b/unicorn_properties/tests/unit/test_properties_approval_sync_function.py index 5612809..4af568f 100644 --- a/unicorn_properties/tests/unit/test_properties_approval_sync_function.py +++ b/unicorn_properties/tests/unit/test_properties_approval_sync_function.py @@ -1,37 +1,35 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 - import os from importlib import reload from unittest import mock -from .lambda_context import LambdaContext from .helper import load_event, return_env_vars_dict @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_handle_status_changed_draft(stepfunction, mocker): - ddbstream_event = load_event('tests/events/dbb_stream_events/contract_status_changed_draft.json') +def test_handle_status_changed_draft(stepfunction, lambda_context): + ddbstream_event = load_event('ddb_stream_events/contract_status_changed_draft') from properties_service import properties_approval_sync_function reload(properties_approval_sync_function) - ret = properties_approval_sync_function.lambda_handler(ddbstream_event, LambdaContext()) + ret = properties_approval_sync_function.lambda_handler(ddbstream_event, lambda_context) assert ret is None # NOTE: This test cannot be implemented at this time because `moto`` does not yet support mocking `stepfunctions.send_task_success` @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_handle_status_changed_approved(caplog, stepfunction, mocker): +def test_handle_status_changed_approved(caplog, stepfunction, lambda_context): pass - # ddbstream_event = load_event('tests/events/dbb_stream_events/status_approved_waiting_for_approval.json') + # ddbstream_event = load_event('ddb_stream_events/status_approved_waiting_for_approval') # from properties_service import properties_approval_sync_function # reload(properties_approval_sync_function) - # ret = properties_approval_sync_function.lambda_handler(ddbstream_event, LambdaContext()) + # ret = properties_approval_sync_function.lambda_handler(ddbstream_event, lambda_context) # assert ret is None # assert 'Contract status for property is APPROVED' in caplog.text diff --git a/unicorn_properties/tests/unit/test_wait_for_contract_approval_function.py b/unicorn_properties/tests/unit/test_wait_for_contract_approval_function.py index c5865d1..1e05d15 100644 --- a/unicorn_properties/tests/unit/test_wait_for_contract_approval_function.py +++ b/unicorn_properties/tests/unit/test_wait_for_contract_approval_function.py @@ -1,18 +1,16 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 - import os from importlib import reload from unittest import mock -from .lambda_context import LambdaContext from .helper import load_event, return_env_vars_dict, create_ddb_table_contracts_with_entry @mock.patch.dict(os.environ, return_env_vars_dict(), clear=True) -def test_handle_wait_for_contract_approval_function(dynamodb, mocker): - stepfunctions_event = load_event('tests/events/lambda/wait_for_contract_approval_function.json') +def test_handle_wait_for_contract_approval_function(dynamodb, lambda_context): + stepfunctions_event = load_event('lambda/wait_for_contract_approval_function') from properties_service import wait_for_contract_approval_function reload(wait_for_contract_approval_function) @@ -22,7 +20,7 @@ def test_handle_wait_for_contract_approval_function(dynamodb, mocker): ddbitem_before = dynamodb.Table('table1').get_item(Key={'property_id': stepfunctions_event['Input']['property_id']}) assert 'sfn_wait_approved_task_token' not in ddbitem_before['Item'] - ret = wait_for_contract_approval_function.lambda_handler(stepfunctions_event, LambdaContext()) + ret = wait_for_contract_approval_function.lambda_handler(stepfunctions_event, lambda_context) ddbitem_after = dynamodb.Table('table1').get_item(Key={'property_id': stepfunctions_event['Input']['property_id']}) assert ret['property_id'] == stepfunctions_event['Input']['property_id'] diff --git a/unicorn_web/integration/event-schemas.yaml b/unicorn_web/integration/event-schemas.yaml index 91a298b..cad6453 100644 --- a/unicorn_web/integration/event-schemas.yaml +++ b/unicorn_web/integration/event-schemas.yaml @@ -17,12 +17,14 @@ Resources: Type: AWS::EventSchemas::Registry Properties: Description: 'Event schemas for Unicorn Web' - RegistryName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebNamespace}}" + RegistryName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebNamespace}}" EventRegistryPolicy: Type: AWS::EventSchemas::RegistryPolicy Properties: - RegistryName: !GetAtt EventRegistry.RegistryName + RegistryName: + Fn::GetAtt: EventRegistry.RegistryName Policy: Version: '2012-10-17' Statement: @@ -30,7 +32,7 @@ Resources: Effect: Allow Principal: AWS: - - !Ref AWS::AccountId + - Ref: AWS::AccountId Action: - schemas:DescribeCodeBinding - schemas:DescribeRegistry @@ -40,140 +42,143 @@ Resources: - schemas:ListSchemaVersions - schemas:SearchSchemas Resource: - - !GetAtt EventRegistry.RegistryArn - - !Sub "arn:${AWS::Partition}:schemas:${AWS::Region}:${AWS::AccountId}:schema/${EventRegistry.RegistryName}*" + - Fn::GetAtt: EventRegistry.RegistryArn + - Fn::Sub: "arn:${AWS::Partition}:schemas:${AWS::Region}:${AWS::AccountId}:schema/${EventRegistry.RegistryName}*" PublicationApprovalRequested: Type: AWS::EventSchemas::Schema Properties: Type: 'OpenApi3' - RegistryName: !GetAtt EventRegistry.RegistryName - SchemaName: !Sub '${EventRegistry.RegistryName}@PublicationApprovalRequested' + RegistryName: + Fn::GetAtt: EventRegistry.RegistryName + SchemaName: + Fn::Sub: '${EventRegistry.RegistryName}@PublicationApprovalRequested' Description: 'The schema for a request to publish a property' - Content: !Sub | - { - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "PublicationApprovalRequested" - }, - "paths": {}, - "components": { - "schemas": { - "AWSEvent": { - "type": "object", - "required": [ - "detail-type", - "resources", - "detail", - "id", - "source", - "time", - "region", - "version", - "account" - ], - "x-amazon-events-detail-type": "PublicationApprovalRequested", - "x-amazon-events-source": "${EventRegistry.RegistryName}", - "properties": { - "detail": { - "$ref": "#/components/schemas/PublicationApprovalRequested" - }, - "account": { - "type": "string" - }, - "detail-type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "region": { - "type": "string" - }, - "resources": { - "type": "array", - "items": { + Content: + Fn::Sub: | + { + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "PublicationApprovalRequested" + }, + "paths": {}, + "components": { + "schemas": { + "AWSEvent": { + "type": "object", + "required": [ + "detail-type", + "resources", + "detail", + "id", + "source", + "time", + "region", + "version", + "account" + ], + "x-amazon-events-detail-type": "PublicationApprovalRequested", + "x-amazon-events-source": "${EventRegistry.RegistryName}", + "properties": { + "detail": { + "$ref": "#/components/schemas/PublicationApprovalRequested" + }, + "account": { + "type": "string" + }, + "detail-type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "version": { "type": "string" } - }, - "source": { - "type": "string" - }, - "time": { - "type": "string", - "format": "date-time" - }, - "version": { - "type": "string" } - } - }, - "PublicationApprovalRequested": { - "type": "object", - "required": [ - "images", - "address", - "listprice", - "contract", - "description", - "currency", - "property_id", - "status" - ], - "properties": { - "address": { - "$ref": "#/components/schemas/Address" - }, - "contract": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "description": { - "type": "string" - }, - "images": { - "type": "array", - "items": { + }, + "PublicationApprovalRequested": { + "type": "object", + "required": [ + "images", + "address", + "listprice", + "contract", + "description", + "currency", + "property_id", + "status" + ], + "properties": { + "address": { + "$ref": "#/components/schemas/Address" + }, + "contract": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "description": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "listprice": { + "type": "string" + }, + "property_id": { + "type": "string" + }, + "status": { "type": "string" } - }, - "listprice": { - "type": "string" - }, - "property_id": { - "type": "string" - }, - "status": { - "type": "string" } - } - }, - "Address": { - "type": "object", - "required": [ - "country", - "number", - "city", - "street" - ], - "properties": { - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "number": { - "type": "string" - }, - "street": { - "type": "string" + }, + "Address": { + "type": "object", + "required": [ + "country", + "number", + "city", + "street" + ], + "properties": { + "city": { + "type": "string" + }, + "country": { + "type": "string" + }, + "number": { + "type": "string" + }, + "street": { + "type": "string" + } } } } } } - } diff --git a/unicorn_web/integration/subscriber-policies.yaml b/unicorn_web/integration/subscriber-policies.yaml index c7aaa03..ad90eb7 100644 --- a/unicorn_web/integration/subscriber-policies.yaml +++ b/unicorn_web/integration/subscriber-policies.yaml @@ -19,12 +19,15 @@ Resources: CrossServiceCreateRulePolicy: Type: AWS::Events::EventBusPolicy Properties: - EventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBus}}" - StatementId: !Sub "OnlyRulesForPropertiesServiceEvents-${Stage}" + EventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBus}}" + StatementId: + Fn::Sub: "OnlyRulesForWebServiceEvents-${Stage}" Statement: Effect: Allow Principal: - AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" + AWS: + Fn::Sub: "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" Action: - events:PutRule - events:DeleteRule @@ -34,14 +37,15 @@ Resources: - events:PutTargets - events:RemoveTargets Resource: - - !Sub + - Fn::Sub: - arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/${eventBusName}/* - - eventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBus}}" + - eventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBus}}" Condition: StringEqualsIfExists: "events:creatorAccount": "${aws:PrincipalAccount}" StringEquals: "events:source": - - !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebNamespace}}" + - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebNamespace}}" "Null": "events:source": "false" diff --git a/unicorn_web/integration/subscriptions.yaml b/unicorn_web/integration/subscriptions.yaml index a3880c0..8078205 100644 --- a/unicorn_web/integration/subscriptions.yaml +++ b/unicorn_web/integration/subscriptions.yaml @@ -19,17 +19,20 @@ Resources: Properties: Name: unicorn.web-PublicationEvaluationCompleted Description: PublicationEvaluationCompleted subscription - EventBusName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + EventBusName: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" EventPattern: source: - - !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" detail-type: - PublicationEvaluationCompleted State: ENABLED Targets: - Id: SendEventTo - Arn: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" - RoleArn: !GetAtt UnicornPropertiesEventBusToUnicornWebEventBusRole.Arn + Arn: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" + RoleArn: + Fn::GetAtt: UnicornPropertiesEventBusToUnicornWebEventBusRole.Arn # This IAM role allows EventBridge to assume the permissions necessary to send events # from the publishing event bus, to the subscribing event bus (UnicornWebEventBusArn) @@ -48,9 +51,11 @@ Resources: Statement: - Effect: Allow Action: events:PutEvents - Resource: !Sub "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" + Resource: + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" Outputs: PublicationEvaluationCompletedSubscription: Description: Rule ARN for Property service event subscription - Value: !GetAtt PublicationEvaluationCompletedSubscriptionRule.Arn + Value: + Fn::GetAtt: PublicationEvaluationCompletedSubscriptionRule.Arn diff --git a/unicorn_web/samconfig.yaml b/unicorn_web/samconfig.yaml index 1e4629c..406b675 100644 --- a/unicorn_web/samconfig.yaml +++ b/unicorn_web/samconfig.yaml @@ -16,7 +16,9 @@ default: disable_rollback: true confirm_changeset: false fail_on_empty_changeset: false - capabilities: CAPABILITY_IAM + capabilities: + - CAPABILITY_IAM + - CAPABILITY_AUTO_EXPAND parameter_overrides: - "Stage=local" validate: @@ -31,51 +33,3 @@ default: local_start_lambda: parameters: warm_containers: EAGER - -schemas: - global: - parameters: - stack_name: uni-prop-local-web-schemas - s3_prefix: uni-prop-local-web-schemas - resolve_s3: true - deploy: - parameters: - template_file: integration/event-schemas.yaml - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - parameter_overrides: - - "Stage=local" - -subscriber-policies: - global: - parameters: - stack_name: uni-prop-local-web-subscriber-policies - s3_prefix: uni-prop-local-web-subscriber-policies - resolve_s3: true - deploy: - parameters: - template_file: ./integration/subscriber-policies.yaml - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - capabilities: CAPABILITY_IAM - parameter_overrides: - - "Stage=local" - -subscriptions: - global: - parameters: - stack_name: uni-prop-local-web-subscriptions - s3_prefix: uni-prop-local-web-subscriptions - resolve_s3: true - deploy: - parameters: - template_file: ./integration/subscriptions.yaml - confirm_changeset: false - fail_on_empty_changeset: false - on_failure: ROLLBACK - capabilities: CAPABILITY_IAM - parameter_overrides: - - "Stage=local" - \ No newline at end of file diff --git a/unicorn_web/template.yaml b/unicorn_web/template.yaml index 40ed3ec..5eb9912 100644 --- a/unicorn_web/template.yaml +++ b/unicorn_web/template.yaml @@ -1,7 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: 2010-09-09 -Transform: +Transform: - AWS::LanguageExtensions - AWS::Serverless-2016-10-31 Description: > @@ -11,10 +11,11 @@ Metadata: cfn-lint: config: ignore_checks: - - ES4000 # Rule disabled because the CatchAll Rule doesn't need a DLQ - - ES6000 # Rule disabled because SQS DLOs don't need a RedrivePolicy - - WS2001 # Rule disabled because check does not support !ToJsonString transform - - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure + - ES4000 # Rule disabled because the CatchAll Rule doesn't need a DLQ + - ES6000 # Rule disabled because SQS DLOs don't need a RedrivePolicy + - WS2001 # Rule disabled because check does not support !ToJsonString transform + - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure + - W3002 Parameters: Stage: @@ -124,7 +125,7 @@ Resources: ScalingConfig: MaximumConcurrency: 5 - # Log group for the RequestApprovalFunction + # Log group for the RequestApprovalFunction RequestApprovalFunctionLogGroup: Type: AWS::Logs::LogGroup UpdateReplacePolicy: Delete @@ -414,7 +415,32 @@ Resources: ] } + #### CLOUDFORMATION NESTED STACKS + EventSchemasStack: + Type: AWS::Serverless::Application + Properties: + Location: "integration/event-schemas.yaml" + Parameters: + Stage: !Ref Stage + SubscriberPoliciesStack: + Type: AWS::Serverless::Application + DependsOn: + - UnicornWebEventBusParam + Properties: + Location: "integration/subscriber-policies.yaml" + Parameters: + Stage: !Ref Stage + SubscriptionsStack: + Type: AWS::Serverless::Application + DependsOn: + - UnicornWebEventBusArnParam + Properties: + Location: "integration/subscriptions.yaml" + Parameters: + Stage: !Ref Stage + Outputs: + #### API GATEWAY OUTPUTS BaseUrl: Description: Web service API endpoint Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}" @@ -422,6 +448,7 @@ Outputs: Description: Web service API endpoint Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/" + #### API ACTIONS OUTPUTS ApiSearchPropertiesByCity: Description: "GET request to list all properties in a given city" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/search/{country}/{city}" @@ -435,14 +462,17 @@ Outputs: Description: "GET request to get the full details of a single property" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/properties/{country}/{city}/{street}/{number}" + #### SQS OUTPUTS IngestQueueUrl: Description: URL for the Ingest SQS Queue Value: !GetAtt UnicornWebIngestQueue.QueueUrl + #### DYNAMODB OUTPUTS WebTableName: Description: Name of the DynamoDB Table for Unicorn Web Value: !Ref WebTable + #### LAMBDA FUNCTIONS OUTPUTS SearchFunctionArn: Description: Search function ARN Value: !GetAtt SearchFunction.Arn @@ -453,5 +483,11 @@ Outputs: Description: Publication evaluation event handler function ARN Value: !GetAtt PublicationApprovedEventHandlerFunction.Arn - UnicornWebCatchAllLogGroupArn: - Value: !GetAtt UnicornWebCatchAllLogGroup.Arn + #### EVENT BRIDGE OUTPUTS + UnicornWebEventBusName: + Value: !GetAtt UnicornWebEventBus.Name + + #### CLOUDWATCH LOGS OUTPUTS + UnicornWebCatchAllLogGroupName: + Description: Log all events on the service's EventBridge Bus + Value: !Ref UnicornWebCatchAllLogGroup