@cloudsnorkel/cdk-github-runners 0.14.1 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsii +44 -25
- package/API.md +16 -0
- package/README.md +1 -1
- package/assets/delete-failed-runner.lambda/index.js +44 -20
- package/assets/idle-runner-repear.lambda/index.js +44 -20
- package/assets/image-builders/aws-image-builder/versioner.lambda/index.js +28 -4
- package/assets/providers/ami-root-device.lambda/index.js +13 -0
- package/assets/setup.lambda/index.js +16 -16
- package/assets/status.lambda/index.js +44 -20
- package/assets/token-retriever.lambda/index.js +44 -20
- package/assets/webhook-handler.lambda/index.js +44 -20
- package/lib/access.js +1 -1
- package/lib/image-builders/api.js +1 -1
- package/lib/image-builders/aws-image-builder/ami.d.ts +5 -0
- package/lib/image-builders/aws-image-builder/ami.js +12 -1
- package/lib/image-builders/aws-image-builder/builder.d.ts +9 -0
- package/lib/image-builders/aws-image-builder/builder.js +4 -2
- package/lib/image-builders/aws-image-builder/deprecated/ami.js +1 -1
- package/lib/image-builders/aws-image-builder/deprecated/container.js +1 -1
- package/lib/image-builders/aws-image-builder/deprecated/linux-components.js +1 -1
- package/lib/image-builders/aws-image-builder/deprecated/windows-components.js +1 -1
- package/lib/image-builders/codebuild-deprecated.js +1 -1
- package/lib/image-builders/components.js +1 -1
- package/lib/image-builders/static.js +1 -1
- package/lib/providers/ami-root-device.lambda.js +14 -1
- package/lib/providers/codebuild.js +2 -2
- package/lib/providers/common.d.ts +2 -0
- package/lib/providers/common.js +7 -4
- package/lib/providers/ec2.js +2 -2
- package/lib/providers/ecs.js +1 -1
- package/lib/providers/fargate.js +2 -2
- package/lib/providers/lambda.js +2 -2
- package/lib/runner.js +1 -1
- package/lib/secrets.js +1 -1
- package/package.json +23 -22
package/.jsii
CHANGED
|
@@ -3675,7 +3675,7 @@
|
|
|
3675
3675
|
"stability": "experimental"
|
|
3676
3676
|
},
|
|
3677
3677
|
"homepage": "https://github.com/CloudSnorkel/cdk-github-runners.git",
|
|
3678
|
-
"jsiiVersion": "5.3.
|
|
3678
|
+
"jsiiVersion": "5.3.53 (build 42cf68d)",
|
|
3679
3679
|
"keywords": [
|
|
3680
3680
|
"aws",
|
|
3681
3681
|
"aws-cdk",
|
|
@@ -3699,7 +3699,7 @@
|
|
|
3699
3699
|
},
|
|
3700
3700
|
"name": "@cloudsnorkel/cdk-github-runners",
|
|
3701
3701
|
"readme": {
|
|
3702
|
-
"markdown": "# GitHub Self-Hosted Runners CDK Constructs\n\n[][7]\n[][6]\n[][8]\n[][11]\n[][12]\n[](https://github.com/CloudSnorkel/cdk-github-runners/actions/workflows/release.yml)\n[](https://github.com/CloudSnorkel/cdk-github-runners/blob/main/LICENSE)\n\nUse this CDK construct to create ephemeral [self-hosted GitHub runners][1] on-demand inside your AWS account.\n\n* 🧩 Easy to configure GitHub integration with a web-based interface\n* 🧠 Customizable runners with decent defaults\n* 🏃🏻 Multiple runner configurations controlled by labels\n* 🔐 Everything fully hosted in your account\n* 🔃 Automatically updated build environment with latest runner version\n\nSelf-hosted runners in AWS are useful when:\n\n* You need easy access to internal resources in your actions\n* You want to pre-install some software for your actions\n* You want to provide some basic AWS API access (but [aws-actions/configure-aws-credentials][2] has more security controls)\n* You are using GitHub Enterprise Server\n\nEphemeral (or on-demand) runners are the [recommended way by GitHub][14] for auto-scaling, and they make sure all jobs run with a clean image. Runners are started on-demand. You don't pay unless a job is running.\n\n## API\n\nThe best way to browse API documentation is on [Constructs Hub][13]. It is available in all supported programming languages.\n\n## Providers\n\nA runner provider creates compute resources on-demand and uses [actions/runner][5] to start a runner.\n\n| | EC2 | CodeBuild | Fargate | ECS | Lambda |\n|------------------|-------------------|----------------------------|----------------|----------------|---------------|\n| **Time limit** | Unlimited | 8 hours | Unlimited | Unlimited | 15 minutes |\n| **vCPUs** | Unlimited | 2, 4, 8, or 72 | 0.25 to 4 | Unlimited | 1 to 6 |\n| **RAM** | Unlimited | 3gb, 7gb, 15gb, or 145gb | 512mb to 30gb | Unlimited | 128mb to 10gb |\n| **Storage** | Unlimited | 50gb to 824gb | 20gb to 200gb | Unlimited | Up to 10gb |\n| **Architecture** | x86_64, ARM64 | x86_64, ARM64 | x86_64, ARM64 | x86_64, ARM64 | x86_64, ARM64 |\n| **sudo** | ✔ | ✔ | ✔ | ✔ | ❌ |\n| **Docker** | ✔ | ✔ (Linux only) | ❌ | ✔ | ❌ |\n| **Spot pricing** | ✔ | ❌ | ✔ | ✔ | ❌ |\n| **OS** | Linux, Windows | Linux, Windows | Linux, Windows | Linux, Windows | Linux |\n\nThe best provider to use mostly depends on your current infrastructure. When in doubt, CodeBuild is always a good choice. Execution history and logs are easy to view, and it has no restrictive limits unless you need to run for more than 8 hours.\n\n* EC2 is useful when you want runners to have complete access to the host\n* ECS is useful when you want to control the infrastructure, like leaving the runner host running for faster startups\n* Lambda is useful for short jobs that can work within time, size and readonly system constraints\n\nYou can also create your own provider by implementing `IRunnerProvider`.\n\n## Installation\n\n1. Install and use the appropriate package\n <details><summary>Python</summary>\n\n ### Install\n Available on [PyPI][6].\n ```bash\n pip install cloudsnorkel.cdk-github-runners\n ```\n ### Use\n ```python\n from aws_cdk import App, Stack\n from cloudsnorkel.cdk_github_runners import GitHubRunners\n\n app = App()\n stack = Stack(app, \"github-runners\")\n GitHubRunners(stack, \"runners\")\n\n app.synth()\n ```\n </details>\n <details><summary>TypeScript or JavaScript</summary>\n\n ### Install\n Available on [npm][7].\n ```bash\n npm i @cloudsnorkel/cdk-github-runners\n ```\n ### Use\n ```typescript\n import { App, Stack } from 'aws-cdk-lib';\n import { GitHubRunners } from '@cloudsnorkel/cdk-github-runners';\n\n const app = new App();\n const stack = new Stack(app, 'github-runners');\n new GitHubRunners(stack, 'runners');\n\n app.synth();\n ```\n </details>\n <details><summary>Java</summary>\n\n ### Install\n Available on [Maven][8].\n ```xml\n <dependency>\n <groupId>com.cloudsnorkel</groupId>\n <artifactId>cdk.github.runners</artifactId>\n </dependency>\n ```\n ### Use\n ```java\n import software.amazon.awscdk.App;\n import software.amazon.awscdk.Stack;\n import com.cloudsnorkel.cdk.github.runners.GitHubRunners;\n\n public class Example {\n public static void main(String[] args){\n App app = new App();\n Stack stack = new Stack(app, \"github-runners\");\n GitHubRunners.Builder.create(stack, \"runners\").build();\n\n app.synth();\n }\n }\n ```\n </details>\n <details><summary>Go</summary>\n\n ### Install\n Available on [GitHub][11].\n ```bash\n go get github.com/CloudSnorkel/cdk-github-runners-go/cloudsnorkelcdkgithubrunners\n ```\n ### Use\n ```go\n package main\n\n import (\n \"github.com/CloudSnorkel/cdk-github-runners-go/cloudsnorkelcdkgithubrunners\"\n \"github.com/aws/aws-cdk-go/awscdk/v2\"\n \"github.com/aws/jsii-runtime-go\"\n )\n\n func main() {\n app := awscdk.NewApp(nil)\n stack := awscdk.NewStack(app, jsii.String(\"github-runners\"), &awscdk.StackProps{})\n cloudsnorkelcdkgithubrunners.NewGitHubRunners(stack, jsii.String(\"runners\"), &cloudsnorkelcdkgithubrunners.GitHubRunnersProps{})\n\n app.Synth(nil)\n }\n ```\n </details>\n <details><summary>.NET</summary>\n\n ### Install\n Available on [Nuget][12].\n ```bash\n dotnet add package CloudSnorkel.Cdk.Github.Runners\n ```\n ### Use\n ```csharp\n using Amazon.CDK;\n using CloudSnorkel;\n\n namespace Example\n {\n sealed class Program\n {\n public static void Main(string[] args)\n {\n var app = new App();\n var stack = new Stack(app, \"github-runners\");\n new GitHubRunners(stack, \"runners\");\n app.Synth();\n }\n }\n }\n ```\n </details>\n2. Use `GitHubRunners` construct in your code (starting with default arguments is fine)\n3. Deploy your stack\n4. Look for the status command output similar to `aws --region us-east-1 lambda invoke --function-name status-XYZ123 status.json`\n ```\n ✅ github-runners-test\n\n ✨ Deployment time: 260.01s\n\n Outputs:\n github-runners-test.runnersstatuscommand4A30F0F5 = aws --region us-east-1 lambda invoke --function-name github-runners-test-runnersstatus1A5771C0-mvttg8oPQnQS status.json\n ```\n5. Execute the status command (you may need to specify `--profile` too) and open the resulting `status.json` file\n6. Open the URL in `github.setup.url` from `status.json` or [manually setup GitHub](SETUP_GITHUB.md) integration as an app or with personal access token\n7. Run status command again to confirm `github.auth.status` and `github.webhook.status` are OK\n8. Trigger a GitHub action that has a `self-hosted` label with `runs-on: [self-hosted, linux, codebuild]` or similar\n9. If the action is not successful, see [troubleshooting](#Troubleshooting)\n\n[](https://youtu.be/wlyv_3V8lIw)\n\n## Customizing\n\nThe default providers configured by `GitHubRunners` are useful for testing but probably not too much for actual production work. They run in the default VPC or no VPC and have no added IAM permissions. You would usually want to configure the providers yourself.\n\nFor example:\n\n```typescript\nlet vpc: ec2.Vpc;\nlet runnerSg: ec2.SecurityGroup;\nlet dbSg: ec2.SecurityGroup;\nlet bucket: s3.Bucket;\n\n// create a custom CodeBuild provider\nconst myProvider = new CodeBuildRunnerProvider(this, 'codebuild runner', {\n labels: ['my-codebuild'],\n vpc: vpc,\n securityGroups: [runnerSg],\n});\n// grant some permissions to the provider\nbucket.grantReadWrite(myProvider);\ndbSg.connections.allowFrom(runnerSg, ec2.Port.tcp(3306), 'allow runners to connect to MySQL database');\n\n// create the runner infrastructure\nnew GitHubRunners(this, 'runners', {\n providers: [myProvider],\n});\n```\n\nAnother way to customize runners is by modifying the image used to spin them up. The image contains the [runner][5], any required dependencies, and integration code with the provider. You may choose to customize this image by adding more packages, for example.\n\n```typescript\nconst myBuilder = FargateRunnerProvider.imageBuilder(this, 'image builder');\nmyBuilder.addComponent(\n RunnerImageComponent.custom({ commands: ['apt install -y nginx xz-utils'] }),\n);\n\nconst myProvider = new FargateRunnerProvider(this, 'fargate runner', {\n labels: ['customized-fargate'],\n imageBuilder: myBuilder,\n});\n\n// create the runner infrastructure\nnew GitHubRunners(this, 'runners', {\n providers: [myProvider],\n});\n```\n\nYour workflow will then look like:\n\n```yaml\nname: self-hosted example\non: push\njobs:\n self-hosted:\n runs-on: [self-hosted, customized-fargate]\n steps:\n - run: echo hello world\n```\n\nWindows images can also be customized the same way.\n\n```typescript\nconst myWindowsBuilder = FargateRunnerProvider.imageBuilder(this, 'Windows image builder', {\n architecture: Architecture.X86_64,\n os: Os.WINDOWS,\n});\nmyWindowsBuilder.addComponent(\n RunnerImageComponent.custom({\n name: 'Ninja',\n commands: [\n 'Invoke-WebRequest -UseBasicParsing -Uri \"https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-win.zip\" -OutFile ninja.zip',\n 'Expand-Archive ninja.zip -DestinationPath C:\\\\actions',\n 'del ninja.zip',\n ],\n }),\n);\n\nconst myProvider = new FargateRunnerProvider(this, 'fargate runner', {\n labels: ['customized-windows-fargate'],\n imageBuilder: myWindowsBuilder,\n});\n\nnew GitHubRunners(this, 'runners', {\n providers: [myProvider],\n});\n```\n\nThe runner OS and architecture is determined by the image it is set to use. For example, to create a Fargate runner provider for ARM64 set the `architecture` property for the image builder to `Architecture.ARM64` in the image builder properties.\n\n```typescript\nnew GitHubRunners(this, 'runners', {\n providers: [\n new FargateRunnerProvider(this, 'fargate runner', {\n labels: ['arm64', 'fargate'],\n imageBuilder: FargateRunnerProvider.imageBuilder(this, 'image builder', {\n architecture: Architecture.ARM64,\n os: Os.LINUX_UBUNTU,\n }),\n }),\n ],\n});\n```\n\n## Architecture\n\n\n\n## Troubleshooting\n\nRunners are started in response to a webhook coming in from GitHub. If there are any issues starting the runner like missing capacity or transient API issues, the provider will keep retrying for 24 hours. Configuration issue related errors like pointing to a missing AMI will not be retried. GitHub itself will cancel the job if it can't find a runner for 24 hours. If your jobs don't start, follow the steps below to examine all parts of this workflow.\n\n1. Always start with the status function, make sure no errors are reported, and confirm all status codes are OK\n2. Make sure `runs-on` in the workflow matches the expected labels set in the runner provider\n3. Diagnose relevant executions of the orchestrator step function by visiting the URL in `troubleshooting.stepFunctionUrl` from `status.json`\n 1. If the execution failed, check your runner provider configuration for errors\n 2. If the execution is still running for a long time, check the execution events to see why runner starting is being retried\n 3. If there are no relevant executions, move to the next step\n4. Confirm the webhook Lambda was called by visiting the URL in `troubleshooting.webhookHandlerUrl` from `status.json`\n 1. If it's not called or logs errors, confirm the webhook settings on the GitHub side\n 2. If you see too many errors, make sure you're only sending `workflow_job` events\n5. When using GitHub app, make sure there are active installations in `github.auth.app.installations`\n\nAll logs are saved in CloudWatch.\n* Log group names can be found in `status.json` for each provider, image builder, and other parts of the system\n* Some useful Logs Insights queries can be enabled with `GitHubRunners.createLogsInsightsQueries()`\n\nTo get `status.json`, check out the CloudFormation stack output for a command that generates it. The command looks like:\n\n```\naws --region us-east-1 lambda invoke --function-name status-XYZ123 status.json\n```\n\n## Monitoring\n\nThere are two important ways to monitor your runners:\n\n1. Make sure runners don't fail to start. When that happens, jobs may sit and wait. Use `GitHubRunners.metricFailed()` to get a metric for the number of failed runner starts. You should use this metric to trigger an alarm.\n2. Make sure runner images don't fail to build. Failed runner image builds mean you will get stuck with out-of-date software on your runners. It may lead to security vulnerabilities, or it may lead to slower runner start-ups as the runner software itself needs to be updated. Use `GitHubRunners.failedImageBuildsTopic()` to get SNS topic that gets notified of failed runner image builds. You should subscribe to this topic.\n\nOther useful metrics to track:\n\n1. Use `GitHubRunners.metricJobCompleted()` to get a metric for the number of completed jobs broken down by labels and job success.\n2. Use `GitHubRunners.metricTime()` to get a metric for the total time a runner is running. This includes the overhead of starting the runner.\n\n## Contributing\n\nIf you use and love this project, please consider contributing.\n\n1. 🪳 If you see something, say something. [Issues][16] help improve the quality of the project.\n * Include relevant logs and package versions for bugs.\n * When possible, describe the use-case behind feature requests.\n1. 🛠️ [Pull requests][17] are welcome.\n * Run `npm run build` before submitting to make sure all tests pass.\n * Allow edits from maintainers so small adjustments can be made easily.\n1. 💵 Consider [sponsoring][15] the project to show your support and optionally get your name listed below.\n\n## Other Options\n\n1. [philips-labs/terraform-aws-github-runner][3] if you're using Terraform\n2. [actions/actions-runner-controller][4] if you're using Kubernetes\n\n\n[1]: https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners\n[2]: https://github.com/marketplace/actions/configure-aws-credentials-for-github-actions\n[3]: https://github.com/philips-labs/terraform-aws-github-runner\n[4]: https://github.com/actions/actions-runner-controller\n[5]: https://github.com/actions/runner\n[6]: https://pypi.org/project/cloudsnorkel.cdk-github-runners\n[7]: https://www.npmjs.com/package/@cloudsnorkel/cdk-github-runners\n[8]: https://central.sonatype.com/artifact/com.cloudsnorkel/cdk.github.runners/\n[9]: https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps\n[10]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token\n[11]: https://pkg.go.dev/github.com/CloudSnorkel/cdk-github-runners-go/cloudsnorkelcdkgithubrunners\n[12]: https://www.nuget.org/packages/CloudSnorkel.Cdk.Github.Runners/\n[13]: https://constructs.dev/packages/@cloudsnorkel/cdk-github-runners/\n[14]: https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling\n[15]: https://github.com/sponsors/CloudSnorkel\n[16]: https://github.com/CloudSnorkel/cdk-github-runners/issues\n[17]: https://github.com/CloudSnorkel/cdk-github-runners/pulls\n"
|
|
3702
|
+
"markdown": "# GitHub Self-Hosted Runners CDK Constructs\n\n[][7]\n[][6]\n[][8]\n[][11]\n[][12]\n[](https://github.com/CloudSnorkel/cdk-github-runners/actions/workflows/release.yml)\n[](https://github.com/CloudSnorkel/cdk-github-runners/blob/main/LICENSE)\n\nUse this CDK construct to create ephemeral [self-hosted GitHub runners][1] on-demand inside your AWS account.\n\n* 🧩 Easy to configure GitHub integration with a web-based interface\n* 🧠 Customizable runners with decent defaults\n* 🏃🏻 Multiple runner configurations controlled by labels\n* 🔐 Everything fully hosted in your account\n* 🔃 Automatically updated build environment with latest runner version\n\nSelf-hosted runners in AWS are useful when:\n\n* You need easy access to internal resources in your actions\n* You want to pre-install some software for your actions\n* You want to provide some basic AWS API access (but [aws-actions/configure-aws-credentials][2] has more security controls)\n* You are using GitHub Enterprise Server\n\nEphemeral (or on-demand) runners are the [recommended way by GitHub][14] for auto-scaling, and they make sure all jobs run with a clean image. Runners are started on-demand. You don't pay unless a job is running.\n\n## API\n\nThe best way to browse API documentation is on [Constructs Hub][13]. It is available in all supported programming languages.\n\n## Providers\n\nA runner provider creates compute resources on-demand and uses [actions/runner][5] to start a runner.\n\n| | EC2 | CodeBuild | Fargate | ECS | Lambda |\n|------------------|-------------------|----------------------------|----------------|----------------|---------------|\n| **Time limit** | Unlimited | 8 hours | Unlimited | Unlimited | 15 minutes |\n| **vCPUs** | Unlimited | 2, 4, 8, or 72 | 0.25 to 4 | Unlimited | 1 to 6 |\n| **RAM** | Unlimited | 3gb, 7gb, 15gb, or 145gb | 512mb to 30gb | Unlimited | 128mb to 10gb |\n| **Storage** | Unlimited | 50gb to 824gb | 20gb to 200gb | Unlimited | Up to 10gb |\n| **Architecture** | x86_64, ARM64 | x86_64, ARM64 | x86_64, ARM64 | x86_64, ARM64 | x86_64, ARM64 |\n| **sudo** | ✔ | ✔ | ✔ | ✔ | ❌ |\n| **Docker** | ✔ | ✔ (Linux only) | ❌ | ✔ | ❌ |\n| **Spot pricing** | ✔ | ❌ | ✔ | ✔ | ❌ |\n| **OS** | Linux, Windows | Linux, Windows | Linux, Windows | Linux, Windows | Linux |\n\nThe best provider to use mostly depends on your current infrastructure. When in doubt, CodeBuild is always a good choice. Execution history and logs are easy to view, and it has no restrictive limits unless you need to run for more than 8 hours.\n\n* EC2 is useful when you want runners to have complete access to the host\n* ECS is useful when you want to control the infrastructure, like leaving the runner host running for faster startups\n* Lambda is useful for short jobs that can work within time, size and readonly system constraints\n\nYou can also create your own provider by implementing `IRunnerProvider`.\n\n## Installation\n\n1. Install and use the appropriate package\n <details><summary>Python</summary>\n\n ### Install\n Available on [PyPI][6].\n ```bash\n pip install cloudsnorkel.cdk-github-runners\n ```\n ### Use\n ```python\n from aws_cdk import App, Stack\n from cloudsnorkel.cdk_github_runners import GitHubRunners\n\n app = App()\n stack = Stack(app, \"github-runners\")\n GitHubRunners(stack, \"runners\")\n\n app.synth()\n ```\n </details>\n <details><summary>TypeScript or JavaScript</summary>\n\n ### Install\n Available on [npm][7].\n ```bash\n npm i @cloudsnorkel/cdk-github-runners\n ```\n ### Use\n ```typescript\n import { App, Stack } from 'aws-cdk-lib';\n import { GitHubRunners } from '@cloudsnorkel/cdk-github-runners';\n\n const app = new App();\n const stack = new Stack(app, 'github-runners');\n new GitHubRunners(stack, 'runners');\n\n app.synth();\n ```\n </details>\n <details><summary>Java</summary>\n\n ### Install\n Available on [Maven][8].\n ```xml\n <dependency>\n <groupId>com.cloudsnorkel</groupId>\n <artifactId>cdk.github.runners</artifactId>\n </dependency>\n ```\n ### Use\n ```java\n import software.amazon.awscdk.App;\n import software.amazon.awscdk.Stack;\n import com.cloudsnorkel.cdk.github.runners.GitHubRunners;\n\n public class Example {\n public static void main(String[] args){\n App app = new App();\n Stack stack = new Stack(app, \"github-runners\");\n GitHubRunners.Builder.create(stack, \"runners\").build();\n\n app.synth();\n }\n }\n ```\n </details>\n <details><summary>Go</summary>\n\n ### Install\n Available on [GitHub][11].\n ```bash\n go get github.com/CloudSnorkel/cdk-github-runners-go/cloudsnorkelcdkgithubrunners\n ```\n ### Use\n ```go\n package main\n\n import (\n \"github.com/CloudSnorkel/cdk-github-runners-go/cloudsnorkelcdkgithubrunners\"\n \"github.com/aws/aws-cdk-go/awscdk/v2\"\n \"github.com/aws/jsii-runtime-go\"\n )\n\n func main() {\n app := awscdk.NewApp(nil)\n stack := awscdk.NewStack(app, jsii.String(\"github-runners\"), &awscdk.StackProps{})\n cloudsnorkelcdkgithubrunners.NewGitHubRunners(stack, jsii.String(\"runners\"), &cloudsnorkelcdkgithubrunners.GitHubRunnersProps{})\n\n app.Synth(nil)\n }\n ```\n </details>\n <details><summary>.NET</summary>\n\n ### Install\n Available on [Nuget][12].\n ```bash\n dotnet add package CloudSnorkel.Cdk.Github.Runners\n ```\n ### Use\n ```csharp\n using Amazon.CDK;\n using CloudSnorkel;\n\n namespace Example\n {\n sealed class Program\n {\n public static void Main(string[] args)\n {\n var app = new App();\n var stack = new Stack(app, \"github-runners\");\n new GitHubRunners(stack, \"runners\");\n app.Synth();\n }\n }\n }\n ```\n </details>\n2. Use `GitHubRunners` construct in your code (starting with default arguments is fine)\n3. Deploy your stack\n4. Look for the status command output similar to `aws --region us-east-1 lambda invoke --function-name status-XYZ123 status.json`\n ```\n ✅ github-runners-test\n\n ✨ Deployment time: 260.01s\n\n Outputs:\n github-runners-test.runnersstatuscommand4A30F0F5 = aws --region us-east-1 lambda invoke --function-name github-runners-test-runnersstatus1A5771C0-mvttg8oPQnQS status.json\n ```\n5. Execute the status command (you may need to specify `--profile` too) and open the resulting `status.json` file\n6. Open the URL in `github.setup.url` from `status.json` or [manually setup GitHub](SETUP_GITHUB.md) integration as an app or with personal access token\n7. Run status command again to confirm `github.auth.status` and `github.webhook.status` are OK\n8. Trigger a GitHub action that has a `self-hosted` label with `runs-on: [self-hosted, linux, codebuild]` or similar\n9. If the action is not successful, see [troubleshooting](#Troubleshooting)\n\n[](https://youtu.be/wlyv_3V8lIw)\n\n## Customizing\n\nThe default providers configured by `GitHubRunners` are useful for testing but probably not too much for actual production work. They run in the default VPC or no VPC and have no added IAM permissions. You would usually want to configure the providers yourself.\n\nFor example:\n\n```typescript\nlet vpc: ec2.Vpc;\nlet runnerSg: ec2.SecurityGroup;\nlet dbSg: ec2.SecurityGroup;\nlet bucket: s3.Bucket;\n\n// create a custom CodeBuild provider\nconst myProvider = new CodeBuildRunnerProvider(this, 'codebuild runner', {\n labels: ['my-codebuild'],\n vpc: vpc,\n securityGroups: [runnerSg],\n});\n// grant some permissions to the provider\nbucket.grantReadWrite(myProvider);\ndbSg.connections.allowFrom(runnerSg, ec2.Port.tcp(3306), 'allow runners to connect to MySQL database');\n\n// create the runner infrastructure\nnew GitHubRunners(this, 'runners', {\n providers: [myProvider],\n});\n```\n\nAnother way to customize runners is by modifying the image used to spin them up. The image contains the [runner][5], any required dependencies, and integration code with the provider. You may choose to customize this image by adding more packages, for example.\n\n```typescript\nconst myBuilder = FargateRunnerProvider.imageBuilder(this, 'image builder');\nmyBuilder.addComponent(\n RunnerImageComponent.custom({ commands: ['apt install -y nginx xz-utils'] }),\n);\n\nconst myProvider = new FargateRunnerProvider(this, 'fargate runner', {\n labels: ['customized-fargate'],\n imageBuilder: myBuilder,\n});\n\n// create the runner infrastructure\nnew GitHubRunners(this, 'runners', {\n providers: [myProvider],\n});\n```\n\nYour workflow will then look like:\n\n```yaml\nname: self-hosted example\non: push\njobs:\n self-hosted:\n runs-on: [self-hosted, customized-fargate]\n steps:\n - run: echo hello world\n```\n\nWindows images can also be customized the same way.\n\n```typescript\nconst myWindowsBuilder = FargateRunnerProvider.imageBuilder(this, 'Windows image builder', {\n architecture: Architecture.X86_64,\n os: Os.WINDOWS,\n});\nmyWindowsBuilder.addComponent(\n RunnerImageComponent.custom({\n name: 'Ninja',\n commands: [\n 'Invoke-WebRequest -UseBasicParsing -Uri \"https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-win.zip\" -OutFile ninja.zip',\n 'Expand-Archive ninja.zip -DestinationPath C:\\\\actions',\n 'del ninja.zip',\n ],\n }),\n);\n\nconst myProvider = new FargateRunnerProvider(this, 'fargate runner', {\n labels: ['customized-windows-fargate'],\n imageBuilder: myWindowsBuilder,\n});\n\nnew GitHubRunners(this, 'runners', {\n providers: [myProvider],\n});\n```\n\nThe runner OS and architecture is determined by the image it is set to use. For example, to create a Fargate runner provider for ARM64 set the `architecture` property for the image builder to `Architecture.ARM64` in the image builder properties.\n\n```typescript\nnew GitHubRunners(this, 'runners', {\n providers: [\n new FargateRunnerProvider(this, 'fargate runner', {\n labels: ['arm64', 'fargate'],\n imageBuilder: FargateRunnerProvider.imageBuilder(this, 'image builder', {\n architecture: Architecture.ARM64,\n os: Os.LINUX_UBUNTU,\n }),\n }),\n ],\n});\n```\n\n## Architecture\n\n\n\n## Troubleshooting\n\nRunners are started in response to a webhook coming in from GitHub. If there are any issues starting the runner like missing capacity or transient API issues, the provider will keep retrying for 24 hours. Configuration issue related errors like pointing to a missing AMI will not be retried. GitHub itself will cancel the job if it can't find a runner for 24 hours. If your jobs don't start, follow the steps below to examine all parts of this workflow.\n\n1. Always start with the status function, make sure no errors are reported, and confirm all status codes are OK\n2. Make sure `runs-on` in the workflow matches the expected labels set in the runner provider\n3. Diagnose relevant executions of the orchestrator step function by visiting the URL in `troubleshooting.stepFunctionUrl` from `status.json`\n 1. If the execution failed, check your runner provider configuration for errors\n 2. If the execution is still running for a long time, check the execution events to see why runner starting is being retried\n 3. If there are no relevant executions, move to the next step\n4. Confirm the webhook Lambda was called by visiting the URL in `troubleshooting.webhookHandlerUrl` from `status.json`\n 1. If it's not called or logs errors, confirm the webhook settings on the GitHub side\n 2. If you see too many errors, make sure you're only sending `workflow_job` events\n5. When using GitHub app, make sure there are active installations in `github.auth.app.installations`\n\nAll logs are saved in CloudWatch.\n* Log group names can be found in `status.json` for each provider, image builder, and other parts of the system\n* Some useful Logs Insights queries can be enabled with `GitHubRunners.createLogsInsightsQueries()`\n\nTo get `status.json`, check out the CloudFormation stack output for a command that generates it. The command looks like:\n\n```\naws --region us-east-1 lambda invoke --function-name status-XYZ123 status.json\n```\n\n## Monitoring\n\nThere are two important ways to monitor your runners:\n\n1. Make sure runners don't fail to start. When that happens, jobs may sit and wait. Use `GitHubRunners.metricFailed()` to get a metric for the number of failed runner starts. You should use this metric to trigger an alarm.\n2. Make sure runner images don't fail to build. Failed runner image builds mean you will get stuck with out-of-date software on your runners. It may lead to security vulnerabilities, or it may lead to slower runner start-ups as the runner software itself needs to be updated. Use `GitHubRunners.failedImageBuildsTopic()` to get SNS topic that gets notified of failed runner image builds. You should subscribe to this topic.\n\nOther useful metrics to track:\n\n1. Use `GitHubRunners.metricJobCompleted()` to get a metric for the number of completed jobs broken down by labels and job success.\n2. Use `GitHubRunners.metricTime()` to get a metric for the total time a runner is running. This includes the overhead of starting the runner.\n\n## Contributing\n\nIf you use and love this project, please consider contributing.\n\n1. 🪳 If you see something, say something. [Issues][16] help improve the quality of the project.\n * Include relevant logs and package versions for bugs.\n * When possible, describe the use-case behind feature requests.\n1. 🛠️ [Pull requests][17] are welcome.\n * Run `npm run build` before submitting to make sure all tests pass.\n * Allow edits from maintainers so small adjustments can be made easily.\n1. 💵 Consider [sponsoring][15] the project to show your support and optionally get your name listed below.\n\n## Other Options\n\n1. [philips-labs/terraform-aws-github-runner][3] if you're using Terraform\n2. [actions/actions-runner-controller][4] if you're using Kubernetes\n\n\n[1]: https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners\n[2]: https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions\n[3]: https://github.com/philips-labs/terraform-aws-github-runner\n[4]: https://github.com/actions/actions-runner-controller\n[5]: https://github.com/actions/runner\n[6]: https://pypi.org/project/cloudsnorkel.cdk-github-runners\n[7]: https://www.npmjs.com/package/@cloudsnorkel/cdk-github-runners\n[8]: https://central.sonatype.com/artifact/com.cloudsnorkel/cdk.github.runners/\n[9]: https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps\n[10]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token\n[11]: https://pkg.go.dev/github.com/CloudSnorkel/cdk-github-runners-go/cloudsnorkelcdkgithubrunners\n[12]: https://www.nuget.org/packages/CloudSnorkel.Cdk.Github.Runners/\n[13]: https://constructs.dev/packages/@cloudsnorkel/cdk-github-runners/\n[14]: https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling\n[15]: https://github.com/sponsors/CloudSnorkel\n[16]: https://github.com/CloudSnorkel/cdk-github-runners/issues\n[17]: https://github.com/CloudSnorkel/cdk-github-runners/pulls\n"
|
|
3703
3703
|
},
|
|
3704
3704
|
"repository": {
|
|
3705
3705
|
"type": "git",
|
|
@@ -4669,7 +4669,7 @@
|
|
|
4669
4669
|
"immutable": true,
|
|
4670
4670
|
"locationInModule": {
|
|
4671
4671
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
4672
|
-
"line":
|
|
4672
|
+
"line": 58
|
|
4673
4673
|
},
|
|
4674
4674
|
"name": "fastLaunchOptions",
|
|
4675
4675
|
"optional": true,
|
|
@@ -4694,6 +4694,25 @@
|
|
|
4694
4694
|
"type": {
|
|
4695
4695
|
"fqn": "aws-cdk-lib.aws_ec2.InstanceType"
|
|
4696
4696
|
}
|
|
4697
|
+
},
|
|
4698
|
+
{
|
|
4699
|
+
"abstract": true,
|
|
4700
|
+
"docs": {
|
|
4701
|
+
"default": "default size for AMI (usually 30GB for Linux and 50GB for Windows)",
|
|
4702
|
+
"remarks": "Use this if you're building images with big components and need more space.",
|
|
4703
|
+
"stability": "experimental",
|
|
4704
|
+
"summary": "Size of volume available for builder instances. This modifies the boot volume size and doesn't add any additional volumes."
|
|
4705
|
+
},
|
|
4706
|
+
"immutable": true,
|
|
4707
|
+
"locationInModule": {
|
|
4708
|
+
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
4709
|
+
"line": 49
|
|
4710
|
+
},
|
|
4711
|
+
"name": "storageSize",
|
|
4712
|
+
"optional": true,
|
|
4713
|
+
"type": {
|
|
4714
|
+
"fqn": "aws-cdk-lib.Size"
|
|
4715
|
+
}
|
|
4697
4716
|
}
|
|
4698
4717
|
],
|
|
4699
4718
|
"symbolId": "src/image-builders/aws-image-builder/builder:AwsImageBuilderRunnerImageBuilderProps"
|
|
@@ -8689,7 +8708,7 @@
|
|
|
8689
8708
|
"kind": "interface",
|
|
8690
8709
|
"locationInModule": {
|
|
8691
8710
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
8692
|
-
"line":
|
|
8711
|
+
"line": 64
|
|
8693
8712
|
},
|
|
8694
8713
|
"name": "FastLaunchOptions",
|
|
8695
8714
|
"properties": [
|
|
@@ -8707,7 +8726,7 @@
|
|
|
8707
8726
|
"immutable": true,
|
|
8708
8727
|
"locationInModule": {
|
|
8709
8728
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
8710
|
-
"line":
|
|
8729
|
+
"line": 75
|
|
8711
8730
|
},
|
|
8712
8731
|
"name": "enabled",
|
|
8713
8732
|
"optional": true,
|
|
@@ -8726,7 +8745,7 @@
|
|
|
8726
8745
|
"immutable": true,
|
|
8727
8746
|
"locationInModule": {
|
|
8728
8747
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
8729
|
-
"line":
|
|
8748
|
+
"line": 84
|
|
8730
8749
|
},
|
|
8731
8750
|
"name": "maxParallelLaunches",
|
|
8732
8751
|
"optional": true,
|
|
@@ -8744,7 +8763,7 @@
|
|
|
8744
8763
|
"immutable": true,
|
|
8745
8764
|
"locationInModule": {
|
|
8746
8765
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
8747
|
-
"line":
|
|
8766
|
+
"line": 91
|
|
8748
8767
|
},
|
|
8749
8768
|
"name": "targetResourceCount",
|
|
8750
8769
|
"optional": true,
|
|
@@ -9879,7 +9898,7 @@
|
|
|
9879
9898
|
"kind": "interface",
|
|
9880
9899
|
"locationInModule": {
|
|
9881
9900
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
9882
|
-
"line":
|
|
9901
|
+
"line": 97
|
|
9883
9902
|
},
|
|
9884
9903
|
"name": "ImageBuilderAsset",
|
|
9885
9904
|
"properties": [
|
|
@@ -9892,7 +9911,7 @@
|
|
|
9892
9911
|
"immutable": true,
|
|
9893
9912
|
"locationInModule": {
|
|
9894
9913
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
9895
|
-
"line":
|
|
9914
|
+
"line": 106
|
|
9896
9915
|
},
|
|
9897
9916
|
"name": "asset",
|
|
9898
9917
|
"type": {
|
|
@@ -9908,7 +9927,7 @@
|
|
|
9908
9927
|
"immutable": true,
|
|
9909
9928
|
"locationInModule": {
|
|
9910
9929
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
9911
|
-
"line":
|
|
9930
|
+
"line": 101
|
|
9912
9931
|
},
|
|
9913
9932
|
"name": "path",
|
|
9914
9933
|
"type": {
|
|
@@ -9934,7 +9953,7 @@
|
|
|
9934
9953
|
},
|
|
9935
9954
|
"locationInModule": {
|
|
9936
9955
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
9937
|
-
"line":
|
|
9956
|
+
"line": 180
|
|
9938
9957
|
},
|
|
9939
9958
|
"parameters": [
|
|
9940
9959
|
{
|
|
@@ -9960,7 +9979,7 @@
|
|
|
9960
9979
|
"kind": "class",
|
|
9961
9980
|
"locationInModule": {
|
|
9962
9981
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
9963
|
-
"line":
|
|
9982
|
+
"line": 167
|
|
9964
9983
|
},
|
|
9965
9984
|
"methods": [
|
|
9966
9985
|
{
|
|
@@ -10006,7 +10025,7 @@
|
|
|
10006
10025
|
},
|
|
10007
10026
|
"locationInModule": {
|
|
10008
10027
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10009
|
-
"line":
|
|
10028
|
+
"line": 282
|
|
10010
10029
|
},
|
|
10011
10030
|
"name": "grantAssetsRead",
|
|
10012
10031
|
"parameters": [
|
|
@@ -10024,7 +10043,7 @@
|
|
|
10024
10043
|
},
|
|
10025
10044
|
"locationInModule": {
|
|
10026
10045
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10027
|
-
"line":
|
|
10046
|
+
"line": 288
|
|
10028
10047
|
},
|
|
10029
10048
|
"name": "prefixCommandsWithErrorHandling",
|
|
10030
10049
|
"parameters": [
|
|
@@ -10068,7 +10087,7 @@
|
|
|
10068
10087
|
"immutable": true,
|
|
10069
10088
|
"locationInModule": {
|
|
10070
10089
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10071
|
-
"line":
|
|
10090
|
+
"line": 171
|
|
10072
10091
|
},
|
|
10073
10092
|
"name": "arn",
|
|
10074
10093
|
"type": {
|
|
@@ -10083,7 +10102,7 @@
|
|
|
10083
10102
|
"immutable": true,
|
|
10084
10103
|
"locationInModule": {
|
|
10085
10104
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10086
|
-
"line":
|
|
10105
|
+
"line": 176
|
|
10087
10106
|
},
|
|
10088
10107
|
"name": "platform",
|
|
10089
10108
|
"type": {
|
|
@@ -10104,7 +10123,7 @@
|
|
|
10104
10123
|
"kind": "interface",
|
|
10105
10124
|
"locationInModule": {
|
|
10106
10125
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10107
|
-
"line":
|
|
10126
|
+
"line": 112
|
|
10108
10127
|
},
|
|
10109
10128
|
"name": "ImageBuilderComponentProperties",
|
|
10110
10129
|
"properties": [
|
|
@@ -10118,7 +10137,7 @@
|
|
|
10118
10137
|
"immutable": true,
|
|
10119
10138
|
"locationInModule": {
|
|
10120
10139
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10121
|
-
"line":
|
|
10140
|
+
"line": 133
|
|
10122
10141
|
},
|
|
10123
10142
|
"name": "commands",
|
|
10124
10143
|
"type": {
|
|
@@ -10139,7 +10158,7 @@
|
|
|
10139
10158
|
"immutable": true,
|
|
10140
10159
|
"locationInModule": {
|
|
10141
10160
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10142
|
-
"line":
|
|
10161
|
+
"line": 126
|
|
10143
10162
|
},
|
|
10144
10163
|
"name": "description",
|
|
10145
10164
|
"type": {
|
|
@@ -10155,7 +10174,7 @@
|
|
|
10155
10174
|
"immutable": true,
|
|
10156
10175
|
"locationInModule": {
|
|
10157
10176
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10158
|
-
"line":
|
|
10177
|
+
"line": 121
|
|
10159
10178
|
},
|
|
10160
10179
|
"name": "displayName",
|
|
10161
10180
|
"type": {
|
|
@@ -10172,7 +10191,7 @@
|
|
|
10172
10191
|
"immutable": true,
|
|
10173
10192
|
"locationInModule": {
|
|
10174
10193
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10175
|
-
"line":
|
|
10194
|
+
"line": 116
|
|
10176
10195
|
},
|
|
10177
10196
|
"name": "platform",
|
|
10178
10197
|
"type": {
|
|
@@ -10188,7 +10207,7 @@
|
|
|
10188
10207
|
"immutable": true,
|
|
10189
10208
|
"locationInModule": {
|
|
10190
10209
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10191
|
-
"line":
|
|
10210
|
+
"line": 138
|
|
10192
10211
|
},
|
|
10193
10212
|
"name": "assets",
|
|
10194
10213
|
"optional": true,
|
|
@@ -10211,7 +10230,7 @@
|
|
|
10211
10230
|
"immutable": true,
|
|
10212
10231
|
"locationInModule": {
|
|
10213
10232
|
"filename": "src/image-builders/aws-image-builder/builder.ts",
|
|
10214
|
-
"line":
|
|
10233
|
+
"line": 145
|
|
10215
10234
|
},
|
|
10216
10235
|
"name": "reboot",
|
|
10217
10236
|
"optional": true,
|
|
@@ -13893,6 +13912,6 @@
|
|
|
13893
13912
|
"symbolId": "src/image-builders/aws-image-builder/deprecated/windows-components:WindowsComponents"
|
|
13894
13913
|
}
|
|
13895
13914
|
},
|
|
13896
|
-
"version": "0.14.
|
|
13897
|
-
"fingerprint": "
|
|
13915
|
+
"version": "0.14.3",
|
|
13916
|
+
"fingerprint": "leEBaC16ulYoOEB1SUy2OxIQrRmMPi6HLI6oCUAisVM="
|
|
13898
13917
|
}
|
package/API.md
CHANGED
|
@@ -5116,6 +5116,7 @@ const awsImageBuilderRunnerImageBuilderProps: AwsImageBuilderRunnerImageBuilderP
|
|
|
5116
5116
|
| --- | --- | --- |
|
|
5117
5117
|
| <code><a href="#@cloudsnorkel/cdk-github-runners.AwsImageBuilderRunnerImageBuilderProps.property.fastLaunchOptions">fastLaunchOptions</a></code> | <code><a href="#@cloudsnorkel/cdk-github-runners.FastLaunchOptions">FastLaunchOptions</a></code> | Options for fast launch. |
|
|
5118
5118
|
| <code><a href="#@cloudsnorkel/cdk-github-runners.AwsImageBuilderRunnerImageBuilderProps.property.instanceType">instanceType</a></code> | <code>aws-cdk-lib.aws_ec2.InstanceType</code> | The instance type used to build the image. |
|
|
5119
|
+
| <code><a href="#@cloudsnorkel/cdk-github-runners.AwsImageBuilderRunnerImageBuilderProps.property.storageSize">storageSize</a></code> | <code>aws-cdk-lib.Size</code> | Size of volume available for builder instances. This modifies the boot volume size and doesn't add any additional volumes. |
|
|
5119
5120
|
|
|
5120
5121
|
---
|
|
5121
5122
|
|
|
@@ -5147,6 +5148,21 @@ The instance type used to build the image.
|
|
|
5147
5148
|
|
|
5148
5149
|
---
|
|
5149
5150
|
|
|
5151
|
+
##### `storageSize`<sup>Optional</sup> <a name="storageSize" id="@cloudsnorkel/cdk-github-runners.AwsImageBuilderRunnerImageBuilderProps.property.storageSize"></a>
|
|
5152
|
+
|
|
5153
|
+
```typescript
|
|
5154
|
+
public readonly storageSize: Size;
|
|
5155
|
+
```
|
|
5156
|
+
|
|
5157
|
+
- *Type:* aws-cdk-lib.Size
|
|
5158
|
+
- *Default:* default size for AMI (usually 30GB for Linux and 50GB for Windows)
|
|
5159
|
+
|
|
5160
|
+
Size of volume available for builder instances. This modifies the boot volume size and doesn't add any additional volumes.
|
|
5161
|
+
|
|
5162
|
+
Use this if you're building images with big components and need more space.
|
|
5163
|
+
|
|
5164
|
+
---
|
|
5165
|
+
|
|
5150
5166
|
### CodeBuildImageBuilderProps <a name="CodeBuildImageBuilderProps" id="@cloudsnorkel/cdk-github-runners.CodeBuildImageBuilderProps"></a>
|
|
5151
5167
|
|
|
5152
5168
|
Properties for CodeBuildImageBuilder construct.
|
package/README.md
CHANGED
|
@@ -356,7 +356,7 @@ If you use and love this project, please consider contributing.
|
|
|
356
356
|
|
|
357
357
|
|
|
358
358
|
[1]: https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners
|
|
359
|
-
[2]: https://github.com/marketplace/actions/configure-aws-credentials-for-github-actions
|
|
359
|
+
[2]: https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions
|
|
360
360
|
[3]: https://github.com/philips-labs/terraform-aws-github-runner
|
|
361
361
|
[4]: https://github.com/actions/actions-runner-controller
|
|
362
362
|
[5]: https://github.com/actions/runner
|