---
AWSTemplateFormatVersion: "2010-09-09"
Description: "Buildkite stack v6.59.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 for AWS 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 for AWS: 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: Base Configuration
        Parameters:
        - BuildkiteAgentToken
        - BuildkiteAgentTokenParameterStorePath
        - BuildkiteAgentTokenParameterStoreKMSKey
        - BuildkiteQueue
        - AgentEndpoint

      - Label:
          default: Signed Pipelines Configuration
        Parameters:
        - PipelineSigningKMSKeyId
        - PipelineSigningKMSKeySpec
        - PipelineSigningKMSAccess
        - PipelineSigningVerificationFailureBehavior
        - BuildkiteAgentSigningKeySSMParameter
        - BuildkiteAgentSigningKeyID
        - BuildkiteAgentVerificationKeySSMParameter

      - Label:
          default: Advanced Configuration
        Parameters:
        - BuildkiteAgentRelease
        - BuildkiteAgentTags
        - BuildkiteAgentTimestampLines
        - BuildkiteAgentExperiments
        - BuildkiteAgentEnableGitMirrors
        - BuildkiteAgentTracingBackend
        - BuildkiteAgentCancelGracePeriod
        - BuildkiteAgentSignalGracePeriod
        - BuildkiteTerminateInstanceAfterJob
        - BuildkiteAgentDisconnectAfterUptime
        - BuildkiteTerminateInstanceOnDiskFull
        - BuildkitePurgeBuildsOnDiskFull
        - BuildkiteAdditionalSudoPermissions
        - BuildkiteWindowsAdministrator
        - BuildkiteAgentScalerServerlessARN
        - EnableEC2LogRetentionPolicy
        - EC2LogRetentionDays
        - LogRetentionDays
        - BuildkiteAgentEnableGracefulShutdown
        - LambdaArchitecture

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

      - Label:
          default: Instance Configuration
        Parameters:
        - ImageId
        - ImageIdParameter
        - InstanceOperatingSystem
        - InstanceTypes
        - CpuCredits
        - EnableInstanceStorage
        - MountTmpfsAtTmp
        - AgentsPerInstance
        - KeyName
        - SecretsBucket
        - SecretsBucketRegion
        - SecretsBucketEncryption
        - SecretsPluginSkipSSHKeyNotFoundWarning
        - ArtifactsBucket
        - ArtifactsBucketRegion
        - ArtifactsS3ACL
        - AuthorizedUsersUrl
        - BootstrapScriptUrl
        - AgentEnvFileUrl
        - RootVolumeSize
        - RootVolumeName
        - RootVolumeType
        - RootVolumeEncrypted
        - RootVolumeDeleteOnTermination
        - RootVolumeIops
        - RootVolumeThroughput
        - ManagedPolicyARNs
        - ScalerManagedPolicyARNs
        - InstanceRoleARN
        - InstanceRoleName
        - InstanceRolePermissionsBoundaryARN
        - InstanceRoleTags
        - IMDSv2Tokens
        - EnableDetailedMonitoring
        - InstanceName
        - ExperimentalEnableResourceLimits
        - ResourceLimitsMemoryHigh
        - ResourceLimitsMemoryMax
        - ResourceLimitsMemorySwapMax
        - ResourceLimitsCPUWeight
        - ResourceLimitsCPUQuota
        - ResourceLimitsIOWeight

      - Label:
          default: Auto-scaling Configuration
        Parameters:
        - MinSize
        - MaxSize
        - InstanceBuffer
        - DisableScaleIn
        - InstanceScaleInProtection
        - OnDemandBaseCapacity
        - OnDemandPercentage
        - SpotAllocationStrategy
        - ScaleOutFactor
        - ScaleOutCooldownPeriod
        - ScaleInIdlePeriod
        - ScaleInCooldownPeriod
        - ScaleOutForWaitingJobs
        - InstanceCreationTimeout
        - ScalerEventSchedulePeriod
        - ScalerMinPollInterval
        - ScalerEnableExperimentalElasticCIMode
        - EnableScheduledScaling
        - ScheduleTimezone
        - ScaleUpSchedule
        - ScaleUpMinSize
        - ScaleDownSchedule
        - ScaleDownMinSize

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

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

      - Label:
          default: Docker Networking Configuration
        Parameters:
        - DockerNetworkingProtocol
        - DockerIPv4AddressPool1
        - DockerIPv4AddressPool2
        - DockerIPv6AddressPool
        - DockerFixedCidrV4
        - DockerFixedCidrV6

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

      - Label:
          default: Plugin Configuration
        Parameters:
        - EnableSecretsPlugin
        - EnableECRPlugin
        - EnableECRCredentialHelper
        - 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:
    Description: >
        Buildkite agent release channel to install.
        'stable' = production-ready (recommended), 'beta' = pre-release with latest features, 'edge' = bleeding-edge development builds.
        Use 'stable' unless specific new features are required.
    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: >
        Optional - Path to Buildkite agent token stored in AWS Systems Manager Parameter Store.
        Supports both parameter paths (e.g., '/buildkite/agent-token') and cross-account SSM parameter ARNs
        (e.g., 'arn:aws:ssm:us-east-1:123456789012:parameter/buildkite/shared-token').
        If provided, this overrides the BuildkiteAgentToken field.
        Recommended for better security instead of hardcoding tokens in the template.
        Use cross-account ARNs to access SSM parameters shared via AWS RAM.
    Type: String
    Default: ""
    AllowedPattern: "^$|^/$|^/[a-zA-Z0-9_.\\-/]+$|^arn:aws:ssm:[a-z0-9-]+:[0-9]{12}:parameter/[a-zA-Z0-9_.\\-/]+$"
    ConstraintDescription: "Expects a leading forward slash for parameter path or full SSM parameter ARN for cross-account access"

  BuildkiteAgentTokenParameterStoreKMSKey:
    Description: Optional - AWS KMS key ID used to encrypt the SSM parameter.
    Type: String
    Default: ""

  AgentEndpoint:
    Description: >
      API endpoint URL for Buildkite agent communication. Most
      customers shouldn't need to change this unless using a custom endpoint agreed with the Buildkite team.
    Type: String
    Default: "https://agent.buildkite.com/v3"

  BuildkiteAgentTags:
    Description: >
      Additional tags to help target specific Buildkite agents in pipeline steps (comma-separated).
      Example: 'environment=production,docker=enabled,size=large'.
      Use these tags in pipeline steps with 'agents: { environment: production }'.
    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: >
        Optional - Agent experiments to enable, comma delimited.
        See https://github.com/buildkite/agent/blob/-/EXPERIMENTS.md.
    Type: String
    Default: ""

  BuildkiteAgentScalerServerlessARN:
    Description: >
      (Deprecated - no longer used) ARN of the Serverless Application Repository that hosts the buildkite-agent-scaler Lambda function.
      The ARN is now automatically selected based on the LambdaArchitecture parameter.
      To use a custom scaler deployment, modify the AgentScalerARN mapping in the template.
    Type: String
    Default: "arn:aws:serverlessrepo:us-east-1:172840064832:applications/buildkite-agent-scaler"

  ScalerEnableExperimentalElasticCIMode:
    Description: >
        Experimental - Enable the Elastic CI Mode with enhanced features like graceful termination and dangling instance detection.
    Type: String
    AllowedValues:
        - "true"
        - "false"
    Default: "false"

  DisableScaleIn:
    Description: Whether the desired count should ever be decreased on the Auto Scaling group. When set to "true" (default), the scaler will not reduce the Auto Scaling group's desired capacity, and instances are expected to self-terminate when idle.
    Type: String
    AllowedValues:
        - "true"
        - "false"
    Default: "true"

  InstanceScaleInProtection:
    Description: Whether new instances launched by the Auto Scaling group should have scale-in protection enabled. When set to "true" (default), instances cannot be terminated by scale-in actions and must self-terminate when idle. Set to "false" to allow CloudFormation and the ASG to terminate instances directly.
    Type: String
    AllowedValues:
        - "true"
        - "false"
    Default: "true"

  EnableScheduledScaling:
    Description: Enable scheduled scaling to automatically adjust MinSize based on time-based schedules
    Type: String
    AllowedValues:
        - "true"
        - "false"
    Default: "false"

  ScheduleTimezone:
    Description: "Timezone for scheduled scaling actions (only used when EnableScheduledScaling is true). See AWS documentation for supported formats: https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html#scheduled-scaling-timezone (America/New_York, UTC, Europe/London, etc.)"
    Type: String
    Default: "UTC"

  ScaleUpSchedule:
    Description: "Cron expression for when to scale up (only used when EnableScheduledScaling is true). See AWS documentation for format details: https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html#scheduled-scaling-cron (\"0 8 * * MON-FRI\" for 8 AM weekdays)"
    Type: String
    Default: "0 8 * * MON-FRI"
    AllowedPattern: '^[0-9*,-/]+ [0-9*,-/]+ [0-9*,-/]+ [0-9*,-/]+ [0-9A-Za-z*,-/]+$'
    ConstraintDescription: "Must be a valid cron expression (5 fields: minute hour day-of-month month day-of-week)"

  ScaleUpMinSize:
    Description: MinSize to set when the ScaleUpSchedule is triggered (applied at the time specified in ScaleUpSchedule, only used when EnableScheduledScaling is true). Cannot exceed MaxSize.
    Type: Number
    Default: 1
    MinValue: 0

  ScaleDownSchedule:
    Description: "Cron expression for when to scale down (only used when EnableScheduledScaling is true). See AWS documentation for format details: https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html#scheduled-scaling-cron (\"0 18 * * MON-FRI\" for 6 PM weekdays)"
    Type: String
    Default: "0 18 * * MON-FRI"
    AllowedPattern: '^[0-9*,-/]+ [0-9*,-/]+ [0-9*,-/]+ [0-9*,-/]+ [0-9A-Za-z*,-/]+$'
    ConstraintDescription: "Must be a valid cron expression (5 fields: minute hour day-of-month month day-of-week)"

  ScaleDownMinSize:
    Description: MinSize to set when the ScaleDownSchedule is triggered (applied at the time specified in ScaleDownSchedule, only used when EnableScheduledScaling is true)
    Type: Number
    Default: 0
    MinValue: 0

  ScaleOutCooldownPeriod:
    Description: Cooldown period in seconds before allowing another scale-out event. Prevents rapid scaling and reduces costs from frequent instance launches.
    Type: Number
    Default: 300

  ScaleInCooldownPeriod:
    Description: Cooldown period in seconds before allowing another scale-in event. Longer periods prevent premature termination when job queues fluctuate.
    Type: Number
    Default: 3600

  EnableEC2LogRetentionPolicy:
    Type: String
    Default: "false"
    AllowedValues: ["true", "false"]
    Description: >
      Enable automatic deletion of old EC2 logs to reduce CloudWatch storage costs.
      Disabled by default to preserve all logs. When enabled, EC2 logs older than EC2LogRetentionDays will be automatically deleted.
      This only affects EC2 instance logs (agents, system logs), not Lambda logs.
      WARNING: Enabling this on existing stacks will delete historical logs older than the retention period - this cannot be undone.

  EC2LogRetentionDays:
    Type: Number
    Description: The number of days to retain CloudWatch Logs for EC2 instances managed by the CloudWatch agent (Buildkite agents, system logs, etc).
    Default: 7
    AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]

  LogRetentionDays:
    Type: Number
    Description: The number of days to retain CloudWatch Logs for Lambda functions in the stack.
    Default: 1
    AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]

  BuildkiteAgentEnableGracefulShutdown:
    Description: >
      Set to true to enable graceful shutdown of Buildkite agents when the ASG is updated with replacement.
      This allows ASGs to be removed in a timely manner during an in-place update of the Elastic CI Stack for AWS, and allows remaining Buildkite agents to finish jobs without interruptions.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

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

  BuildkiteAgentSigningKeySSMParameter:
    Description: Existing SSM Parameter Store path to a JSON Web Key Set (JWKS) containing a key to sign jobs with.
    Type: String
    Default: ""
    AllowedPattern: "^$|^/[a-zA-Z0-9_.\\-/]+$"
    ConstraintDescription: "Expects a leading forward slash"

  BuildkiteAgentSigningKeyID:
    Description: The ID of the key in the JWKS to use for signing jobs. If not specified, and the JWKS contains only one key, that key will be used.
    Type: String
    Default: ""

  BuildkiteAgentVerificationKeySSMParameter:
    Description: Existing SSM Parameter Store path to a JSON Web Key Set (JWKS) containing keys with which to verify jobs.
    Type: String
    Default: ""
    AllowedPattern: "^$|^/[a-zA-Z0-9_.\\-/]+$"
    ConstraintDescription: "Expects a leading forward slash"

  BuildkiteAgentCancelGracePeriod:
    Description: The number of seconds a canceled or timed out job is given to gracefully terminate and upload its artifacts.
    Type: Number
    Default: 60
    MinValue: 1

  BuildkiteAgentSignalGracePeriod:
    Description: >
        The number of seconds given to a subprocess to handle being sent `cancel-signal`.
        After this period has elapsed, SIGKILL will be sent.
    Type: Number
    Default: -1
    MinValue: -1

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

  BuildkiteTerminateInstanceOnDiskFull:
    Description: Set to 'true' to terminate the instance when disk space is critically low (default is to exit job with code 1).
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  BuildkitePurgeBuildsOnDiskFull:
    Description: Set to 'true' to purge build directories as a last resort when disk space is critically low.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  BuildkiteAgentDisconnectAfterUptime:
    Description: >
      The maximum uptime in seconds before the Buildkite agent stops accepting new jobs and shuts down
      after any running jobs complete. Set to 0 to disable uptime-based termination.
      This helps regularly cycle out machines and prevent resource accumulation issues.
    Type: Number
    Default: 0
    MinValue: 0

  ExperimentalEnableResourceLimits:
    Description: >
      Experimental - If true, enables systemd resource limits for the Buildkite agent.
      This helps prevent resource exhaustion by limiting CPU, memory, and I/O usage. Useful for shared instances running multiple agents or resource-intensive builds.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  ResourceLimitsMemoryHigh:
    Description: >
      Experimental - Sets the MemoryHigh limit for the Buildkite agent slice.
      The value can be a percentage (e.g., '90%') or an absolute value (e.g., '4G').
    Type: String
    Default: '90%'
    AllowedPattern: '^(\d+([KkMmGgTt])?|(?:[1-9][0-9]?|100)%|infinity)$'
    ConstraintDescription: "Must be a percentage (e.g., 90%), a value in bytes with an optional unit [K,M,G,T] (e.g., 4G), or 'infinity'."

  ResourceLimitsMemoryMax:
    Description: >
      Experimental - Sets the MemoryMax limit for the Buildkite agent slice.
      The value can be a percentage (e.g., '90%') or an absolute value (e.g., '4G').
    Type: String
    Default: '90%'
    AllowedPattern: '^(\d+([KkMmGgTt])?|(?:[1-9][0-9]?|100)%|infinity)$'
    ConstraintDescription: "Must be a percentage (e.g., 90%), a value in bytes with an optional unit [K,M,G,T] (e.g., 4G), or 'infinity'."

  ResourceLimitsMemorySwapMax:
    Description: >
      Experimental - Sets the MemorySwapMax limit for the Buildkite agent slice.
      The value can be a percentage (e.g., '90%') or an absolute value (e.g., '4G').
    Type: String
    Default: '90%'
    AllowedPattern: '^(\d+([KkMmGgTt])?|(?:[1-9][0-9]?|100)%|infinity)$'
    ConstraintDescription: "Must be a percentage (e.g., 90%), a value in bytes with an optional unit [K,M,G,T] (e.g., 4G), or 'infinity'."

  ResourceLimitsCPUWeight:
    Description: >
      Experimental - Sets the CPU weight for the Buildkite agent slice (1-10000, default 100).
      Higher values give more CPU time to the agent.
    Type: Number
    Default: 100
    MinValue: 1
    MaxValue: 10000

  ResourceLimitsCPUQuota:
    Description: >
      Experimental - Sets the CPU quota for the Buildkite agent slice.
      Takes a percentage value, suffixed with "%".
    Type: String
    Default: '90%'
    AllowedPattern: '^\d+%$'
    ConstraintDescription: "Must be a percentage value (e.g., 90%)."

  ResourceLimitsIOWeight:
    Description: >
      Experimental - Sets the I/O weight for the Buildkite agent slice (1-10000, default 80).
      Higher values give more I/O bandwidth to the agent.
    Type: Number
    Default: 80
    MinValue: 1
    MaxValue: 10000

  BuildkiteAdditionalSudoPermissions:
    Description: >
        Optional - Comma-separated list of specific commands (full paths) that build jobs can run with sudo privileges.
        Include only commands essential for builds. Leave blank unless builds require specific system-level operations.
    Type: String
    Default: ""

  BuildkiteWindowsAdministrator:
    Description: >
        Add buildkite-agent user to Windows Administrators group.
        This provides full system access for build jobs.
        Set to 'false' if builds don't require administrator privileges for additional security isolation.
    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 start on each EC2 instance.
      NOTE: If an agent crashes or is terminated, it won't be automatically restarted, leaving fewer active agents on that instance.
      The ScaleInIdlePeriod parameter controls when the entire instance terminates (when all agents are idle), not individual agent restarts.
      Consider enabling ScalerEnableExperimentalElasticCIMode for better agent management, or use fewer agents per instance with more instances for high availability.
    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"

  SecretsPluginSkipSSHKeyNotFoundWarning:
    Description: Optional - Skip warning when SSH key is not found in the secrets bucket.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

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

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

  ArtifactsS3ACL:
    Description: Optional - ACL to use for S3 artifact uploads.
    Type: String
    AllowedValues:
      - "private"
      - "public-read"
      - "public-read-write"
      - "authenticated-read"
      - "aws-exec-read"
      - "bucket-owner-read"
      - "bucket-owner-full-control"
    Default: "private"

  BootstrapScriptUrl:
    Description: >
      Optional - URI for a script to run on each instance during boot.
      Supported URI schemes: S3 object URI (s3://bucket/key),
      HTTPS URL (https://example.com/script.sh), or local file path (file:///path/to/script).
    Type: String
    Default: ""

  AgentEnvFileUrl:
    Description: >
        Optional - URI containing environment variables for the Buildkite agent process itself (not for builds).
        Supported URI schemes: S3 object URI (s3://bucket/key), SSM parameter path (ssm:/path/to/param),
        HTTPS URL (https://example.com/script.sh), or local file path (file:///path/to/script).
        These variables configure agent behavior like proxy settings or debugging options.
        For build environment variables, use pipeline 'env' configuration instead.
    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: >
        EC2 instance types to use (comma-separated, up to 25).
        The first type listed is preferred for OnDemand instances.
        Additional types improve Spot instance availability but make costs less predictable.
        Examples: 't3.large' for light workloads, 'm5.xlarge,m5a.xlarge' for CPU-intensive builds, 'c5.2xlarge,c5.4xlarge' for compute-heavy tasks.
    Type: String
    Default: "t3.large"
    MinLength: 1
    AllowedPattern: "^[\\w-\\.]+(,[\\w-\\.]*){0,24}$"
    ConstraintDescription: "must contain 1-25 instance types separated by commas. No space before/after the comma."

  CpuCredits:
    Description: Credit option for CPU usage of burstable instances. Sets the CreditSpecification.CpuCredits property in the LaunchTemplate for T-class instance types (t2, t3, t3a, t4g).
    Type: String
    AllowedValues:
      - standard
      - unlimited
    Default: "unlimited"

  MaxSize:
    Description: Maximum number of instances. Controls cost ceiling and prevents runaway scaling.
    Type: Number
    Default: 10
    MinValue: 0

  MinSize:
    Description: Minimum number of instances. Ensures baseline capacity for immediate job execution.
    Type: Number
    Default: 0
    MinValue: 0

  InstanceBuffer:
    Description: Number of idle instances to keep running. Lower values save costs, higher values reduce wait times for new jobs.
    Type: Number
    Default: 0

  ScalerEventSchedulePeriod:
    Description: >
      How often the Event Schedule for buildkite-agent-scaler is triggered.
      Should be an expression with units. Example: '30 seconds', '1 minute', '5 minutes'.
    Type: String
    Default: "1 minute"

  ScalerMinPollInterval:
    Description: >
      Minimum time between auto-scaler checks for new build jobs (e.g., '30s', '1m').
    Type: String
    Default: "10s"

  OnDemandBaseCapacity:
    Description: >
      Specify how much On-Demand capacity the Auto Scaling group should have for its base portion before scaling by percentages.
      The maximum group size will be increased (but not decreased) to this value.
    Type: Number
    Default: 0
    MinValue: 0

  OnDemandPercentage:
    Description: >
      Percentage of instances to launch as OnDemand vs Spot instances.
      OnDemand instances provide guaranteed availability at higher cost.
      Spot instances offer 60-90% cost savings but may be interrupted by AWS.
      Use 100% for critical workloads, lower values when jobs can handle unexpected instance interruptions.
    Type: Number
    Default: 100
    MinValue: 0
    MaxValue: 100

  SpotAllocationStrategy:
    Description: >
      Strategy for selecting Spot instance types to minimize interruptions and costs.
      'capacity-optimized' (recommended) chooses types with the most available capacity.
      'price-capacity-optimized' balances low prices with availability.
      'lowest-price' prioritizes cost savings.
      'capacity-optimized-prioritized' follows InstanceTypes order while optimizing for capacity.
    Type: String
    Default: "capacity-optimized"
    AllowedValues:
      - price-capacity-optimized
      - capacity-optimized
      - lowest-price
      - capacity-optimized-prioritized

  ScaleOutFactor:
    Description: >
      Multiplier for scale-out speed.
      Values higher than 1.0 create instances more aggressively, values lower than 1.0 more conservatively.
      Use higher values for time-sensitive workloads, lower values to control costs.
    Type: Number
    Default: 1.0

  ScaleInIdlePeriod:
    Description: >
      Number of seconds ALL agents on an instance must be idle before the instance is terminated.
      When all AgentsPerInstance agents are idle for this duration, the entire instance is terminated, not individual agents.
      This parameter controls instance-level scaling behavior.
    Type: Number
    Default: 600

  ScaleOutForWaitingJobs:
    Description: >
      Scale up instances for pipeline steps queued behind manual approval or wait steps.
      When enabled, the scaler will provision instances even when jobs can't start immediately due to pipeline waits.
      Ensure ScaleInIdlePeriod is long enough to keep instances running during wait periods.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  InstanceCreationTimeout:
    Description: Optional - Timeout period for Auto Scaling 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: Optional - Name of the root block device for the AMI.
    Type: String
    Default: ""

  RootVolumeType:
    Description: >
      Type of root volume to use. If specifying `io1` or `io2`, specify `RootVolumeIOPS` as well for optimal performance.
      See https://docs.aws.amazon.com/ebs/latest/userguide/provisioned-iops.html for more details.
    Type: String
    Default: "gp3"

  RootVolumeThroughput:
    Description: If the `RootVolumeType` is gp3, the throughput (MB/s data transfer rate) to provision for the root volume.
    Type: Number
    Default: 125

  RootVolumeIops:
    Description: If the `RootVolumeType` is gp3, io1, or io2, the number of IOPS to provision for the root volume.
    Type: Number
    Default: 1000

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

  RootVolumeDeleteOnTermination:
    Description: Whether to delete the root EBS volume when the instance is terminated.
    Type: String
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  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: ""

  ScalerManagedPolicyARNs:
    Type: CommaDelimitedList
    Description: Optional - Comma separated list of managed IAM policy ARNs to attach to the autoscaling Lambda execution role.
    Default: ""

  IMDSv2Tokens:
    Type: String
    Description: >
      Security setting for EC2 instance metadata access.
      'Required' enforces secure token-based access (recommended for security), 'Optional' allows both secure and legacy access methods.
      Use 'Required' unless legacy applications require the older metadata service.
    AllowedValues:
      - optional
      - required
    Default: "optional"

  InstanceRoleName:
    Type: String
    Description: Optional - A name for the IAM Role attached to the Instance Profile when creating a new role. Ignored when InstanceRoleARN is provided.
    Default: ""

  InstanceRolePermissionsBoundaryARN:
    Type: String
    Description: Optional - The ARN of the policy used to set the permissions boundary for the role when creating a new role. Ignored when InstanceRoleARN is provided.
    Default: ""

  InstanceRoleTags:
    Description: >
      Optional - Comma-separated key=value pairs for instance IAM role tags (up to 5 tags).
      Example: 'Environment=production,Team=platform,Purpose=ci'.
      Note: Keys and values cannot contain '=' characters.
      Only applied when creating a new role, ignored when InstanceRoleARN is provided.
    Type: String
    Default: ""
    AllowedPattern: "^$|^[\\w\\s_.:/+\\-@]+=[\\w\\s_.:/+\\-@]*(,[\\w\\s_.:/+\\-@]+=[\\w\\s_.:/+\\-@]*){0,4}$"

  InstanceRoleARN:
    Type: String
    Description: >
      Optional - ARN of an existing IAM role to attach to instances instead of creating a new role.
      When specified, the stack will not create any IAM roles or policies, and will use this role instead.
      The role must have all necessary permissions for Buildkite agents to function correctly.
      This is useful when you want to share a single IAM role across multiple queues/stacks.
      Supports roles with custom paths up to 10 levels deep.
      See https://buildkite.com/docs/agent/v3/aws/elastic-ci-stack/ec2-linux-and-windows/managing-elastic-ci-stack#using-custom-iam-roles
      for required permissions and configuration examples.
    Default: ""
    AllowedPattern: "^$|^arn:aws:iam::[0-9]+:role/.*$"

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

  ECRAccessPolicy:
    Type: String
    Description: >
      Docker image registry permissions for agents.
      'none' = no access, 'readonly' = pull images only, 'poweruser' = pull/push images, 'full' = complete ECR access.
      The '-pullthrough' variants (e.g., 'readonly-pullthrough') add permissions to enable automatic caching of public Docker images, reducing pull times and bandwidth costs.
    AllowedValues:
      - none
      - readonly
      - readonly-pullthrough
      - poweruser
      - poweruser-pullthrough
      - full
    Default: "none"

  AssociatePublicIpAddress:
    Type: String
    Description: >
      Give instances public IP addresses for direct internet access.
      Set to 'false' for a more isolated environment if the VPC has alternative outbound internet access configured.
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  EnableVpcEndpoints:
    Type: String
    Description: >
      Enable VPC endpoints for AWS services (S3, ECR, SSM, Secrets Manager, KMS).
      Only available when the stack creates a new VPC (VpcId parameter is empty).
      Interface endpoints incur hourly charges per availability zone. S3 uses a gateway endpoint which is free.
      Reduces internet traffic, improves security, and lowers data transfer costs for AWS service communication.
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  DockerNetworkingProtocol:
    Type: String
    Description: >
      Which IP version to enable for docker containers and building docker images.
      Only applies to Linux instances, not Windows.
    AllowedValues:
      - "ipv4"
      - "dualstack"
    Default: "ipv4"

  DockerIPv4AddressPool1:
    Type: String
    Description: Primary IPv4 CIDR block for Docker default address pools. Must not conflict with host network or VPC CIDR. Only applies to Linux instances, not Windows.
    Default: "172.17.0.0/12"
    AllowedPattern: "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\/(?:[0-9]|[12][0-9]|3[0-2])$"
    ConstraintDescription: "Must be a valid IPv4 CIDR block (e.g., 172.17.0.0/12)"

  DockerIPv4AddressPool2:
    Type: String
    Description: Secondary IPv4 CIDR block for Docker default address pools. Only applies to Linux instances, not Windows.
    Default: "192.168.0.0/16"
    AllowedPattern: "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\/(?:[0-9]|[12][0-9]|3[0-2])$"
    ConstraintDescription: "Must be a valid IPv4 CIDR block (e.g., 192.168.0.0/16)"

  DockerIPv6AddressPool:
    Type: String
    Description: IPv6 CIDR block for Docker default address pools in dualstack mode. Only applies to Linux instances, not Windows.
    Default: "2001:db8:2::/104"
    AllowedPattern: "^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:))\\/(?:[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8])$"
    ConstraintDescription: "Must be a valid IPv6 CIDR block (e.g., 2001:db8:2::/104)"

  DockerFixedCidrV4:
    Type: String
    Description: >
      Optional IPv4 CIDR block for Docker's fixed-cidr option. Restricts the IP range Docker uses for container networking on the default bridge.
      Must be a subset of the first pool in DockerIPv4AddressPool1 (Docker allocates docker0 from the first pool).
      Leave empty to disable. Useful to prevent conflicts with external services like databases. Only applies to Linux instances, not Windows.
    Default: ""
    AllowedPattern: "^$|^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\/(?:[0-9]|[12][0-9]|3[0-2])$"
    ConstraintDescription: "Must be empty or a valid IPv4 CIDR block (e.g., 172.17.1.0/24)"

  DockerFixedCidrV6:
    Type: String
    Description: >
      IPv6 CIDR block for Docker's fixed-cidr-v6 option in dualstack mode. Restricts the IP range Docker uses for IPv6 container networking.
      Only applies to Linux instances in dualstack mode, not Windows.
    Default: "2001:db8:1::/64"
    AllowedPattern: "^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:))\\/(?:[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8])$"
    ConstraintDescription: "Must be a valid IPv6 CIDR block (e.g., 2001:db8:1::/64)"

  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"

  EnableECRCredentialHelper:
    Type: String
    Description: Enable Amazon ECR Credential Helper in ECR plugin for Docker authentication.
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  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"

  DockerPruneUntil:
    Type: String
    Description: >
      Retention period for Docker images and build cache during garbage collection.
      Docker will delete resources older than this threshold, keeping resources created within this timeframe.
      Accepts duration strings like '30m' (30 minutes), '4h' (4 hours), '1h30m' (1.5 hours), '7d' (7 days).
      Default 4h means resources older than 4 hours will be pruned.
    Default: "4h"
    AllowedPattern: "^(\\d+[smhd])+$"
    ConstraintDescription: "Must be a duration string like '30m', '4h', '1h30m', or '7d'. Valid units: s (seconds), m (minutes), h (hours), d (days)."

  EnablePreExitDiskCleanup:
    Type: String
    Description: >
      Controls whether disk space check also runs in the pre-exit hook after jobs complete.
      Disk cleanup always runs in the environment hook when disk space is low.
      When enabled, the same check also runs in the pre-exit hook to reclaim resources generated during job execution.
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  DockerBuilderPruneEnabled:
    Type: String
    Description: >
      Controls whether Docker builder cache is pruned during garbage collection.
      When enabled, Docker builder cache will run after Docker image pruning.
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  EnableInstanceStorage:
    Type: String
    Description: >
      Mount available NVMe Instance Storage at /mnt/ephemeral, and use it to store docker images and
      containers, and the build working directory. You must ensure that the instance types have
      instance storage available for this to have any effect. See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-store-volumes.html
    AllowedValues:
      - "true"
      - "false"
    Default: "false"

  MountTmpfsAtTmp:
    Type: String
    Description: >
      Controls the filesystem mounted at /tmp.
      By default, /tmp is a tmpfs (memory-backed filesystem).
      Disabling this causes /tmp to be stored in the root filesystem.
    AllowedValues:
      - "true"
      - "false"
    Default: "true"

  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 - Customize the EC2 instance Name tag.
    Default: ""

  PipelineSigningKMSKeyId:
    Type: String
    Description: Optional - Identifier or ARN of existing KMS key for pipeline signing. Leave blank to create a new key when PipelineSigningKMSKeySpec is specified.
    Default: ""

  PipelineSigningKMSKeySpec:
    Type: String
    Description: Key specification for pipeline signing KMS key. Set to 'none' to disable pipeline signing, or 'ECC_NIST_P256' to enable with automatic key creation.
    AllowedValues:
      - "ECC_NIST_P256"
      - "none"
    Default: "none"

  PipelineSigningKMSAccess:
    Type: String
    Description: Access permissions for pipeline signing. 'sign-and-verify' allows both operations, 'verify' restricts to verification only.
    AllowedValues:
      - "sign-and-verify"
      - "verify"
    Default: "sign-and-verify"

  PipelineSigningVerificationFailureBehavior:
    Type: String
    Description: The behavior when a job is received without a valid verifiable signature (without a signature, with an invalid signature, or with a signature that fails verification).
    AllowedValues:
      - "block"
      - "warn"
    Default: "block"

  LambdaArchitecture:
    Type: String
    Description: CPU architecture for Lambda functions (x86_64 or arm64). arm64 provides better price-performance but requires compatible dependencies.
    AllowedValues:
      - "x86_64"
      - "arm64"
    Default: "x86_64"

Rules:
  HasToken:
    Assertions:
      - Assert:
          !Or
            - !Not
              - !Equals
                - !Ref BuildkiteAgentToken
                - ""
            - !Not
              - !Equals
                - !Ref BuildkiteAgentTokenParameterStorePath
                - ""
        AssertDescription: "You must provide BuildkiteAgentToken or BuildkiteAgentTokenParameterStorePath"
  HasPipelineSigningKMSKey:
    Assertions:
      - Assert:
          !Or
            - !Equals
              - !Ref PipelineSigningKMSKeyId
              - ""
            - !Equals
              - !Ref PipelineSigningKMSKeySpec
              - "none"
        AssertDescription: "You must provide either a PipelineSigningKMSKeyId, or select a PipelineSigningKMSKeySpec, but not both"

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'

  PipelineSigningKMSKey:
    Value:
      !If [ CreatePipelineSigningKMSKey, !Ref PipelineSigningKMSKey, "none" ]
    Export:
      Name: !Sub '${AWS::StackName}-PipelineSigningKMSKey'

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

  InstanceRoleName:
    Value: !If
      - UseCustomIAMRole
      - !If
        - HasCustomRolePath
        - !If
          - RolePathIndex11Empty
          - !If
            - RolePathIndex10Empty
            - !If
              - RolePathIndex9Empty
              - !If
                - RolePathIndex8Empty
                - !If
                  - RolePathIndex7Empty
                  - !If
                    - RolePathIndex6Empty
                    - !If
                      - RolePathIndex5Empty
                      - !If
                        - RolePathIndex4Empty
                        - !If
                          - RolePathIndex3Empty
                          - !Select [ 2, !Split [ "/", !Ref InstanceRoleARN ] ]
                          - !Select [ 3, !Split [ "/", !Ref InstanceRoleARN ] ]
                        - !Select [ 4, !Split [ "/", !Ref InstanceRoleARN ] ]
                      - !Select [ 5, !Split [ "/", !Ref InstanceRoleARN ] ]
                    - !Select [ 6, !Split [ "/", !Ref InstanceRoleARN ] ]
                  - !Select [ 7, !Split [ "/", !Ref InstanceRoleARN ] ]
                - !Select [ 8, !Split [ "/", !Ref InstanceRoleARN ] ]
              - !Select [ 9, !Split [ "/", !Ref InstanceRoleARN ] ]
            - !Select [ 10, !Split [ "/", !Ref InstanceRoleARN ] ]
          - !Select [ 11, !Split [ "/", !Ref InstanceRoleARN ] ]
        - !Select [ 1, !Split [ "/", !Ref InstanceRoleARN ] ]
      - !Ref IAMRole
    Export:
      Name: !Sub '${AWS::StackName}-InstanceRoleName'

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

    EnableVpcEndpoints:
      !And
        - !Equals [ !Ref EnableVpcEndpoints, "true" ]
        - !Condition CreateVpcResources

    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, "" ] ]

    UseCustomIAMRole:
      !Not [ !Equals [ !Ref InstanceRoleARN, "" ] ]

    CreateIAMRole:
      !Equals [ !Ref InstanceRoleARN, "" ]

    # Support up to 10 levels of custom paths in IAM role ARNs
    # Pad the ARN with empty segments to ensure we always have at least 12 elements after split
    RolePathIndex11Empty:
      !Equals [ !Select [ 11, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex10Empty:
      !Equals [ !Select [ 10, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex9Empty:
      !Equals [ !Select [ 9, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex8Empty:
      !Equals [ !Select [ 8, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex7Empty:
      !Equals [ !Select [ 7, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex6Empty:
      !Equals [ !Select [ 6, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex5Empty:
      !Equals [ !Select [ 5, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex4Empty:
      !Equals [ !Select [ 4, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex3Empty:
      !Equals [ !Select [ 3, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    RolePathIndex2Empty:
      !Equals [ !Select [ 2, !Split [ "/", !Sub [ "${ARN}///////////", { ARN: !Ref InstanceRoleARN } ] ] ], "" ]

    HasCustomRolePath:
      !Not [ !Condition RolePathIndex2Empty ]

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

    UseInstanceRoleTag1:
      !Not [ !Equals [ !Select [ "0", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ], "" ] ]
    UseInstanceRoleTag2:
      !Not [ !Equals [ !Select [ "1", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ], "" ] ]
    UseInstanceRoleTag3:
      !Not [ !Equals [ !Select [ "2", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ], "" ] ]
    UseInstanceRoleTag4:
      !Not [ !Equals [ !Select [ "3", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ], "" ] ]
    UseInstanceRoleTag5:
      !Not [ !Equals [ !Select [ "4", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ], "" ] ]

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

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

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

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

    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, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType5:
      !Not [ !Equals [ !Select [ "4", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType6:
      !Not [ !Equals [ !Select [ "5", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType7:
      !Not [ !Equals [ !Select [ "6", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType8:
      !Not [ !Equals [ !Select [ "7", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType9:
      !Not [ !Equals [ !Select [ "8", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType10:
      !Not [ !Equals [ !Select [ "9", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType11:
      !Not [ !Equals [ !Select [ "10", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType12:
      !Not [ !Equals [ !Select [ "11", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType13:
      !Not [ !Equals [ !Select [ "12", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType14:
      !Not [ !Equals [ !Select [ "13", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType15:
      !Not [ !Equals [ !Select [ "14", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType16:
      !Not [ !Equals [ !Select [ "15", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType17:
      !Not [ !Equals [ !Select [ "16", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType18:
      !Not [ !Equals [ !Select [ "17", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType19:
      !Not [ !Equals [ !Select [ "18", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType20:
      !Not [ !Equals [ !Select [ "19", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType21:
      !Not [ !Equals [ !Select [ "20", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType22:
      !Not [ !Equals [ !Select [ "21", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType23:
      !Not [ !Equals [ !Select [ "22", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType24:
      !Not [ !Equals [ !Select [ "23", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

    UseInstanceType25:
      !Not [ !Equals [ !Select [ "24", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""] ] ] ], ""] ]

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

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

    AddECRPullThrough:
      !Or
        - !Equals [ !Ref ECRAccessPolicy, "readonly-pullthrough" ]
        - !Equals [ !Ref ECRAccessPolicy, "poweruser-pullthrough" ]

    UseCustomerManagedParameterPath:
      !Not [ !Equals [ !Ref BuildkiteAgentTokenParameterStorePath, "" ] ]
    UseCustomerManagedKeyForParameterStore:
      !Not [ !Equals [ !Ref BuildkiteAgentTokenParameterStoreKMSKey, "" ] ]
    CreateAgentTokenParameter:
      !Equals [ !Ref BuildkiteAgentTokenParameterStorePath, "" ]
    IsParameterStorePathARN:
      !And
        - !Not [ !Equals [ !Ref BuildkiteAgentTokenParameterStorePath, "" ] ]
        - !Equals [ !Select [ 0, !Split [ ":", !Ref BuildkiteAgentTokenParameterStorePath ] ], "arn" ]

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

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

    EnableBuildkiteAgentGracefulShutdown:
      !Equals [ !Ref BuildkiteAgentEnableGracefulShutdown, "true" ]

    UsePipelineSigningKMSKey:
      !Not [ !Equals [ !Ref PipelineSigningKMSKeyId, "" ] ]

    CreatePipelineSigningKMSKey:
      !And
      - !Equals [ !Ref PipelineSigningKMSKeyId, "" ]
      - !Not [ !Equals [ !Ref PipelineSigningKMSKeySpec, "none" ] ]

    HasPipelineSigningKMSKey:
      !Or [ !Condition CreatePipelineSigningKMSKey, !Condition UsePipelineSigningKMSKey ]

    HasSigningKMSAccessSignAndVerify:
      !Equals [ !Ref PipelineSigningKMSAccess, "sign-and-verify" ]

    PipelineSigningKMSKeyIsARN:
      !Equals [!Select [0, !Split [":", !Ref PipelineSigningKMSKeyId]], "arn"]

    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" ]

    # 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 ] ], "c7gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c7gn" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c8g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c8gb" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c8gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "c8gn" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "g5g" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "hpc7g" ]
        - !Or
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "i4g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "i8g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "i8ge" ]
          - !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" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m7gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m8g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m8gb" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m8gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "m8gn" ]
        - !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 ] ], "r7gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "r8g" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "r8gb" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "r8gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "r8gn" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "t4g" ]
        - !Or
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "x2gd" ]
          - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "x8g" ]

    UsingBurstableInstances:
      !Or
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "t2" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "t3" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "t3a" ]
        - !Equals [ !Select [ 0, !Split [ ".", !Ref InstanceTypes ] ], "t4g" ]

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

    IsRootVolumeIsGp3:
      !Equals [ !Ref RootVolumeType, "gp3" ]

    IsRootVolumeIsIo1OrIo2OrGp3:
      !Or
        - !Equals [ !Ref RootVolumeType, "io1" ]
        - !Equals [ !Ref RootVolumeType, "io2" ]
        - !Equals [ !Ref RootVolumeType, "gp3" ]

    EnableScheduledScaling:
      !Equals [ !Ref EnableScheduledScaling, "true" ]

    IsLambdaArchitectureArm64:
      !Equals [ !Ref LambdaArchitecture, "arm64" ]

    HasVariableSizeAndArm64:
      !And
        - !Condition HasVariableSize
        - !Condition IsLambdaArchitectureArm64

    HasVariableSizeAndX8664:
      !And
        - !Condition HasVariableSize
        - !Not [ !Condition IsLambdaArchitectureArm64 ]

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

  AgentScalerARN:
    x86-64: { ARN: 'arn:aws:serverlessrepo:us-east-1:172840064832:applications/buildkite-agent-scaler' }
    arm64: { ARN: 'arn:aws:serverlessrepo:us-east-1:172840064832:applications/buildkite-agent-scaler-arm64' }

  # Generated from Makefile via build/mappings.yml
  AWSRegion2AMI:
    us-east-1 : { linuxamd64: ami-08cedd54043c426d5, linuxarm64: ami-06fbdec00f83f3d68, windows: ami-0bc34f7125053470f }
    us-east-2 : { linuxamd64: ami-0e0d16602deae536f, linuxarm64: ami-042cb4c08e36a0863, windows: ami-0603041863f8aa487 }
    us-west-1 : { linuxamd64: ami-09b16d4648520dca9, linuxarm64: ami-0e7c165362a4fa641, windows: ami-07b9330161a334397 }
    us-west-2 : { linuxamd64: ami-0630d07f7a4d9e8ec, linuxarm64: ami-052591cafbeba73ac, windows: ami-0c3588e46d5b85d7f }
    af-south-1 : { linuxamd64: ami-03c00a6a238c268f7, linuxarm64: ami-02a65a539f23d902b, windows: ami-0910c05b4a44017cc }
    ap-east-1 : { linuxamd64: ami-0c05c819bb9c0e5d8, linuxarm64: ami-0380f6a31b030d5f3, windows: ami-01062f6f9cd809940 }
    ap-south-1 : { linuxamd64: ami-05475a8704341a86c, linuxarm64: ami-0acae83c3bca67f36, windows: ami-0abcf86af97c4a23b }
    ap-northeast-2 : { linuxamd64: ami-0cbf1afb7f8a0eb80, linuxarm64: ami-00b2522586fa2fce8, windows: ami-022b9ac0135ec77c4 }
    ap-northeast-1 : { linuxamd64: ami-0ee5de98e6e6fecae, linuxarm64: ami-0b3b157521f4cf320, windows: ami-0ca0510b5fbbd9c83 }
    ap-southeast-2 : { linuxamd64: ami-089bb24032f009613, linuxarm64: ami-0a112237271b9998e, windows: ami-01af7179704b851ca }
    ap-southeast-1 : { linuxamd64: ami-01a775323c3996af1, linuxarm64: ami-04e12ce74603931d7, windows: ami-0f7758683dcedaf21 }
    ca-central-1 : { linuxamd64: ami-0733247f0455b48df, linuxarm64: ami-05825d626d2c31f37, windows: ami-076ac235926c15d39 }
    eu-central-1 : { linuxamd64: ami-079433a99fb53ecf4, linuxarm64: ami-08618f0d94b0655ad, windows: ami-0ee98dc02aa9594cb }
    eu-west-1 : { linuxamd64: ami-016bb25c2c3c88ebf, linuxarm64: ami-090ee7b6e142baafd, windows: ami-092cab51cf9c7b139 }
    eu-west-2 : { linuxamd64: ami-05018a73f070bf5f8, linuxarm64: ami-0e96e8a0b27b76443, windows: ami-00b4a89e2d22e4f05 }
    eu-south-1 : { linuxamd64: ami-01e419733a4cd808f, linuxarm64: ami-02c39b57b8b8bde46, windows: ami-0161859b3a8fcde9f }
    eu-west-3 : { linuxamd64: ami-018d79dec7c21c871, linuxarm64: ami-076792adea410570a, windows: ami-0878b80dee796f642 }
    eu-north-1 : { linuxamd64: ami-0959843cfb5e8b772, linuxarm64: ami-06672ed56be203ca6, windows: ami-0d92827beb55731ba }
    sa-east-1 : { linuxamd64: ami-0ad20b98ef79c3274, linuxarm64: ami-002ca17f57fe82f34, windows: ami-00b97b4f86f12a680 }

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Condition: CreateVpcResources
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      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 ]
        - []
      # By referencing these resources, CloudFormation creates implicit dependencies
      Subnet0RouteCheck: !If [ CreateVpcResources, !Ref Subnet0Routes, !Ref "AWS::NoValue" ]
      Subnet1RouteCheck: !If [ CreateVpcResources, !Ref Subnet1Routes, !Ref "AWS::NoValue" ]
      RouteDefaultCheck: !If [ CreateVpcResources, !Ref RouteDefault, !Ref "AWS::NoValue" ]

  VpcEndpointsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: EnableVpcEndpoints
    Properties:
      GroupDescription: Security group for VPC endpoints
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: !GetAtt Vpc.CidrBlock
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-vpc-endpoints'

  S3GatewayEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Gateway
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
      RouteTableIds:
        - !Ref Routes

  EcrApiEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ecr.api'
      SubnetIds:
        - !Ref Subnet0
        - !Ref Subnet1
      SecurityGroupIds:
        - !Ref VpcEndpointsSecurityGroup
      PrivateDnsEnabled: true

  EcrDkrEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ecr.dkr'
      SubnetIds:
        - !Ref Subnet0
        - !Ref Subnet1
      SecurityGroupIds:
        - !Ref VpcEndpointsSecurityGroup
      PrivateDnsEnabled: true

  SsmEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'
      SubnetIds:
        - !Ref Subnet0
        - !Ref Subnet1
      SecurityGroupIds:
        - !Ref VpcEndpointsSecurityGroup
      PrivateDnsEnabled: true

  SsmMessagesEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'
      SubnetIds:
        - !Ref Subnet0
        - !Ref Subnet1
      SecurityGroupIds:
        - !Ref VpcEndpointsSecurityGroup
      PrivateDnsEnabled: true

  Ec2MessagesEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages'
      SubnetIds:
        - !Ref Subnet0
        - !Ref Subnet1
      SecurityGroupIds:
        - !Ref VpcEndpointsSecurityGroup
      PrivateDnsEnabled: true

  SecretsManagerEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.secretsmanager'
      SubnetIds:
        - !Ref Subnet0
        - !Ref Subnet1
      SecurityGroupIds:
        - !Ref VpcEndpointsSecurityGroup
      PrivateDnsEnabled: true

  KmsEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnableVpcEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref Vpc
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.kms'
      SubnetIds:
        - !Ref Subnet0
        - !Ref Subnet1
      SecurityGroupIds:
        - !Ref VpcEndpointsSecurityGroup
      PrivateDnsEnabled: true

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

  PipelineSigningKMSKey:
    Type: AWS::KMS::Key
    Condition: CreatePipelineSigningKMSKey
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Properties:
      Description: Key used to sign and verify pipelines.
      KeySpec: !Ref PipelineSigningKMSKeySpec
      KeyUsage: SIGN_VERIFY
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-PipelineSigningKey'

  # Allow ec2 instances to assume a role and be granted the IAMPolicies
  IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !If
          - UseCustomIAMRole
          - !If
            - HasCustomRolePath
            - !If
              - RolePathIndex11Empty
              - !If
                - RolePathIndex10Empty
                - !If
                  - RolePathIndex9Empty
                  - !If
                    - RolePathIndex8Empty
                    - !If
                      - RolePathIndex7Empty
                      - !If
                        - RolePathIndex6Empty
                        - !If
                          - RolePathIndex5Empty
                          - !If
                            - RolePathIndex4Empty
                            - !If
                              - RolePathIndex3Empty
                              - !Select [ 2, !Split [ "/", !Ref InstanceRoleARN ] ]
                              - !Select [ 3, !Split [ "/", !Ref InstanceRoleARN ] ]
                            - !Select [ 4, !Split [ "/", !Ref InstanceRoleARN ] ]
                          - !Select [ 5, !Split [ "/", !Ref InstanceRoleARN ] ]
                        - !Select [ 6, !Split [ "/", !Ref InstanceRoleARN ] ]
                      - !Select [ 7, !Split [ "/", !Ref InstanceRoleARN ] ]
                    - !Select [ 8, !Split [ "/", !Ref InstanceRoleARN ] ]
                  - !Select [ 9, !Split [ "/", !Ref InstanceRoleARN ] ]
                - !Select [ 10, !Split [ "/", !Ref InstanceRoleARN ] ]
              - !Select [ 11, !Split [ "/", !Ref InstanceRoleARN ] ]
            - !Select [ 1, !Split [ "/", !Ref InstanceRoleARN ] ]
          - !Ref IAMRole

  IAMRole:
    Type: AWS::IAM::Role
    Condition: CreateIAMRole
    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
          - AddECRPullThrough
          - PolicyName: ECRPullThrough
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - ecr:CreateRepository
                    - ecr:BatchImportUpstreamImage
                    - ecr:GetImageCopyStatus
                    - ecr:InitiateLayerUpload
                    - ecr:UploadLayerPart
                    - ecr:CompleteLayerUpload
                    - ecr:PutImage
                  Resource: "*"
          - !Ref 'AWS::NoValue'
        - !If
          - HasPipelineSigningKMSKey
          - PolicyName: PipelineSigningKMSKeyAccess
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    !If
                      - HasSigningKMSAccessSignAndVerify
                      - - kms:Sign
                        - kms:Verify
                        - kms:GetPublicKey
                      - - kms:Verify
                        - kms:GetPublicKey
                  Resource:
                    !If
                      - CreatePipelineSigningKMSKey
                      - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${PipelineSigningKMSKey}
                      - !If
                        - PipelineSigningKMSKeyIsARN
                        - !Ref PipelineSigningKMSKeyId
                        - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${PipelineSigningKMSKeyId}
          - !Ref 'AWS::NoValue'
        - !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:
                  !If
                    - IsParameterStorePathARN
                    - !Ref BuildkiteAgentTokenParameterStorePath
                    - !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: /
      Tags:
        - Key: CreatedBy
          Value: buildkite-elastic-ci-stack
        - !If
          - UseInstanceRoleTag1
          - Key: !Select [ "0", !Split [ "=", !Select [ "0", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
            Value: !Select [ "1", !Split [ "=", !Select [ "0", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
          - !Ref 'AWS::NoValue'
        - !If
          - UseInstanceRoleTag2
          - Key: !Select [ "0", !Split [ "=", !Select [ "1", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
            Value: !Select [ "1", !Split [ "=", !Select [ "1", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
          - !Ref 'AWS::NoValue'
        - !If
          - UseInstanceRoleTag3
          - Key: !Select [ "0", !Split [ "=", !Select [ "2", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
            Value: !Select [ "1", !Split [ "=", !Select [ "2", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
          - !Ref 'AWS::NoValue'
        - !If
          - UseInstanceRoleTag4
          - Key: !Select [ "0", !Split [ "=", !Select [ "3", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
            Value: !Select [ "1", !Split [ "=", !Select [ "3", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
          - !Ref 'AWS::NoValue'
        - !If
          - UseInstanceRoleTag5
          - Key: !Select [ "0", !Split [ "=", !Select [ "4", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
            Value: !Select [ "1", !Split [ "=", !Select [ "4", !Split [ ",", !Join [ ",", [ !Ref InstanceRoleTags, "", "", "", "", "" ] ] ] ] ] ]
          - !Ref 'AWS::NoValue'

  IAMPolicies:
    Type: AWS::IAM::Policy
    Condition: CreateIAMRole
    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
              - logs:PutRetentionPolicy
            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
    UpdateReplacePolicy: Retain
    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
    UpdateReplacePolicy: Retain
    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
                DeleteOnTermination: !Ref RootVolumeDeleteOnTermination
                Throughput: !If [ IsRootVolumeIsGp3, !Ref RootVolumeThroughput, !Ref "AWS::NoValue" ]
                Iops: !If [ IsRootVolumeIsIo1OrIo2OrGp3, !Ref RootVolumeIops, !Ref "AWS::NoValue" ]
          CreditSpecification: !If
            - UsingBurstableInstances
            - CpuCredits: !Ref CpuCredits
            - !Ref "AWS::NoValue"
          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"
            - ResourceType: volume
              Tags:
                - Key: Name
                  Value: !If [ UseStackNameForInstanceName, !Ref "AWS::StackName", !Ref InstanceName ]
                - 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}"
                  $Env:DOCKER_NETWORKING_PROTOCOL="${DockerNetworkingProtocol}"
                  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.59.0"
                  $Env:BUILDKITE_STACK_DEPLOYED_BY="cloudformation"
                  $Env:BUILDKITE_SCALE_IN_IDLE_PERIOD="${ScaleInIdlePeriod}"
                  $Env:BUILDKITE_SECRETS_BUCKET="${LocalSecretsBucket}"
                  $Env:BUILDKITE_SECRETS_BUCKET_REGION="${LocalSecretsBucketRegion}"
                  $Env:BUILDKITE_SECRETS_PLUGIN_SKIP_SSH_KEY_NOT_FOUND_WARNING="${SecretsPluginSkipSSHKeyNotFoundWarning}"
                  $Env:BUILDKITE_ARTIFACTS_BUCKET="${ArtifactsBucket}"
                  $Env:BUILDKITE_S3_DEFAULT_REGION="${LocalArtifactsBucketRegion}"
                  $Env:BUILDKITE_S3_ACL="${ArtifactsS3ACL}"
                  $Env:BUILDKITE_AGENT_TOKEN_PATH="${AgentTokenPath}"
                  $Env:BUILDKITE_AGENTS_PER_INSTANCE="${AgentsPerInstance}"
                  $Env:BUILDKITE_AGENT_ENDPOINT="${AgentEndpoint}"
                  $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_SIGNING_KEY_PATH="${BuildkiteAgentSigningKeySSMParameter}"
                  $Env:BUILDKITE_AGENT_SIGNING_KEY_ID="${BuildkiteAgentSigningKeyID}"
                  $Env:BUILDKITE_AGENT_VERIFICATION_KEY_PATH="${BuildkiteAgentVerificationKeySSMParameter}"
                  $Env:BUILDKITE_AGENT_RELEASE="${BuildkiteAgentRelease}"
                  $Env:BUILDKITE_QUEUE="${BuildkiteQueue}"
                  $Env:BUILDKITE_AGENT_ENABLE_GIT_MIRRORS="${BuildkiteAgentEnableGitMirrors}"
                  $Env:BUILDKITE_ELASTIC_BOOTSTRAP_SCRIPT="${BootstrapScriptUrl}"
                  $Env:BUILDKITE_AGENT_SIGNING_KMS_KEY="${PipelineSigningKMSKey}"
                  $Env:BUILDKITE_AGENT_JOB_VERIFICATION_NO_SIGNATURE_BEHAVIOR="${PipelineSigningVerificationFailureBehavior}"
                  $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_AGENT_DISCONNECT_AFTER_UPTIME="${BuildkiteAgentDisconnectAfterUptime}"
                  $Env:BUILDKITE_TERMINATE_INSTANCE_ON_DISK_FULL="${BuildkiteTerminateInstanceOnDiskFull}"
                  $Env:BUILDKITE_PURGE_BUILDS_ON_DISK_FULL="${BuildkitePurgeBuildsOnDiskFull}"
                  $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:ECR_CREDENTIAL_HELPER_ENABLED="${EnableECRCredentialHelper}"
                  $Env:DOCKER_LOGIN_PLUGIN_ENABLED="${EnableDockerLoginPlugin}"
                  $Env:AWS_REGION="${AWS::Region}"
                  $Env:ENABLE_EC2_LOG_RETENTION_POLICY="${EnableEC2LogRetentionPolicy}"
                  $Env:EC2_LOG_RETENTION_DAYS="${EC2LogRetentionDays}"
                  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
                  LocalArtifactsBucketRegion: !If
                    - UseArtifactsBucket
                    - !If
                      - IsArtifactsBucketRegionEmpty
                      - !Ref "AWS::Region"
                      - !Ref ArtifactsBucketRegion
                    - !Ref "AWS::Region"
                  AgentTokenPath: !If
                    - UseCustomerManagedParameterPath
                    - !Ref BuildkiteAgentTokenParameterStorePath
                    - !Ref BuildkiteAgentTokenParameter
                  PipelineSigningKMSKey: !If
                    - CreatePipelineSigningKMSKey
                    - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${PipelineSigningKMSKey}
                    - !Ref PipelineSigningKMSKeyId
              - !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}" \
                  BUILDKITE_MOUNT_TMPFS_AT_TMP="${MountTmpfsAtTmp}" \
                    /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} \
                  DOCKER_PRUNE_UNTIL=${DockerPruneUntil} \
                  ENABLE_PRE_EXIT_DISK_CLEANUP=${EnablePreExitDiskCleanup} \
                  DOCKER_BUILDER_PRUNE_ENABLED=${DockerBuilderPruneEnabled} \
                  DOCKER_NETWORKING_PROTOCOL=${DockerNetworkingProtocol} \
                  DOCKER_IPV4_ADDRESS_POOL_1=${DockerIPv4AddressPool1} \
                  DOCKER_IPV4_ADDRESS_POOL_2=${DockerIPv4AddressPool2} \
                  DOCKER_IPV6_ADDRESS_POOL=${DockerIPv6AddressPool} \
                  DOCKER_FIXED_CIDR_V4="${DockerFixedCidrV4}" \
                  DOCKER_FIXED_CIDR_V6="${DockerFixedCidrV6}" \
                  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.59.0" \
                  BUILDKITE_STACK_DEPLOYED_BY="cloudformation" \
                  BUILDKITE_SCALE_IN_IDLE_PERIOD="${ScaleInIdlePeriod}" \
                  BUILDKITE_SECRETS_BUCKET="${LocalSecretsBucket}" \
                  BUILDKITE_SECRETS_BUCKET_REGION="${LocalSecretsBucketRegion}" \
                  BUILDKITE_SECRETS_PLUGIN_SKIP_SSH_KEY_NOT_FOUND_WARNING="${SecretsPluginSkipSSHKeyNotFoundWarning}" \
                  BUILDKITE_ARTIFACTS_BUCKET="${ArtifactsBucket}" \
                  BUILDKITE_S3_DEFAULT_REGION="${LocalArtifactsBucketRegion}" \
                  BUILDKITE_S3_ACL="${ArtifactsS3ACL}" \
                  BUILDKITE_AGENT_TOKEN_PATH="${AgentTokenPath}" \
                  BUILDKITE_AGENTS_PER_INSTANCE="${AgentsPerInstance}" \
                  BUILDKITE_AGENT_ENDPOINT="${AgentEndpoint}" \
                  BUILDKITE_AGENT_TAGS="${BuildkiteAgentTags}" \
                  BUILDKITE_AGENT_TIMESTAMP_LINES="${BuildkiteAgentTimestampLines}" \
                  BUILDKITE_AGENT_EXPERIMENTS="${BuildkiteAgentExperiments}" \
                  BUILDKITE_AGENT_TRACING_BACKEND="${BuildkiteAgentTracingBackend}" \
                  BUILDKITE_AGENT_SIGNING_KEY_PATH="${BuildkiteAgentSigningKeySSMParameter}" \
                  BUILDKITE_AGENT_SIGNING_KEY_ID="${BuildkiteAgentSigningKeyID}" \
                  BUILDKITE_AGENT_VERIFICATION_KEY_PATH="${BuildkiteAgentVerificationKeySSMParameter}" \
                  BUILDKITE_AGENT_RELEASE="${BuildkiteAgentRelease}" \
                  BUILDKITE_AGENT_CANCEL_GRACE_PERIOD="${BuildkiteAgentCancelGracePeriod}" \
                  BUILDKITE_AGENT_SIGNAL_GRACE_PERIOD_SECONDS="${BuildkiteAgentSignalGracePeriod}" \
                  BUILDKITE_AGENT_SIGNING_KMS_KEY="${PipelineSigningKMSKey}" \
                  BUILDKITE_AGENT_JOB_VERIFICATION_NO_SIGNATURE_BEHAVIOR="${PipelineSigningVerificationFailureBehavior}" \
                  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_AGENT_DISCONNECT_AFTER_UPTIME="${BuildkiteAgentDisconnectAfterUptime}" \
                  BUILDKITE_TERMINATE_INSTANCE_ON_DISK_FULL="${BuildkiteTerminateInstanceOnDiskFull}" \
                  BUILDKITE_PURGE_BUILDS_ON_DISK_FULL="${BuildkitePurgeBuildsOnDiskFull}" \
                  BUILDKITE_ADDITIONAL_SUDO_PERMISSIONS="${BuildkiteAdditionalSudoPermissions}" \
                  AWS_DEFAULT_REGION="${AWS::Region}" \
                  SECRETS_PLUGIN_ENABLED="${EnableSecretsPlugin}" \
                  ECR_PLUGIN_ENABLED="${EnableECRPlugin}" \
                  ECR_CREDENTIAL_HELPER_ENABLED="${EnableECRCredentialHelper}" \
                  DOCKER_LOGIN_PLUGIN_ENABLED="${EnableDockerLoginPlugin}" \
                  DOCKER_EXPERIMENTAL="${EnableDockerExperimental}" \
                  DOCKER_PRUNE_UNTIL="${DockerPruneUntil}" \
                  ENABLE_PRE_EXIT_DISK_CLEANUP="${EnablePreExitDiskCleanup}" \
                  DOCKER_BUILDER_PRUNE_ENABLED="${DockerBuilderPruneEnabled}" \
                  DOCKER_USERNS_REMAP=${EnableDockerUserNamespaceRemap} \
                  AWS_REGION="${AWS::Region}" \
                  ENABLE_RESOURCE_LIMITS="${ExperimentalEnableResourceLimits}" \
                  RESOURCE_LIMITS_MEMORY_HIGH="${ResourceLimitsMemoryHigh}" \
                  RESOURCE_LIMITS_MEMORY_MAX="${ResourceLimitsMemoryMax}" \
                  RESOURCE_LIMITS_MEMORY_SWAP_MAX="${ResourceLimitsMemorySwapMax}" \
                  RESOURCE_LIMITS_CPU_WEIGHT="${ResourceLimitsCPUWeight}" \
                  RESOURCE_LIMITS_CPU_QUOTA="${ResourceLimitsCPUQuota}" \
                  RESOURCE_LIMITS_IO_WEIGHT="${ResourceLimitsIOWeight}" \
                  ENABLE_EC2_LOG_RETENTION_POLICY="${EnableEC2LogRetentionPolicy}" \
                  EC2_LOG_RETENTION_DAYS="${EC2LogRetentionDays}" \
                    /usr/local/bin/bk-install-elastic-stack.sh
                  --==BOUNDARY==--
                - LocalSecretsBucket: !If
                    - CreateSecretsBucket
                    - !Ref ManagedSecretsBucket
                    - !Ref SecretsBucket
                  LocalSecretsBucketRegion: !If
                    - CreateSecretsBucket
                    - !Ref "AWS::Region"
                    - !Ref SecretsBucketRegion
                  LocalArtifactsBucketRegion: !If
                    - UseArtifactsBucket
                    - !If
                      - IsArtifactsBucketRegionEmpty
                      - !Ref "AWS::Region"
                      - !Ref ArtifactsBucketRegion
                    - !Ref "AWS::Region"
                  AgentTokenPath: !If
                    - UseCustomerManagedParameterPath
                    - !Ref BuildkiteAgentTokenParameterStorePath
                    - !Ref BuildkiteAgentTokenParameter
                  PipelineSigningKMSKey: !If
                    - CreatePipelineSigningKMSKey
                    - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${PipelineSigningKMSKey}
                    - !Ref PipelineSigningKMSKeyId

  AgentAutoScaleGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    DependsOn:
      - VpcComplete
    Properties:
      VPCZoneIdentifier: !If [ "CreateVpcResources", [ !Ref Subnet0, !Ref Subnet1 ], !Ref Subnets ]
      MixedInstancesPolicy:
        InstancesDistribution:
          OnDemandBaseCapacity: !Ref OnDemandBaseCapacity
          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"
          - !If
            - UseInstanceType5
            - InstanceType: !Select [ "4", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType6
            - InstanceType: !Select [ "5", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType7
            - InstanceType: !Select [ "6", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType8
            - InstanceType: !Select [ "7", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType9
            - InstanceType: !Select [ "8", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType10
            - InstanceType: !Select [ "9", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType11
            - InstanceType: !Select [ "10", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType12
            - InstanceType: !Select [ "11", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType13
            - InstanceType: !Select [ "12", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType14
            - InstanceType: !Select [ "13", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType15
            - InstanceType: !Select [ "14", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType16
            - InstanceType: !Select [ "15", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType17
            - InstanceType: !Select [ "16", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType18
            - InstanceType: !Select [ "17", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType19
            - InstanceType: !Select [ "18", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType20
            - InstanceType: !Select [ "19", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType21
            - InstanceType: !Select [ "20", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType22
            - InstanceType: !Select [ "21", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType23
            - InstanceType: !Select [ "22", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType24
            - InstanceType: !Select [ "23", !Split [ ",", !Join [ ",", [ !Ref InstanceTypes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] ] ] ]
            - !Ref "AWS::NoValue"
          - !If
            - UseInstanceType25
            - InstanceType: !Select [ "24", !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: !Ref InstanceScaleInProtection
      Tags:
        - Key: BuildkiteQueue
          PropagateAtLaunch: false
          Value: !Ref BuildkiteQueue
        - Key: AgentsPerInstance
          PropagateAtLaunch: false
          Value: !Ref AgentsPerInstance
    CreationPolicy:
      ResourceSignal:
        Timeout: !If [ UseDefaultInstanceCreationTimeout, !If [ UseWindowsAgents, PT10M, PT5M ], !Ref InstanceCreationTimeout ]
        Count: !Ref MinSize
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: true

  ScheduledScaleUpAction:
    Condition: EnableScheduledScaling
    Type: AWS::AutoScaling::ScheduledAction
    Properties:
      AutoScalingGroupName: !Ref AgentAutoScaleGroup
      ScheduledActionName: !Sub "${AWS::StackName}-ScaleUp"
      Recurrence: !Ref ScaleUpSchedule
      MinSize: !Ref ScaleUpMinSize
      TimeZone: !Ref ScheduleTimezone

  ScheduledScaleDownAction:
    Condition: EnableScheduledScaling
    Type: AWS::AutoScaling::ScheduledAction
    Properties:
      AutoScalingGroupName: !Ref AgentAutoScaleGroup
      ScheduledActionName: !Sub "${AWS::StackName}-ScaleDown"
      Recurrence: !Ref ScaleDownSchedule
      MinSize: !Ref ScaleDownMinSize
      TimeZone: !Ref ScheduleTimezone

  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.13'
      Architectures:
        - !Ref LambdaArchitecture

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

  StopBuildkiteAgentsRole:
    Type: AWS::IAM::Role
    Condition: EnableBuildkiteAgentGracefulShutdown
    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: DescribeASGs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "autoscaling:DescribeAutoScalingGroups"
                Resource: "*"
        - PolicyName: ModifyASGs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "autoscaling:UpdateAutoScalingGroup"
                Resource: !Sub arn:${AWS::Partition}:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${AWS::StackName}-AgentAutoScaleGroup-*
        - PolicyName: RunStopBuildkiteDocument
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "ssm:SendCommand"
                Resource:
                  - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}::document/AWS-RunShellScript
                  - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}::document/AWS-RunPowerShellScript
        - PolicyName: StopBuildkiteInstances
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "ssm:SendCommand"
                Resource:
                  - !Sub arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/*
                Condition:
                  StringEquals:
                    "aws:resourceTag/aws:cloudformation:logical-id": "AgentAutoScaleGroup"

  StopBuildkiteAgentsFunction:
    Type: AWS::Lambda::Function
    Condition: EnableBuildkiteAgentGracefulShutdown
    Properties:
      Description: Gracefully stops all Buildkite agents in a given Auto Scaling group.
      Code:
        ZipFile: |
          import boto3
          import logging
          import cfnresponse

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

          autoscaling_client = boto3.client("autoscaling")
          ssm_client = boto3.client("ssm")

          def handler(event, context):
              logger.info(f"Received event: {event}")

              # only trigger on update upon replacement events
              if event["RequestType"] == "Update":
                  try:
                      props = event["OldResourceProperties"]
                      autoscaling_group_name = props["AutoScalingGroupName"]

                      # Scale ASG down to zero, to allow Buildkite agents to terminate
                      force_instance_termination(autoscaling_group_name)

                      # Stop all Buildkite agents in the old ASG
                      stop_bk_agents(autoscaling_group_name)

                      # Send success response to CloudFormation
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, "CustomResourcePhysicalID")
                  except Exception as e:
                      logger.error(f"Error: {str(e)}")
                      cfnresponse.send(event, context, cfnresponse.FAILED, {"Error": str(e)}, "CustomResourcePhysicalID")
              else:
                  # For Create and Delete events, just send success response
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, "CustomResourcePhysicalID")

          def force_instance_termination(autoscaling_group_name):
              """Forces all EC2 instances to terminate in the specified Auto Scaling group by setting the desired capacity to zero."""
              logger.info(f"Setting the desired capacity of {autoscaling_group_name} to zero")
              autoscaling_client.update_auto_scaling_group(
                  AutoScalingGroupName=autoscaling_group_name,
                  MinSize=0,
                  DesiredCapacity=0
              )

          def stop_bk_agents(autoscaling_group_name):
              """Gracefully terminates Buildkite agents running in the given Auto Scaling Group."""
              stack_name = autoscaling_group_name.split("-AgentAutoScaleGroup")[0]

              logger.info(f"Stopping BK agents in {stack_name}")
              response = ssm_client.send_command(
                  Targets=[
                      {
                          "Key": "tag:aws:autoscaling:groupName",
                          "Values": [autoscaling_group_name]
                      }
                  ],
                  DocumentName="AWS-RunShellScript",
                  Comment=f"Stopping BK agents in {stack_name}",
                  Parameters={
                      "commands": ["sudo kill -s SIGTERM $(/bin/pidof buildkite-agent)"]
                  }
              )
              logger.info(f"SSM command response: {response}")
      Handler: index.handler
      Role: !GetAtt StopBuildkiteAgentsRole.Arn
      Runtime: "python3.13"
      Architectures:
        - !Ref LambdaArchitecture

  StopBuildkiteAgents:
    Type: AWS::CloudFormation::CustomResource
    Condition: EnableBuildkiteAgentGracefulShutdown
    Version: 1.0
    Properties:
      ServiceToken: !GetAtt StopBuildkiteAgentsFunction.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

  # Note: Autoscaling and AutoscalingArm64 resources are identical except for the
  # ApplicationId mapping key. This duplication is required because SAM Transform
  # only supports !Ref and !FindInMap in ApplicationId (no nested !If). Both resources
  # use LambdaArchitecture parameter via conditions to determine which is created.
  # IMPORTANT: Keep Parameters in sync between both resources!

  Autoscaling:
    Type: AWS::Serverless::Application
    Condition: HasVariableSizeAndX8664
    Properties:
      Location:
        ApplicationId: !FindInMap
          - AgentScalerARN
          - "x86-64"
          - ARN
        SemanticVersion: 1.11.2
      Parameters:
        BuildkiteAgentTokenParameter: !If [ UseCustomerManagedParameterPath, !Ref BuildkiteAgentTokenParameterStorePath, !Ref BuildkiteAgentTokenParameter ]
        BuildkiteAgentTokenParameterStoreKMSKey: !If [ UseCustomerManagedKeyForParameterStore, !Ref BuildkiteAgentTokenParameterStoreKMSKey, "" ]
        RolePermissionsBoundaryARN: !If [ SetInstanceRolePermissionsBoundaryARN, !Ref InstanceRolePermissionsBoundaryARN, "" ]
        ManagedPolicyARNs: !Join [ ',', !Ref ScalerManagedPolicyARNs ]
        AgentEndpoint: !Ref AgentEndpoint
        BuildkiteQueue: !Ref BuildkiteQueue
        AgentsPerInstance: !Ref AgentsPerInstance
        MinSize: !Ref MinSize
        MaxSize: !Ref MaxSize
        InstanceBuffer: !Ref InstanceBuffer
        AgentAutoScaleGroup: !Ref AgentAutoScaleGroup
        ScaleOutFactor: !Ref ScaleOutFactor
        ScaleOutForWaitingJobs: !Ref ScaleOutForWaitingJobs
        ScaleInCooldownPeriod: !Ref ScaleInCooldownPeriod
        ScaleOutCooldownPeriod: !Ref ScaleOutCooldownPeriod
        EventSchedulePeriod: !Ref ScalerEventSchedulePeriod
        MinPollInterval: !Ref ScalerMinPollInterval
        LogRetentionDays: !Ref LogRetentionDays
        EnableElasticCIMode: !Ref ScalerEnableExperimentalElasticCIMode
        DisableScaleIn: !Ref DisableScaleIn

  AutoscalingArm64:
    Type: AWS::Serverless::Application
    Condition: HasVariableSizeAndArm64
    Properties:
      Location:
        ApplicationId: !FindInMap
          - AgentScalerARN
          - "arm64"
          - ARN
        SemanticVersion: 1.11.2
      Parameters:
        BuildkiteAgentTokenParameter: !If [ UseCustomerManagedParameterPath, !Ref BuildkiteAgentTokenParameterStorePath, !Ref BuildkiteAgentTokenParameter ]
        BuildkiteAgentTokenParameterStoreKMSKey: !If [ UseCustomerManagedKeyForParameterStore, !Ref BuildkiteAgentTokenParameterStoreKMSKey, "" ]
        RolePermissionsBoundaryARN: !If [ SetInstanceRolePermissionsBoundaryARN, !Ref InstanceRolePermissionsBoundaryARN, "" ]
        ManagedPolicyARNs: !Join [ ',', !Ref ScalerManagedPolicyARNs ]
        AgentEndpoint: !Ref AgentEndpoint
        BuildkiteQueue: !Ref BuildkiteQueue
        AgentsPerInstance: !Ref AgentsPerInstance
        MinSize: !Ref MinSize
        MaxSize: !Ref MaxSize
        InstanceBuffer: !Ref InstanceBuffer
        AgentAutoScaleGroup: !Ref AgentAutoScaleGroup
        ScaleOutFactor: !Ref ScaleOutFactor
        ScaleOutForWaitingJobs: !Ref ScaleOutForWaitingJobs
        ScaleInCooldownPeriod: !Ref ScaleInCooldownPeriod
        ScaleOutCooldownPeriod: !Ref ScaleOutCooldownPeriod
        EventSchedulePeriod: !Ref ScalerEventSchedulePeriod
        MinPollInterval: !Ref ScalerMinPollInterval
        LogRetentionDays: !Ref LogRetentionDays
        EnableElasticCIMode: !Ref ScalerEnableExperimentalElasticCIMode
        DisableScaleIn: !Ref DisableScaleIn
