---
AWSTemplateFormatVersion: "2010-09-09"
Description: "Buildkite stack v6.1.0"

# The Buildkite Elastic CI Stack for AWS gives you a private,
# autoscaling Buildkite Agent cluster. Use it to parallelize
# large test suites across thousands of nodes, run tests and
# deployments for Linux or Windows based services and apps,
# or run AWS ops tasks.
#
# To gain a better understanding of how Elastic CI Stack works
# and how to use it most effectively and securely, check out
# the following resources:
#
# * Elastic CI Stack for AWS Overview: https://buildkite.com/docs/agent/v3/elastic_ci_aws
# * Elastic CI Stack for AWS Tutorial: https://buildkite.com/docs/tutorials/elastic-ci-stack-aws
# * Running Buildkite Agent on AWS: https://buildkite.com/docs/agent/v3/aws
# * GitHub Repo for Elastic CI Stack: https://github.com/buildkite/elastic-ci-stack-for-aws
# * Template Parameters for Elastic CI Stack for AWS: https://buildkite.com/docs/agent/v3/elastic-ci-aws/parameters
# * Using AWS Secrets Manager: https://buildkite.com/docs/agent/v3/aws/secrets-manager
# * VPC Design: https://buildkite.com/docs/agent/v3/aws/vpc
# * CloudFormation Service Role: https://buildkite.com/docs/agent/v3/elastic-ci-aws/cloudformation-service-role

Transform: AWS::Serverless-2016-10-31

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Buildkite Configuration
        Parameters:
        - BuildkiteAgentTokenParameterStorePath
        - BuildkiteAgentTokenParameterStoreKMSKey
        - BuildkiteAgentToken
        - BuildkiteQueue

      - Label:
          default: Advanced Buildkite Configuration
        Parameters:
        - BuildkiteAgentRelease
        - BuildkiteAgentTags
        - BuildkiteAgentTimestampLines
        - BuildkiteAgentExperiments
        - BuildkiteAgentEnableGitMirrors
        - BuildkiteAgentTracingBackend
        - BuildkiteTerminateInstanceAfterJob
        - BuildkiteAdditionalSudoPermissions
        - BuildkiteWindowsAdministrator
        - BuildkiteAgentScalerServerlessARN
        - BuildkiteAgentScalerVersion

      - Label:
          default: Network Configuration
        Parameters:
        - VpcId
        - Subnets
        - AvailabilityZones
        - SecurityGroupIds
        - AssociatePublicIpAddress

      - Label:
          default: Instance Configuration
        Parameters:
        - ImageId
        - ImageIdParameter
        - InstanceOperatingSystem
        - InstanceTypes
        - EnableInstanceStorage
        - AgentsPerInstance
        - KeyName
        - SecretsBucket
        - SecretsBucketRegion
        - SecretsBucketEncryption
        - ArtifactsBucket
        - AuthorizedUsersUrl
        - BootstrapScriptUrl
        - AgentEnvFileUrl
        - RootVolumeSize
        - RootVolumeName
        - RootVolumeType
        - RootVolumeEncrypted
        - ManagedPolicyARNs
        - InstanceRoleName
        - InstanceRolePermissionsBoundaryARN
        - IMDSv2Tokens
        - EnableDetailedMonitoring
        - InstanceName

      - Label:
          default: Auto-scaling Configuration
        Parameters:
        - MinSize
        - MaxSize
        - OnDemandPercentage
        - SpotAllocationStrategy
        - ScaleOutFactor
        - ScaleInIdlePeriod
        - ScaleOutForWaitingJobs
        - InstanceCreationTimeout
        - ScalerEventScheduleRate
        - ScalerMinPollInterval

      - Label:
          default: Cost Allocation Configuration
        Parameters:
        - EnableCostAllocationTags
        - CostAllocationTagName
        - CostAllocationTagValue

      - Label:
          default: Docker Daemon Configuration
        Parameters:
        - EnableDockerUserNamespaceRemap
        - EnableDockerExperimental

      - Label:
          default: Docker Registry Configuration
        Parameters:
        - ECRAccessPolicy

      - Label:
          default: Plugin Configuration
        Parameters:
        - EnableSecretsPlugin
        - EnableECRPlugin
        - EnableDockerLoginPlugin

