###
# (c) 2022 Shoreditch Ops Ltd. All rights reserved.
#
# This file is part of Artillery Pro (https://artillery.io)
#
# NOTICE: All information contained herein is, and remains
# the property of Shoreditch Ops Ltd. The intellectual and
# technical concepts contained herein are proprietary to
# Shoreditch Ops Ltd and are protected by copyright law.
#
# Modification and/or redistribution of this material is
# strictly forbidden unless prior written permission is
# obtained from Shoreditch Ops Ltd.
###

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  ConfigTable:
    Type: "AWS::DynamoDB::Table"
    Properties:
      AttributeDefinitions:
        - AttributeName: name
          AttributeType: "S"
      KeySchema:
        - AttributeName: name
          KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      TableName: # "<%- AWS_ACCOUNT_ID -%>-artillery-pro-config"
        Fn::Join:
          - ""
          - - Ref: AWS::AccountId
            - "-artillery-pro-config"
  # NOTE: CloudFormation does not allow more than one update to a global
  # index per stack update. Adding a new index + renaming a field in another
  # one would not be allowed for example.
  TestRunsTable:
    Type: "AWS::DynamoDB::Table"
    Properties:
      TableName:
        Fn::Join:
          - ""
          - - "artilleryio-test-runs-"
            - Ref: AWS::AccountId
      AttributeDefinitions:
        - AttributeName: testRunId
          AttributeType: "S"
        - AttributeName: kind
          AttributeType: "S"
        - AttributeName: createdTime
          AttributeType: "S"
        - AttributeName: statusName
          AttributeType: "S"
      KeySchema:
        - AttributeName: testRunId
          KeyType: "HASH"
        - AttributeName: kind
          KeyType: "RANGE"
      BillingMode: "PAY_PER_REQUEST"
      GlobalSecondaryIndexes:
        - IndexName: "test-runs-kind-index"
          KeySchema:
            - AttributeName: kind
              KeyType: "HASH"
            - AttributeName: testRunId
              KeyType: "RANGE"
          Projection:
            ProjectionType: "ALL"
        - IndexName: "test-runs-createdtime-index"
          KeySchema:
            - AttributeName: kind
              KeyType: "HASH"
            - AttributeName: createdTime
              KeyType: "RANGE"
          Projection:
            ProjectionType: "ALL"
        - IndexName: "test-runs-laststatus-index"
          KeySchema:
            - AttributeName: statusName
              KeyType: "HASH"
            - AttributeName: testRunId
              KeyType: "RANGE"
          Projection:
            ProjectionType: "ALL"
  CommonObjectsTable:
    Type: "AWS::DynamoDB::Table"
    Properties:
      TableName:
        Fn::Join:
          - ""
          - - "artilleryio-common-objects-"
            - Ref: AWS::AccountId
      AttributeDefinitions:
        - AttributeName: kind
          AttributeType: "S"
      KeySchema:
        - AttributeName: kind
          KeyType: "HASH"
      BillingMode: "PAY_PER_REQUEST"
  LockTable:
    Type: "AWS::DynamoDB::Table"
    Properties:
      AttributeDefinitions:
        - AttributeName: lockName
          AttributeType: "S"
      KeySchema:
        - AttributeName: lockName
          KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      TableName: # "<%- AWS_ACCOUNT_ID -%>-artillery-pro-lock"
        Fn::Join:
          - ""
          - - Ref: AWS::AccountId
            - "-artillery-pro-lock"
  # <% // TODO: Might not want this %>
  # <% // This role may be assumed by an IAM user (if permitted) to run tests %>
  ArtilleryCLIUserRole:
    Type: "AWS::IAM::Role"
    Properties:
      MaxSessionDuration: 43200 # 12 hours
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - Ref: AWS::AccountId
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - Ref: CLIUserPolicy
      RoleName: !Join ["", ["<%- CLI_USER_ROLE_NAME -%>", "-", {Ref: "AWS::Region"}]]

  # <% // This policy may be attached directly to IAM users to allow them to run tests %>
  CLIUserPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      Description: "This policy grants permissions required to run Artillery Pro tests (see https://artillery.io/pro)"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - secretsmanager:GetSecretValue
            Resource: "arn:aws:secretsmanager:*:*:secret:artilleryio/*"
          - Effect: Allow
            Action:
              - rds-data:BatchExecuteStatement
              - rds-data:BeginTransaction
              - rds-data:CommitTransaction
              - rds-data:ExecuteStatement
              - rds-data:RollbackTransaction
            Resource:
              - "<%- DEFAULT_AURORA_SERVERLESS_V1_CLUSTER_ARN -%>"
          - Effect: Allow
            Action:
              - ec2:DescribeRouteTables
              - ec2:DescribeVpcs
              - ec2:DescribeSubnets
            Resource:
              - "*"
          - Effect: Allow
            Action:
              - ecs:ListClusters
              - ecs:CreateCluster
              - ecs:DescribeClusters
              - ecs:ListContainerInstances
              - ecs:SubmitTaskStateChange
              - ecs:DescribeTasks
              - ecs:ListTasks
              - ecs:RegisterTaskDefinition
              - ecs:ListTaskDefinitions
              - ecs:DescribeTaskDefinition
              - ecs:StartTask
              - ecs:StopTask
              - ecs:RunTask
              - ecs:DeregisterTaskDefinition
            Resource:
              - "*"

          # Allow Artillery CLI to create AWS service role for ECS when creating a Fargate cluster
          # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using-service-linked-roles.html#create-service-linked-role

          - Effect: Allow
            Action:
              - iam:CreateServiceLinkedRole
            Resource:
              - "arn:aws:iam::*:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS*"
            Condition:
              StringLike:
                iam:AWSServiceName: ecs.amazonaws.com

          - Effect: Allow
            Action:
              - dynamodb:*
            Resource:
              #
              # Tables:
              #
              - Fn::GetAtt:
                  - LockTable
                  - Arn
              - Fn::GetAtt:
                  - ConfigTable
                  - Arn
              - Fn::Join:
                - ""
                - - Fn::GetAtt:
                    - TestRunsTable
                    - Arn
                  - "*"
              - Fn::GetAtt:
                  - CommonObjectsTable
                  - Arn
              # Indexes:
              - Fn::Join:
                - ""
                - - Fn::GetAtt:
                    - LockTable
                    - Arn
                  - "/index/*"
              - Fn::Join:
                - ""
                - - Fn::GetAtt:
                    - ConfigTable
                    - Arn
                  - "/index/*"
              - Fn::Join:
                - ""
                - - Fn::GetAtt:
                    - CommonObjectsTable
                    - Arn
                  - "/index/*"
          - Effect: Allow
            Action:
              - iam:PassRole
            Resource:
              - Fn::GetAtt:
                  - WorkerRole
                  - Arn
          - Effect: Allow
            Action:
              - sqs:DeleteMessage
              - sqs:DeleteMessageBatch
              - sqs:GetQueueAttributes
              - sqs:GetQueueUrl
              - sqs:ReceiveMessage
              - sqs:SendMessage
              - sqs:SendMessageBatch
              - sqs:CreateQueue
              - sqs:DeleteQueue
            Resource:
              - !Join ["", ["arn:aws:sqs:*:", {Ref: "AWS::AccountId"}, ":artilleryio*"]]
          # ListQueues does not support scoping to individual resources:
          # https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonsqs.html#amazonsqs-queue
          - Effect: Allow
            Action:
              - sqs:ListQueues
            Resource:
              - "*"
          - Effect: Allow
            Action:
              - s3:DeleteObject
              - s3:GetObject
              - s3:GetObjectAcl
              - s3:GetObjectTagging
              - s3:GetObjectVersion
              - s3:PutObject
              - s3:PutObjectAcl
              - s3:ListBucket
              - s3:GetBucketLocation
              - s3:GetBucketLogging
              - s3:GetBucketPolicy
              - s3:GetBucketTagging
              - s3:PutBucketPolicy
              - s3:PutBucketTagging
              - s3:PutMetricsConfiguration
              - s3:GetLifecycleConfiguration
              - s3:PutLifecycleConfiguration
            Resource:
              - Fn::Join:
                - ""
                - - "arn:aws:s3:::"
                  - "<%- BUCKET_NAME_PREFIX %>"
                  - "-"
                  - Ref: AWS::AccountId
              - Fn::Join:
                - ""
                - - "arn:aws:s3:::"
                  - "<%- BUCKET_NAME_PREFIX %>"
                  - "-"
                  - Ref: AWS::AccountId
                  - "/*"
          - Effect: Allow
            Action:
              - cloudformation:DescribeStacks
            Resource: "*"
          - Effect: Allow
            Action:
              - cloudformation:DescribeStackEvents
              - cloudformation:DescribeStackResource
              - cloudformation:DescribeStackResources
            Resource:
              - Ref: AWS::StackId
          - Effect: Allow
            Action:
              - "ssm:PutParameter"
              - "ssm:GetParameter"
              - "ssm:GetParameters"
              - "ssm:DeleteParameter"
              - "ssm:DescribeParameters"
              - "ssm:GetParametersByPath"
            Resource:
              - !Join ["", ["arn:aws:ssm:", {Ref: "AWS::Region"}, ":", {Ref: "AWS::AccountId"}, ":parameter/artilleryio*"]]
      ManagedPolicyName: !Join ["", ["<%- CLI_USER_POLICY_NAME %>", "-", {Ref: "AWS::Region"}]]
  LogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: "<%- LOGGROUP_NAME %>"
      RetentionInDays: 30

  #
  # Role for the containers running on the ECS cluster.
  #
  WorkerRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "ecs-tasks.amazonaws.com"
                - "ecs.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      # Policies for worker containers:
      Policies:
        - PolicyName: "parameter-store-access-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "ssm:DescribeParameters"
                Resource:
                  - "*"
              - Effect: Allow
                Action:
                  - "ssm:GetParameters"
                  - "ssm:GetParameter"
                Resource:
                  - !Join ["", ["arn:aws:ssm:", {Ref: "AWS::Region"}, ":", {Ref: "AWS::AccountId"}, ":parameter/artilleryio*"]]
        - PolicyName: "base-docker-image-pull-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "ecr:BatchCheckLayerAvailability"
                  - "ecr:GetDownloadUrlForLayer"
                  - "ecr:BatchGetImage"
                  - "ecr:ListImages"
                Resource:
                  <% SUPPORTED_REGIONS.forEach((r) => { %>
                  - "arn:aws:ecr:<%- r %>:301676560329:repository/artillery-pro/aws-ecs-node"
                  - "arn:aws:ecr:<%- r %>:301676560329:repository/artillery-pro/aws-ecs-node-dev"
                  <% }); %>
        - PolicyName: "docker-login"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "ecr:GetAuthorizationToken"
                Resource:
                  - "*"
        - PolicyName: "<%- LOGGROUP_NAME -%>-access-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:*"
                Resource:
                  - "arn:aws:logs:*:<%- AWS_ACCOUNT_ID -%>:log-group:artilleryio-log-group*:*" # TODO: can write to any log group
        - PolicyName: "sqs-access-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "sqs:*"
                Resource:
                  - !Join ["", ["arn:aws:sqs:*:", {Ref: "AWS::AccountId"}, ":artilleryio*"]]
        - PolicyName: # "<%- BUCKET_NAME_PREFIX -%>-<%- AWS_ACCOUNT_ID -%>-access-policy"
            Fn::Join:
              - ""
              - - "<%- BUCKET_NAME_PREFIX %>"
                - Ref: AWS::AccountId
                - "-access-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "s3:*"
                Resource:
                  - Fn::Join:
                      - ""
                      - - "arn:aws:s3:::"
                        - "<%- BUCKET_NAME_PREFIX %>"
                        - "-"
                        - Ref: AWS::AccountId
                  - Fn::Join:
                      - ""
                      - - "arn:aws:s3:::"
                        - "<%- BUCKET_NAME_PREFIX %>"
                        - "-"
                        - Ref: AWS::AccountId
                        - "/*"
Outputs:
  LockTable:
    Value:
      Fn::GetAtt:
        - LockTable
        - Arn
  ConfigTable:
    Value:
      Fn::GetAtt:
        - ConfigTable
        - Arn
  WorkerRoleArn:
    Value:
      Fn::GetAtt:
        - WorkerRole
        - Arn
  CLIUserPolicyArn:
    Value:
      Ref: CLIUserPolicy
  CLIUserRoleArn:
    Value:
      Fn::GetAtt:
        - ArtilleryCLIUserRole
        - Arn
