AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS UrbanIO SiteWise Integration (qs-1s83daalb)

Rules:
  SupportedRegions:
    Assertions:
      - Assert:
          !Or
            - !Equals
              - !Ref AWS::Region
              - us-east-1
            - !Equals
              - !Ref AWS::Region
              - us-west-2
            - !Equals
              - !Ref AWS::Region
              - eu-west-1
            - !Equals
              - !Ref AWS::Region
              - eu-central-1
            - !Equals
              - !Ref AWS::Region
              - ap-southeast-1
            - !Equals
              - !Ref AWS::Region
              - ap-southeast-2
        AssertDescription: Stack Deployment not to this region not supported

Conditions:
  HasEmailAddress: !Not
    - !Equals
      - !Ref EmailAddress
      - ''

  HasS3BucketName: !Not
     - !Equals
      - !Ref S3Bucket
      - ''

  AllowedToCreateCrossAccount: !Not
    - !Equals 
      - !Ref AllowCrossAccountAccess
      - 'false'

Metadata:
  QuickStartDocumentation:
    EntrypointName: Urban.io for AWS IoT SiteWise
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Urban.io configuration
        Parameters:
          - S3Bucket
          - EmailAddress
          - UrbanioAccountNumber
      - Label:
          default: AWS account access
        Parameters:
          - AllowCrossAccountAccess
      - Label:
          default: AWS Quick Start configuration
        Parameters:
          - QSS3BucketName
          - QSS3KeyPrefix

      
    ParameterLabels:
      S3Bucket:
        default: S3 bucket

      QSS3BucketName:
        default: Quick Start S3 bucket name

      QSS3KeyPrefix:
        default: Quick Start S3 key prefix

      UrbanioAccountNumber:
        default: Urban.io AWS account number

      EmailAddress:
        default: The email address to be used for alarm notifications

      AllowCrossAccountAccess:
        default: Allow cross-account access

Globals:
  Function:
      Handler: handler.handler
      MemorySize: 128
      Runtime: python3.8
      Timeout: 600

Parameters:
  QSS3BucketName:
    AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$
    ConstraintDescription: The Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-).
    Description: Name of the S3 bucket for your copy of the Quick Start assets. 
      Keep the default name unless you are customizing the template. 
      Changing the name updates code references to point to a new Quick 
      Start location. This name can include numbers, lowercase letters, 
      uppercase letters, and hyphens, but do not start or end with a hyphen (-). 
      See https://aws-quickstart.github.io/option1.html.
    Type: String
    Default: aws-quickstart

  QSS3KeyPrefix:
    AllowedPattern: ^[0-9a-zA-Z-/]*$
    ConstraintDescription: The Quick Start key prefix can include numbers, lowercase
      letters, uppercase letters, hyphens (-), and forward slash (/).
    Description: S3 key prefix that is used to simulate a directory for your copy
      of the Quick Start assets. Keep the default prefix unless you are customizing
      the template. Changing this prefix updates code references to point to a new
      Quick Start location. This prefix can include numbers, lowercase letters, uppercase
      letters, hyphens (-), and forward slashes (/). See https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
      and https://aws-quickstart.github.io/option1.html.
    Type: String
    Default: quickstart-urbanio-iot-aas/

  S3Bucket:
    Type: String
    Description:  The S3 bucket for the initial integration setup files and object definitions. Please keep the exact name as it will be required in app.urban.io during the integration. The name for the S3 bucket - must be globally unique (3-63 lowercase letters or numbers)
    AllowedPattern: '^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$'
    ConstraintDescription: 3-63 characters; must contain only lowercase letters or numbers.

  EmailAddress:
    Type: String
    Description: Email address to be use for Amazon CloudWatch alarm notifications.
    ConstraintDescription: Enter a valid email.

  AllowCrossAccountAccess:
    Type: String
    Description: Grant Urban.io permissions to upload data to Amazon S3 and send messages to AWS IoT Core topics.
    AllowedValues:
      - 'false'
      - 'true'
    Default: 'false'

  UrbanioAccountNumber:
    Type: String
    AllowedPattern: ^[0-9]*$
    Description: Urban.io AWS account number.
    ConstraintDescription: Enter a valid Account ID.

