Skip to content

Commit

Permalink
feat: add 2 CodeBuild projects for GitHub runners
Browse files Browse the repository at this point in the history
- One for Lambda
- One for standard Linux
  • Loading branch information
eoinsha committed Oct 2, 2024
1 parent 7330535 commit d86ba94
Show file tree
Hide file tree
Showing 17 changed files with 2,901 additions and 2 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/build-on-lambda.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Build

on:
push:
branches:
- "*"

jobs:
build:
runs-on:
- codebuild-gha-runners-lambda-${{ github.run_id }}-${{ github.run_attempt }}
- instance-size:10GB

steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
name: Lint
- name: Build TypeScript
run: npm run build
- name: CDK Synth
run: npx cdk synth --context githuborg=test
12 changes: 12 additions & 0 deletions .github/workflows/manual-lambda.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Manual Lambda

on: workflow_dispatch

jobs:
build:
runs-on:
- codebuild-gha-runners-lambda-${{ github.run_id }}-${{ github.run_attempt }}

steps:
- name: sleep
run: sleep 60
12 changes: 12 additions & 0 deletions .github/workflows/manual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Manual Standard

on: workflow_dispatch

jobs:
build:
runs-on:
- codebuild-gha-runners-${{ github.run_id }}-${{ github.run_attempt }}

steps:
- name: sleep
run: sleep 60
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out

*.sw?
import-source-credentials.json
.vscode/
6 changes: 6 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# codebuild-gha-runners
CodeBuild Runners for GitHub Actions
# GitHub Actions Runners with CodeBuild ⚡️ 👷‍♀️

10 changes: 10 additions & 0 deletions bin/gha-runners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import { CodeConnectionStack } from '../lib/connconnection-stack'
import { GhaRunnersStack } from '../lib/gha-runners-stack'

const app = new cdk.App()
const connectionStack = new CodeConnectionStack(app, 'ConnectionStack', {})
const ghaRunnersStack = new GhaRunnersStack(app, 'GhaRunnersStack', {})
ghaRunnersStack.addDependency(connectionStack, 'Connection must be created first and be approved in AWS Console')
37 changes: 37 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.2/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 120
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded",
"trailingCommas": "none"
}
}
}
67 changes: 67 additions & 0 deletions cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"app": "npx ts-node --prefer-ts-exts bin/gha-runners.ts",
"watch": {
"include": ["**"],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false
}
}
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
}
66 changes: 66 additions & 0 deletions lib/connconnection-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as cdk from 'aws-cdk-lib'
import * as codeconn from 'aws-cdk-lib/aws-codeconnections'
import * as iam from 'aws-cdk-lib/aws-iam'
import * as ssm from 'aws-cdk-lib/aws-ssm'
import type { Construct } from 'constructs'
import { CODEBUILD_POLICY_ARN_PARAM_NAME, GITHUB_CODECONNECTION_ARN_PARAM_NAME } from './constants'

/**
* This stack provides a CodeConnection connection, allowing services like CodeBuild to connect
* to GitHub. After creation, the connection must be manually finalised
* in the management console before it is used. That's why we keep it in a separate stack.
*/
export class CodeConnectionStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)

// Adding this policy to the same stack as the CodeBuild project
// resulted in IAM eventual consistency problems, causing
// the CodeConnection to be inaccessible
// ("User is not authorized to access connection")
// Providing it as a managed policy in a separate stack mitigates this.
const codebuildConnectionPolicy = new iam.ManagedPolicy(this, 'CodeBuildPolicy', {
managedPolicyName: `${cdk.Stack.of(this).stackName}CodeBuildPolicy`,
document: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['codeconnections:Get*', 'codeconnections:List*', 'codeconnections:Pass*', 'codeconnections:Use*'],
resources: ['*']
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'codebuild:ListConnectedOAuthAccounts',
'codebuild:ListRepositories',
'codebuild:PersistOAuthToken',
'codebuild:ImportSourceCredentials'
],
resources: ['*']
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
resources: ['*']
})
]
})
})

const conn = new codeconn.CfnConnection(this, 'Conn', {
connectionName: 'GitHubFt',
providerType: 'GitHub'
})

