AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Handson base infrastructure (EC2 + ALB + RDS / PostgreSQL Multi-AZ 2-tier).
  Equivalent to the EC2 hands-on. Creates VPC, subnets, NAT, ALB, 2x EC2
  (Nginx + Node.js) and a Multi-AZ RDS, then auto-initializes sample data.

Parameters:
  DBPassword:
    Type: String
    NoEcho: true
    Default: AdminPass123!
    MinLength: 8
    Description: RDS master password (master username is 'admin').
  InstanceType:
    Type: String
    Default: t2.micro
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

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 }]
  WebApSubnet1:
    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-webap-1a }]
  WebApSubnet2:
    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-webap-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-webap }]
  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 WebApSubnet1 }
  PrivateAssoc2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: { RouteTableId: !Ref PrivateRT, SubnetId: !Ref WebApSubnet2 }

  # ---------- 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 }]
  WebApSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: WebAP server security group
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 80, ToPort: 80, SourceSecurityGroupId: !Ref AlbSG }
      Tags: [{ Key: Name, Value: sg-webap }]
  RdsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: RDS PostgreSQL security group
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 5432, ToPort: 5432, SourceSecurityGroupId: !Ref WebApSG }
      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

  # ---------- IAM (SSM) ----------
  InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: handson-ec2-ssm-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: { Service: ec2.amazonaws.com }
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles: [!Ref InstanceRole]

  # ---------- EC2 WebAP x2 ----------
  WebAp1:
    Type: AWS::EC2::Instance
    DependsOn: [RDS, PrivateRoute]
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: !Ref InstanceType
      SubnetId: !Ref WebApSubnet1
      SecurityGroupIds: [!Ref WebApSG]
      IamInstanceProfile: !Ref InstanceProfile
      Tags: [{ Key: Name, Value: handson-webap-1a }]
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          dnf install -y nginx nodejs npm postgresql16
          systemctl enable --now nginx
          mkdir -p /home/ec2-user/app && cd /home/ec2-user/app
          npm init -y
          npm install express pg
          cat > app.js <<'APP'
          const express=require('express');const {Pool}=require('pg');const os=require('os');
          const app=express();
          const pool=new Pool({host:process.env.DB_HOST,port:5432,user:'admin',password:process.env.DB_PASS,database:'handson_db',ssl:{rejectUnauthorized:false},max:10});
          app.get('/health',(q,r)=>r.json({status:'ok',server:os.hostname()}));
          app.get('/',(q,r)=>r.json({message:'WebAP server is running!',server:os.hostname(),tier:'webap'}));
          app.get('/users',async(q,r)=>{try{const x=await pool.query('SELECT * FROM users ORDER BY id');r.json({count:x.rowCount,users:x.rows,server:os.hostname()});}catch(e){r.status(500).json({error:e.message});}});
          app.listen(8080,'127.0.0.1',()=>console.log('up'));
          APP
          export DB_HOST=${RDS.Endpoint.Address}
          export DB_PASS=${DBPassword}
          npm install -g pm2
          pm2 start app.js --name webap-app
          pm2 save
          cat > /etc/nginx/conf.d/webap.conf <<'NGINX'
          server {
            listen 80; server_name _;
            location /health { proxy_pass http://127.0.0.1:8080/health; access_log off; }
            location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
          }
          NGINX
          nginx -t && systemctl reload nginx
          export PGPASSWORD=${DBPassword}
          psql -h ${RDS.Endpoint.Address} -U admin -d handson_db -c "CREATE TABLE IF NOT EXISTS users(id SERIAL PRIMARY KEY,name VARCHAR(100) NOT NULL,email VARCHAR(200) NOT NULL UNIQUE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);"
          psql -h ${RDS.Endpoint.Address} -U admin -d handson_db -c "INSERT INTO users(name,email) SELECT v.n,v.e FROM (VALUES ('田中 太郎','taro@example.com'),('鈴木 花子','hanako@example.com'),('佐藤 一郎','ichiro@example.com')) AS v(n,e) WHERE NOT EXISTS (SELECT 1 FROM users);"

  WebAp2:
    Type: AWS::EC2::Instance
    DependsOn: [RDS, PrivateRoute]
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: !Ref InstanceType
      SubnetId: !Ref WebApSubnet2
      SecurityGroupIds: [!Ref WebApSG]
      IamInstanceProfile: !Ref InstanceProfile
      Tags: [{ Key: Name, Value: handson-webap-1c }]
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          dnf install -y nginx nodejs npm postgresql16
          systemctl enable --now nginx
          mkdir -p /home/ec2-user/app && cd /home/ec2-user/app
          npm init -y
          npm install express pg
          cat > app.js <<'APP'
          const express=require('express');const {Pool}=require('pg');const os=require('os');
          const app=express();
          const pool=new Pool({host:process.env.DB_HOST,port:5432,user:'admin',password:process.env.DB_PASS,database:'handson_db',ssl:{rejectUnauthorized:false},max:10});
          app.get('/health',(q,r)=>r.json({status:'ok',server:os.hostname()}));
          app.get('/',(q,r)=>r.json({message:'WebAP server is running!',server:os.hostname(),tier:'webap'}));
          app.get('/users',async(q,r)=>{try{const x=await pool.query('SELECT * FROM users ORDER BY id');r.json({count:x.rowCount,users:x.rows,server:os.hostname()});}catch(e){r.status(500).json({error:e.message});}});
          app.listen(8080,'127.0.0.1',()=>console.log('up'));
          APP
          export DB_HOST=${RDS.Endpoint.Address}
          export DB_PASS=${DBPassword}
          npm install -g pm2
          pm2 start app.js --name webap-app
          pm2 save
          cat > /etc/nginx/conf.d/webap.conf <<'NGINX'
          server {
            listen 80; server_name _;
            location /health { proxy_pass http://127.0.0.1:8080/health; access_log off; }
            location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
          }
          NGINX
          nginx -t && systemctl reload nginx

  # ---------- ALB ----------
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: handson-tg-webap
      VpcId: !Ref VPC
      Protocol: HTTP
      Port: 80
      TargetType: instance
      HealthCheckProtocol: HTTP
      HealthCheckPath: /health
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
      Targets:
        - { Id: !Ref WebAp1 }
        - { Id: !Ref WebAp2 }
  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 }

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