Skip to content

Commit

Permalink
Add AWS CodeBuild Project
Browse files Browse the repository at this point in the history
This will create an Embedded Linux ready AWS CodeBuild project that can be used to connect to a source e.g. GitHub Actions
  • Loading branch information
thomas-roos committed Oct 15, 2024
1 parent 0d4acaa commit 6a73273
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 61 deletions.
2 changes: 1 addition & 1 deletion assets/build-image/ubuntu_22_04/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gawk wget git diffstat unzip texinfo gcc build-essential chrpath \
socat cpio python3 python3-pip python3-pexpect xz-utils debianutils \
iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev \
python3-subunit mesa-common-dev zstd liblz4-tool file locales
python3-subunit mesa-common-dev zstd liblz4-tool file locales xterm

# Install packages used elsewhere in the build
RUN apt-get install -y --no-install-recommends \
Expand Down
2 changes: 1 addition & 1 deletion lib/build-image-repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class BuildImageRepoStack extends cdk.Stack {

this.repository = new ecr.Repository(this, 'BuildImageRepo', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteImages: true,
emptyOnDelete: true,
});
}
}
2 changes: 2 additions & 0 deletions lib/constructs/source-repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export enum ProjectKind {
Renesas = 'renesas',
/** Build an IMX image using NXP layers. */
NxpImx = 'nxp-imx',
/** Build no pipeline, just CodeBuild project to connect with GitHub actions. */
CodeBuild = 'codebuild',
}

