Open nmussy opened 2 months ago
Hi Jimmy @nmussy - There is a update on the CloudFormation definition/documentation for AWS::CodeBuild::Fleet
👍. I can already see those props in CFN:
OverflowBehavior
FleetVpcConfig
, which I requested via https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/2047It seems to move fast 💯 - Just scalingConfiguration
is pending/missing in CFN, isn't it?
I've updated the issue, thanks for letting me know
I have developed a workaround using a custom resource to call the UpdateFleet API call after Cloudformation has provisioned the Fleet. Not pretty, but it works. Like me.
codebuild_nest.py
from aws_cdk import (
# Duration,
NestedStack,
CfnTag,
CfnOutput,
aws_codebuild as codebuild,
aws_ec2 as ec2,
Lazy,
aws_iam as iam,
RemovalPolicy,
)
from constructs import Construct
from motley.components.CICD.codebuild_updater import CodeBuildUpdater
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_arn = f"arn:aws:ec2:{self.region}:{self.account}:subnet/{subnet_id}"
role = CodeBuildSecurityNest(
self,
"CodeBuildSecurityNest",
subnet_id=subnet_id,
removal_policy=removal_policy,
).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_depends_on(role.node.default_child)
fleet.add_depends_on(role.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))
# Update fleet to enable ScalingConfiguration
# DEMO
params = {
"arn": fleet.attr_arn,
"scalingConfiguration": {
"desiredCapacity": 6,
"maxCapacity": 12,
"scalingType": "TARGET_TRACKING_SCALING",
"targetTrackingScalingConfigs": [
{"metricType": "FLEET_UTILIZATION_RATE", "targetValue": 80.0}
],
},
}
updater = CodeBuildUpdater(
self,
"CodeBuildUpdater",
parameters=params,
service_role=role,
)
# Ensure Updater runs AFTER resource has been created.
updater.node.add_dependency(fleet)
class CodeBuildSecurityNest(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_arn = f"arn:aws:ec2:{self.region}:{self.account}:subnet/{subnet_id}"
CfnOutput(self, "subnet_arn", value=subnet_arn)
self.role = iam.Role(
self,
"CodeBuildRole",
assumed_by=iam.CompositePrincipal(
iam.ServicePrincipal("codebuild.amazonaws.com"),
iam.ServicePrincipal("lambda.amazonaws.com"),
),
)
self.role.apply_removal_policy(removal_policy)
CfnOutput(self, "CodeBuildRoleArn", value=self.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(self.role)
codebuild_updater.py
from aws_cdk import (
# Duration,
NestedStack,
CfnTag,
CfnOutput,
aws_iam as iam,
aws_codebuild as codebuild,
aws_ec2 as ec2,
custom_resources as cr,
Duration,
aws_lambda as lambda_,
aws_logs as logs,
Lazy,
aws_iam as iam,
RemovalPolicy,
)
from constructs import Construct
from datetime import datetime
class CodeBuildUpdater(Construct):
def __init__(
self,
scope: Construct,
construct_id: str,
parameters: dict,
service_role: iam.IRole,
removal_policy: RemovalPolicy = RemovalPolicy.DESTROY,
**kwargs,
) -> None:
super().__init__(scope, construct_id, **kwargs)
self.log_group = logs.LogGroup(
self,
"LogGroup",
retention=logs.RetentionDays.ONE_WEEK,
removal_policy=removal_policy,
)
CfnOutput(
self,
"LogGroupArn",
description="The ARN of this log group.",
value=self.log_group.log_group_arn,
)
CfnOutput(
self,
"LogGroupName",
description="The name of this log group.",
value=self.log_group.log_group_name,
)
custom_resource = cr.AwsCustomResource(
self,
"UpdateFleet",
log_group=self.log_group,
role=service_role,
on_update=cr.AwsSdkCall( # will also be called for a CREATE event
service="codebuild",
action="UpdateFleet",
parameters=parameters,
physical_resource_id=cr.PhysicalResourceId.of(f"{datetime.now()}"),
),
policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE
),
install_latest_aws_sdk=False,
timeout=Duration.seconds(5),
removal_policy=removal_policy,
)
Name of the resource
ScalingConfiguration
Resource name
AWS::CodeBuild::Fleet
Description
ScalingConfiguration
This field is currently implemented as
scalingConfiguration
in the API:codebuild:CreateFleet
codebuild:UpdateFleet
Other Details
This coverage gap is going to prevent me from being able to fully implement the CDK
Fleet
L2 Construct, see https://github.com/aws/aws-cdk/pull/29754