Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing AWS::CodeBuild::Fleet ScalingConfiguration attribute #1998

Open
nmussy opened this issue Apr 7, 2024 · 3 comments
Open

Missing AWS::CodeBuild::Fleet ScalingConfiguration attribute #1998

nmussy opened this issue Apr 7, 2024 · 3 comments

Comments

@nmussy
Copy link

nmussy commented Apr 7, 2024

Name of the resource

ScalingConfiguration

Resource name

AWS::CodeBuild::Fleet

Description

ScalingConfiguration

This field is currently implemented as scalingConfiguration in the API:

Other Details

This coverage gap is going to prevent me from being able to fully implement the CDK Fleet L2 Construct, see aws/aws-cdk#29754

@rgoltz
Copy link

rgoltz commented May 24, 2024

Hi Jimmy @nmussy - There is a update on the CloudFormation definition/documentation for AWS::CodeBuild::Fleet 👍. I can already see those props in CFN:

It seems to move fast 💯 - Just scalingConfiguration is pending/missing in CFN, isn't it?

@nmussy nmussy changed the title Missing AWS::CodeBuild::Fleet OverflowBehavior and ScalingConfiguration attributes Missing AWS::CodeBuild::Fleet ScalingConfiguration attribute May 25, 2024
@nmussy
Copy link
Author

nmussy commented May 25, 2024

I've updated the issue, thanks for letting me know

@FarrOut
Copy link

FarrOut commented Jun 2, 2024

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.

CDK code

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,
        )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
coverage-roadmap
  
Researching
Development

No branches or pull requests

3 participants