new ssm.StringParameter(this, 'CodeBuildPolicyArnParam', {
parameterName: CODEBUILD_POLICY_ARN_PARAM_NAME,
stringValue: codebuildConnectionPolicy.managedPolicyArn,
description: 'Managed policy for CodeBuild allowing CodeConnection usage'
})
new ssm.StringParameter(this, 'ConnectionArnParam', {
parameterName: GITHUB_CODECONNECTION_ARN_PARAM_NAME,
stringValue: conn.attrConnectionArn,
description: 'CodeConnection connection ARN for access to GitHub from'
})
}
}
2 changes: 2 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const GITHUB_CODECONNECTION_ARN_PARAM_NAME = '/github/codeconnection-arn'
export const CODEBUILD_POLICY_ARN_PARAM_NAME = '/codebuild-connection-policy-arn'
92 changes: 92 additions & 0 deletions lib/gha-runners-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as cdk from 'aws-cdk-lib'
import * as codebuild from 'aws-cdk-lib/aws-codebuild'
import * as iam from 'aws-cdk-lib/aws-iam'
import * as ssm from 'aws-cdk-lib/aws-ssm'
import type { Construct } from 'constructs'
import { CODEBUILD_POLICY_ARN_PARAM_NAME, GITHUB_CODECONNECTION_ARN_PARAM_NAME } from './constants'

/**
* This stack sets up CodeBuild projects to run GitHub Actions runners.
*/
export class GhaRunnersStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)

const githubOrganisation = scope.node.tryGetContext('githuborg')
if (!githubOrganisation) {
throw new Error("Context variable 'githuborg' is required. This should be the name of your GitHub organisation")
}

const connectionArn = ssm.StringParameter.fromStringParameterName(
this,
'ConnectionArnParam',
GITHUB_CODECONNECTION_ARN_PARAM_NAME
).stringValue
const codeBuildPolicyArnParam = ssm.StringParameter.fromStringParameterName(
this,
'CodeBuildPolicyArnParam',
CODEBUILD_POLICY_ARN_PARAM_NAME
).stringValue
const projectServiceRole = new iam.Role(this, 'CodeBuildServiceRole', {
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
path: '/service-role/'
})
projectServiceRole.addManagedPolicy(
iam.ManagedPolicy.fromManagedPolicyArn(this, 'CodeBuildManagedPolicy', codeBuildPolicyArnParam)
)

const environmentsBySuffix: Record<string, codebuild.CfnProject.EnvironmentProperty> = {
'': {
computeType: 'BUILD_GENERAL1_SMALL',
image: 'aws/codebuild/standard:5.0',
type: 'LINUX_CONTAINER'
},
'-lambda': {
computeType: 'BUILD_LAMBDA_10GB',
image: 'aws/codebuild/amazonlinux-x86_64-lambda-standard:nodejs20',
type: 'LINUX_LAMBDA_CONTAINER'
}
}

const commonCodeBuildProjectProps: Omit<codebuild.CfnProjectProps, 'name' | 'environment'> = {
source: {
gitCloneDepth: 1,
type: 'GITHUB',
location: 'CODEBUILD_DEFAULT_WEBHOOK_SOURCE_LOCATION',
auth: {
// Change type to 'OAUTH' if you want to use a GitHub PAT that has already been
// loaded into your AWS account+region with `import-source-credentials`.
type: 'CODECONNECTIONS',
resource: connectionArn
}
},
triggers: {
webhook: true,
scopeConfiguration: {
name: githubOrganisation
},
filterGroups: [
[
{
type: 'EVENT',
pattern: 'WORKFLOW_JOB_QUEUED'
}
]
]
},
artifacts: {
type: 'NO_ARTIFACTS'
},
concurrentBuildLimit: 60,
serviceRole: projectServiceRole.roleArn
}

for (const [suffix, environment] of Object.entries(environmentsBySuffix)) {
new codebuild.CfnProject(this, `RunnerProject${suffix}`, {
...commonCodeBuildProjectProps,
environment: environment,
name: `gha-runners${suffix}`
})
}
}
}
Loading

0 comments on commit d86ba94

Please sign in to comment.