export interface SourceRepoProps extends cdk.StackProps {
Expand Down
303 changes: 303 additions & 0 deletions lib/embedded-linux-codebuild-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as events from "aws-cdk-lib/aws-events";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as iam from "aws-cdk-lib/aws-iam";
import * as efs from "aws-cdk-lib/aws-efs";
import * as kms from "aws-cdk-lib/aws-kms";
import * as s3 from "aws-cdk-lib/aws-s3";

import {
BuildSpec,
ComputeType,
FileSystemLocation,
LinuxBuildImage,
Project,
} from "aws-cdk-lib/aws-codebuild";
import { IRepository } from "aws-cdk-lib/aws-ecr";

import {
ISecurityGroup,
IVpc,
Peer,
Port,
SecurityGroup,
} from "aws-cdk-lib/aws-ec2";
import { SourceRepo, ProjectKind } from "./constructs/source-repo";

Check warning on line 27 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (16.x)

'SourceRepo' is defined but never used

Check warning on line 27 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'SourceRepo' is defined but never used

Check warning on line 27 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'SourceRepo' is defined but never used

Check warning on line 27 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'SourceRepo' is defined but never used

Check warning on line 27 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'SourceRepo' is defined but never used
import { VMImportBucket } from "./vm-import-bucket";
import { Asset } from "aws-cdk-lib/aws-s3-assets";
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
import { RemovalPolicy } from "aws-cdk-lib";

/**
* Properties to allow customizing the build.
*/
export interface EmbeddedLinuxCodebuildProjectProps
extends cdk.StackProps {
/** ECR Repository where the Build Host Image resides. */
readonly imageRepo: IRepository;
/** Tag for the Build Host Image */
readonly imageTag?: string;
/** VPC where the networking setup resides. */
readonly vpc: IVpc;
/** The type of project being built. */
readonly projectKind?: ProjectKind;
/** A name for the layer-repo that is created. Default is 'layer-repo' */
readonly layerRepoName?: string;
/** Additional policy statements to add to the build project. */
readonly buildPolicyAdditions?: iam.PolicyStatement[];
/** Access logging bucket to use */
readonly accessLoggingBucket?: s3.Bucket;
/** Access logging prefix to use */
readonly serverAccessLogsPrefix?: string;
/** Artifact bucket to use */
readonly artifactBucket?: s3.Bucket;
/** Output bucket to use */
readonly outputBucket?: s3.Bucket | VMImportBucket;
/** Prefix for S3 object within bucket */
readonly subDirectoryName?: string;
}

/**
* The stack for creating a build pipeline.
*
* See {@link EmbeddedLinuxCodebuildProjectProps} for configration options.
*/
export class EmbeddedLinuxCodebuildProjectStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props: EmbeddedLinuxCodebuildProjectProps
) {
super(scope, id, props);

/** Set up networking access and EFS FileSystems. */

const projectSg = new SecurityGroup(this, "BuildProjectSecurityGroup", {
vpc: props.vpc,
description: "Security Group to allow attaching EFS",
});
projectSg.addIngressRule(
Peer.ipv4(props.vpc.vpcCidrBlock),
Port.tcp(2049),
"NFS Mount Port"
);

const sstateFS = this.addFileSystem("SState", props.vpc, projectSg);
const dlFS = this.addFileSystem("Downloads", props.vpc, projectSg);
const tmpFS = this.addFileSystem("Temp", props.vpc, projectSg);

let outputBucket: s3.IBucket | VMImportBucket;
let environmentVariables = {};

Check failure on line 92 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (16.x)

'environmentVariables' is never reassigned. Use 'const' instead

Check failure on line 92 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'environmentVariables' is never reassigned. Use 'const' instead

Check failure on line 92 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'environmentVariables' is never reassigned. Use 'const' instead

Check failure on line 92 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'environmentVariables' is never reassigned. Use 'const' instead

Check failure on line 92 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'environmentVariables' is never reassigned. Use 'const' instead
let scriptAsset!: Asset;

Check warning on line 93 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (16.x)

'scriptAsset' is defined but never used

Check warning on line 93 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'scriptAsset' is defined but never used

Check warning on line 93 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'scriptAsset' is defined but never used

Check warning on line 93 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'scriptAsset' is defined but never used

Check warning on line 93 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'scriptAsset' is defined but never used
let accessLoggingBucket: s3.IBucket;

if (props.accessLoggingBucket) {
accessLoggingBucket = props.accessLoggingBucket;
} else {
accessLoggingBucket = new s3.Bucket(this, "ArtifactAccessLogging", {
versioned: true,
enforceSSL: true,
});
}

if (props.outputBucket) {
outputBucket = props.outputBucket;
} else {
outputBucket = new s3.Bucket(this, "PipelineOutput", {

Check warning on line 108 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (16.x)

'outputBucket' is assigned a value but never used

Check warning on line 108 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'outputBucket' is assigned a value but never used

Check warning on line 108 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'outputBucket' is assigned a value but never used

Check warning on line 108 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'outputBucket' is assigned a value but never used

Check warning on line 108 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'outputBucket' is assigned a value but never used
versioned: true,
enforceSSL: true,
serverAccessLogsBucket: accessLoggingBucket,
});
}

let artifactBucket: s3.IBucket;

if (props.artifactBucket) {
artifactBucket = props.artifactBucket;
} else {
const encryptionKey = new kms.Key(this, "PipelineArtifactKey", {
removalPolicy: RemovalPolicy.DESTROY,
enableKeyRotation: true,
});
artifactBucket = new s3.Bucket(this, "PipelineArtifacts", {

Check warning on line 124 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (16.x)

'artifactBucket' is assigned a value but never used

Check warning on line 124 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'artifactBucket' is assigned a value but never used

Check warning on line 124 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'artifactBucket' is assigned a value but never used

Check warning on line 124 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'artifactBucket' is assigned a value but never used

Check warning on line 124 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'artifactBucket' is assigned a value but never used
versioned: true,
enforceSSL: true,
serverAccessLogsBucket: accessLoggingBucket,
encryptionKey,
encryption: s3.BucketEncryption.KMS,
blockPublicAccess: new s3.BlockPublicAccess(
s3.BlockPublicAccess.BLOCK_ALL
),
});
}
/** Create our CodeBuild Project. */
const project = new Project(
this,
"EmbeddedLinuxCodebuildProject",
{
buildSpec: BuildSpec.fromObject({
version: "0.2",
phases: {
build: {
commands: ['echo "DUMMY BUILDSPEC - can not be empty"'],
},
},
artifacts: {
files: ["**/*"],
"base-directory": ".",
},
}),
environment: {
computeType: ComputeType.X2_LARGE,
buildImage: LinuxBuildImage.fromEcrRepository(
props.imageRepo,
props.imageTag
),
privileged: true,
environmentVariables,
},
timeout: cdk.Duration.hours(4),
vpc: props.vpc,
securityGroups: [projectSg],
fileSystemLocations: [
FileSystemLocation.efs({
identifier: "tmp_dir",
location: tmpFS,
mountPoint: "/build-output",
}),
FileSystemLocation.efs({
identifier: "sstate_cache",
location: sstateFS,
mountPoint: "/sstate-cache",
}),
FileSystemLocation.efs({
identifier: "dl_dir",
location: dlFS,
mountPoint: "/downloads",
}),
],
logging: {
cloudWatch: {
logGroup: new LogGroup(this, "PipelineBuildLogs", {
retention: RetentionDays.TEN_YEARS,
}),
},
},
}
);

if (props.buildPolicyAdditions) {
props.buildPolicyAdditions.map((p) => project.addToRolePolicy(p));
}

project.addToRolePolicy(this.addProjectPolicies());

project.role?.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSCodeBuildAdminAccess")
);

