AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Handson base infrastructure (ECS Fargate + RDS / PostgreSQL Multi-AZ 2-tier).
  Equivalent to the ECS Fargate hands-on. The container image must be pushed to
  ECR beforehand and its URI passed via the ImageUri parameter.

Parameters:
  DBPassword:
    Type: String
    NoEcho: true
    Default: AdminPass123!
    MinLength: 8
    Description: RDS master password (master username is 'admin').
  ImageUri:
    Type: String
    Description: >
      ECR image URI pushed beforehand, e.g.
      123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/handson-ecr:latest

Resources:

  # ---------- Network ----------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags: [{ Key: Name, Value: handson-vpc }]

  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: [{ Key: Name, Value: handson-igw }]
  IGWAttach:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags: [{ Key: Name, Value: handson-subnet-public-1a }]
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: !Select [1, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags: [{ Key: Name, Value: handson-subnet-public-1c }]
  EcsSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.3.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags: [{ Key: Name, Value: handson-subnet-private-ecs-1a }]
  EcsSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.4.0/24
      AvailabilityZone: !Select [1, !GetAZs '']
      Tags: [{ Key: Name, Value: handson-subnet-private-ecs-1c }]
  DbSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.5.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags: [{ Key: Name, Value: handson-subnet-private-db-1a }]
  DbSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.6.0/24
      AvailabilityZone: !Select [1, !GetAZs '']
      Tags: [{ Key: Name, Value: handson-subnet-private-db-1c }]

  EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags: [{ Key: Name, Value: handson-natgw }]

  PublicRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags: [{ Key: Name, Value: handson-rt-public }]
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: IGWAttach
    Properties:
      RouteTableId: !Ref PublicRT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW
  PublicAssoc1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: { RouteTableId: !Ref PublicRT, SubnetId: !Ref PublicSubnet1 }
  PublicAssoc2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: { RouteTableId: !Ref PublicRT, SubnetId: !Ref PublicSubnet2 }

  PrivateRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags: [{ Key: Name, Value: handson-rt-private-ecs }]
  PrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRT
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway
  PrivateAssoc1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: { RouteTableId: !Ref PrivateRT, SubnetId: !Ref EcsSubnet1 }
  PrivateAssoc2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: { RouteTableId: !Ref PrivateRT, SubnetId: !Ref EcsSubnet2 }

  # ---------- Security Groups ----------
  AlbSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ALB security group
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 80,  ToPort: 80,  CidrIp: 0.0.0.0/0 }
        - { IpProtocol: tcp, FromPort: 443, ToPort: 443, CidrIp: 0.0.0.0/0 }
      Tags: [{ Key: Name, Value: sg-alb }]
  EcsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ECS Fargate task security group
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 8080, ToPort: 8080, SourceSecurityGroupId: !Ref AlbSG }
      Tags: [{ Key: Name, Value: sg-ecs }]
  RdsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: RDS PostgreSQL security group
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 5432, ToPort: 5432, SourceSecurityGroupId: !Ref EcsSG }
      Tags: [{ Key: Name, Value: sg-rds }]

  # ---------- RDS ----------
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: DB subnet group for Multi-AZ RDS
      SubnetIds: [!Ref DbSubnet1, !Ref DbSubnet2]
      Tags: [{ Key: Name, Value: handson-db-subnet-group }]
  RDS:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      DBInstanceIdentifier: handson-rds
      Engine: postgres
      EngineVersion: '16'
      DBInstanceClass: db.t3.micro
      AllocatedStorage: '20'
      StorageType: gp2
      MultiAZ: true
      DBName: handson_db
      MasterUsername: admin
      MasterUserPassword: !Ref DBPassword
      DBSubnetGroupName: !Ref DBSubnetGroup
      VPCSecurityGroups: [!Ref RdsSG]
      PubliclyAccessible: false
      BackupRetentionPeriod: 0
      DeletionProtection: false

  # ---------- ECS ----------
  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: handson-ecs-task-execution-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: { Service: ecs-tasks.amazonaws.com }
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /ecs/handson
      RetentionInDays: 7

  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: handson-ecs-cluster

  TaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: handson-task-def
      Cpu: '256'
      Memory: '512'
      NetworkMode: awsvpc
      RequiresCompatibilities: [FARGATE]
      ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn
      ContainerDefinitions:
        - Name: handson-app
          Image: !Ref ImageUri
          Essential: true
          PortMappings:
            - { ContainerPort: 8080, Protocol: tcp }
          Environment:
            - { Name: DB_HOST, Value: !GetAtt RDS.Endpoint.Address }
            - { Name: DB_PORT, Value: '5432' }
            - { Name: DB_USER, Value: admin }
            - { Name: DB_PASS, Value: !Ref DBPassword }
            - { Name: DB_NAME, Value: handson_db }
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: handson

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: handson-tg-ecs
      VpcId: !Ref VPC
      Protocol: HTTP
      Port: 8080
      TargetType: ip
      HealthCheckProtocol: HTTP
      HealthCheckPath: /health
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: handson-alb
      Scheme: internet-facing
      Type: application
      Subnets: [!Ref PublicSubnet1, !Ref PublicSubnet2]
      SecurityGroups: [!Ref AlbSG]
  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - { Type: forward, TargetGroupArn: !Ref TargetGroup }

  Service:
    Type: AWS::ECS::Service
    DependsOn: [Listener, PrivateRoute, RDS]
    Properties:
      ServiceName: handson-ecs-service
      Cluster: !Ref Cluster
      TaskDefinition: !Ref TaskDef
      DesiredCount: 2
      LaunchType: FARGATE
      HealthCheckGracePeriodSeconds: 120
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          Subnets: [!Ref EcsSubnet1, !Ref EcsSubnet2]
          SecurityGroups: [!Ref EcsSG]
      LoadBalancers:
        - ContainerName: handson-app
          ContainerPort: 8080
          TargetGroupArn: !Ref TargetGroup

Outputs:
  ALBUrl:
    Description: Access URL (open in browser)
    Value: !Sub "http://${ALB.DNSName}"
  RDSEndpoint:
    Description: RDS endpoint address
    Value: !GetAtt RDS.Endpoint.Address
