AWSTemplateFormatVersion: '2010-09-09'
Description: Creates an ECR repository, CodeBuild project, and IAM role

Resources:

  # ECR Repository
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: damage-repo

 #CodeBuild IAM Policy
  CodeBuildProjectPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: CodeBuildProjectPolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - ecr:GetAuthorizationToken
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:GetRepositoryPolicy
              - ecr:DescribeImages
              - ecr:BatchGetImage
              - ecr:InitiateLayerUpload
              - ecr:UploadLayerPart
              - ecr:CompleteLayerUpload
              - ecr:PutImage
              - ecr:SetRepositoryPolicy
            Resource: "*"
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: "*"

  # CodeBuild Project Role
  CodeBuildRole:
    Type: AWS::IAM::Role
    DependsOn: CodeBuildProjectPolicy
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Ref CodeBuildProjectPolicy

  # CodeBuild Project      
  IngestionCodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: ing-codebuild-project
      Description: CodeBuild project for building Docker images
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
        PrivilegedMode: true
      Source:
        Type: GITHUB
        Location: !Sub 'https://github.com/vinipedroni/public-test-repo.git'
        BuildSpec: containers/ingestion/buildspec.yaml
      EncryptionKey: alias/aws/s3

  InferenceCodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: inf-codebuild-project
      Description: CodeBuild project for building Docker images
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
        PrivilegedMode: true
      Source:
        Type: GITHUB
        Location: !Sub 'https://github.com/vinipedroni/public-test-repo.git'
        BuildSpec: containers/inference/buildspec.yaml
      EncryptionKey: alias/aws/s3

# Custom Resource to run CodeBuild project
  CodeBuildIngestionStart:
    Type: AWS::CloudFormation::CustomResource
    DependsOn: InferenceCodeBuildProject
    Properties:
      ServiceToken: !GetAtt CodeBuildLambdaFunction.Arn
      ProjectName: !Ref IngestionCodeBuildProject

# Custom Resource to run CodeBuild project
  CodeBuildInferenceStart:
    Type: AWS::CloudFormation::CustomResource
    DependsOn: IngestionCodeBuildProject
    Properties:
      ServiceToken: !GetAtt CodeBuildLambdaFunction.Arn
      ProjectName: !Ref InferenceCodeBuildProject

  # Lambda Function to run CodeBuild project
  CodeBuildLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          import cfnresponse

          def lambda_handler(event, context):
              try:
                  codebuild = boto3.client('codebuild')
                  response = codebuild.start_build(
                      projectName=event['ResourceProperties']['ProjectName']
                  )
                  build_id = response['build']['id']
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {'BuildId': build_id})
              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Reason': str(e)})
      Handler: index.lambda_handler
      Runtime: python3.12
      Role: !GetAtt LambdaExecutionRole.Arn
      Timeout: 300

#Lambda Role IAM Policy
  LambdaRolePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: LambdaRolePolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - codebuild:*
            Resource:
              - !GetAtt IngestionCodeBuildProject.Arn
              - !GetAtt InferenceCodeBuildProject.Arn
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: "*"
          - Effect: Allow
            Action:
              - iam:PassRole
            Resource:
              - !GetAtt TaskRole.Arn
              - !GetAtt TaskExecutionRole.Arn


  # IAM Role for Lambda Function
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Ref LambdaRolePolicy

# VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: damage-vpc

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway

  # Attach Internet Gateway to VPC
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # Public Subnet 1
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.0.0/24

  # Public Subnet 2
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.1.0/24

  # Route Table for Public Subnets
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  # Route for Internet Gateway
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Fargate tasks
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 8501
          ToPort: 8501
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0


  # Associate Public Subnet 1 with Public Route Table
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  # Associate Public Subnet 2 with Public Route Table
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  # ECS Cluster
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: damage-ecs-cluster

  # Task Definition
  InfTaskDefinition:
      Type: AWS::ECS::TaskDefinition
      Properties:
        Family: inf-definition
        Cpu: 256
        Memory: 512
        TaskRoleArn: !Ref TaskRole
        ExecutionRoleArn: !Ref TaskExecutionRole
        ContainerDefinitions:
          - Name: repair-cost-container
            Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/damage-repo:inference
            PortMappings:
              - ContainerPort: 8501
            LogConfiguration:
              LogDriver: awslogs
              Options:
                awslogs-group: !Ref LogGroup
                awslogs-region: !Ref AWS::Region
                awslogs-stream-prefix: repair-cost-container
        RequiresCompatibilities:
          - FARGATE
        NetworkMode: awsvpc