/** Here we create the logic to check for presence of ECR image on the CodePipeline automatic triggering upon resource creation,
* and stop the execution if the image does not exist. */
const fnOnPipelineCreate = new lambda.Function(
this,
"OSImageCheckOnStart",
{
runtime: lambda.Runtime.PYTHON_3_10,
handler: "index.handler",
code: lambda.Code.fromInline(`
import boto3
import json
ecr_client = boto3.client('ecr')
codepipeline_client = boto3.client('codepipeline')
def handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
response = ecr_client.describe_images(repositoryName='${props.imageRepo.repositoryName}', filter={'tagStatus': 'TAGGED'})
for i in response['imageDetails']:
if '${props.imageTag}' in i['imageTags']:
break
else:
print('OS image not found. Stopping execution.')
response = codepipeline_client.stop_pipeline_execution(
pipelineName=event['detail']['pipeline'],
pipelineExecutionId=event['detail']['execution-id'],
abandon=True,
reason='OS image not found in ECR repository. Stopping pipeline until image is present.')
`),
logRetention: RetentionDays.TEN_YEARS,
}
);

const pipelineCreateRule = new events.Rule(this, "OnPipelineStartRule", {
eventPattern: {
detailType: ["CodePipeline Pipeline Execution State Change"],
source: ["aws.codepipeline"],
detail: {
state: ["STARTED"],
"execution-trigger": {
"trigger-type": ["CreatePipeline"],
},
},
},
});
pipelineCreateRule.addTarget(
new targets.LambdaFunction(fnOnPipelineCreate)
);
const ecrPolicy = new iam.PolicyStatement({

Check warning on line 249 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (16.x)

'ecrPolicy' is assigned a value but never used

Check warning on line 249 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'ecrPolicy' is assigned a value but never used

Check warning on line 249 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'ecrPolicy' is assigned a value but never used

Check warning on line 249 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (18.x)

'ecrPolicy' is assigned a value but never used

Check warning on line 249 in lib/embedded-linux-codebuild-project.ts

View workflow job for this annotation

GitHub Actions / Run-CDK-Tests (20.x)

'ecrPolicy' is assigned a value but never used
actions: ["ecr:DescribeImages"],
resources: [props.imageRepo.repositoryArn],
});
}

/**
* Adds an EFS FileSystem to the VPC and SecurityGroup.
*
* @param name - A name to differentiate the filesystem.
* @param vpc - The VPC the Filesystem resides in.
* @param securityGroup - A SecurityGroup to allow access to the filesystem from.
* @returns The filesystem location URL.
*
*/
private addFileSystem(
name: string,
vpc: IVpc,
securityGroup: ISecurityGroup
): string {
const fs = new efs.FileSystem(
this,
`EmbeddedLinuxPipeline${name}Filesystem`,
{
vpc,
removalPolicy: cdk.RemovalPolicy.DESTROY,
}
);

fs.connections.allowFrom(securityGroup, Port.tcp(2049));

const fsId = fs.fileSystemId;
const region = cdk.Stack.of(this).region;

return `${fsId}.efs.${region}.amazonaws.com:/`;
}

private addProjectPolicies(): iam.PolicyStatement {
return new iam.PolicyStatement({
actions: [
"ec2:DescribeSecurityGroups",
"codestar-connections:GetConnection",
"codestar-connections:GetConnectionToken",
"codeconnections:GetConnectionToken",
"codeconnections:GetConnection",
"codeconnections:UseConnection",
"codebuild:ListConnectedOAuthAccounts",
"codebuild:ListRepositories",
"codebuild:PersistOAuthToken",
"codebuild:ImportSourceCredentials",
],
resources: ["*"],
});
}
}
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './build-image-data';
export * from './build-image-repo';
export * from './build-image-pipeline';
export * from './embedded-linux-pipeline';
export * from './embedded-linux-codebuild-project';
export * from './constructs/source-repo';
Loading

0 comments on commit 6a73273

Please sign in to comment.