AWSTemplateFormatVersion: "2010-09-09"

Description: Amazon EKS - Windows Node Group.

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: EKS Cluster
        Parameters:
          - ClusterName
          - ClusterControlPlaneSecurityGroup
      - Label:
          default: Worker Node Configuration
        Parameters:
          - NodeGroupName
          - NodeAutoScalingGroupMinSize
          - NodeAutoScalingGroupDesiredCapacity
          - NodeAutoScalingGroupMaxSize
          - NodeInstanceType
          - NodeImageIdSSMParam
          - NodeImageId
          - NodeVolumeSize
          - KeyName
          - BootstrapArguments
          - DisableIMDSv1
      - Label:
          default: Worker Network Configuration
        Parameters:
          - VpcId
          - NodeSecurityGroups
          - Subnets

Parameters:
  BootstrapArguments:
    Type: String
    Default: ""
    Description: "Arguments to pass to the bootstrap script."

  ClusterControlPlaneSecurityGroup:
    Type: "AWS::EC2::SecurityGroup::Id"
    Description: The security group of the cluster control plane.

  NodeSecurityGroups:
    Type: "List<AWS::EC2::SecurityGroup::Id>"
    Description: The security group (or groups) assigned to the Linux worker nodes. For clusters created with eksctl, specify both the Linux node security group and the cluster shared node security group for inter-node communication.

  ClusterName:
    Type: String
    Description: The cluster name provided when the cluster was created. If it is incorrect, nodes will not be able to join the cluster.

  KeyName:
    Type: "AWS::EC2::KeyPair::KeyName"
    Description: The EC2 Key Pair to allow SSH access to the instances

  NodeAutoScalingGroupDesiredCapacity:
    Type: Number
    Default: 3
    Description: Desired capacity of Node Group ASG.

  NodeAutoScalingGroupMaxSize:
    Type: Number
    Default: 4
    Description: Maximum size of Node Group ASG. Set to at least 1 greater than NodeAutoScalingGroupDesiredCapacity.

  NodeAutoScalingGroupMinSize:
    Type: Number
    Default: 1
    Description: Minimum size of Node Group ASG.

  NodeGroupName:
    Type: String
    Description: Unique identifier for the Node Group.

  NodeImageId:
    Type: String
    Default: ""
    Description: (Optional) Specify your own custom image ID. This value overrides any AWS Systems Manager Parameter Store value specified above.

  NodeImageIdSSMParam:
    Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
    Default: /aws/service/ami-windows-latest/Windows_Server-2019-English-Core-EKS_Optimized-1.17/image_id 
    Description: AWS Systems Manager Parameter Store parameter of the AMI ID for the Windows worker node instances.

  DisableIMDSv1:
    Type: String
    Default: "false"
    AllowedValues:
      - "false"
      - "true"

  NodeInstanceType:
    Type: String
    Default: m5.large
    AllowedValues:
      - c1.medium
      - c1.xlarge
      - c5.12xlarge
      - c5.18xlarge
      - c5.24xlarge
      - c5.2xlarge
      - c5.4xlarge
      - c5.9xlarge
      - c5.large
      - c5.metal
      - c5.xlarge
      - c5d.18xlarge
      - c5d.2xlarge
      - c5d.4xlarge
      - c5d.9xlarge
      - c5d.large
      - c5d.xlarge
      - c5n.18xlarge
      - c5n.2xlarge
      - c5n.4xlarge
      - c5n.9xlarge
      - c5n.large
      - c5n.xlarge
      - cc2.8xlarge
      - h1.16xlarge
      - h1.2xlarge
      - h1.4xlarge
      - h1.8xlarge
      - i3.16xlarge
      - i3.2xlarge
      - i3.4xlarge
      - i3.8xlarge
      - i3.large
      - i3.metal
      - i3.xlarge
      - i3en.12xlarge
      - i3en.24xlarge
      - i3en.2xlarge
      - i3en.3xlarge
      - i3en.6xlarge
      - i3en.large
      - i3en.xlarge
      - m1.large
      - m1.medium
      - m1.small
      - m1.xlarge
      - m2.2xlarge
      - m2.4xlarge
      - m2.xlarge
      - m3.2xlarge
      - m3.large
      - m3.medium
      - m3.xlarge
      - m4.16xlarge
      - m5.12xlarge
      - m5.16xlarge
      - m5.24xlarge
      - m5.2xlarge
      - m5.4xlarge
      - m5.8xlarge
      - m5.large
      - m5.metal
      - m5.xlarge
      - m5a.12xlarge
      - m5a.16xlarge
      - m5a.24xlarge
      - m5a.2xlarge
      - m5a.4xlarge
      - m5a.8xlarge
      - m5a.large
      - m5a.xlarge
      - m5ad.24xlarge
      - m5ad.2xlarge
      - m5ad.4xlarge
      - m5ad.large
      - m5ad.xlarge
      - m5d.12xlarge
      - m5d.16xlarge
      - m5d.24xlarge
      - m5d.2xlarge
      - m5d.4xlarge
      - m5d.8xlarge
      - m5d.large
      - m5d.metal
      - m5d.xlarge
      - r4.16xlarge
      - r4.2xlarge
      - r4.4xlarge
      - r4.8xlarge
      - r4.large
      - r4.xlarge
      - r5.12xlarge
      - r5.16xlarge
      - r5.24xlarge
      - r5.2xlarge
      - r5.4xlarge
      - r5.8xlarge
      - r5.large
      - r5.metal
      - r5.xlarge
      - r5a.12xlarge
      - r5a.16xlarge
      - r5a.24xlarge
      - r5a.2xlarge
      - r5a.4xlarge
      - r5a.8xlarge
      - r5a.large
      - r5a.xlarge
      - r5ad.12xlarge
      - r5ad.24xlarge
      - r5ad.2xlarge
      - r5ad.4xlarge
      - r5ad.large
      - r5ad.xlarge
      - r5d.12xlarge
      - r5d.16xlarge
      - r5d.24xlarge
      - r5d.2xlarge
      - r5d.4xlarge
      - r5d.8xlarge
      - r5d.large
      - r5d.metal
      - r5d.xlarge
      - t1.micro
      - t2.2xlarge
      - t2.large
      - t2.medium
      - t2.micro
      - t2.nano
      - t2.small
      - t2.xlarge
      - t3.2xlarge
      - t3.large
      - t3.medium
      - t3.micro
      - t3.nano
      - t3.small
      - t3.xlarge
      - t3a.2xlarge
      - t3a.large
      - t3a.medium
      - t3a.micro
      - t3a.nano
      - t3a.small
      - t3a.xlarge
      - x1.16xlarge
      - x1.32xlarge
      - x1e.16xlarge
      - x1e.2xlarge
      - x1e.32xlarge
      - x1e.4xlarge
      - x1e.8xlarge
      - x1e.xlarge
      - z1d.12xlarge
      - z1d.2xlarge
      - z1d.3xlarge
      - z1d.6xlarge
      - z1d.large
      - z1d.metal
      - z1d.xlarge
    ConstraintDescription: Must be a valid EC2 instance type
    Description: EC2 instance type for the node instances

  NodeVolumeSize:
    Type: Number
    Default: 50
    Description: Node volume size (should be at least 50 gb)

  Subnets:
    Type: "List<AWS::EC2::Subnet::Id>"
    Description: The subnets to launch the worker nodes into.

  VpcId:
    Type: "AWS::EC2::VPC::Id"
    Description: The VPC of the Linux worker nodes