Parameters:
  KeyName:
    Description: Optional - SSH keypair used to access the buildkite instances via ec2_user, setting this will enable SSH ingress
    Type: String
    Default: ""

  BuildkiteAgentRelease:
    Type: String
    AllowedValues:
      - stable
      - beta
      - edge
    Default: "stable"

  BuildkiteAgentToken:
    Description: Buildkite agent registration token. Or, preload it into SSM Parameter Store and use BuildkiteAgentTokenParameterStorePath for secure environments.
    Type: String
    NoEcho: true
    Default: ""

  BuildkiteAgentTokenParameterStorePath:
    Description: Existing SSM Parameter Store path to the Buildkite agent registration token (takes precedence over BuildkiteAgentToken). Expects a leading slash ('/').
    Type: String
    Default: ""
    AllowedPattern: "^$|^/[a-zA-Z0-9_.\\-/]+$"
    ConstraintDescription: "Expects a leading forward slash"

  BuildkiteAgentTokenParameterStoreKMSKey:
    Description: AWS KMS key ID used to encrypt the SSM parameter (if encrypted)
    Type: String
    Default: ""

  BuildkiteAgentTags:
    Description: Additional tags separated by commas to provide to the agent. E.g os=linux,llamas=always
    Type: String
    Default: ""

  BuildkiteAgentTimestampLines:
    Description: Set to true to prepend timestamps to every line of output
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  BuildkiteAgentExperiments:
    Description: Agent experiments to enable, comma delimited. See https://github.com/buildkite/agent/blob/master/EXPERIMENTS.md.
    Type: String
    Default: ""

  BuildkiteAgentScalerServerlessARN:
    Description: ARN of the Serverless Application Repository that hosts the version of buildkite-agent-scaler to run. This needs to be public or shared with your AWS account. See https://aws.amazon.com/serverless/serverlessrepo/.
    Type: String
    Default: arn:aws:serverlessrepo:us-east-1:172840064832:applications/buildkite-agent-scaler

  BuildkiteAgentScalerVersion:
    Description: Version of the buildkite-agent-scaler to use
    Type: String
    Default: "1.5.0"

  BuildkiteAgentTracingBackend:
    Description: The tracing backend to use for CI tracing. See https://buildkite.com/docs/agent/v3/tracing
    Type: String
    AllowedValues:
      - ""
      - "datadog"
      - "opentelemetry"
    Default: ""

  BuildkiteTerminateInstanceAfterJob:
    Description: Set to "true" to terminate the instance after a job has completed.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  BuildkiteAdditionalSudoPermissions:
    Description: Optional - Comma separated list of commands to allow the buildkite-agent user to run using sudo. Note that the commands should be fully qualified paths to executables.
    Type: String
    Default: ""

  BuildkiteWindowsAdministrator:
    Description: Set to "true" to add the local "buildkite-agent" user account to the local Windows Administrator group.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  BuildkiteQueue:
    Description: Queue name that agents will use, targeted in pipeline steps using "queue={value}"
    Type: String
    Default: default
    MinLength: 1

  AgentsPerInstance:
    Description: Number of Buildkite agents to run on each instance
    Type: Number
    Default: 1
    MinValue: 1

  SecretsBucket:
    Description: Optional - Name of an existing S3 bucket containing pipeline secrets (Created if left blank)
    Type: String
    Default: ""

  SecretsBucketRegion:
    Description: Optional - Region for the SecretsBucket. If blank the bucket's region is dynamically discovered.
    Type: String
    Default: ""

  SecretsBucketEncryption:
    Description: Indicates whether the SecretsBucket should enforce encryption at rest and in transit
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  ArtifactsBucket:
    Description: Optional - Name of an existing S3 bucket for build artifact storage
    Type: String
    Default: ""

  BootstrapScriptUrl:
    Description: Optional - HTTPS or S3 URL for a script to run on each instance during boot
    Type: String
    Default: ""

  AgentEnvFileUrl:
    Description: Optional - HTTPS or S3 URL for a list of environment variables to propagate into the agent's execution environment. Note that these environment variables **will not** be passed into builds running on the agent, only to the agent process itself.
    Type: String
    Default: ""

  AuthorizedUsersUrl:
    Description: Optional - HTTPS or S3 URL to periodically download ssh authorized_keys from, setting this will enable SSH ingress. authorized_keys are applied to ec2_user
    Type: String
    Default: ""

  VpcId:
    Type: String
    Description: Optional - Id of an existing VPC to launch instances into. Leave blank to have a new VPC created
    Default: ""

  Subnets:
    Type: CommaDelimitedList
    Description: Optional - Comma separated list of two existing VPC subnet ids where EC2 instances will run. Required if setting VpcId.
    Default: ""

  AvailabilityZones:
    Type: CommaDelimitedList
    Description: Optional - Comma separated list of AZs that subnets are created in (if Subnets parameter is not specified)
    Default: ""

  InstanceTypes:
    Description: Comma-separated list with 1-4 instance types. The order is a prioritized preference for launching OnDemand instances, and a non-prioritized list of types to consider for Spot Instances (where used).
    Type: String
    Default: t3.large
    MinLength: 1
    AllowedPattern: "^[\\w\\.]+(,[\\w\\.]*){0,3}$"
    ConstraintDescription: "must contain 1-4 instance types separated by commas. No space before/after the comma."

  MaxSize:
    Description: Maximum number of instances
    Type: Number
    Default: 10
    MinValue: 0

  MinSize:
    Description: Minimum number of instances
    Type: Number
    Default: 0
    MinValue: 0

  ScalerEventScheduleRate:
    Description: How often the event schedule for buildkite-agent-scaler is triggered (in minutes)
    Type: String
    Default: "1"
  
  ScalerMinPollInterval:
    Description: Minimum interval at which the auto scaler should poll the AWS API
    Type: String
    Default: "10s"

  OnDemandPercentage:
    Description: Percentage of total instances that should launch as OnDemand. Default is 100% OnDemand - reduce this to use some Spot Instances when they're available and cheaper than the OnDemand price. A value of 70 means 70% OnDemand and 30% Spot Instances.
    Type: Number
    Default: 100
    MinValue: 0
    MaxValue: 100

  SpotAllocationStrategy:
    Description: The strategy for allocating Spot Instances when launching or replacing instances. If choosing `capacity-optimized-prioritized`, the order you specify in InstanceTypes will be the priority.
    Type: String
    Default: capacity-optimized
    AllowedValues:
      - price-capacity-optimized
      - capacity-optimized
      - lowest-price
      - capacity-optimized-prioritized

  ScaleOutFactor:
    Description: A decimal factor to apply to scale out changes to speed up or slow down scale-out
    Type: Number
    Default: 1.0

  ScaleInIdlePeriod:
    Description: Number of seconds an agent must be idle before terminating
    Type: Number
    Default: 600

  ScaleOutForWaitingJobs:
    Type: String
    Description: Whether to scale-out for steps behind wait steps. Make sure you have a long enough idle period!
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  InstanceCreationTimeout:
    Description: Timeout period for Autoscaling Group Creation Policy
    Type: String
    Default: ""

  RootVolumeSize:
    Description: Size of each instance's root EBS volume (in GB)
    Type: Number
    Default: 250
    MinValue: 10

  RootVolumeName:
    Description: Name of the root block device for your AMI
    Type: String
    Default: ""

  RootVolumeType:
    Description: Type of root volume to use
    Type: String
    Default: "gp3"

  RootVolumeEncrypted:
    Description: Indicates whether the EBS volume is encrypted
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  SecurityGroupIds:
    Type: String
    Description: Optional - Comma separated list of security group ids to assign to instances
    Default: ""

  ImageId:
    Type: String
    Description: Optional - Custom AMI to use for instances (must be based on the stack's AMI)
    Default: ""

  ImageIdParameter:
    Type: String
    Description: Optional - Custom AMI SSM Parameter to use for instances (must be based on the stack's AMI)
    Default: ""

  ManagedPolicyARNs:
    Type: CommaDelimitedList
    Description: Optional - Comma separated list of managed IAM policy ARNs to attach to the instance role
    Default: ""

  IMDSv2Tokens:
    Type: String
    Description: Whether IMDSv2 tokens must be used for the Instance Metadata Service.
    AllowedValues:
      - optional
      - required
    Default: optional

  InstanceRoleName:
    Type: String
    Description: Optional - A name for the IAM Role attached to the Instance Profile
    Default: ""

  InstanceRolePermissionsBoundaryARN:
    Type: String
    Description: The ARN of the policy used to set the permissions boundary for the role.
    Default: ""

  InstanceOperatingSystem:
    Type: String
    Description: The operating system to run on the instances
    AllowedValues:
      - linux
      - windows
    Default: "linux"

  ECRAccessPolicy:
    Type: String
    Description: ECR access policy to give container instances
    AllowedValues:
      - none
      - readonly
      - poweruser
      - full
    Default: "none"

  AssociatePublicIpAddress:
    Type: String
    Description: Associate instances with public IP addresses
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  EnableSecretsPlugin:
    Type: String
    Description: Enables s3-secrets plugin for all pipelines
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  EnableECRPlugin:
    Type: String
    Description: Enables ecr plugin for all pipelines
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  EnableDockerLoginPlugin:
    Type: String
    Description: Enables docker-login plugin for all pipelines
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  EnableDockerUserNamespaceRemap:
    Type: String
    Description: Enables Docker user namespace remapping so docker runs as buildkite-agent
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  EnableDockerExperimental:
    Type: String
    Description: Enables Docker experimental features
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  EnableInstanceStorage:
    Type: String
    Description: Mount available NVMe Instance Storage at /mnt/ephemeral
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  EnableCostAllocationTags:
    Type: String
    Description: Enables AWS Cost Allocation tags for all resources in the stack. See https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/cost-alloc-tags.html
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  CostAllocationTagName:
    Type: String
    Description: The name of the Cost Allocation Tag used for billing purposes
    Default: "CreatedBy"

  CostAllocationTagValue:
    Type: String
    Description: The value of the Cost Allocation Tag used for billing purposes
    Default: "buildkite-elastic-ci-stack-for-aws"

  BuildkiteAgentEnableGitMirrors:
    Type: String
    Description: Enables git-mirrors in the agent
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  EnableDetailedMonitoring:
    Type: String
    Description: Enable detailed EC2 monitoring
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  InstanceName:
    Type: String
    Description: Optional - Customise the EC2 instance Name tag
    Default: ""

Rules:
  HasToken:
    Assertions:
      - Assert:
          !Or
            - !Not
              - !Equals
                - !Ref BuildkiteAgentToken
                - ""
            - !Not
              - !Equals
                - !Ref BuildkiteAgentTokenParameterStorePath
                - ""
        AssertDescription: "You must provide BuildkiteAgentToken or BuildkiteAgentTokenParameterStorePath"

Outputs:
  VpcId:
    Value:
      !If [ CreateVpcResources, !Ref Vpc, !Ref VpcId ]
    Export:
      Name: !Sub '${AWS::StackName}-VpcId'

  ManagedSecretsBucket:
    Value:
      !If [ CreateSecretsBucket, !Ref ManagedSecretsBucket, "Undefined" ]
    Export:
      Name: !Sub '${AWS::StackName}-ManagedSecretsBucket'

  ManagedSecretsLoggingBucket:
    Value:
      !If [ CreateSecretsBucket, !Ref ManagedSecretsLoggingBucket, "Undefined" ]
    Export:
      Name: !Sub '${AWS::StackName}-ManagedSecretsLoggingBucket'

  AutoScalingGroupName:
    Value: !Ref AgentAutoScaleGroup
    Export:
      Name: !Sub '${AWS::StackName}-AutoScalingGroupName'

  InstanceRoleName:
    Value: !Ref IAMRole
    Export:
      Name: !Sub '${AWS::StackName}-InstanceRoleName'

Conditions:
    CreateVpcResources:
      !Equals [ !Ref VpcId, "" ]

    CreateSecurityGroup:
      !Equals [ !Ref SecurityGroupIds, "" ]

    CreateSecretsBucket:
      !And
         - !Equals [ !Ref EnableSecretsPlugin, "true"]
         - !Equals [ !Ref SecretsBucket, "" ]

    EnforceSecretsBucketEncryption:
      !And
         - !Condition CreateSecretsBucket
         - !Equals [ !Ref SecretsBucketEncryption, "true"]

    SetInstanceRoleName:
      !Not [ !Equals [ !Ref InstanceRoleName, "" ] ]

    SetInstanceRolePermissionsBoundaryARN:
      !Not [ !Equals [ !Ref InstanceRolePermissionsBoundaryARN, "" ] ]

    UseSpecifiedSecretsBucket:
      !Not [ !Equals [ !Ref SecretsBucket, "" ] ]

    HasSecretsBucket:
      !Or [ !Condition CreateSecretsBucket, !Condition UseSpecifiedSecretsBucket ]

    UseSpecifiedAvailabilityZones:
      !Not [ !Equals [ !Join [ "", !Ref AvailabilityZones ], "" ]  ]

    UseArtifactsBucket:
      !Not [ !Equals [ !Ref ArtifactsBucket, "" ] ]

    HasImageId:
      !Not [ !Equals [ !Ref ImageId, "" ] ]
    HasImageIdParameter:
      !Not [ !Equals [ !Ref ImageIdParameter, "" ] ]

    UseDefaultInstanceCreationTimeout:
      !Equals [ !Ref InstanceCreationTimeout, "" ]

    UseDefaultRootVolumeName:
      !Equals [ !Ref RootVolumeName, "" ]

    UseInstanceType2:
      !Not [ !Equals [ !Select [ "1", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ], ""] ]

    UseInstanceType3:
      !Not [ !Equals [ !Select [ "2", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ], ""] ]

    UseInstanceType4:
      !Not [ !Equals [ !Select [ "3", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ], ""] ]

    UseManagedPolicyARN:
      !Not [ !Equals [ !Join [ "", !Ref ManagedPolicyARNs ], "" ] ]

    UseECR:
      !Not [ !Equals [ !Ref ECRAccessPolicy, "none" ] ]

    UseCustomerManagedParameterPath:
      !Not [ !Equals [ !Ref BuildkiteAgentTokenParameterStorePath, "" ] ]
    UseCustomerManagedKeyForParameterStore:
      !Not [ !Equals [ !Ref BuildkiteAgentTokenParameterStoreKMSKey, "" ] ]
    CreateAgentTokenParameter:
      !Equals [ !Ref BuildkiteAgentTokenParameterStorePath, "" ]

    HasVariableSize:
      !Not [ !Equals [ !Ref MaxSize, !Ref MinSize ] ]

    UseCostAllocationTags:
      !Equals [ !Ref EnableCostAllocationTags, "true" ]

    HasKeyName:
      !Not [ !Equals [ !Ref KeyName, "" ] ]

    EnableSshIngress:
      !And
        - { Condition : CreateSecurityGroup }
        # Enable ingress if a key can be specified another way
        - !Or
          - { Condition: HasKeyName }
          - !Not [ !Equals [ !Ref AuthorizedUsersUrl, "" ] ]

    # Whether or not there's any managed polices to attach
    HasManagedPolicies:
      !Or [ { Condition: UseManagedPolicyARN }, { Condition: UseECR } ]

    UseWindowsAgents:
      !Equals [ !Ref InstanceOperatingSystem, "windows" ]

    UseLinuxAgents:
      !Equals [ !Ref InstanceOperatingSystem, "linux" ]

    # Unfortunately, Cloudformation's !Or intrinsic function only accepts
    # between 2 and 10 arguments.  To get around this, we're grouping the
    # instance families in sub-conditionals.  At least this doesn't force us
    # into using a Custom Resource.
    UsingArmInstances:
      !Or
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "a1" ]
        - !Or
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c6g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c6gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c6gn" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c7g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c7gn" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "g5g" ]
        - !Or
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "i4g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "Im4gn" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "Is4gen" ]
        - !Or
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m6g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m6gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m7g" ]
        - !Or
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "r6g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "r6gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "r7g" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "t4g" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "x2gd" ]

    UseStackNameForInstanceName:
      !Equals [ !Ref InstanceName, "" ]

