AWSTemplateFormatVersion: '2010-09-09'
Description: Cloudformation template for Coder and AWS Modernization Workshop
Parameters:
  KubernetesVersion:
    Description: Kubernetes version
    Type: String
    Default: "1.32"

  CoderVersion:
    Description: Coder version
    Type: String
    Default: "2.29.0"

  EKSClusterName:
    Description: Name of EKS Cluster
    Type: String
    Default: "coder-aws-cluster"

  WorkerNodeInstanceType:
    Description: Worker Node cluster instances
    Type: String
    Default: "t3.large"

  Username:
    Type: String
    Description: Master username for the database
    Default: coder
  
  Password:
    Type: String
    Description: Master password for the database
    NoEcho: true
    MinLength: 8
    Default: coderworkshop
  
  DatabaseName:
    Type: String
    Description: Initial database name
    Default: coder

Resources:
################## PERMISSIONS AND ROLES #################
  WorkshopAdminRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: coder-and-aws-workshop-admin
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
                - codebuild.amazonaws.com
                - pods.eks.amazonaws.com
            Action:
              - sts:AssumeRole
              - sts:TagSession
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess
      Path: "/"

  coderLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName:
          Fn::Join:
          - ''
          - - coderLambdaPolicy-
            - Ref: AWS::Region
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
          - Effect: Allow
            Action:
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:*"
          - Effect: Allow
            Action:
            - cloudformation:DescribeStacks
            - cloudformation:DescribeStackEvents
            - cloudformation:DescribeStackResource
            - cloudformation:DescribeStackResources
            Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/*"
          - Effect: Allow
            Action:
            - ec2:DescribeInstances
            - ec2:DescribeInstanceTypeOfferings
            - ec2:AssociateIamInstanceProfile
            - ec2:ModifyInstanceAttribute
            - ec2:ReplaceIamInstanceProfileAssociation
            - ec2:DescribeIamInstanceProfileAssociations
            - ec2:DescribeVolumes
            - ec2:ModifyVolume
            - ec2:DescribeVolumesModifications
            - ec2:RebootInstances
            Resource: "*"
          - Effect: Allow
            Action:
            - iam:ListInstanceProfiles
            Resource: !Sub "arn:aws:iam::${AWS::AccountId}:instance-profile/*"
          - Effect: Allow
            Action:
            - iam:PassRole
            Resource: !GetAtt WorkshopAdminRole.Arn
          - Effect: Allow
            Action:
            - ssm:DescribeInstanceInformation
            - ssm:SendCommand
            - ssm:GetCommandInvocation
            - ssm:ListCommandInvocations
            - ssm:PutParameter
            - ssm:GetParameter
            Resource: "*"

  KMSSecretsKey:
    Type: AWS::KMS::Key
    Properties:
      Description: "key for EKS secrets encryption"
      Enabled: true
      EnableKeyRotation: true
      KeyPolicy:
        Version: '2012-10-17'
        Id: key-default-1
        Statement:
        - Sid: Enable IAM User Permissions
          Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
          Action:
            - kms:Create*
            - kms:Describe*
            - kms:Enable*
            - kms:List*
            - kms:Put*
            - kms:Update*
            - kms:Revoke*
            - kms:Disable*
            - kms:Get*
            - kms:Delete*
            - kms:ScheduleKeyDeletion
            - kms:CancelKeyDeletion
            - kms:GenerateDataKey
            - kms:Encrypt
            - kms:Decrypt
          Resource: '*'

################## INSTANCE PROFILE #####################

  WorkshopInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    DependsOn: WorkshopAdminRole
    Properties:
      Path: "/"
      InstanceProfileName: !Sub "coder-workshop-ec2-workspace-profile"
      Roles:
        - !Ref WorkshopAdminRole

################## VPC Setup #####################

  CoderVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 192.168.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub '${EKSClusterName}/VPC'
        - Key: alpha.eksctl.io/cluster-name
          Value: ${EKSClusterName}

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/InternetGateway

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref CoderVPC

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref CoderVPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 192.168.0.0/19
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/SubnetPublic1
        - Key: kubernetes.io/role/elb
          Value: '1'

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref CoderVPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 192.168.32.0/19
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/SubnetPublic2
        - Key: kubernetes.io/role/elb
          Value: '1'

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref CoderVPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 192.168.96.0/19
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/SubnetPrivate1
        - Key: kubernetes.io/role/internal-elb
          Value: '1'

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref CoderVPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 192.168.128.0/19
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/SubnetPrivate2
        - Key: kubernetes.io/role/internal-elb
          Value: '1'

  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value:  ${EKSClusterName}/NATIP

  NatGateway2EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value:  ${EKSClusterName}/NATIP

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/NATGateway

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/NATGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref CoderVPC
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/PublicRouteTable

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref CoderVPC
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/PrivateRouteTable1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref CoderVPC
      Tags:
        - Key: Name
          Value: ${EKSClusterName}/PrivateRouteTable2

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

################## Aurora PostgreSQL #####################

  AuroraSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Aurora PostgreSQL cluster
      VpcId: !Ref CoderVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 5432
          ToPort: 5432
          CidrIp: 192.168.0.0/16
          Description: Allow PostgreSQL access from VPC
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic
      Tags:
        - Key: Name
          Value: !Sub '${EKSClusterName}-aurora-sg'

  CoderAuroraSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupName: coderaurorasubnetgroup
      DBSubnetGroupDescription: "Subnet group for Coder' Aurora PostgreSQL - same as EKS cluster"
      SubnetIds: 
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2

  # Create Aurora PostgreSQL Serverless cluster
  CoderAuroraCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      DBClusterIdentifier: !Ref DatabaseName
      Engine: aurora-postgresql
      EngineVersion: '16.6'
      MasterUsername: !Ref Username
      MasterUserPassword: !Ref Password
      DatabaseName: !Ref DatabaseName
      Port: 5432
      DBSubnetGroupName: !Ref CoderAuroraSubnetGroup
      VpcSecurityGroupIds:
        - !Ref AuroraSecurityGroup
      BackupRetentionPeriod: 3
      StorageEncrypted: true
      ServerlessV2ScalingConfiguration:
        MinCapacity: 0.5
        MaxCapacity: 128.0

  # Create Aurora PostgreSQL instance
  CoderAuroraInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub '${DatabaseName}-instance'
      DBInstanceClass: db.serverless
      Engine: aurora-postgresql
      DBClusterIdentifier: !Ref CoderAuroraCluster
      PubliclyAccessible: false

################## EKS Bootstrap #####################

  BuildProject:
    DependsOn: [WorkshopInstanceProfile, CoderAuroraInstance]
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Sub CodeBuild-${AWS::StackName}
      ServiceRole: !GetAtt WorkshopAdminRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
          GroupName: !Sub "/aws/codebuild/CodeBuild-${AWS::StackName}"
          StreamName: build-log
      EncryptionKey: !GetAtt KMSSecretsKey.Arn
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
        EnvironmentVariables:
          - Name: WAIT_HANDLE_URL
            Value: !Ref EKSClusterWaitHandle
          - Name: KMS_ARN
            Value: !GetAtt KMSSecretsKey.Arn
          - Name: EKSClusterName
            Value: !Ref EKSClusterName
          - Name: AWSRegion
            Value: !Sub "${AWS::Region}"
          - Name: STACK_NAME
            Value: !Ref AWS::StackName
          - Name: KubernetesVersion
            Value: !Ref KubernetesVersion
          - Name: CoderVersion
            Value: !Ref CoderVersion
          - Name: WorkerNodeInstanceType
            Value: !Ref WorkerNodeInstanceType
          - Name: VpcId
            Value: !Ref CoderVPC
          - Name: PublicSubnet1
            Value: !Ref PublicSubnet1
          - Name: PublicSubnet2
            Value: !Ref PublicSubnet2
          - Name: PrivateSubnet1
            Value: !Ref PrivateSubnet1
          - Name: PrivateSubnet2
            Value: !Ref PrivateSubnet2
          - Name: Username
            Value: !Ref Username
          - Name: Password
            Value: !Ref Password
          - Name: DatabaseName
            Value: !Ref DatabaseName
          - Name: PostgreSQLClusterAddress
            Value: !GetAtt CoderAuroraCluster.Endpoint.Address
          - Name: PostgreSQLPort
            Value: !GetAtt CoderAuroraCluster.Endpoint.Port
          
      Source:
        Type: NO_SOURCE
        BuildSpec: |
          version: 0.2
          phases:
            install:
              runtime-versions:
                python: 3.12
              commands:
                - echo ">>> installed python 3.12"
                - export BUILD_SUCCESS=false
            pre_build:
              commands:
                - echo ">>> build cluster config with Region $AWSRegion"
                - |
                  cat << EOF > cluster-config.yaml
                  apiVersion: eksctl.io/v1alpha5
                  kind: ClusterConfig
                  metadata:
                    name: ${EKSClusterName}
                    region: ${AWSRegion}
                    version: "${KubernetesVersion}"
                  
                  # Proper Auto Mode configuration as per AWS docs
                  autoModeConfig:
                    enabled: true
                  
                  vpc:
                    id: ${VpcId}
                    subnets:
                      public:
                        us-west-2a:
                          id: ${PublicSubnet1}
                        us-west-2b:
                          id: ${PublicSubnet2}
                      private:
                        us-west-2a:
                          id: ${PrivateSubnet1}
                        us-west-2b:
                          id: ${PrivateSubnet2}
                  
                  addons:
                    - name: aws-ebs-csi-driver
                      version: latest  
                      
                  cloudWatch:
                    clusterLogging:
                      enableTypes: ["*"]
                  
                  iam:
                    withOIDC: true
                  
                  secretsEncryption:
                    keyARN: ${KMS_ARN}
                  EOF
                - cat cluster-config.yaml
                - pip3 install --upgrade --user awscli
                - curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
                - chmod +x ./kubectl
                - curl --silent --fail --retry 5 --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
                - mv -v /tmp/eksctl /usr/local/bin
                - eksctl version
                - curl --silent --fail --retry 5 --location "https://get.helm.sh/helm-v3.16.4-linux-amd64.tar.gz" | tar xz -C /tmp
                - mv -v /tmp/linux-amd64/helm /usr/local/bin
                - helm version
                - export PATH=$PWD/:$PATH
            build:
              commands:
                - echo ">>> creating EKS cluster in region $AWSRegion with Auto Mode"
                - |
                  if eksctl create cluster -f cluster-config.yaml; then
                    export BUILD_SUCCESS=true
                    echo "EKS cluster creation successful"
                  else
                    echo "EKS cluster creation failed"
                    if [ -n "$WAIT_HANDLE_URL" ]; then
                      curl -X PUT -H 'Content-Type:' --data-binary '{"Status":"FAILURE","Reason":"EKS Cluster Creation Failed","UniqueId":"EKSCluster","Data":"EKS Cluster Creation Failed"}' "$WAIT_HANDLE_URL"
                      echo "Sent FAILURE signal to CloudFormation"
                    fi
                    exit 1
                  fi
            post_build:
              commands:
                - echo ">>> Creating post-build script"
                - |
                  cat > post_build.sh << 'POST_EOF'
                  #!/bin/bash
                  set -e
                  
                  if [ "$BUILD_SUCCESS" = "true" ]; then
                    echo "EKS cluster creation successful"
                    
                    aws eks update-kubeconfig --region ${AWSRegion} --name ${EKSClusterName}
                    
                    ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
                    PARTICIPANT_ROLE_ARN="arn:aws:iam::$ACCOUNT_ID:role/WSParticipantRole"
                    
                    curl -o storageclass.yaml https://codermktplc-assets.s3.us-east-1.amazonaws.com/community-edition/storageclass.yaml
                    kubectl apply -f storageclass.yaml
                    
                    kubectl create configmap aws-auth -n kube-system --from-literal=mapRoles="[{\"rolearn\":\"$PARTICIPANT_ROLE_ARN\",\"username\":\"workshop-user\",\"groups\":[\"system:masters\"]}]" --dry-run=client -o yaml | kubectl apply -f -
                    kubectl create namespace coder
                    
                    PostgreSQLConnectionURL="postgresql://${Username}:${Password}@${PostgreSQLClusterAddress}:${PostgreSQLPort}/${DatabaseName}"
                    kubectl create secret generic coder-db-url -n coder --from-literal=url="$PostgreSQLConnectionURL"
                    
                    curl -o coder-core-values-v2.yaml https://codermktplc-assets.s3.us-east-1.amazonaws.com/community-edition/coder-core-values-v2.yaml
                    
                    helm repo add coder-v2 https://helm.coder.com/v2
                    helm repo update
                    helm install coder coder-v2/coder --namespace coder --values coder-core-values-v2.yaml --version "${CoderVersion}"
                    
                    for i in {1..30}; do
                      kubectl get service coder -n coder -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' > lb_hostname.txt 2>/dev/null || echo "" > lb_hostname.txt
                      CODER_LB_HOSTNAME=$(cat lb_hostname.txt | tr -d '\n')
                      if [ -n "$CODER_LB_HOSTNAME" ]; then
                        echo "Load balancer ready: $CODER_LB_HOSTNAME"
                        break
                      fi
                      echo "Waiting for load balancer... ($i/30)"
                      sleep 30
                    done

                    aws eks create-pod-identity-association \
                    --cluster-name coder-aws-cluster \
                    --namespace coder \
                    --service-account coder \
                    --role-arn arn:aws:iam::$ACCOUNT_ID:role/coder-and-aws-workshop-admin
                    
                    if [ -n "$CODER_LB_HOSTNAME" ]; then
                      curl -o cloudfront-config.json https://codermktplc-assets.s3.us-east-1.amazonaws.com/community-edition/cloudfront-config.json
                      sed -i "s|CODER_LB_HOSTNAME_PLACEHOLDER|$CODER_LB_HOSTNAME|g" cloudfront-config.json
 
                      # Create the CloudFront distribution
                      CLOUDFRONT_RESULT=$(aws cloudfront create-distribution --distribution-config file://cloudfront-config.json)
                      echo "$CLOUDFRONT_RESULT" > cloudfront-result.json

                      # Extract distribution information
                      DISTRIBUTION_ID=$(echo "$CLOUDFRONT_RESULT" | jq -r '.Distribution.Id')
                      CLOUDFRONT_DOMAIN=$(echo "$CLOUDFRONT_RESULT" | jq -r '.Distribution.DomainName')

                      echo "CloudFront Distribution ID: $DISTRIBUTION_ID"
                      echo "CloudFront Domain: $CLOUDFRONT_DOMAIN"

                      # Check deployment status
                      echo "Waiting for CloudFront distribution to deploy..."
                      while true; do
                        STATUS=$(aws cloudfront get-distribution --id $DISTRIBUTION_ID --query 'Distribution.Status' --output text)
                        echo "Distribution Status: $STATUS"
                        if [ "$STATUS" = "Deployed" ]; then
                          echo "CloudFront distribution is ready!"
                          break
                        fi
                        echo "Waiting 60 seconds before checking again..."
                        sleep 60
                      done

                      # Construct the new URLs
                      CODER_CLOUDFRONT_URL="https://$CLOUDFRONT_DOMAIN"
                      CODER_CLOUDFRONT_WILDCARD="*.$CLOUDFRONT_DOMAIN"

                      echo "New Coder Access URL: $CODER_CLOUDFRONT_URL"
                      echo "New Coder Wildcard URL: $CODER_CLOUDFRONT_WILDCARD"

                       # Store hostname in SSM for CloudFormation retrieval
                      aws ssm put-parameter --name "/eks/${EKSClusterName}/coder-cloudfront-url" --value "$CODER_CLOUDFRONT_URL" --type "String" --overwrite

                      sed -i "s|https://coder.example.com|$CODER_CLOUDFRONT_URL|g" coder-core-values-v2.yaml
                      sed -i "s|\*.coder.example.com|$CODER_CLOUDFRONT_WILDCARD|g" coder-core-values-v2.yaml
                      helm upgrade coder coder-v2/coder --namespace coder --values coder-core-values-v2.yaml --version "${CoderVersion}"
                    fi
                    
                    aws ssm put-parameter --name "/eks/${EKSClusterName}/cluster-name" --value "${EKSClusterName}" --type "String" --overwrite
                    aws ssm put-parameter --name "/eks/${EKSClusterName}/region" --value "${AWSRegion}" --type "String" --overwrite
                  fi
                  POST_EOF
                - chmod +x post_build.sh
                - ./post_build.sh
                - echo ">>> Signaling CloudFormation"
                - |
                  if [ "$BUILD_SUCCESS" = "true" ]; then
                    curl -X PUT -H 'Content-Type:' --data-binary '{"Status":"SUCCESS","Reason":"EKS Cluster Created","UniqueId":"EKSCluster","Data":"Deployment Complete"}' "$WAIT_HANDLE_URL"
                  else
                    curl -X PUT -H 'Content-Type:' --data-binary '{"Status":"FAILURE","Reason":"EKS Cluster Creation Failed","UniqueId":"EKSCluster","Data":"Deployment Failed"}' "$WAIT_HANDLE_URL"
                  fi

  GetCoderCloudFrontURLLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: SSMReadAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/eks/${EKSClusterName}/coder-cloudfront-url"

  GetCoderCloudFrontURLLambda:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.12
      Handler: index.handler
      Role: !GetAtt GetCoderCloudFrontURLLambdaRole.Arn
      Timeout: 60
      Code:
        ZipFile: |
          import boto3
          import json
          import urllib.request
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def handler(event, context):
              logger.info(f'Event: {json.dumps(event)}')
              
              response_data = {}
              
              try:
                  if event['RequestType'] == 'Delete':
                      response_data['Status'] = 'SUCCESS'
                  else:
                      ssm = boto3.client('ssm')
                      cluster_name = event['ResourceProperties']['ClusterName']
                      
                      try:
                          response = ssm.get_parameter(
                              Name=f'/eks/{cluster_name}/coder-cloudfront-url'
                          )
                          cfurl = response['Parameter']['Value']
                          response_data['CoderCloudFrontURL'] = cfurl
                          logger.info(f'Retrieved Cloud Front URL: {cfurl}')
                      except ssm.exceptions.ParameterNotFound:
                          logger.warning('Parameter not found, returning empty Cloud Front URL')
                          response_data['CoderCloudFrontURL'] = ''
                      
                      response_data['Status'] = 'SUCCESS'
                      
              except Exception as e:
                  logger.error(f'Error: {str(e)}')
                  response_data['Status'] = 'FAILED'
                  response_data['Reason'] = str(e)
              
              send_response(event, context, response_data)
              return response_data

          def send_response(event, context, response_data):
              response_body = json.dumps({
                  'Status': response_data['Status'],
                  'Reason': response_data.get('Reason', 'See CloudWatch logs'),
                  'PhysicalResourceId': context.log_stream_name,
                  'StackId': event['StackId'],
                  'RequestId': event['RequestId'],
                  'LogicalResourceId': event['LogicalResourceId'],
                  'Data': response_data
              })
              
              req = urllib.request.Request(
                  url=event['ResponseURL'],
                  data=response_body.encode('utf-8'),
                  headers={'content-type': '', 'content-length': str(len(response_body))},
                  method='PUT'
              )
              
              with urllib.request.urlopen(req) as response:
                  logger.info(f'Response sent: {response.getcode()}')

  CoderCloudFrontURLRetriever:
    Type: Custom::CoderCloudFrontURL
    DependsOn: EKSClusterWaitCondition
    Properties:
      ServiceToken: !GetAtt GetCoderCloudFrontURLLambda.Arn
      ClusterName: !Ref EKSClusterName

  TriggerBuildLambdaIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
                - codebuild.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
        - arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess
      Policies:
        - PolicyName: !Sub IAMPolicy-${AWS::StackName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                    - codebuild:StartBuild
                    - codebuild:BatchGetBuilds
                    - codebuild:ListBuildsForProject
                    - codebuild:BatchGetProjects
                    - codebuild:StopBuild
                Resource: !GetAtt BuildProject.Arn

  TriggerBuildLambda:
    Type: AWS::Lambda::Function
    Properties:
      Description: function to trigger CodeBuild project
      Handler: index.handler
      Role: !GetAtt TriggerBuildLambdaIamRole.Arn
      Runtime: python3.12
      Timeout: 60
      ReservedConcurrentExecutions: 5
      Code:
        ZipFile: |
          import boto3
          import logging
          import json
          import urllib.request
          import time

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          codebuild_client = boto3.client('codebuild')

          def handler(event, context):
              logger.info('Incoming Event: {0}'.format(event))
              response = {}
              response['PhysicalResourceId'] = 'codebuildtrigger-' + str(int(time.time()))
              response['StackId'] = event['StackId']
              response['RequestId'] = event['RequestId']
              response['LogicalResourceId'] = event['LogicalResourceId']

              if event['RequestType'] == 'Delete':
                  logger.info('Nothing to do. Request Type : {0}'.format(event['RequestType']))
                  response['Status'] = 'SUCCESS'
                  send_cfn_response(event, context, response['Status'], response)
              elif event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
                  try:
                      # Log the environment variables we're passing to make debugging easier
                      wait_handle_url = event['ResourceProperties']['WaitHandleUrl']
                      logger.info(f"WaitHandleUrl being passed to CodeBuild: {wait_handle_url}")
                      
                      build = codebuild_client.start_build(
                          projectName=event['ResourceProperties']['CodebuildProjectName'],
                          environmentVariablesOverride=[
                              {
                                  'name': 'WAIT_HANDLE_URL',
                                  'value': wait_handle_url,
                                  'type': 'PLAINTEXT'
                              }
                          ]
                      )
                      logger.info(f"CodeBuild started with build ID: {build['build']['id']}")
                      response['Status'] = 'SUCCESS'
                      send_cfn_response(event, context, response['Status'], response)
                  except Exception as e:
                      logger.error('Error: {0}'.format(str(e)))
                      response['Status'] = 'FAILED'
                      response['Reason'] = str(e)
                      send_cfn_response(event, context, response['Status'], response)

          def send_cfn_response(event, context, response_status, response_data):
              response_body = json.dumps({
                  'Status': response_status,
                  'Reason': response_data.get('Reason', 'See the details in CloudWatch Log Stream: ' + context.log_stream_name),
                  'PhysicalResourceId': response_data.get('PhysicalResourceId', context.log_stream_name),
                  'StackId': event['StackId'],
                  'RequestId': event['RequestId'],
                  'LogicalResourceId': event['LogicalResourceId'],
                  'Data': response_data
              })

              headers = {
                  'content-type': '',
                  'content-length': str(len(response_body))
              }

              logger.info(f"Sending response to: {event['ResponseURL']}")
              
              req = urllib.request.Request(url=event['ResponseURL'], data=response_body.encode('utf-8'), headers=headers, method='PUT')

              try:
                  with urllib.request.urlopen(req) as response:
                      logger.info(f"Response sent. Status code: {response.getcode()}")
                      logger.info(f"Status message: {response.msg}")
              except Exception as e:
                  logger.error(f"Failed to send response: {str(e)}")
                
  CustomTriggerBuild:
    Type: Custom::TriggerBuild
    DependsOn: BuildProject
    Properties:
      ServiceToken: !GetAtt TriggerBuildLambda.Arn
      CodebuildProjectName: !Ref BuildProject
      WaitHandleUrl: !Ref EKSClusterWaitHandle

  EKSClusterWaitCondition:
    Type: AWS::CloudFormation::WaitCondition
    Properties:
      Handle: !Ref EKSClusterWaitHandle
      Timeout: '1800'  # 30 minutes
      Count: 1

  EKSClusterWaitHandle:
    Type: AWS::CloudFormation::WaitConditionHandle

Outputs:
  EKSClusterName:
    Description: EKS Cluster Name
    Value: !Ref EKSClusterName
  
  CoderURL:
    Description: Coder Cloud Front URL
    Value: !GetAtt CoderCloudFrontURLRetriever.CoderCloudFrontURL
  
  PostgreSQLConnectionURLWithoutPassword:
    Description: PostgreSQL Connection URL (without password)
    Value: !Sub 'postgresql://${Username}@${CoderAuroraCluster.Endpoint.Address}:${CoderAuroraCluster.Endpoint.Port}/${DatabaseName}'

  CloudFormationStack:
    Description: Stack name for reference in scripts
    Value: !Ref AWS::StackName
