aws-cloudformation / cloudformation-coverage-roadmap

The AWS CloudFormation Public Coverage Roadmap
https://aws.amazon.com/cloudformation/
Creative Commons Attribution Share Alike 4.0 International
1.1k stars 53 forks source link

[AWS::CodeBuild::Fleet] - [Coverage] - add reserved capacity fleets with VPC-support #2047

Open rgoltz opened 1 month ago

rgoltz commented 1 month ago

Name of the resource

AWS::CodeBuild::Fleet

Resource name

No response

Description

Normally CodeBuild provides only on-demand fleets, which are destroyed when the build finishes. Now, CodeBuild also offers reserved capacity fleets with VPC-support (EC2 that are maintained by CodeBuild) - These hosts remain available to receive subsequent build requests, which reduces build start-up latencies:

Following the "Working with reserved capacity"-docs, the API Reference should already Support Reserved Capacity for CreateFleet, UpdateFleet and BatchGetFleets. In order to use this capability, please add support within CloudFormation.

Other Details

No response

FarrOut commented 1 month ago

FleetVpcConfig and OverflowBehavior has been implemented.

Please find test code below

class CodeBuildNest(NestedStack):

    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        subnet_id: str,
        removal_policy: RemovalPolicy = RemovalPolicy.RETAIN,
        vpc: ec2.IVpc = None,
        **kwargs,
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # subnet_id = str(vpc.private_subnets[0].subnet_id)
        subnet_arn = f"arn:aws:ec2:{self.region}:{self.account}:subnet/{subnet_id}"
        # subnet = ec2.Subnet.from_subnet_id(self, "subnet", subnet_id)

        role = iam.Role(
            self,
            "CodeBuildRole",
            assumed_by=iam.ServicePrincipal("codebuild.amazonaws.com"),
        )

        CfnOutput(self, "CodeBuildRoleArn", value=role.role_arn)

        policy = iam.Policy(
            self,
            "codebuild-fleet-policy",
            statements=[
                iam.PolicyStatement(
                    actions=[
                        "ec2:CreateNetworkInterface",
                        "ec2:DescribeDhcpOptions",
                        "ec2:DescribeNetworkInterfaces",
                        "ec2:DeleteNetworkInterface",
                        "ec2:DescribeSubnets",
                        "ec2:DescribeSecurityGroups",
                        "ec2:DescribeVpcs",
                    ],
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    actions=[
                        "ec2:CreateNetworkInterfacePermission",
                        "ec2:ModifyNetworkInterfaceAttribute",
                    ],
                    effect=iam.Effect.ALLOW,
                    resources=[
                        f"arn:aws:ec2:{self.region}:{self.account}:network-interface/*"
                    ],
                    conditions={
                        # Doesn't work for some reason
                        # "StringEquals": {
                        #     "ec2:AuthorizedService": "codebuild.amazonaws.com"
                        # },
                        "ArnEquals": {"ec2:Subnet": [subnet_arn]},
                    },
                ),
            ],
        )
        policy.attach_to_role(role)

        fleet = codebuild.CfnFleet(
            self,
            "MyCfnFleet",
            base_capacity=3,
            compute_type="BUILD_GENERAL1_SMALL",
            environment_type="LINUX_CONTAINER",            
            tags=[CfnTag(key="Name", value="MyLinuxFleet")],
        )
        fleet.apply_removal_policy(removal_policy)

        # To avoid "Not authorized to perform DescribeSecurityGroups"
        # https://stackoverflow.com/a/60776576
        fleet.add_depends_on(policy.node.default_child)

        fleet.add_override("Properties.FleetVpcConfig.VpcId", vpc.vpc_id)
        fleet.add_override(
            "Properties.FleetVpcConfig.Subnets",
            [subnet_id],
        )

        sg = ec2.SecurityGroup(self, "BuildFleetSecurityGroup", vpc=vpc)
        fleet.add_override(
            "Properties.FleetVpcConfig.SecurityGroupIds", [sg.security_group_id]
        )

        fleet.add_override(
            "Properties.FleetServiceRole",
            role.role_arn,
        )

        fleet.add_override("Properties.OverflowBehavior", "QUEUE")

        CfnOutput(self, "FleetId", value=fleet.ref)
        CfnOutput(self, "FleetArn", value=fleet.attr_arn)
        CfnOutput(self, "FleetName", value=str(fleet.name))
{
 "Resources": {
  "CodeBuildRole728CBADE": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "codebuild.amazonaws.com"
       }
      }
     ],
     "Version": "2012-10-17"
    }
   }
  },
  "codebuildfleetpolicyC442E78B": {
   "Type": "AWS::IAM::Policy",
   "Properties": {
    "PolicyDocument": {
     "Statement": [
      {
       "Action": [
        "ec2:CreateNetworkInterface",
        "ec2:DeleteNetworkInterface",
        "ec2:DescribeDhcpOptions",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcs"
       ],
       "Effect": "Allow",
       "Resource": "*"
      },
      {
       "Action": [
        "ec2:CreateNetworkInterfacePermission",
        "ec2:ModifyNetworkInterfaceAttribute"
       ],
       "Condition": {
        "ArnEquals": {
         "ec2:Subnet": [
          "arn:aws:ec2:eu-west-1:000000000000:subnet/subnet-xxxxxxxxxxxxxxxxx"
         ]
        }
       },
       "Effect": "Allow",
       "Resource": "arn:aws:ec2:eu-west-1:000000000000:network-interface/*"
      }
     ],
     "Version": "2012-10-17"
    },
    "PolicyName": "codebuildfleetpolicyC442E78B",
    "Roles": [
     {
      "Ref": "CodeBuildRole728CBADE"
     }
    ]
   }
  },
  "MyCfnFleet": {
   "Type": "AWS::CodeBuild::Fleet",
   "Properties": {
    "BaseCapacity": 3,
    "ComputeType": "BUILD_GENERAL1_SMALL",
    "EnvironmentType": "LINUX_CONTAINER",
    "Tags": [
     {
      "Key": "Name",
      "Value": "MyLinuxFleet"
     }
    ],
    "FleetVpcConfig": {
     "VpcId": {
      "Fn::ImportValue": "CdkPythonNetworkingStack:VpcId"
     },
     "Subnets": [
      "subnet-xxxxxxxxxxxxxxxxx"
     ],
     "SecurityGroupIds": [
      {
       "Fn::GetAtt": [
        "BuildFleetSecurityGroup647CEC48",
        "GroupId"
       ]
      }
     ]
    },
    "FleetServiceRole": {
     "Fn::GetAtt": [
      "CodeBuildRole728CBADE",
      "Arn"
     ]
    },
    "OverflowBehavior": "QUEUE"
   },
   "DependsOn": [
    "codebuildfleetpolicyC442E78B"
   ],
   "UpdateReplacePolicy": "Delete",
   "DeletionPolicy": "Delete"
  },
  "BuildFleetSecurityGroup647CEC48": {
   "Type": "AWS::EC2::SecurityGroup",
   "Properties": {
    "GroupDescription": "CodeBuildStack/CodeBuildNest/BuildFleetSecurityGroup",
    "SecurityGroupEgress": [
     {
      "CidrIp": "0.0.0.0/0",
      "Description": "Allow all outbound traffic by default",
      "IpProtocol": "-1"
     }
    ],
    "VpcId": {
     "Fn::ImportValue": "CdkPythonNetworkingStack:VpcId"
    }
   }
  }
 },
 "Outputs": {
  "CodeBuildRoleArn": {
   "Value": {
    "Fn::GetAtt": [
     "CodeBuildRole728CBADE",
     "Arn"
    ]
   }
  },
  "FleetId": {
   "Value": {
    "Ref": "MyCfnFleet"
   }
  },
  "FleetArn": {
   "Value": {
    "Fn::GetAtt": [
     "MyCfnFleet",
     "Arn"
    ]
   }
  },
  "FleetName": {
   "Value": "None"
  }
 }
}