Mappings:
  ECRManagedPolicy:
    none      : { Policy: '' }
    readonly  : { Policy: 'arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly' }
    poweruser : { Policy: 'arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser' }
    full      : { Policy: 'arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess' }

  # Generated from Makefile via build/mappings.yml
  AWSRegion2AMI:
    us-east-1 : { linuxamd64: ami-02c624694950f12f2, linuxarm64: ami-0906085fff9eb6ee2, windows: ami-0f2e0560773d385d3 }
    us-east-2 : { linuxamd64: ami-09685e8799eed2604, linuxarm64: ami-00063e44e1132e62e, windows: ami-0bba370f27fb441e7 }
    us-west-1 : { linuxamd64: ami-0f1f4edf45a6ac6b4, linuxarm64: ami-04b8f8a1709a5bbe0, windows: ami-0ba68194fa7a306b4 }
    us-west-2 : { linuxamd64: ami-0b81e6e704393b564, linuxarm64: ami-066535f38d5abf100, windows: ami-0eac52061cd4cec6c }
    af-south-1 : { linuxamd64: ami-020010377a8d2c10d, linuxarm64: ami-0825cbf3daadd2755, windows: ami-07191f505b398d828 }
    ap-east-1 : { linuxamd64: ami-0f3d777af16ffddd8, linuxarm64: ami-0143afd2f42b88a36, windows: ami-0cc88efce0f95d836 }
    ap-south-1 : { linuxamd64: ami-019da7aa2b5289dca, linuxarm64: ami-0e83a3f63a38c965d, windows: ami-0abaaca14cecff63d }
    ap-northeast-2 : { linuxamd64: ami-008f13ceb0dc0b83e, linuxarm64: ami-08dd7c863984015e2, windows: ami-0003f44f5d8db9a50 }
    ap-northeast-1 : { linuxamd64: ami-01891e9a81ecfeed6, linuxarm64: ami-0093beab101f0466b, windows: ami-0c98514ff04dbc218 }
    ap-southeast-2 : { linuxamd64: ami-0ee093f14cad2bac1, linuxarm64: ami-063b4297d53b22e86, windows: ami-028eae5461d4b9b1d }
    ap-southeast-1 : { linuxamd64: ami-039df335d354a1295, linuxarm64: ami-03570a45e93d12b3f, windows: ami-0ae5daad96860ec27 }
    ca-central-1 : { linuxamd64: ami-0878a5f64eaefab5a, linuxarm64: ami-07ac2fdc55f36e219, windows: ami-0367ec1fae4d8f57a }
    eu-central-1 : { linuxamd64: ami-0a460ce0e7062efb4, linuxarm64: ami-09969d70a8f2e854a, windows: ami-0fac32d15c96500cc }
    eu-west-1 : { linuxamd64: ami-0570be2296be996e3, linuxarm64: ami-094f90537b9dc4eec, windows: ami-04f4fe9fe77c97316 }
    eu-west-2 : { linuxamd64: ami-088ad08f61e97b93b, linuxarm64: ami-0cd4a945aee81a982, windows: ami-07cfbf5f7e15c2b18 }
    eu-south-1 : { linuxamd64: ami-0a747ba0d90816536, linuxarm64: ami-060a21188e3a117ac, windows: ami-0ccb2372d4b853e2c }
    eu-west-3 : { linuxamd64: ami-0303dff68ab8dba39, linuxarm64: ami-077c7edc1da611d0e, windows: ami-059e34012cac21e4c }
    eu-north-1 : { linuxamd64: ami-0433686104312c697, linuxarm64: ami-0d8f86a7245705731, windows: ami-03b8f7a2bd6a1bcd7 }
    me-south-1 : { linuxamd64: ami-0faf48978697fde13, linuxarm64: ami-07caab3156f497492, windows: ami-0a4dfb03c3cadfc5f }
    sa-east-1 : { linuxamd64: ami-0fb05428f87e82294, linuxarm64: ami-0323cd847ae0aa8d9, windows: ami-0ad3f236db5556e34 }

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Condition: CreateVpcResources
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Ref 'AWS::StackName'

  Gateway:
    Type: AWS::EC2::InternetGateway
    Condition: CreateVpcResources
    Properties:
      Tags:
        - Key: Name
          Value: !Ref 'AWS::StackName'

  GatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Condition: CreateVpcResources
    Properties:
      InternetGatewayId: !Ref Gateway
      VpcId: !Ref Vpc

  Subnet0:
    Type: AWS::EC2::Subnet
    Condition: CreateVpcResources
    DependsOn:
      - GatewayAttachment
    Properties:
      AvailabilityZone:
        !If
          - "UseSpecifiedAvailabilityZones"
          - !Select [ 0, !Ref AvailabilityZones ]
          - !Select [ 0, !GetAZs '' ]
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Ref 'AWS::StackName'

  Subnet1:
    Type: AWS::EC2::Subnet
    Condition: CreateVpcResources
    DependsOn:
      - GatewayAttachment
    Properties:
      AvailabilityZone:
        !If
          - "UseSpecifiedAvailabilityZones"
          - !Select [ 1, !Ref AvailabilityZones ]
          - !Select [ 1, !GetAZs '' ]
      CidrBlock: 10.0.2.0/24
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Ref 'AWS::StackName'

  Routes:
    Type: AWS::EC2::RouteTable
    Condition: CreateVpcResources
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Ref 'AWS::StackName'

  RouteDefault:
    Type: AWS::EC2::Route
    Condition: CreateVpcResources
    DependsOn:
      - GatewayAttachment
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref Gateway
      RouteTableId: !Ref Routes

  Subnet0Routes:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Condition: CreateVpcResources
    Properties:
      SubnetId: !Ref Subnet0
      RouteTableId: !Ref Routes

  Subnet1Routes:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Condition: CreateVpcResources
    Properties:
      SubnetId: !Ref Subnet1
      RouteTableId: !Ref Routes

  # A resource that depends on the leaf nodes of the VPC configuration so that
  # a strict ordering can be established on teardown.
  VpcComplete:
    Type: AWS::CloudFormation::WaitConditionHandle
    Metadata:
      VpcResources: !If
        - CreateVpcResources
        - [ !Ref RouteDefault, !Ref Subnet0Routes, !Ref Subnet1Routes ]
        - !Ref "AWS::NoValue"

  BuildkiteAgentTokenParameter:
    Type: AWS::SSM::Parameter
    Condition: CreateAgentTokenParameter
    Properties:
      Name: !Sub "/${AWS::StackName}/buildkite/agent-token"
      Type: String
      Value: !Ref BuildkiteAgentToken

  # Allow ec2 instances to assume a role and be granted the IAMPolicies
  IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles: [ !Ref IAMRole ]

  IAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !If [ SetInstanceRoleName, !Ref InstanceRoleName, !Sub "${AWS::StackName}-Role" ]
      PermissionsBoundary: !If [ SetInstanceRolePermissionsBoundaryARN, !Ref InstanceRolePermissionsBoundaryARN, !Ref "AWS::NoValue" ]
      ManagedPolicyArns: !If
          - HasManagedPolicies
          # Support multiple policies to attach by merging the values together and splitting on ','
          - !Split
            - ','
            # Join will skip over AWS::NoValue values
            - !Join
             - ','
             - - !If
                 - UseECR
                 - !FindInMap [ ECRManagedPolicy, !Ref ECRAccessPolicy, 'Policy' ]
                 - !Ref 'AWS::NoValue'
               # This may support multiple values of its own (separated by commas)
               - !If
                 - UseManagedPolicyARN
                 - !Join [ ',', !Ref ManagedPolicyARNs ]
                 - !Ref 'AWS::NoValue'
          - !Ref 'AWS::NoValue'
      Policies:
        - !If
          - UseCustomerManagedKeyForParameterStore
          - PolicyName: DecryptAgentToken
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - kms:Decrypt
                  Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${BuildkiteAgentTokenParameterStoreKMSKey}
          - !Ref 'AWS::NoValue'
        - PolicyName: ReadAgentToken
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: ssm:GetParameter
                Resource:
                  !Sub
                    - arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${ParameterPath}
                    - ParameterPath: !If [ UseCustomerManagedParameterPath, !Ref BuildkiteAgentTokenParameterStorePath, !Ref BuildkiteAgentTokenParameter ]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ autoscaling.amazonaws.com, ec2.amazonaws.com ]
            Action: sts:AssumeRole
      Path: /

  IAMPolicies:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: InstancePolicy
      PolicyDocument:
        Statement:
          - !If
            - HasSecretsBucket
            - Sid: SecretsBucket
              Effect: Allow
              Action:
                - s3:Get*
                - s3:List*
              Resource:
                - !Sub
                  - "arn:aws:s3:::${Bucket}/*"
                  - Bucket: !If [ CreateSecretsBucket, !Ref ManagedSecretsBucket, !Ref SecretsBucket ]
                - !Sub
                  - "arn:aws:s3:::${Bucket}"
                  - Bucket: !If [ CreateSecretsBucket, !Ref ManagedSecretsBucket, !Ref SecretsBucket ]
            - !Ref "AWS::NoValue"
          - !If
            - UseArtifactsBucket
            - Sid: ArtifactsBucket
              Effect: Allow
              Action:
                - s3:GetObject
                - s3:GetObjectAcl
                - s3:GetObjectVersion
                - s3:GetObjectVersionAcl
                - s3:ListBucket
                - s3:PutObject
                - s3:PutObjectAcl
                - s3:PutObjectVersionAcl
              Resource:
                - !Sub "arn:aws:s3:::${ArtifactsBucket}/*"
                - !Sub "arn:aws:s3:::${ArtifactsBucket}"
            - !Ref "AWS::NoValue"
          - Effect: Allow
            Action:
              - autoscaling:DescribeAutoScalingInstances
              - cloudwatch:PutMetricData
              - cloudformation:DescribeStackResource
              - ec2:DescribeTags
            Resource: "*"
          - Sid: TerminateInstance
            Effect: Allow
            Action:
              - autoscaling:SetInstanceHealth
              - autoscaling:TerminateInstanceInAutoScalingGroup
            Resource: !Sub arn:${AWS::Partition}:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${AWS::StackName}-AgentAutoScaleGroup-*
          - Sid: Logging
            Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
              - logs:DescribeLogGroups
              - logs:DescribeLogStreams
            Resource: "*"
          - Sid: Ssm
            Effect: Allow
            Action:
              - ssm:DescribeInstanceProperties
              - ssm:ListAssociations
              - ssm:PutInventory
              - ssm:UpdateInstanceInformation
              - ssmmessages:CreateControlChannel
              - ssmmessages:CreateDataChannel
              - ssmmessages:OpenControlChannel
              - ssmmessages:OpenDataChannel
              - ec2messages:AcknowledgeMessage
              - ec2messages:DeleteMessage
              - ec2messages:FailMessage
              - ec2messages:GetEndpoint
              - ec2messages:GetMessages
              - ec2messages:SendReply
            Resource: "*"
      Roles:
        - !Ref IAMRole

  ManagedSecretsLoggingBucket:
    Type: AWS::S3::Bucket
    Condition: CreateSecretsBucket
    DeletionPolicy: Retain
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        !If
        - EnforceSecretsBucketEncryption
        -
          ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
        - !Ref "AWS::NoValue"
      Tags:
        - !If
          - UseCostAllocationTags
          - Key: !Ref CostAllocationTagName
            Value: !Ref CostAllocationTagValue
          - !Ref "AWS::NoValue"

  ManagedSecretsLoggingBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Condition: CreateSecretsBucket
    Properties:
      Bucket: !Ref ManagedSecretsLoggingBucket
      PolicyDocument:
        Id: ManagedSecretsLoggingBucketPolicy
        Version: "2012-10-17"
        Statement:
        - Sid: S3ServerAccessLogsPolicy # Grant permissions to the logging service principal using a bucket policy (https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html)
          Effect: Allow
          Principal:
            Service: 'logging.s3.amazonaws.com'
          Action:
          - 's3:PutObject'
          Resource:
          - !GetAtt 'ManagedSecretsLoggingBucket.Arn'
          - !Sub '${ManagedSecretsLoggingBucket.Arn}/*'
          Condition:
            ArnLike:
              'aws:SourceArn': !GetAtt 'ManagedSecretsBucket.Arn'
            StringEquals:
              'aws:SourceAccount': !Ref 'AWS::AccountId'
        - !If
          - EnforceSecretsBucketEncryption
          - Sid: AllowSSLRequestsOnly # AWS Foundational Security Best Practices v1.0.0 S3.5
            Effect: Deny
            Principal: '*'
            Action: 's3:*'
            Resource:
            - !GetAtt 'ManagedSecretsLoggingBucket.Arn'
            - !Sub '${ManagedSecretsLoggingBucket.Arn}/*'
            Condition:
              Bool:
                'aws:SecureTransport': false
          - !Ref "AWS::NoValue"

  ManagedSecretsBucket:
    Type: AWS::S3::Bucket
    Condition: CreateSecretsBucket
    DeletionPolicy: Retain
    Properties:
      BucketEncryption:
        !If
        - EnforceSecretsBucketEncryption
        -
          ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
        - !Ref "AWS::NoValue"
      LoggingConfiguration:
        DestinationBucketName: !Ref ManagedSecretsLoggingBucket
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - !If
          - UseCostAllocationTags
          - Key: !Ref CostAllocationTagName
            Value: !Ref CostAllocationTagValue
          - !Ref "AWS::NoValue"

  ManagedSecretsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Condition: EnforceSecretsBucketEncryption
    Properties:
      Bucket: !Ref ManagedSecretsBucket
      PolicyDocument:
        Id: ManagedSecretsBucketPolicy
        Version: "2012-10-17"
        Statement:
        - Sid: AllowSSLRequestsOnly # AWS Foundational Security Best Practices v1.0.0 S3.5
          Effect: Deny
          Principal: '*'
          Action: 's3:*'
          Resource:
          - !GetAtt 'ManagedSecretsBucket.Arn'
          - !Sub '${ManagedSecretsBucket.Arn}/*'
          Condition:
            Bool:
              'aws:SecureTransport': false

  ImageIdParameterStack:
    Type: AWS::CloudFormation::Stack
    Condition: HasImageIdParameter
    Properties:
      TemplateURL: https://s3.amazonaws.com/buildkite-aws-stack/ssm-ami/releases/0.1.0.yml
      Parameters:
        AmiParameterPath: !Ref ImageIdParameter

  AgentLaunchTemplate:
    Type: "AWS::EC2::LaunchTemplate"
    Properties:
      LaunchTemplateData:
          NetworkInterfaces:
            - DeviceIndex: 0
              AssociatePublicIpAddress: { Ref: AssociatePublicIpAddress }
              Groups: !Split [ ",", !If [ "CreateSecurityGroup", !Ref SecurityGroup, !Ref SecurityGroupIds ] ]
          KeyName: !If [ "HasKeyName", !Ref KeyName, !Ref 'AWS::NoValue' ]
          IamInstanceProfile:
            Arn: !GetAtt "IAMInstanceProfile.Arn"
          InstanceType: !Select [ "0", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ]
          MetadataOptions:
            HttpTokens: !Ref IMDSv2Tokens
            # Allow containers using a Docker network on the host to receive IDMSv2 responses
            HttpPutResponseHopLimit: 2
            InstanceMetadataTags: enabled
          Monitoring:
            Enabled: !Ref EnableDetailedMonitoring
          ImageId: !If
            - HasImageId
            - !Ref ImageId
            - !If
              - HasImageIdParameter
              - !GetAtt ImageIdParameterStack.Outputs.ImageId
              - !If
                - UseWindowsAgents
                - !FindInMap
                  - AWSRegion2AMI
                  - !Ref 'AWS::Region'
                  - 'windows'
                - !If
                  - UsingArmInstances
                  - !FindInMap
                    - AWSRegion2AMI
                    - !Ref 'AWS::Region'
                    - 'linuxarm64'
                  - !FindInMap
                    - AWSRegion2AMI
                    - !Ref 'AWS::Region'
                    - 'linuxamd64'
          BlockDeviceMappings:
            - DeviceName: !If [ UseDefaultRootVolumeName, !If [ UseWindowsAgents, /dev/sda1, /dev/xvda ], !Ref RootVolumeName ]
              Ebs: { VolumeSize: !Ref RootVolumeSize, VolumeType: !Ref RootVolumeType, Encrypted: !Ref RootVolumeEncrypted }
          TagSpecifications:
            - ResourceType: instance
              Tags:
                - Key: Role
                  Value: buildkite-agent
                - Key: Name
                  Value: !If [ UseStackNameForInstanceName, !Ref AWS::StackName, !Ref InstanceName ]
                - Key: BuildkiteAgentRelease
                  Value: !Ref BuildkiteAgentRelease
                - Key: BuildkiteQueue
                  Value: !Ref BuildkiteQueue
                - !If
                  - UseCostAllocationTags
                  - Key: !Ref CostAllocationTagName
                    Value: !Ref CostAllocationTagValue
                  - !Ref "AWS::NoValue"
          UserData:
            Fn::Base64: !If
              - UseWindowsAgents
              - !Sub
                - |
                  <powershell>
                  $Env:DOCKER_USERNS_REMAP="${EnableDockerUserNamespaceRemap}"
                  $Env:DOCKER_EXPERIMENTAL="${EnableDockerExperimental}"
                  powershell -file C:\buildkite-agent\bin\bk-configure-docker.ps1 >> C:\buildkite-agent\elastic-stack.log

                  $Env:BUILDKITE_STACK_NAME="${AWS::StackName}"
                  $Env:BUILDKITE_STACK_VERSION="v6.1.0"
                  $Env:BUILDKITE_SCALE_IN_IDLE_PERIOD="${ScaleInIdlePeriod}"
                  $Env:BUILDKITE_SECRETS_BUCKET="${LocalSecretsBucket}"
                  $Env:BUILDKITE_SECRETS_BUCKET_REGION="${LocalSecretsBucketRegion}"
                  $Env:BUILDKITE_AGENT_TOKEN_PATH="${AgentTokenPath}"
                  $Env:BUILDKITE_AGENTS_PER_INSTANCE="${AgentsPerInstance}"
                  $Env:BUILDKITE_AGENT_TAGS="${BuildkiteAgentTags}"
                  $Env:BUILDKITE_AGENT_TIMESTAMP_LINES="${BuildkiteAgentTimestampLines}"
                  $Env:BUILDKITE_AGENT_EXPERIMENTS="${BuildkiteAgentExperiments}"
                  $Env:BUILDKITE_AGENT_TRACING_BACKEND="${BuildkiteAgentTracingBackend}"
                  $Env:BUILDKITE_AGENT_RELEASE="${BuildkiteAgentRelease}"
                  $Env:BUILDKITE_QUEUE="${BuildkiteQueue}"
                  $Env:BUILDKITE_AGENT_ENABLE_GIT_MIRRORS="${BuildkiteAgentEnableGitMirrors}"
                  $Env:BUILDKITE_ELASTIC_BOOTSTRAP_SCRIPT="${BootstrapScriptUrl}"
                  $Env:BUILDKITE_ENV_FILE_URL="${AgentEnvFileUrl}"
                  $Env:BUILDKITE_AUTHORIZED_USERS_URL="${AuthorizedUsersUrl}"
                  $Env:BUILDKITE_ECR_POLICY="${ECRAccessPolicy}"
                  $Env:BUILDKITE_TERMINATE_INSTANCE_AFTER_JOB="${BuildkiteTerminateInstanceAfterJob}"
                  $Env:BUILDKITE_ADDITIONAL_SUDO_PERMISSIONS="${BuildkiteAdditionalSudoPermissions}"
                  $Env:BUILDKITE_WINDOWS_ADMINISTRATOR="${BuildkiteWindowsAdministrator}"
                  $Env:AWS_DEFAULT_REGION="${AWS::Region}"
                  $Env:SECRETS_PLUGIN_ENABLED="${EnableSecretsPlugin}"
                  $Env:ECR_PLUGIN_ENABLED="${EnableECRPlugin}"
                  $Env:DOCKER_LOGIN_PLUGIN_ENABLED="${EnableDockerLoginPlugin}"
                  $Env:AWS_REGION="${AWS::Region}"
                  powershell -file C:\buildkite-agent\bin\bk-install-elastic-stack.ps1 >> C:\buildkite-agent\elastic-stack.log
                  </powershell>
                - {
                    LocalSecretsBucket: !If [ CreateSecretsBucket, !Ref ManagedSecretsBucket, !Ref SecretsBucket ],
                    LocalSecretsBucketRegion: !If [ CreateSecretsBucket, !Ref "AWS::Region", !Ref SecretsBucketRegion ],
                    AgentTokenPath: !If [ UseCustomerManagedParameterPath, !Ref BuildkiteAgentTokenParameterStorePath, !Ref BuildkiteAgentTokenParameter ],
                  }
              - !Sub
                - |
                  Content-Type: multipart/mixed; boundary="==BOUNDARY=="
                  MIME-Version: 1.0
                  --==BOUNDARY==
                  Content-Type: text/cloud-config; charset="us-ascii"
                  #cloud-config
                  cloud_final_modules:
                    - [scripts-user, always]
                  --==BOUNDARY==
                  Content-Type: text/x-shellscript; charset="us-ascii"
                  #!/bin/bash -v
                  BUILDKITE_ENABLE_INSTANCE_STORAGE="${EnableInstanceStorage}" \
                    /usr/local/bin/bk-mount-instance-storage.sh
                  --==BOUNDARY==
                  Content-Type: text/x-shellscript; charset="us-ascii"
                  #!/bin/bash -v
                  DOCKER_USERNS_REMAP=${EnableDockerUserNamespaceRemap} \
                  DOCKER_EXPERIMENTAL=${EnableDockerExperimental} \
                  BUILDKITE_ENABLE_INSTANCE_STORAGE="${EnableInstanceStorage}" \
                    /usr/local/bin/bk-configure-docker.sh
                  --==BOUNDARY==
                  Content-Type: text/x-shellscript; charset="us-ascii"
                  #!/bin/bash -v
                  BUILDKITE_STACK_NAME="${AWS::StackName}" \
                  BUILDKITE_STACK_VERSION="v6.1.0" \
                  BUILDKITE_SCALE_IN_IDLE_PERIOD="${ScaleInIdlePeriod}" \
                  BUILDKITE_SECRETS_BUCKET="${LocalSecretsBucket}" \
                  BUILDKITE_SECRETS_BUCKET_REGION="${LocalSecretsBucketRegion}" \
                  BUILDKITE_AGENT_TOKEN_PATH="${AgentTokenPath}" \
                  BUILDKITE_AGENTS_PER_INSTANCE="${AgentsPerInstance}" \
                  BUILDKITE_AGENT_TAGS="${BuildkiteAgentTags}" \
                  BUILDKITE_AGENT_TIMESTAMP_LINES="${BuildkiteAgentTimestampLines}" \
                  BUILDKITE_AGENT_EXPERIMENTS="${BuildkiteAgentExperiments}" \
                  BUILDKITE_AGENT_TRACING_BACKEND="${BuildkiteAgentTracingBackend}" \
                  BUILDKITE_AGENT_RELEASE="${BuildkiteAgentRelease}" \
                  BUILDKITE_QUEUE="${BuildkiteQueue}" \
                  BUILDKITE_AGENT_ENABLE_GIT_MIRRORS="${BuildkiteAgentEnableGitMirrors}" \
                  BUILDKITE_ELASTIC_BOOTSTRAP_SCRIPT="${BootstrapScriptUrl}" \
                  BUILDKITE_ENV_FILE_URL=${AgentEnvFileUrl} \
                  BUILDKITE_ENABLE_INSTANCE_STORAGE="${EnableInstanceStorage}" \
                  BUILDKITE_AUTHORIZED_USERS_URL="${AuthorizedUsersUrl}" \
                  BUILDKITE_ECR_POLICY="${ECRAccessPolicy}" \
                  BUILDKITE_TERMINATE_INSTANCE_AFTER_JOB="${BuildkiteTerminateInstanceAfterJob}" \
                  BUILDKITE_ADDITIONAL_SUDO_PERMISSIONS="${BuildkiteAdditionalSudoPermissions}" \
                  AWS_DEFAULT_REGION="${AWS::Region}" \
                  SECRETS_PLUGIN_ENABLED="${EnableSecretsPlugin}" \
                  ECR_PLUGIN_ENABLED="${EnableECRPlugin}" \
                  DOCKER_LOGIN_PLUGIN_ENABLED="${EnableDockerLoginPlugin}" \
                  DOCKER_EXPERIMENTAL="${EnableDockerExperimental}" \
                  AWS_REGION="${AWS::Region}" \
                    /usr/local/bin/bk-install-elastic-stack.sh
                  --==BOUNDARY==--
                - {
                    LocalSecretsBucket: !If [ CreateSecretsBucket, !Ref ManagedSecretsBucket, !Ref SecretsBucket ],
                    LocalSecretsBucketRegion: !If [ CreateSecretsBucket, !Ref "AWS::Region", !Ref SecretsBucketRegion ],
                    AgentTokenPath: !If [ UseCustomerManagedParameterPath, !Ref BuildkiteAgentTokenParameterStorePath, !Ref BuildkiteAgentTokenParameter ],
                  }

  AgentAutoScaleGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    DependsOn:
      - IAMPolicies
      - VpcComplete
    Properties:
      VPCZoneIdentifier: !If [ "CreateVpcResources", [ !Ref Subnet0, !Ref Subnet1 ], !Ref Subnets ]
      MixedInstancesPolicy:
        InstancesDistribution:
          OnDemandPercentageAboveBaseCapacity: !Ref OnDemandPercentage
          SpotAllocationStrategy: !Ref SpotAllocationStrategy
        LaunchTemplate:
          LaunchTemplateSpecification:
            LaunchTemplateId: !Ref AgentLaunchTemplate
            Version: !GetAtt "AgentLaunchTemplate.LatestVersionNumber"
          Overrides:
          - InstanceType: !Select [ "0", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ]
          - !If
            - UseInstanceType2
            - InstanceType: !Select [ "1", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType3
            - InstanceType: !Select [ "2", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType4
            - InstanceType: !Select [ "3", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
      MinSize: !Ref MinSize
      MaxSize: !Ref MaxSize
      Cooldown: 60
      MetricsCollection:
        - Granularity: 1Minute
          Metrics:
            - GroupMinSize
            - GroupMaxSize
            - GroupInServiceInstances
            - GroupTerminatingInstances
            - GroupPendingInstances
            - GroupDesiredCapacity
      TerminationPolicies:
        - OldestLaunchConfiguration
        - ClosestToNextInstanceHour
      NewInstancesProtectedFromScaleIn: true
    CreationPolicy:
      ResourceSignal:
        Timeout: !If [ UseDefaultInstanceCreationTimeout, !If [ UseWindowsAgents, PT10M, PT5M ], !Ref InstanceCreationTimeout ]
        Count: !Ref MinSize
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: true

  AsgProcessSuspenderRole:
    Type: AWS::IAM::Role
    Properties:
      PermissionsBoundary: !If [ SetInstanceRolePermissionsBoundaryARN, !Ref InstanceRolePermissionsBoundaryARN, !Ref "AWS::NoValue" ]
      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: AsgProcessModification
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'autoscaling:SuspendProcesses'
                Resource: !Sub arn:${AWS::Partition}:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${AWS::StackName}-AgentAutoScaleGroup-*

  AzRebalancingSuspenderFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: 'Disables AZ Rebalancing on the agent ASG'
      Code:
        ZipFile: |
          import cfnresponse
          import boto3
          def handler(event, context):
            try:
              if event['RequestType'] == 'Delete':
                cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, "CustomResourcePhysicalID")
              else:
                client = boto3.client('autoscaling')
                props = event['ResourceProperties']
                response = client.suspend_processes(AutoScalingGroupName=props['AutoScalingGroupName'], ScalingProcesses=['AZRebalance'])
                cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, "CustomResourcePhysicalID")
            except BaseException as err:
              print('ERROR: ', err)
              cfnresponse.send(event, context, cfnresponse.FAILED, {}, "CustomResourcePhysicalID")
      Handler: index.handler
      Role: !GetAtt AsgProcessSuspenderRole.Arn
      Runtime: 'python3.10'

  AzRebalancingSuspender:
    Type: AWS::CloudFormation::CustomResource
    Version: 1.0
    Properties:
      ServiceToken: !GetAtt AzRebalancingSuspenderFunction.Arn
      AutoScalingGroupName: !Ref AgentAutoScaleGroup

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: CreateSecurityGroup
    Properties:
      GroupDescription: Enable access to agents
      VpcId: !If [ "CreateVpcResources",  !Ref Vpc, !Ref VpcId ]
      Tags:
      - Key: Name
        Value: !Ref 'AWS::StackName'

  SecurityGroupSshIngress:
    Condition: EnableSshIngress
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !GetAtt SecurityGroup.GroupId
      IpProtocol: tcp
      FromPort: 22
      ToPort: 22
      CidrIp: 0.0.0.0/0

  Autoscaling:
    Type: AWS::Serverless::Application
    Condition: HasVariableSize
    Properties:
      Location:
        ApplicationId: !Ref BuildkiteAgentScalerServerlessARN
        SemanticVersion: !Ref BuildkiteAgentScalerVersion
      Parameters:
        BuildkiteAgentTokenParameter: !If [ UseCustomerManagedParameterPath, !Ref BuildkiteAgentTokenParameterStorePath, !Ref BuildkiteAgentTokenParameter ]
        BuildkiteAgentTokenParameterStoreKMSKey: !If [ UseCustomerManagedKeyForParameterStore, !Ref BuildkiteAgentTokenParameterStoreKMSKey, "" ]
        RolePermissionsBoundaryARN: !If [ SetInstanceRolePermissionsBoundaryARN, !Ref InstanceRolePermissionsBoundaryARN, "" ]
        BuildkiteQueue: !Ref BuildkiteQueue
        AgentsPerInstance: !Ref AgentsPerInstance
        MinSize: !Ref MinSize
        MaxSize: !Ref MaxSize
        AgentAutoScaleGroup: !Ref AgentAutoScaleGroup
        ScaleOutFactor: !Ref ScaleOutFactor
        ScaleOutForWaitingJobs: !Ref ScaleOutForWaitingJobs
        EventScheduleRate: !Ref ScalerEventScheduleRate
        MinPollInterval: !Ref ScalerMinPollInterval