Mappings:
  PartitionMap:
    aws:
      EC2ServicePrincipal: "ec2.amazonaws.com"
    aws-us-gov:
      EC2ServicePrincipal: "ec2.amazonaws.com"
    aws-cn:
      EC2ServicePrincipal: "ec2.amazonaws.com.cn"

Conditions:
  HasNodeImageId: !Not
    - "Fn::Equals":
        - Ref: NodeImageId
        - ""

  IMDSv1Disabled:
    "Fn::Equals":
      - !Ref DisableIMDSv1
      - "true"

Resources:
  NodeInstanceRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !FindInMap [PartitionMap, !Ref "AWS::Partition", EC2ServicePrincipal]
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy"
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEKS_CNI_Policy"
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
      Path: /

  NodeInstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Path: /
      Roles:
        - Ref: NodeInstanceRole

  NodeLaunchTemplate:
    Type: "AWS::EC2::LaunchTemplate"
    Properties:
      LaunchTemplateData:
        BlockDeviceMappings:
          - DeviceName: /dev/sda1
            Ebs:
              DeleteOnTermination: true
              VolumeSize: !Ref NodeVolumeSize
              VolumeType: gp2
        IamInstanceProfile:
          Arn: !GetAtt NodeInstanceProfile.Arn
        ImageId: !If
          - HasNodeImageId
          - Ref: NodeImageId
          - Ref: NodeImageIdSSMParam
        InstanceType: !Ref NodeInstanceType
        KeyName: !Ref KeyName
        SecurityGroupIds: !Ref NodeSecurityGroups
        UserData: !Base64
          "Fn::Sub": |
              <powershell>
              [string]$EKSBinDir = "$env:ProgramFiles\Amazon\EKS"
              [string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1'
              [string]$EKSBootstrapScriptFile = "$EKSBinDir\$EKSBootstrapScriptName"
              [string]$cfn_signal = "$env:ProgramFiles\Amazon\cfn-bootstrap\cfn-signal.exe"
              & $EKSBootstrapScriptFile -EKSClusterName ${ClusterName} ${BootstrapArguments} 3>&1 4>&1 5>&1 6>&1
              $LastError = if ($?) { 0 } else { $Error[0].Exception.HResult }
              & $cfn_signal --exit-code=$LastError `
                --stack="${AWS::StackName}" `
                --resource="NodeGroup" `
                --region=${AWS::Region}
              </powershell>
        MetadataOptions:
          HttpPutResponseHopLimit : 2
          HttpEndpoint: enabled
          HttpTokens: !If
            - IMDSv1Disabled
            - required
            - optional

  NodeGroup:
    Type: "AWS::AutoScaling::AutoScalingGroup"
    Properties:
      DesiredCapacity: !Ref NodeAutoScalingGroupDesiredCapacity
      LaunchTemplate:
        LaunchTemplateId: !Ref NodeLaunchTemplate
        Version: !GetAtt NodeLaunchTemplate.LatestVersionNumber
      MaxSize: !Ref NodeAutoScalingGroupMaxSize
      MinSize: !Ref NodeAutoScalingGroupMinSize
      Tags:
        - Key: Name
          PropagateAtLaunch: "true"
          Value: !Sub ${ClusterName}-${NodeGroupName}-Node
        - Key: !Sub kubernetes.io/cluster/${ClusterName}
          PropagateAtLaunch: "true"
          Value: owned
      VPCZoneIdentifier: !Ref Subnets
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MaxBatchSize: "1"
        MinInstancesInService: !Ref NodeAutoScalingGroupDesiredCapacity
        PauseTime: PT5M

Outputs:
  NodeInstanceRole:
    Description: The node instance role
    Value: !GetAtt NodeInstanceRole.Arn

  NodeAutoScalingGroup:
    Description: The autoscaling group
    Value: !Ref NodeGroup