@cloudsnorkel/cdk-github-runners
Version:
CDK construct to create GitHub Actions self-hosted runners. Creates ephemeral runners on demand. Easy to deploy and highly customizable.
566 lines • 102 kB
JavaScript
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitHubRunners = void 0;
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
const cdk = require("aws-cdk-lib");
const aws_cdk_lib_1 = require("aws-cdk-lib");
const constructs_1 = require("constructs");
const access_1 = require("./access");
const delete_failed_runner_function_1 = require("./delete-failed-runner-function");
const idle_runner_repear_function_1 = require("./idle-runner-repear-function");
const providers_1 = require("./providers");
const secrets_1 = require("./secrets");
const setup_function_1 = require("./setup-function");
const status_function_1 = require("./status-function");
const token_retriever_function_1 = require("./token-retriever-function");
const utils_1 = require("./utils");
const webhook_1 = require("./webhook");
const webhook_redelivery_1 = require("./webhook-redelivery");
/**
* Create all the required infrastructure to provide self-hosted GitHub runners. It creates a webhook, secrets, and a step function to orchestrate all runs. Secrets are not automatically filled. See README.md for instructions on how to setup GitHub integration.
*
* By default, this will create a runner provider of each available type with the defaults. This is good enough for the initial setup stage when you just want to get GitHub integration working.
*
* ```typescript
* new GitHubRunners(this, 'runners');
* ```
*
* Usually you'd want to configure the runner providers so the runners can run in a certain VPC or have certain permissions.
*
* ```typescript
* const vpc = ec2.Vpc.fromLookup(this, 'vpc', { vpcId: 'vpc-1234567' });
* const runnerSg = new ec2.SecurityGroup(this, 'runner security group', { vpc: vpc });
* const dbSg = ec2.SecurityGroup.fromSecurityGroupId(this, 'database security group', 'sg-1234567');
* const bucket = new s3.Bucket(this, 'runner bucket');
*
* // create a custom CodeBuild provider
* const myProvider = new CodeBuildRunnerProvider(
* this, 'codebuild runner',
* {
* labels: ['my-codebuild'],
* vpc: vpc,
* securityGroups: [runnerSg],
* },
* );
* // grant some permissions to the provider
* bucket.grantReadWrite(myProvider);
* dbSg.connections.allowFrom(runnerSg, ec2.Port.tcp(3306), 'allow runners to connect to MySQL database');
*
* // create the runner infrastructure
* new GitHubRunners(
* this,
* 'runners',
* {
* providers: [myProvider],
* }
* );
* ```
*/
class GitHubRunners extends constructs_1.Construct {
constructor(scope, id, props) {
super(scope, id);
this.props = props;
this.extraLambdaEnv = {};
this.secrets = new secrets_1.Secrets(this, 'Secrets');
this.extraLambdaProps = {
vpc: this.props?.vpc,
vpcSubnets: this.props?.vpcSubnets,
allowPublicSubnet: this.props?.allowPublicSubnet,
securityGroups: this.lambdaSecurityGroups(),
layers: this.props?.extraCertificates ? [new aws_cdk_lib_1.aws_lambda.LayerVersion(scope, 'Certificate Layer', {
description: 'Layer containing GitHub Enterprise Server certificate for cdk-github-runners',
code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(this.props.extraCertificates),
})] : undefined,
};
this.connections = new aws_cdk_lib_1.aws_ec2.Connections({ securityGroups: this.extraLambdaProps.securityGroups });
if (this.props?.extraCertificates) {
this.extraLambdaEnv.NODE_EXTRA_CA_CERTS = '/opt/certs.pem';
}
if (this.props?.providers) {
this.providers = this.props.providers;
}
else {
this.providers = [
new providers_1.CodeBuildRunnerProvider(this, 'CodeBuild'),
new providers_1.LambdaRunnerProvider(this, 'Lambda'),
new providers_1.FargateRunnerProvider(this, 'Fargate'),
];
}
if (this.providers.length == 0) {
throw new Error('At least one runner provider is required');
}
this.checkIntersectingLabels();
this.orchestrator = this.stateMachine(props);
this.webhook = new webhook_1.GithubWebhookHandler(this, 'Webhook Handler', {
orchestrator: this.orchestrator,
secrets: this.secrets,
access: this.props?.webhookAccess ?? access_1.LambdaAccess.lambdaUrl(),
supportedLabels: this.providers.map(p => {
return {
provider: p.node.path,
labels: p.labels,
};
}),
requireSelfHostedLabel: this.props?.requireSelfHostedLabel ?? true,
});
this.redeliverer = new webhook_redelivery_1.GithubWebhookRedelivery(this, 'Webhook Redelivery', {
secrets: this.secrets,
});
this.setupUrl = this.setupFunction();
this.statusFunction();
}
stateMachine(props) {
const tokenRetrieverTask = new aws_cdk_lib_1.aws_stepfunctions_tasks.LambdaInvoke(this, 'Get Runner Token', {
lambdaFunction: this.tokenRetriever(),
payloadResponseOnly: true,
resultPath: '$.runner',
});
let deleteFailedRunnerFunction = this.deleteFailedRunner();
const deleteFailedRunnerTask = new aws_cdk_lib_1.aws_stepfunctions_tasks.LambdaInvoke(this, 'Delete Failed Runner', {
lambdaFunction: deleteFailedRunnerFunction,
payloadResponseOnly: true,
resultPath: '$.delete',
payload: aws_cdk_lib_1.aws_stepfunctions.TaskInput.fromObject({
runnerName: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$$.Execution.Name'),
owner: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.owner'),
repo: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.repo'),
installationId: aws_cdk_lib_1.aws_stepfunctions.JsonPath.numberAt('$.installationId'),
error: aws_cdk_lib_1.aws_stepfunctions.JsonPath.objectAt('$.error'),
}),
});
deleteFailedRunnerTask.addRetry({
errors: [
'RunnerBusy',
],
interval: cdk.Duration.minutes(1),
backoffRate: 1,
maxAttempts: 60,
});
const idleReaper = this.idleReaper();
const queueIdleReaperTask = new aws_cdk_lib_1.aws_stepfunctions_tasks.SqsSendMessage(this, 'Queue Idle Reaper', {
queue: this.idleReaperQueue(idleReaper),
messageBody: aws_cdk_lib_1.aws_stepfunctions.TaskInput.fromObject({
executionArn: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$$.Execution.Id'),
runnerName: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$$.Execution.Name'),
owner: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.owner'),
repo: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.repo'),
installationId: aws_cdk_lib_1.aws_stepfunctions.JsonPath.numberAt('$.installationId'),
maxIdleSeconds: (props?.idleTimeout ?? cdk.Duration.minutes(5)).toSeconds(),
}),
resultPath: aws_cdk_lib_1.aws_stepfunctions.JsonPath.DISCARD,
});
const providerChooser = new aws_cdk_lib_1.aws_stepfunctions.Choice(this, 'Choose provider');
for (const provider of this.providers) {
const providerTask = provider.getStepFunctionTask({
runnerTokenPath: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.runner.token'),
runnerNamePath: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$$.Execution.Name'),
githubDomainPath: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.runner.domain'),
ownerPath: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.owner'),
repoPath: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.repo'),
registrationUrl: aws_cdk_lib_1.aws_stepfunctions.JsonPath.stringAt('$.runner.registrationUrl'),
});
providerChooser.when(aws_cdk_lib_1.aws_stepfunctions.Condition.and(aws_cdk_lib_1.aws_stepfunctions.Condition.stringEquals('$.provider', provider.node.path)), providerTask);
}
providerChooser.otherwise(new aws_cdk_lib_1.aws_stepfunctions.Succeed(this, 'Unknown label'));
const runProviders = new aws_cdk_lib_1.aws_stepfunctions.Parallel(this, 'Run Providers').branch(new aws_cdk_lib_1.aws_stepfunctions.Parallel(this, 'Error Handler').branch(
// we get a token for every retry because the token can expire faster than the job can timeout
tokenRetrieverTask.next(providerChooser)).addCatch(
// delete runner on failure as it won't remove itself and there is a limit on the number of registered runners
deleteFailedRunnerTask, {
resultPath: '$.error',
}));
if (props?.retryOptions?.retry ?? true) {
const interval = props?.retryOptions?.interval ?? cdk.Duration.minutes(1);
const maxAttempts = props?.retryOptions?.maxAttempts ?? 23;
const backoffRate = props?.retryOptions?.backoffRate ?? 1.3;
const totalSeconds = interval.toSeconds() * backoffRate ** maxAttempts / (backoffRate - 1);
if (totalSeconds >= cdk.Duration.days(1).toSeconds()) {
// https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits
// "Job queue time - Each job for self-hosted runners can be queued for a maximum of 24 hours. If a self-hosted runner does not start executing the job within this limit, the job is terminated and fails to complete."
aws_cdk_lib_1.Annotations.of(this).addWarning(`Total retry time is greater than 24 hours (${Math.floor(totalSeconds / 60 / 60)} hours). Jobs expire after 24 hours so it would be a waste of resources to retry further.`);
}
runProviders.addRetry({
interval,
maxAttempts,
backoffRate,
// we retry on everything
// deleted idle runners will also fail, but the reaper will stop this step function to avoid endless retries
});
}
let logOptions;
if (this.props?.logOptions) {
this.stateMachineLogGroup = new aws_cdk_lib_1.aws_logs.LogGroup(this, 'Logs', {
logGroupName: props?.logOptions?.logGroupName,
retention: props?.logOptions?.logRetention ?? aws_cdk_lib_1.aws_logs.RetentionDays.ONE_MONTH,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
logOptions = {
destination: this.stateMachineLogGroup,
includeExecutionData: props?.logOptions?.includeExecutionData ?? true,
level: props?.logOptions?.level ?? aws_cdk_lib_1.aws_stepfunctions.LogLevel.ALL,
};
}
const stateMachine = new aws_cdk_lib_1.aws_stepfunctions.StateMachine(this, 'Runner Orchestrator', {
definitionBody: aws_cdk_lib_1.aws_stepfunctions.DefinitionBody.fromChainable(queueIdleReaperTask.next(runProviders)),
logs: logOptions,
});
stateMachine.grantRead(idleReaper);
stateMachine.grantExecution(idleReaper, 'states:StopExecution');
for (const provider of this.providers) {
provider.grantStateMachine(stateMachine);
}
return stateMachine;
}
tokenRetriever() {
const func = new token_retriever_function_1.TokenRetrieverFunction(this, 'token-retriever', {
description: 'Get token from GitHub Actions used to start new self-hosted runner',
environment: {
GITHUB_SECRET_ARN: this.secrets.github.secretArn,
GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn,
...this.extraLambdaEnv,
},
timeout: cdk.Duration.seconds(30),
logGroup: (0, utils_1.singletonLogGroup)(this, utils_1.SingletonLogType.ORCHESTRATOR),
loggingFormat: aws_cdk_lib_1.aws_lambda.LoggingFormat.JSON,
...this.extraLambdaProps,
});
this.secrets.github.grantRead(func);
this.secrets.githubPrivateKey.grantRead(func);
return func;
}
deleteFailedRunner() {
const func = new delete_failed_runner_function_1.DeleteFailedRunnerFunction(this, 'delete-runner', {
description: 'Delete failed GitHub Actions runner on error',
environment: {
GITHUB_SECRET_ARN: this.secrets.github.secretArn,
GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn,
...this.extraLambdaEnv,
},
timeout: cdk.Duration.seconds(30),
logGroup: (0, utils_1.singletonLogGroup)(this, utils_1.SingletonLogType.ORCHESTRATOR),
loggingFormat: aws_cdk_lib_1.aws_lambda.LoggingFormat.JSON,
...this.extraLambdaProps,
});
this.secrets.github.grantRead(func);
this.secrets.githubPrivateKey.grantRead(func);
return func;
}
statusFunction() {
const statusFunction = new status_function_1.StatusFunction(this, 'status', {
description: 'Provide user with status about self-hosted GitHub Actions runners',
environment: {
WEBHOOK_SECRET_ARN: this.secrets.webhook.secretArn,
GITHUB_SECRET_ARN: this.secrets.github.secretArn,
GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn,
SETUP_SECRET_ARN: this.secrets.setup.secretArn,
WEBHOOK_URL: this.webhook.url,
WEBHOOK_HANDLER_ARN: this.webhook.handler.latestVersion.functionArn,
STEP_FUNCTION_ARN: this.orchestrator.stateMachineArn,
STEP_FUNCTION_LOG_GROUP: this.stateMachineLogGroup?.logGroupName ?? '',
SETUP_FUNCTION_URL: this.setupUrl,
...this.extraLambdaEnv,
},
timeout: cdk.Duration.minutes(3),
logGroup: (0, utils_1.singletonLogGroup)(this, utils_1.SingletonLogType.SETUP),
loggingFormat: aws_cdk_lib_1.aws_lambda.LoggingFormat.JSON,
...this.extraLambdaProps,
});
const providers = this.providers.map(provider => provider.status(statusFunction));
// expose providers as stack metadata as it's too big for Lambda environment variables
// specifically integration testing got an error because lambda update request was >5kb
const stack = cdk.Stack.of(this);
const f = statusFunction.node.defaultChild;
f.addPropertyOverride('Environment.Variables.LOGICAL_ID', f.logicalId);
f.addPropertyOverride('Environment.Variables.STACK_NAME', stack.stackName);
f.addMetadata('providers', providers);
statusFunction.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({
actions: ['cloudformation:DescribeStackResource'],
resources: [stack.stackId],
}));
this.secrets.webhook.grantRead(statusFunction);
this.secrets.github.grantRead(statusFunction);
this.secrets.githubPrivateKey.grantRead(statusFunction);
this.secrets.setup.grantRead(statusFunction);
this.orchestrator.grantRead(statusFunction);
new cdk.CfnOutput(this, 'status command', {
value: `aws --region ${stack.region} lambda invoke --function-name ${statusFunction.functionName} status.json`,
});
const access = this.props?.statusAccess ?? access_1.LambdaAccess.noAccess();
const url = access.bind(this, 'status access', statusFunction);
if (url !== '') {
new cdk.CfnOutput(this, 'status url', {
value: url,
});
}
}
setupFunction() {
const setupFunction = new setup_function_1.SetupFunction(this, 'setup', {
description: 'Setup GitHub Actions integration with self-hosted runners',
environment: {
SETUP_SECRET_ARN: this.secrets.setup.secretArn,
WEBHOOK_SECRET_ARN: this.secrets.webhook.secretArn,
GITHUB_SECRET_ARN: this.secrets.github.secretArn,
GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn,
WEBHOOK_URL: this.webhook.url,
...this.extraLambdaEnv,
},
timeout: cdk.Duration.minutes(3),
logGroup: (0, utils_1.singletonLogGroup)(this, utils_1.SingletonLogType.SETUP),
loggingFormat: aws_cdk_lib_1.aws_lambda.LoggingFormat.JSON,
...this.extraLambdaProps,
});
// this.secrets.webhook.grantRead(setupFunction);
this.secrets.webhook.grantWrite(setupFunction);
this.secrets.github.grantRead(setupFunction);
this.secrets.github.grantWrite(setupFunction);
// this.secrets.githubPrivateKey.grantRead(setupFunction);
this.secrets.githubPrivateKey.grantWrite(setupFunction);
this.secrets.setup.grantRead(setupFunction);
this.secrets.setup.grantWrite(setupFunction);
const access = this.props?.setupAccess ?? access_1.LambdaAccess.lambdaUrl();
return access.bind(this, 'setup access', setupFunction);
}
checkIntersectingLabels() {
// this "algorithm" is very inefficient, but good enough for the tiny datasets we expect
for (const p1 of this.providers) {
for (const p2 of this.providers) {
if (p1 == p2) {
continue;
}
if (p1.labels.every(l => p2.labels.includes(l))) {
if (p2.labels.every(l => p1.labels.includes(l))) {
throw new Error(`Both ${p1.node.path} and ${p2.node.path} use the same labels [${p1.labels.join(', ')}]`);
}
aws_cdk_lib_1.Annotations.of(p1).addWarning(`Labels [${p1.labels.join(', ')}] intersect with another provider (${p2.node.path} -- [${p2.labels.join(', ')}]). If a workflow specifies the labels [${p1.labels.join(', ')}], it is not guaranteed which provider will be used. It is recommended you do not use intersecting labels`);
}
}
}
}
idleReaper() {
return new idle_runner_repear_function_1.IdleRunnerRepearFunction(this, 'Idle Reaper', {
description: 'Stop idle GitHub runners to avoid paying for runners when the job was already canceled',
environment: {
GITHUB_SECRET_ARN: this.secrets.github.secretArn,
GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn,
...this.extraLambdaEnv,
},
logGroup: (0, utils_1.singletonLogGroup)(this, utils_1.SingletonLogType.ORCHESTRATOR),
loggingFormat: aws_cdk_lib_1.aws_lambda.LoggingFormat.JSON,
timeout: cdk.Duration.minutes(5),
...this.extraLambdaProps,
});
}
idleReaperQueue(reaper) {
// see this comment to understand why it's a queue that's out of the step function
// https://github.com/CloudSnorkel/cdk-github-runners/pull/314#issuecomment-1528901192
const queue = new aws_cdk_lib_1.aws_sqs.Queue(this, 'Idle Reaper Queue', {
deliveryDelay: cdk.Duration.minutes(10),
visibilityTimeout: cdk.Duration.minutes(10),
});
reaper.addEventSource(new aws_cdk_lib_1.aws_lambda_event_sources.SqsEventSource(queue, {
reportBatchItemFailures: true,
maxBatchingWindow: cdk.Duration.minutes(1),
}));
this.secrets.github.grantRead(reaper);
this.secrets.githubPrivateKey.grantRead(reaper);
return queue;
}
lambdaSecurityGroups() {
if (!this.props?.vpc) {
if (this.props?.securityGroup) {
cdk.Annotations.of(this).addWarning('securityGroup is specified, but vpc is not. securityGroup will be ignored');
}
if (this.props?.securityGroups) {
cdk.Annotations.of(this).addWarning('securityGroups is specified, but vpc is not. securityGroups will be ignored');
}
return undefined;
}
if (this.props.securityGroups) {
if (this.props.securityGroup) {
cdk.Annotations.of(this).addWarning('Both securityGroup and securityGroups are specified. securityGroup will be ignored');
}
return this.props.securityGroups;
}
if (this.props.securityGroup) {
return [this.props.securityGroup];
}
return [new aws_cdk_lib_1.aws_ec2.SecurityGroup(this, 'Management Lambdas Security Group', { vpc: this.props.vpc })];
}
/**
* Metric for the number of GitHub Actions jobs completed. It has `ProviderLabels` and `Status` dimensions. The status can be one of "Succeeded", "SucceededWithIssues", "Failed", "Canceled", "Skipped", or "Abandoned".
*
* **WARNING:** this method creates a metric filter for each provider. Each metric has a status dimension with six possible values. These resources may incur cost.
*/
metricJobCompleted(props) {
if (!this.jobsCompletedMetricFilters) {
// we can't use logs.FilterPattern.spaceDelimited() because it has no support for ||
// status list taken from https://github.com/actions/runner/blob/be9632302ceef50bfb36ea998cea9c94c75e5d4d/src/Sdk/DTWebApi/WebApi/TaskResult.cs
// we need "..." for Lambda that prefixes some extra data to log lines
const pattern = aws_cdk_lib_1.aws_logs.FilterPattern.literal('[..., marker = "CDKGHA", job = "JOB", done = "DONE", labels, status = "Succeeded" || status = "SucceededWithIssues" || status = "Failed" || status = "Canceled" || status = "Skipped" || status = "Abandoned"]');
this.jobsCompletedMetricFilters = this.providers.map(p => p.logGroup.addMetricFilter(`${p.logGroup.node.id} filter`, {
metricNamespace: 'GitHubRunners',
metricName: 'JobCompleted',
filterPattern: pattern,
metricValue: '1',
// can't with dimensions -- defaultValue: 0,
dimensions: {
ProviderLabels: '$labels',
Status: '$status',
},
}));
for (const metricFilter of this.jobsCompletedMetricFilters) {
if (metricFilter.node.defaultChild instanceof aws_cdk_lib_1.aws_logs.CfnMetricFilter) {
metricFilter.node.defaultChild.addPropertyOverride('MetricTransformations.0.Unit', 'Count');
}
else {
aws_cdk_lib_1.Annotations.of(metricFilter).addWarning('Unable to set metric filter Unit to Count');
}
}
}
return new aws_cdk_lib_1.aws_cloudwatch.Metric({
namespace: 'GitHubRunners',
metricName: 'JobsCompleted',
unit: aws_cdk_lib_1.aws_cloudwatch.Unit.COUNT,
statistic: aws_cdk_lib_1.aws_cloudwatch.Statistic.SUM,
...props,
}).attachTo(this);
}
/**
* Metric for successful executions.
*
* A successful execution doesn't always mean a runner was started. It can be successful even without any label matches.
*
* A successful runner doesn't mean the job it executed was successful. For that, see {@link metricJobCompleted}.
*/
metricSucceeded(props) {
return this.orchestrator.metricSucceeded(props);
}
/**
* Metric for failed runner executions.
*
* A failed runner usually means the runner failed to start and so a job was never executed. It doesn't necessarily mean the job was executed and failed. For that, see {@link metricJobCompleted}.
*/
metricFailed(props) {
return this.orchestrator.metricFailed(props);
}
/**
* Metric for the interval, in milliseconds, between the time the execution starts and the time it closes. This time may be longer than the time the runner took.
*/
metricTime(props) {
return this.orchestrator.metricTime(props);
}
/**
* Creates a topic for notifications when a runner image build fails.
*
* Runner images are rebuilt every week by default. This provides the latest GitHub Runner version and software updates.
*
* If you want to be sure you are using the latest runner version, you can use this topic to be notified when a build fails.
*/
failedImageBuildsTopic() {
const topic = new aws_cdk_lib_1.aws_sns.Topic(this, 'Failed Runner Image Builds');
const stack = cdk.Stack.of(this);
cdk.Aspects.of(stack).add(new providers_1.CodeBuildImageBuilderFailedBuildNotifier(topic));
cdk.Aspects.of(stack).add(new providers_1.AwsImageBuilderFailedBuildNotifier(providers_1.AwsImageBuilderFailedBuildNotifier.createFilteringTopic(this, topic)));
return topic;
}
/**
* Creates CloudWatch Logs Insights saved queries that can be used to debug issues with the runners.
*
* * "Webhook errors" helps diagnose configuration issues with GitHub integration
* * "Ignored webhook" helps understand why runners aren't started
* * "Ignored jobs based on labels" helps debug label matching issues
* * "Webhook started runners" helps understand which runners were started
*/
createLogsInsightsQueries() {
new aws_cdk_lib_1.aws_logs.QueryDefinition(this, 'Webhook errors', {
queryDefinitionName: 'GitHub Runners/Webhook errors',
logGroups: [this.webhook.handler.logGroup],
queryString: new aws_cdk_lib_1.aws_logs.QueryString({
filterStatements: [
`strcontains(@logStream, "${this.webhook.handler.functionName}")`,
'level = "ERROR"',
],
sort: '@timestamp desc',
limit: 100,
}),
});
new aws_cdk_lib_1.aws_logs.QueryDefinition(this, 'Orchestration errors', {
queryDefinitionName: 'GitHub Runners/Orchestration errors',
logGroups: [(0, utils_1.singletonLogGroup)(this, utils_1.SingletonLogType.ORCHESTRATOR)],
queryString: new aws_cdk_lib_1.aws_logs.QueryString({
filterStatements: [
'level = "ERROR"',
],
sort: '@timestamp desc',
limit: 100,
}),
});
new aws_cdk_lib_1.aws_logs.QueryDefinition(this, 'Runner image build errors', {
queryDefinitionName: 'GitHub Runners/Runner image build errors',
logGroups: [(0, utils_1.singletonLogGroup)(this, utils_1.SingletonLogType.RUNNER_IMAGE_BUILD)],
queryString: new aws_cdk_lib_1.aws_logs.QueryString({
filterStatements: [
'strcontains(message, "error") or strcontains(message, "ERROR") or strcontains(message, "Error") or level = "ERROR"',
],
sort: '@timestamp desc',
limit: 100,
}),
});
new aws_cdk_lib_1.aws_logs.QueryDefinition(this, 'Ignored webhooks', {
queryDefinitionName: 'GitHub Runners/Ignored webhooks',
logGroups: [this.webhook.handler.logGroup],
queryString: new aws_cdk_lib_1.aws_logs.QueryString({
fields: ['@timestamp', 'message.notice'],
filterStatements: [
`strcontains(@logStream, "${this.webhook.handler.functionName}")`,
'strcontains(message.notice, "Ignoring")',
],
sort: '@timestamp desc',
limit: 100,
}),
});
new aws_cdk_lib_1.aws_logs.QueryDefinition(this, 'Ignored jobs based on labels', {
queryDefinitionName: 'GitHub Runners/Ignored jobs based on labels',
logGroups: [this.webhook.handler.logGroup],
queryString: new aws_cdk_lib_1.aws_logs.QueryString({
fields: ['@timestamp', 'message.notice'],
filterStatements: [
`strcontains(@logStream, "${this.webhook.handler.functionName}")`,
'strcontains(message.notice, "Ignoring labels")',
],
sort: '@timestamp desc',
limit: 100,
}),
});
new aws_cdk_lib_1.aws_logs.QueryDefinition(this, 'Webhook started runners', {
queryDefinitionName: 'GitHub Runners/Webhook started runners',
logGroups: [this.webhook.handler.logGroup],
queryString: new aws_cdk_lib_1.aws_logs.QueryString({
fields: ['@timestamp', 'message.sfnInput.jobUrl', 'message.sfnInput.labels', 'message.sfnInput.provider'],
filterStatements: [
`strcontains(@logStream, "${this.webhook.handler.functionName}")`,
'message.sfnInput.jobUrl like /http.*/',
],
sort: '@timestamp desc',
limit: 100,
}),
});
new aws_cdk_lib_1.aws_logs.QueryDefinition(this, 'Webhook redeliveries', {
queryDefinitionName: 'GitHub Runners/Webhook redeliveries',
logGroups: [this.redeliverer.handler.logGroup],
queryString: new aws_cdk_lib_1.aws_logs.QueryString({
fields: ['@timestamp', 'message.notice', 'message.deliveryId', 'message.guid'],
filterStatements: [
'isPresent(message.deliveryId)',
],
sort: '@timestamp desc',
limit: 100,
}),
});
}
}
exports.GitHubRunners = GitHubRunners;
_a = JSII_RTTI_SYMBOL_1;
GitHubRunners[_a] = { fqn: "@cloudsnorkel/cdk-github-runners.GitHubRunners", version: "0.14.11" };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVubmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3J1bm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLG1DQUFtQztBQUNuQyw2Q0FZcUI7QUFDckIsMkNBQXVDO0FBQ3ZDLHFDQUF3QztBQUN4QyxtRkFBNkU7QUFDN0UsK0VBQXlFO0FBQ3pFLDJDQVFxQjtBQUNyQix1Q0FBb0M7QUFDcEMscURBQWlEO0FBQ2pELHVEQUFtRDtBQUNuRCx5RUFBb0U7QUFDcEUsbUNBQThEO0FBQzlELHVDQUFpRDtBQUNqRCw2REFBK0Q7QUEySy9EOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Q0c7QUFDSCxNQUFhLGFBQWMsU0FBUSxzQkFBUztJQTJCMUMsWUFBWSxLQUFnQixFQUFFLEVBQVUsRUFBVyxLQUEwQjtRQUMzRSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRGdDLFVBQUssR0FBTCxLQUFLLENBQXFCO1FBTDVELG1CQUFjLEdBQTRCLEVBQUUsQ0FBQztRQVE1RCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksaUJBQU8sQ0FBQyxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDNUMsSUFBSSxDQUFDLGdCQUFnQixHQUFHO1lBQ3RCLEdBQUcsRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUc7WUFDcEIsVUFBVSxFQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsVUFBVTtZQUNsQyxpQkFBaUIsRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLGlCQUFpQjtZQUNoRCxjQUFjLEVBQUUsSUFBSSxDQUFDLG9CQUFvQixFQUFFO1lBQzNDLE1BQU0sRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLGlCQUFpQixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksd0JBQU0sQ0FBQyxZQUFZLENBQUMsS0FBSyxFQUFFLG1CQUFtQixFQUFFO29CQUMzRixXQUFXLEVBQUUsOEVBQThFO29CQUMz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