# Application Load Balancer
  InferenceALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2

  # ALB Security Group
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for the Application Load Balancer
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0


  # ALB Listener
  InferenceALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref InferenceALBTargetGroup
      LoadBalancerArn: !Ref InferenceALB
      Port: 80
      Protocol: HTTP

  # ALB Target Group
  InferenceALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckPath: /
      Name: inference-alb-tg
      Port: 8501
      Protocol: HTTP
      TargetType: ip
      VpcId: !Ref VPC
    # Task Definition
  IngTaskDefinition:
      Type: AWS::ECS::TaskDefinition
      Properties:
        Family: ingestion-definition
        Cpu: 256
        Memory: 512
        TaskRoleArn: !Ref TaskRole
        ExecutionRoleArn: !Ref TaskExecutionRole
        ContainerDefinitions:
          - Name: repair-cost-container
            Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/damage-repo:ingestion
            PortMappings:
              - ContainerPort: 8501
            LogConfiguration:
              LogDriver: awslogs
              Options:
                awslogs-group: !Ref LogGroup
                awslogs-region: !Ref AWS::Region
                awslogs-stream-prefix: repair-cost-container
        RequiresCompatibilities:
          - FARGATE
        NetworkMode: awsvpc

  ECSTasksPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: task-execution-policy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "bedrock:*"
            Resource:
              - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-haiku-20240307-v1:0"
              - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/amazon.titan-embed-image-v1" 
          - Effect: Allow
            Action:
              - "ssm:*"
            Resource: 
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/car-repair/collection-domain-name"
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/car-repair/distribution-domain-name"
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/car-repair/s3-bucket"
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/car-repair/s3-bucket-source"
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/car-repair/security-group"
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/car-repair/subnet"
          - Effect: Allow
            Action:
              - "ecr:*"
            Resource:
              - !Sub "arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/damage-repo"
          - Effect: Allow
            Action:
              - "ecr:GetAuthorizationToken"
            Resource: '*'
          - Effect: Allow
            Action:
              - "ecs:*"
            Resource:
              - !Sub "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:task/*"
              - !Sub "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/damage-ecs-cluster"
              - !Sub "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:task-definition/inf-definition"
              - !Sub "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:task-definition/ingestion-definition"
              - !Sub "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:service/inference-service"
          - Effect: Allow
            Action:
              - s3:*
            Resource:
              - !Sub "arn:aws:s3:::${RepairCostBucket}"
              - !Sub "arn:aws:s3:::${RepairCostBucket}/*"
              - !Sub "arn:aws:s3:::${SourceS3Bucket}"
              - !Sub "arn:aws:s3:::${SourceS3Bucket}/*"
          - Effect: Allow
            Action:
              - "aoss:*"
            Resource:
              - !Sub "arn:aws:aoss:${AWS::Region}:${AWS::AccountId}:collection/*"
              - !Sub "arn:aws:aoss:${AWS::Region}:${AWS::AccountId}:index/*"
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource:
              - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*"
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref ECSTasksPolicy

  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref ECSTasksPolicy


  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: repair-cost-container-logs
      RetentionInDays: 7

  # Service
  InferenceECSService:
    Type: AWS::ECS::Service
    DependsOn: 
    - CodeBuildInferenceStart
    - InferenceALBTargetGroup
    - InferenceALBListener
    - CloudFrontDistribution
    Properties:
      ServiceName: inference-service
      Cluster: !Ref ECSCluster
      TaskDefinition: !Ref InfTaskDefinition
      LaunchType: FARGATE
      DesiredCount: 1
      NetworkConfiguration:
        AwsVpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref SecurityGroup
          Subnets:
            - !Ref PublicSubnet1
            - !Ref PublicSubnet2
      LoadBalancers:
      - ContainerName: repair-cost-container
        ContainerPort: 8501
        TargetGroupArn: !Ref InferenceALBTargetGroup

  NetworkPolicy:
    Type: AWS::OpenSearchServerless::SecurityPolicy
    Properties: 
      Name: os-public-policy 
      Type: network 
      Policy: |
        [{
          "Rules": [
            {"ResourceType": "collection", "Resource": ["collection/repair-cost-collection"]},
            {"ResourceType": "dashboard", "Resource": ["collection/repair-cost-collection"]}
          ],  
          "AllowFromPublic": true
        }]

  Encryption:
    Type: AWS::OpenSearchServerless::SecurityPolicy
    Properties:
      Name: encryption-policy
      Type: encryption 
      Policy: >-
        {"Rules":[{"ResourceType":"collection","Resource":["collection/repair-cost-collection"]}],"AWSOwnedKey":true}

  Collection:
    DependsOn: Encryption
    Type: AWS::OpenSearchServerless::Collection
    Properties:
      Name: repair-cost-collection
      Type: VECTORSEARCH
  
  AccessPolicy:
    Type: AWS::OpenSearchServerless::AccessPolicy 
    Properties:
      Name: collection-access-policy
      Type: data
      Policy: 
        !Sub >-
         [{"Description":"Access for
         test-user","Rules":[{"ResourceType":"index","Resource":["index/*/*"],"Permission":["aoss:*"]},
         {"ResourceType":"collection","Resource":["collection/repair-cost-collection"],"Permission":["aoss:*"]}],
         "Principal":["${TaskRole.Arn}"]}]

  RepairCostBucket:
    Type: AWS::S3::Bucket
  SourceS3Bucket:
    Type: AWS::S3::Bucket

  RepairCostDistributionOai:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: repair-cost-oai
  
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties: 
     Bucket: !Ref RepairCostBucket
     PolicyDocument:
      Statement:
        - Action:
            - 's3:GetObject'
          Effect: Allow
          Resource: !Join
            - ''
            - - 'arn:aws:s3:::'
              - !Ref RepairCostBucket
              - /*
          Principal: 
            CanonicalUser: !GetAtt RepairCostDistributionOai.S3CanonicalUserId

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn: RepairCostBucket
    Properties:
      DistributionConfig:
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
            - PUT
            - POST
            - DELETE
            - PATCH
            - OPTIONS
          CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
          OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3
          TargetOriginId: InferenceContainerOrigin
          ViewerProtocolPolicy: redirect-to-https
        Enabled: true
        HttpVersion: http2
        IPV6Enabled: true
        Origins:
          - Id: InferenceContainerOrigin
            DomainName: !GetAtt InferenceALB.DNSName
            CustomOriginConfig:
              HTTPPort: 80
              HTTPSPort: 443
              OriginProtocolPolicy: http-only
          - Id: S3Origin
            DomainName: !Join ['', [!Ref RepairCostBucket, '.s3.', !Ref 'AWS::Region', '.amazonaws.com']]
            S3OriginConfig: 
              OriginAccessIdentity: !Join ['', ['origin-access-identity/cloudfront/', !Ref RepairCostDistributionOai]]
        PriceClass: PriceClass_100
        ViewerCertificate:
          CloudFrontDefaultCertificate: true
          MinimumProtocolVersion: TLSv1.2_2021
        CacheBehaviors:
          - PathPattern: '/repair-data/*'
            AllowedMethods:
              - GET
              - HEAD
            ForwardedValues:
              QueryString: false
              Headers:
                - Origin
                - Access-Control-Request-Headers
                - Access-Control-Request-Method
            TargetOriginId: S3Origin
            ViewerProtocolPolicy: redirect-to-https
            MinTTL: 0
            DefaultTTL: 300
            MaxTTL: 1200

  BucketNameParameter:
      Type: AWS::SSM::Parameter
      Properties: 
        Name: "/car-repair/s3-bucket"
        Type: String  
        Value: !Ref RepairCostBucket

  S3SourceBucketNameParameter:
      Type: AWS::SSM::Parameter
      Properties: 
        Name: "/car-repair/s3-bucket-source"
        Type: String  
        Value: !Ref SourceS3Bucket

  CollectionName:
    Type: AWS::SSM::Parameter 
    Properties:
      Name: "/car-repair/collection-domain-name"
      Type: String
      Value: !GetAtt Collection.CollectionEndpoint

  DistributionDomainNameParameter:  
    Type: AWS::SSM::Parameter
    Properties:
      Name: "/car-repair/distribution-domain-name"
      Type: String
      Value: !GetAtt CloudFrontDistribution.DomainName

  SubnetIDParameter:  
    Type: AWS::SSM::Parameter
    Properties:
      Name: "/car-repair/subnet"
      Type: String
      Value: !Ref PublicSubnet1
      
  SGIDParameter:  
    Type: AWS::SSM::Parameter
    Properties:
      Name: "/car-repair/security-group"
      Type: String
      Value: !Ref SecurityGroup

Outputs:
  InferenceUIURL:
    Value: !GetAtt CloudFrontDistribution.DomainName
    Description: Domain name of the CloudFront distribution
  ECRRepository:
    Value: !Ref ECRRepository
    Description: Domain name of the CloudFront distribution
  S3BucketFrontEnd:
    Value: !Ref RepairCostBucket
    Description: Domain name of the CloudFront distribution
  SourceS3Bucket:
    Value: !Ref SourceS3Bucket
    Description: Domain name of the CloudFront distribution