Resources:
  ## S3 bucket
  SitewiseS3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketName: !If
        - HasS3BucketName
        - !Ref S3Bucket
        - !Sub ${AWS::StackName}-${AWS::Region}

  LambdaZipsBucket:
    Type: AWS::S3::Bucket

  ## DynamoDB Tables
  SitewiseModels:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub ${AWS::StackName}-${AWS::Region}-SiteWise-Models
      AttributeDefinitions:
      - AttributeName: Name
        AttributeType: S
      KeySchema:
      - AttributeName: Name
        KeyType: HASH
      BillingMode: PAY_PER_REQUEST

  SitewiseAssets:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub ${AWS::StackName}-${AWS::Region}-SiteWise-Assets
      AttributeDefinitions:
      - AttributeName: Id
        AttributeType: S
      KeySchema:
      - AttributeName: Id
        KeyType: HASH
      BillingMode: PAY_PER_REQUEST

  SitewiseEventsMapping:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName:  !Sub ${AWS::StackName}-${AWS::Region}-SiteWise-Events-Mapping
      AttributeDefinitions:
      - AttributeName: Type
        AttributeType: S
      KeySchema:
      - AttributeName: Type
        KeyType: HASH
      BillingMode: PAY_PER_REQUEST

  SitewiseIntegrationPoints:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName:  !Sub ${AWS::StackName}-${AWS::Region}-SiteWise-Integration-Points
      AttributeDefinitions:
      - AttributeName: Id
        AttributeType: S
      KeySchema:
      - AttributeName: Id
        KeyType: HASH
      BillingMode: PAY_PER_REQUEST

  ## Lambda functions
  CopyZips:
    Type: Custom::CopyZips
    DependsOn:
      - CopyZipsRole
    Properties:
      ServiceToken: !GetAtt CopyZipsFunction.Arn
      DestBucket: !Ref LambdaZipsBucket
      SourceBucket: !Ref QSS3BucketName
      Prefix: !Ref QSS3KeyPrefix
      DestPrefix: !Ref QSS3KeyPrefix
      Objects:
        - functions/packages/asset-model-converter/lambda.zip
        - functions/packages/association-updater/lambda.zip
        - functions/packages/attributes-updater/lambda.zip
        - functions/packages/iot-core-to-sqs/lambda.zip
        - functions/packages/lifecycle-events-mapping-updater/lambda.zip
        - functions/packages/s3-to-lifecycle-events-converter/lambda.zip

  CopyZipsFunction:
    Type: AWS::Serverless::Function
    Properties:
      Description: Copies objects from a source S3 bucket to a destination.
      Handler: index.handler
      Role: !GetAtt CopyZipsRole.Arn
      Timeout: 240
      InlineCode: |
        import json
        import logging
        import threading
        import boto3
        import cfnresponse
        def copy_objects(source_bucket, dest_bucket, prefix, dest_prefix, objects):
            s3 = boto3.client('s3')
            for o in objects:
                key = prefix + o
                dest_key = dest_prefix + o
                copy_source = {
                    'Bucket': source_bucket,
                    'Key': key
                }
                print('copy_source: %s' % copy_source)
                print('dest_bucket = %s'%dest_bucket)
                print('key = %s' %key)
                print('key = %s' %dest_key)
                s3.copy_object(CopySource=copy_source, Bucket=dest_bucket,
                      Key=dest_key)
        def delete_objects(bucket, prefix, objects):
            s3 = boto3.client('s3')
            objects = {'Objects': [{'Key': prefix + o} for o in objects]}
            s3.delete_objects(Bucket=bucket, Delete=objects)
        def timeout(event, context):
            logging.error('Execution is about to time out, sending failure response to CloudFormation')
            cfnresponse.send(event, context, cfnresponse.FAILED, {}, None)
        def handler(event, context):
            # make sure we send a failure to CloudFormation if the function
            # is going to timeout
            timer = threading.Timer((context.get_remaining_time_in_millis()
                      / 1000.00) - 0.5, timeout, args=[event, context])
            timer.start()
            print('Received event: %s' % json.dumps(event))
            status = cfnresponse.SUCCESS
            try:
                source_bucket = event['ResourceProperties']['SourceBucket']
                dest_bucket = event['ResourceProperties']['DestBucket']
                prefix = event['ResourceProperties']['Prefix']
                dest_prefix = event['ResourceProperties']['DestPrefix']
                objects = event['ResourceProperties']['Objects']
                if event['RequestType'] == 'Delete':
                    delete_objects(dest_bucket, dest_prefix, objects)
                else:
                    copy_objects(source_bucket, dest_bucket, prefix, dest_prefix, objects)
            except Exception as e:
                logging.error('Exception: %s' % e, exc_info=True)
                status = cfnresponse.FAILED
            finally:
                timer.cancel()
                cfnresponse.send(event, context, status, {}, None)

  LifecycleEventsConverterLambda:
    DependsOn: CopyZips
    Type: AWS::Serverless::Function
    Properties:
      CodeUri:
        Bucket: !Ref LambdaZipsBucket
        Key: !Sub ${QSS3KeyPrefix}functions/packages/s3-to-lifecycle-events-converter/lambda.zip
      Role: !GetAtt LifecycleEventsConverterRole.Arn
      Environment:
        Variables:
          LIFECICLE_EVENTS_QUEUE_URL: !Ref SQSLifecycleEventsQueue
          DYNAMO_INTEGRATION_POINTS_TABLE_NAME: !Ref SitewiseIntegrationPoints
      Events:
        S3EventAssets:
          Type: S3
          Properties:
            Bucket: !Ref SitewiseS3Bucket
            Events: s3:ObjectCreated:*
            Filter:
              S3Key:
                Rules:
                  - Name: prefix
                    Value: sitewise/assets/

  LifecycleEventsMappingUpdaterLambda:
    DependsOn: CopyZips
    Type: AWS::Serverless::Function
    Properties:
      CodeUri:
        Bucket: !Ref LambdaZipsBucket
        Key:  !Sub ${QSS3KeyPrefix}functions/packages/lifecycle-events-mapping-updater/lambda.zip
      Role: !GetAtt LifecycleEventsMappingUpdaterRole.Arn
      Environment:
        Variables:
          DYNAMO_EVENT_MAPPING_TABLE_NAME: !Ref SitewiseEventsMapping
      Events:
        S3EventAssets:
          Type: S3
          Properties:
            Bucket: !Ref SitewiseS3Bucket
            Events: s3:ObjectCreated:*
            Filter:
              S3Key:
                Rules:
                  - Name: prefix
                    Value: sitewise/events/

  IoTCoreToSQSLambda:
    DependsOn: CopyZips
    Type: AWS::Serverless::Function
    Properties:
      CodeUri:
        Bucket: !Ref LambdaZipsBucket
        Key: !Sub ${QSS3KeyPrefix}functions/packages/iot-core-to-sqs/lambda.zip
      Role: !GetAtt IoTCoreToSQSRole.Arn
      Environment:
        Variables:
          LIFECICLE_EVENTS_QUEUE_URL: !Ref SQSLifecycleEventsQueue


  AssetModelConverterLambda:
    DependsOn: LifecycleEventsConverterLambda
    Type: AWS::Serverless::Function
    Properties:
      CodeUri:
        Bucket: !Ref LambdaZipsBucket
        Key: !Sub ${QSS3KeyPrefix}functions/packages/asset-model-converter/lambda.zip
      Role: !GetAtt AssetModelConverterRole.Arn
      Environment:
        Variables:
          DYNAMO_ASSETS_TABLE_NAME: !Ref SitewiseAssets
          DYNAMO_MODELS_TABLE_NAME: !Ref SitewiseModels
          DYNAMO_INTEGRATION_POINTS_TABLE_NAME: !Ref SitewiseIntegrationPoints
          ASSETS_TO_ASSOCIATE_QUEUE_URL: !Ref SQSCreatedAssetsQueue
      Events:
        SQSLifecycleEventsQueue:
          Type: SQS
          Properties:
            Queue: !GetAtt SQSLifecycleEventsQueue.Arn
            BatchSize: 10

  AssociationUpdaterLambda:
    DependsOn: AssetModelConverterLambda
    Type: AWS::Serverless::Function
    Properties:
      CodeUri:
        Bucket: !Ref LambdaZipsBucket
        Key: !Sub ${QSS3KeyPrefix}functions/packages/association-updater/lambda.zip
      Role: !GetAtt AssociationUpdaterRole.Arn
      Environment:
        Variables:
          DYNAMO_ASSETS_TABLE_NAME: !Ref SitewiseAssets
          ASSETS_TO_UPDATE_QUEUE_URL: !Ref SQSAssociatedAssetsQueue
          DYNAMO_INTEGRATION_POINTS_TABLE_NAME: !Ref SitewiseIntegrationPoints
      Events:
        SQSCreatedAssetsQueue:
          Type: SQS
          Properties:
            Queue: !GetAtt SQSCreatedAssetsQueue.Arn
            BatchSize: 10

  AttributesUpdaterLambda:
    DependsOn: AssetModelConverterLambda
    Type: AWS::Serverless::Function
    Properties:
      CodeUri:
        Bucket: !Ref LambdaZipsBucket
        Key:  !Sub ${QSS3KeyPrefix}functions/packages/attributes-updater/lambda.zip
      Role: !GetAtt AttributesUpdaterRole.Arn
      Environment:
        Variables:
          DYNAMO_ASSETS_TABLE_NAME: !Ref SitewiseAssets
          DYNAMO_EVENT_MAPPING_TABLE_NAME: !Ref SitewiseEventsMapping
      Events:
        SQSAssociatedAssetsQueue:
          Type: SQS
          Properties:
            Queue: !GetAtt SQSAssociatedAssetsQueue.Arn
            BatchSize: 10

  ## Lambda Roles
  UrbanioAssumedRole:
    Type: AWS::IAM::Role
    Condition: AllowedToCreateCrossAccount
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Join
                  - ''
                  - - 'arn:'
                    - !Ref AWS::Partition
                    - ':iam::'
                    - !Ref UrbanioAccountNumber
                    - ':root'
            Action: sts:AssumeRole
      Path: /
      Policies:
      - PolicyName: CrossAccountS3AllowReadAccessPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - s3:PutObject
            - s3:DeleteObject
            - s3:GetObject
            - s3:ListBucket
            - s3:RestoreObject
            Resource:
              - !Join
                - ''
                - - !GetAtt SitewiseS3Bucket.Arn
                  - '/*'
              - !GetAtt SitewiseS3Bucket.Arn
          - Effect: "Allow"
            Action:
             - iot:Publish
            Resource:
               - !Join
                  - ''
                  - - 'arn:'
                    - !Ref AWS::Partition
                    - ':iot:*:*:'
                    - 'topic/*'

  IoTSiteWiseMeasurementRuleRole:
    Type: AWS::IAM::Role
    Properties:
      Description: Role for IoT SiteWise Measurement Update
      AssumeRolePolicyDocument:
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: iot.amazonaws.com
        Version: '2012-10-17'
      Path: /service-role/
      Policies:
      - PolicyName: IoTSiteWiseMeasurement-RolePolicy
        PolicyDocument:
          Statement:
          # SiteWise
          - Action:
              - iotsitewise:BatchPutAssetPropertyValue
            Effect: Allow
            Resource:
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':iotsitewise:'
                  - !Ref AWS::Region
                  - ':'
                  - !Ref AWS::AccountId
                  - ':*'
          Version: '2012-10-17'

  CopyZipsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - !Join
          - ''
          - - 'arn:'
            - !Ref AWS::Partition
            - ':iam::aws:policy/AmazonS3FullAccess'
      Path: /
      Policies:
        - PolicyName: lambda-copier
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource:
                  - !Join
                    - ''
                    - - 'arn:'
                      - !Ref AWS::Partition
                      - ':s3:::'
                      - !Ref QSS3BucketName
                  - !Join
                    - ''
                    - - 'arn:'
                      - !Ref AWS::Partition
                      - ':s3:::'
                      - !Ref QSS3BucketName
                      - !Ref QSS3KeyPrefix
                      - "*"
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                  - s3:GetObject
                  - s3:ListBucket
                  - s3:RestoreObject
                Resource:
                  - !GetAtt LambdaZipsBucket.Arn
                  - !Join
                    -  ''
                    - - !GetAtt LambdaZipsBucket.Arn
                      - '/'
                      - !Ref QSS3KeyPrefix
                      -  '*'

  LifecycleEventsConverterRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: '/'
      Policies:
      - PolicyName: sqs-lifecycle-events-role
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
            - sqs:CreateQueue
            - sqs:DeleteMessage
            - sqs:DeleteMessageBatch
            - sqs:DeleteQueue
            - sqs:GetQueueAttributes
            - sqs:GetQueueUrl
            - sqs:ListDeadLetterSourceQueues
            - sqs:ListQueueTags
            - sqs:ListQueues
            - sqs:PurgeQueue
            - sqs:ReceiveMessage
            - sqs:RemovePermission
            - sqs:SendMessage
            - sqs:SendMessageBatch
            - sqs:SetQueueAttributes
            - sqs:TagQueue
            - sqs:UntagQueue
            Resource:
              - !GetAtt SQSLifecycleEventsQueue.Arn
      - PolicyName: IoTCoreToSQSDynamodbWritePolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - dynamodb:BatchGetItem
            - dynamodb:BatchWriteItem
            - dynamodb:ConditionCheckItem
            - dynamodb:CreateGlobalTable
            - dynamodb:DeleteItem
            - dynamodb:DescribeLimits
            - dynamodb:DescribeStream
            - dynamodb:DescribeTable
            - dynamodb:GetItem
            - dynamodb:GetRecords
            - dynamodb:GetShardIterator
            - dynamodb:ListStreams
            - dynamodb:ListTables
            - dynamodb:PutItem
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:UpdateItem
            Resource:
              - !GetAtt SitewiseEventsMapping.Arn
              - !GetAtt SitewiseIntegrationPoints.Arn
      - PolicyName: s3AllowReadPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - s3:PutObject
            - s3:DeleteObject
            - s3:GetObject
            - s3:ListBucket
            - s3:RestoreObject
            Resource:
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
                  - '/*'
  IoTCoreToSQSRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/CloudWatchReadOnlyAccess'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/AWSIoTSiteWiseFullAccess'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action:  sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
      Path: '/'
      Policies:
      - PolicyName: IoTCoreToSQSDynamodbWritePolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - dynamodb:BatchGetItem
            - dynamodb:BatchWriteItem
            - dynamodb:ConditionCheckItem
            - dynamodb:CreateGlobalTable
            - dynamodb:DeleteItem
            - dynamodb:DescribeLimits
            - dynamodb:DescribeStream
            - dynamodb:DescribeTable
            - dynamodb:GetItem
            - dynamodb:GetRecords
            - dynamodb:GetShardIterator
            - dynamodb:ListStreams
            - dynamodb:ListTables
            - dynamodb:PutItem
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:UpdateItem
            Resource:
              - !GetAtt SitewiseModels.Arn
              - !GetAtt SitewiseAssets.Arn
      - PolicyName: IoTCoreToSQSPolicy
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
            - sqs:CreateQueue
            - sqs:DeleteMessage
            - sqs:DeleteMessageBatch
            - sqs:DeleteQueue
            - sqs:GetQueueAttributes
            - sqs:GetQueueUrl
            - sqs:ListDeadLetterSourceQueues
            - sqs:ListQueueTags
            - sqs:ListQueues
            - sqs:PurgeQueue
            - sqs:ReceiveMessage
            - sqs:RemovePermission
            - sqs:SendMessage
            - sqs:SendMessageBatch
            - sqs:SetQueueAttributes
            - sqs:TagQueue
            - sqs:UntagQueue
            Resource:
              - !GetAtt SQSLifecycleEventsQueue.Arn
      - PolicyName: s3AllowReadPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - s3:PutObject
            - s3:DeleteObject
            - s3:GetObject
            - s3:ListBucket
            - s3:RestoreObject
            Resource:
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
                  - '/*'

  LifecycleEventsMappingUpdaterRole:
    Type: AWS::IAM::Role    
    Properties:
      ManagedPolicyArns:
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/CloudWatchReadOnlyAccess'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: '/'
      Policies:
      - PolicyName: DynamodbWritePolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - dynamodb:BatchGetItem
            - dynamodb:BatchWriteItem
            - dynamodb:ConditionCheckItem
            - dynamodb:CreateGlobalTable
            - dynamodb:DeleteItem
            - dynamodb:DescribeLimits
            - dynamodb:DescribeStream
            - dynamodb:DescribeTable
            - dynamodb:GetItem
            - dynamodb:GetRecords
            - dynamodb:GetShardIterator
            - dynamodb:ListStreams
            - dynamodb:ListTables
            - dynamodb:PutItem
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:UpdateItem
            Resource:
              - !GetAtt SitewiseEventsMapping.Arn
      - PolicyName: s3AllowReadPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - s3:PutObject
            - s3:DeleteObject
            - s3:GetObject
            - s3:ListBucket
            - s3:RestoreObject
            Resource:
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
                  - '/*'

  AssetModelConverterRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/CloudWatchReadOnlyAccess'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/AWSIoTSiteWiseFullAccess'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action:  sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
      Path: '/'
      Policies:
      - PolicyName: DynamodbWritePolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - dynamodb:BatchGetItem
            - dynamodb:BatchWriteItem
            - dynamodb:ConditionCheckItem
            - dynamodb:CreateGlobalTable
            - dynamodb:DeleteItem
            - dynamodb:DescribeLimits
            - dynamodb:DescribeStream
            - dynamodb:DescribeTable
            - dynamodb:GetItem
            - dynamodb:GetRecords
            - dynamodb:GetShardIterator
            - dynamodb:ListStreams
            - dynamodb:ListTables
            - dynamodb:PutItem
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:UpdateItem
            Resource:
              - !GetAtt SitewiseModels.Arn
              - !GetAtt SitewiseAssets.Arn
              - !GetAtt SitewiseIntegrationPoints.Arn
      - PolicyName: s3-allow-read-sitewise
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - s3:PutObject
            - s3:DeleteObject
            - s3:GetObject
            - s3:ListBucket
            - s3:RestoreObject
            Resource:
              - !Join
                - ''
                - - !GetAtt SitewiseS3Bucket.Arn
                  - '/*'
              - !GetAtt SitewiseS3Bucket.Arn
      - PolicyName: SqsAssetModelConverterPolicy
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
            - sqs:CreateQueue
            - sqs:DeleteMessage
            - sqs:DeleteMessageBatch
            - sqs:DeleteQueue
            - sqs:GetQueueAttributes
            - sqs:GetQueueUrl
            - sqs:ListDeadLetterSourceQueues
            - sqs:ListQueueTags
            - sqs:ListQueues
            - sqs:PurgeQueue
            - sqs:ReceiveMessage
            - sqs:RemovePermission
            - sqs:SendMessage
            - sqs:SendMessageBatch
            - sqs:SetQueueAttributes
            - sqs:TagQueue
            - sqs:UntagQueue
            Resource:
              - !GetAtt SQSLifecycleEventsQueue.Arn
              - !GetAtt SQSCreatedAssetsQueue.Arn

  AssociationUpdaterRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/CloudWatchReadOnlyAccess'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/AWSIoTSiteWiseFullAccess'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
      Policies:
      - PolicyName: DynamodbWritePolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - dynamodb:BatchGetItem
            - dynamodb:BatchWriteItem
            - dynamodb:ConditionCheckItem
            - dynamodb:CreateGlobalTable
            - dynamodb:DeleteItem
            - dynamodb:DescribeLimits
            - dynamodb:DescribeStream
            - dynamodb:DescribeTable
            - dynamodb:GetItem
            - dynamodb:GetRecords
            - dynamodb:GetShardIterator
            - dynamodb:ListStreams
            - dynamodb:ListTables
            - dynamodb:PutItem
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:UpdateItem
            Resource:
              - !GetAtt SitewiseAssets.Arn
              - !GetAtt SitewiseIntegrationPoints.Arn
      - PolicyName: s3AllowReadPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - s3:PutObject
            - s3:DeleteObject
            - s3:GetObject
            - s3:ListBucket
            - s3:RestoreObject
            Resource:
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
                  - '/*'
      - PolicyName: sqs-association-updater-role
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
            - sqs:CreateQueue
            - sqs:DeleteMessage
            - sqs:DeleteMessageBatch
            - sqs:DeleteQueue
            - sqs:GetQueueAttributes
            - sqs:GetQueueUrl
            - sqs:ListDeadLetterSourceQueues
            - sqs:ListQueueTags
            - sqs:ListQueues
            - sqs:PurgeQueue
            - sqs:ReceiveMessage
            - sqs:RemovePermission
            - sqs:SendMessage
            - sqs:SendMessageBatch
            - sqs:SetQueueAttributes
            - sqs:TagQueue
            - sqs:UntagQueue
            Resource:
              - !GetAtt SQSAssociatedAssetsQueue.Arn
              - !GetAtt SQSCreatedAssetsQueue.Arn

  AttributesUpdaterRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/CloudWatchReadOnlyAccess'
      - !Join
        - ''
        - - 'arn:'
          - !Ref AWS::Partition
          - ':iam::aws:policy/AWSIoTSiteWiseFullAccess'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
      Policies:
      - PolicyName: dynamodb-write-sitewise
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - dynamodb:BatchGetItem
            - dynamodb:BatchWriteItem
            - dynamodb:ConditionCheckItem
            - dynamodb:CreateGlobalTable
            - dynamodb:DeleteItem
            - dynamodb:DescribeLimits
            - dynamodb:DescribeStream
            - dynamodb:DescribeTable
            - dynamodb:GetItem
            - dynamodb:GetRecords
            - dynamodb:GetShardIterator
            - dynamodb:ListStreams
            - dynamodb:ListTables
            - dynamodb:PutItem
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:UpdateItem
            Resource:
              - !GetAtt SitewiseAssets.Arn
              - !GetAtt SitewiseEventsMapping.Arn
      - PolicyName: s3AllowReadPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: "Allow"
            Action:
            - s3:PutObject
            - s3:DeleteObject
            - s3:GetObject
            - s3:ListBucket
            - s3:RestoreObject
            Resource:
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
              - !Join
                - ''
                - - 'arn:'
                  - !Ref AWS::Partition
                  - ':s3:::'
                  - !If
                    - HasS3BucketName
                    - !Ref S3Bucket
                    - !Sub ${AWS::StackName}-${AWS::Region}
                  - '/*'
      - PolicyName: sqs-attibute-updater-role
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
            - sqs:CreateQueue
            - sqs:DeleteMessage
            - sqs:DeleteMessageBatch
            - sqs:DeleteQueue
            - sqs:GetQueueAttributes
            - sqs:GetQueueUrl
            - sqs:ListDeadLetterSourceQueues
            - sqs:ListQueueTags
            - sqs:ListQueues
            - sqs:PurgeQueue
            - sqs:ReceiveMessage
            - sqs:RemovePermission
            - sqs:SendMessage
            - sqs:SendMessageBatch
            - sqs:SetQueueAttributes
            - sqs:TagQueue
            - sqs:UntagQueue
            Resource:
              - !GetAtt SQSAssociatedAssetsQueue.Arn

  IotToSqsTopicRuleLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt IoTCoreToSQSLambda.Arn
      Principal: iot.amazonaws.com
      SourceAccount: !Ref 'AWS::AccountId'
      SourceArn: !GetAtt IotToSqsTopicRule.Arn

  ## SQS
  SQSLifecycleEventsQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${AWS::StackName}-${AWS::Region}-lifecycle-events.fifo
      FifoQueue: true
      ContentBasedDeduplication: true
      VisibilityTimeout: 1200

  SQSAssociatedAssetsQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${AWS::StackName}-${AWS::Region}-associated-assets.fifo
      FifoQueue: true
      ContentBasedDeduplication: true
      VisibilityTimeout: 1200

  SQSCreatedAssetsQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${AWS::StackName}-${AWS::Region}-created-assets.fifo
      FifoQueue: true
      ContentBasedDeduplication: true
      VisibilityTimeout: 1200

  ## IOT Tropic
  IotToSqsTopicRule:
    Type: AWS::IoT::TopicRule
    Properties:
      RuleName: !Join
        - ''
        - !Split
          - '-'
          - !Sub '${AWS::StackName}_${AWS::Region}_send_lifecycle_event_to_lambda_iot_topic_rule'
      TopicRulePayload:
        RuleDisabled: false
        Sql: SELECT * FROM 'urbanio/lifecycle/#'
        Actions:
        - Lambda:
            FunctionArn: !GetAtt IoTCoreToSQSLambda.Arn

  UpdateSitewiseMeasurementsRule:
    Type: AWS::IoT::TopicRule
    Properties:
      RuleName: !Join
        - ''
        - !Split
          - '-'
          - !Sub '${AWS::StackName}_${AWS::Region}_update_sitewise_measurements_iot_topic_rule'
      TopicRulePayload:
        RuleDisabled: false
        Sql: SELECT * FROM 'urbanio/reading/#'
        Actions:
        - IotSiteWise:
            PutAssetPropertyValueEntries:
              - PropertyAlias: '/urbanio/device/${metadata.ref.d}/${reading.ch}/${reading.dt}'
                PropertyValues:
                  - Timestamp:
                      TimeInSeconds: '${reading.ts / 1000}'
                    Value:
                      DoubleValue: '${reading.scaled.v}'
            RoleArn: !GetAtt IoTSiteWiseMeasurementRuleRole.Arn

  ## CloudWatch Alarms
  LifecycleEventsConverterLambdaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: Alarm if lambda errors out too many times
      AlarmActions:
      - !If
        - HasEmailAddress
        - !Ref AlarmSNSTopic
        - !Ref AWS::NoValue
      Namespace: AWS/Lambda
      MetricName: Errors
      Dimensions:
      - Name: FunctionName
        Value: !Ref LifecycleEventsConverterLambda
      Statistic: Sum
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 1
      EvaluationPeriods: 5
      Period: 60
      TreatMissingData: notBreaching
      Unit: Count

  LifecycleEventsMappingUpdaterLambdaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: Alarm if lambda errors out too many times
      AlarmActions:
      - !If
        - HasEmailAddress
        - !Ref AlarmSNSTopic
        - !Ref AWS::NoValue
      Namespace: AWS/Lambda
      MetricName: Errors
      Dimensions:
      - Name: FunctionName
        Value: !Ref LifecycleEventsMappingUpdaterLambda
      Statistic: Sum
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 1
      EvaluationPeriods: 5
      Period: 60
      TreatMissingData: notBreaching
      Unit: Count

  IoTCoreToSQSLambdaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: Alarm if lambda errors out too many times
      AlarmActions:
      - !If
        - HasEmailAddress
        - !Ref AlarmSNSTopic
        - !Ref AWS::NoValue
      Namespace: AWS/Lambda
      MetricName: Errors
      Dimensions:
      - Name: FunctionName
        Value: !Ref IoTCoreToSQSLambda
      Statistic: Sum
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 1
      EvaluationPeriods: 5
      Period: 60
      TreatMissingData: notBreaching
      Unit: Count

  AssetModelConverterLambdaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: Alarm if lambda errors out too many times
      AlarmActions:
      - !If
        - HasEmailAddress
        - !Ref AlarmSNSTopic
        - !Ref AWS::NoValue
      Namespace: AWS/Lambda
      MetricName: Errors
      Dimensions:
      - Name: FunctionName
        Value: !Ref AssetModelConverterLambda
      Statistic: Sum
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 1
      EvaluationPeriods: 5
      Period: 60
      TreatMissingData: notBreaching
      Unit: Count

  AssociationUpdaterLambdaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: Alarm if lambda errors out too many times
      AlarmActions:
      - !If
        - HasEmailAddress
        - !Ref AlarmSNSTopic
        - !Ref AWS::NoValue
      Namespace: AWS/Lambda
      MetricName: Errors
      Dimensions:
      - Name: FunctionName
        Value: !Ref AssociationUpdaterLambda
      Statistic: Sum
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 1
      EvaluationPeriods: 5
      Period: 60
      TreatMissingData: notBreaching
      Unit: Count

  AttributesUpdaterLambdaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: Alarm if lambda errors out too many times
      AlarmActions:
      - !If
        - HasEmailAddress
        - !Ref AlarmSNSTopic
        - !Ref AWS::NoValue
      Namespace: AWS/Lambda
      MetricName: Errors
      Dimensions:
      - Name: FunctionName
        Value: !Ref AttributesUpdaterLambda
      Statistic: Sum
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 1
      EvaluationPeriods: 5
      Period: 60
      TreatMissingData: notBreaching
      Unit: Count

  AlarmSNSTopic:
    Type: AWS::SNS::Topic
    Condition: HasEmailAddress
    Properties: {}

  AlarmSNSSubscription:
    Type: AWS::SNS::Subscription
    Condition: HasEmailAddress
    Properties:
      Endpoint: !Ref EmailAddress
      Protocol: email
      TopicArn: !Ref AlarmSNSTopic

  ## Output Parameters
Outputs:
  UrbanioSitewiseS3Bucket:
    Description: The AWS IoT SiteWise S3 bucket
    Value:
      Ref: SitewiseS3Bucket

  CrossAccountRoleId:
    Condition: AllowedToCreateCrossAccount
    Description: The logical ID of the IAM role
    Value: !Ref UrbanioAssumedRole

  CrossAccountRolArn:
    Condition: AllowedToCreateCrossAccount
    Description: The ARN of the IAM role
    Value: !GetAtt UrbanioAssumedRole.Arn

  Postdeployment:
    Description: See the deployment guide for post-deployment steps.
    Value: https://aws.amazon.com/quickstart/?quickstart-all.sort-by=item.additionalFields.sortDate&quickstart-all.sort-order=desc&awsm.page-quickstart-all=5