@cloudsnorkel/cdk-github-runners 0.14.14 → 0.14.16

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.
Files changed (65) hide show
  1. package/.jsii +832 -287
  2. package/API.md +576 -6
  3. package/README.md +149 -0
  4. package/SETUP_GITHUB.md +99 -3
  5. package/assets/delete-failed-runner.lambda/index.js +40 -31
  6. package/assets/idle-runner-repear.lambda/index.js +40 -31
  7. package/assets/providers/lambda-runner.sh +2 -1
  8. package/assets/setup.lambda/index.html +12 -7
  9. package/assets/setup.lambda/index.js +26 -26
  10. package/assets/status.lambda/index.js +40 -31
  11. package/assets/token-retriever.lambda/index.js +40 -31
  12. package/assets/webhook-handler.lambda/index.js +110 -43
  13. package/assets/webhook-redelivery.lambda/index.js +40 -31
  14. package/lib/access.js +1 -1
  15. package/lib/image-builders/api.js +1 -1
  16. package/lib/image-builders/aws-image-builder/ami.d.ts +1 -2
  17. package/lib/image-builders/aws-image-builder/ami.js +6 -13
  18. package/lib/image-builders/aws-image-builder/builder.d.ts +4 -2
  19. package/lib/image-builders/aws-image-builder/builder.js +36 -34
  20. package/lib/image-builders/aws-image-builder/container.d.ts +2 -2
  21. package/lib/image-builders/aws-image-builder/container.js +7 -12
  22. package/lib/image-builders/aws-image-builder/deprecated/ami.js +1 -1
  23. package/lib/image-builders/aws-image-builder/deprecated/container.js +1 -1
  24. package/lib/image-builders/aws-image-builder/deprecated/linux-components.js +1 -1
  25. package/lib/image-builders/aws-image-builder/deprecated/windows-components.js +1 -1
  26. package/lib/image-builders/aws-image-builder/index.d.ts +0 -1
  27. package/lib/image-builders/aws-image-builder/index.js +1 -2
  28. package/lib/image-builders/aws-image-builder/workflow.d.ts +4 -4
  29. package/lib/image-builders/aws-image-builder/workflow.js +7 -10
  30. package/lib/image-builders/codebuild-deprecated.js +1 -1
  31. package/lib/image-builders/components.js +1 -1
  32. package/lib/image-builders/static.js +1 -1
  33. package/lib/index.d.ts +1 -0
  34. package/lib/index.js +2 -1
  35. package/lib/providers/codebuild.d.ts +1 -0
  36. package/lib/providers/codebuild.js +22 -9
  37. package/lib/providers/common.d.ts +59 -0
  38. package/lib/providers/common.js +11 -4
  39. package/lib/providers/composite.d.ts +61 -0
  40. package/lib/providers/composite.js +229 -0
  41. package/lib/providers/ec2.d.ts +1 -0
  42. package/lib/providers/ec2.js +22 -13
  43. package/lib/providers/ecs.d.ts +4 -0
  44. package/lib/providers/ecs.js +16 -6
  45. package/lib/providers/fargate.d.ts +1 -0
  46. package/lib/providers/fargate.js +19 -9
  47. package/lib/providers/index.d.ts +1 -0
  48. package/lib/providers/index.js +2 -1
  49. package/lib/providers/lambda.d.ts +1 -0
  50. package/lib/providers/lambda.js +8 -5
  51. package/lib/runner.d.ts +29 -5
  52. package/lib/runner.js +57 -24
  53. package/lib/secrets.js +1 -1
  54. package/lib/webhook-handler.lambda.d.ts +11 -0
  55. package/lib/webhook-handler.lambda.js +81 -14
  56. package/lib/webhook.d.ts +52 -7
  57. package/lib/webhook.js +4 -2
  58. package/package.json +16 -20
  59. package/assets/image-builders/aws-image-builder/versioner.lambda/index.js +0 -2115
  60. package/lib/image-builders/aws-image-builder/common.d.ts +0 -10
  61. package/lib/image-builders/aws-image-builder/common.js +0 -48
  62. package/lib/image-builders/aws-image-builder/versioner-function.d.ts +0 -13
  63. package/lib/image-builders/aws-image-builder/versioner-function.js +0 -23
  64. package/lib/image-builders/aws-image-builder/versioner.lambda.d.ts +0 -7
  65. package/lib/image-builders/aws-image-builder/versioner.lambda.js +0 -115
package/README.md CHANGED
@@ -296,6 +296,155 @@ new GitHubRunners(this, 'runners', {
296
296
  });
297
297
  ```
298
298
 
299
+ ### Composite Providers
300
+
301
+ Composite providers allow you to combine multiple runner providers with different strategies. There are two types:
302
+
303
+ **Fallback Strategy**: Try providers in order until one succeeds. Useful for trying spot instances first, then falling back to on-demand if spot capacity is unavailable.
304
+
305
+ ```typescript
306
+ // Try spot instances first, fall back to on-demand if spot is unavailable
307
+ const ecsFallback = CompositeProvider.fallback(this, 'ECS Fallback', [
308
+ new EcsRunnerProvider(this, 'ECS Spot', {
309
+ labels: ['ecs', 'linux', 'x64'],
310
+ spot: true,
311
+ // ... other config
312
+ }),
313
+ new EcsRunnerProvider(this, 'ECS On-Demand', {
314
+ labels: ['ecs', 'linux', 'x64'],
315
+ spot: false,
316
+ // ... other config
317
+ }),
318
+ ]);
319
+
320
+ new GitHubRunners(this, 'runners', {
321
+ providers: [ecsFallback],
322
+ });
323
+ ```
324
+
325
+ **Weighted Distribution Strategy**: Randomly select a provider based on weights. Useful for distributing load across multiple availability zones or instance types.
326
+
327
+ ```typescript
328
+ // Distribute 60% of traffic to AZ-1, 40% to AZ-2
329
+ const distributedProvider = CompositeProvider.distribute(this, 'Fargate Distribution', [
330
+ {
331
+ weight: 3, // 3/(3+2) = 60%
332
+ provider: new FargateRunnerProvider(this, 'Fargate AZ-1', {
333
+ labels: ['fargate', 'linux', 'x64'],
334
+ subnetSelection: vpc.selectSubnets({
335
+ availabilityZones: [vpc.availabilityZones[0]],
336
+ }),
337
+ // ... other config
338
+ }),
339
+ },
340
+ {
341
+ weight: 2, // 2/(3+2) = 40%
342
+ provider: new FargateRunnerProvider(this, 'Fargate AZ-2', {
343
+ labels: ['fargate', 'linux', 'x64'],
344
+ subnetSelection: vpc.selectSubnets({
345
+ availabilityZones: [vpc.availabilityZones[1]],
346
+ }),
347
+ // ... other config
348
+ }),
349
+ },
350
+ ]);
351
+
352
+ new GitHubRunners(this, 'runners', {
353
+ providers: [distributedProvider],
354
+ });
355
+ ```
356
+
357
+ **Important**: All providers in a composite must have the exact same labels. This ensures any provisioned runner can match the labels requested by the GitHub workflow job.
358
+
359
+ ### Custom Provider Selection
360
+
361
+ By default, providers are selected based on label matching: the first provider that has all the labels requested by the job is selected. You can customize this behavior using a provider selector Lambda function to:
362
+
363
+ * Filter out certain jobs (prevent runner provisioning)
364
+ * Dynamically select a provider based on job characteristics (repository, branch, time of day, etc.)
365
+ * Customize labels for the runner (add, remove, or modify labels dynamically)
366
+
367
+ The selector function receives the full GitHub webhook payload, a map of all available providers and their labels, and the default provider/labels that would have been selected. It returns the provider to use (or `undefined` to skip runner creation) and the labels to assign to the runner.
368
+
369
+ **Example: Route jobs to different providers based on repository**
370
+
371
+ ```typescript
372
+ import { ComputeType } from 'aws-cdk-lib/aws-codebuild';
373
+ import { Function, Code, Runtime } from 'aws-cdk-lib/aws-lambda';
374
+ import { GitHubRunners, CodeBuildRunnerProvider } from '@cloudsnorkel/cdk-github-runners';
375
+
376
+ const defaultProvider = new CodeBuildRunnerProvider(this, 'default', {
377
+ labels: ['custom-runner', 'default'],
378
+ });
379
+ const productionProvider = new CodeBuildRunnerProvider(this, 'production', {
380
+ labels: ['custom-runner', 'production'],
381
+ computeType: ComputeType.LARGE,
382
+ });
383
+
384
+ const providerSelector = new Function(this, 'provider-selector', {
385
+ runtime: Runtime.NODEJS_LATEST,
386
+ handler: 'index.handler',
387
+ code: Code.fromInline(`
388
+ exports.handler = async (event) => {
389
+ const { payload, providers, defaultProvider, defaultLabels } = event;
390
+
391
+ // Route production repos to dedicated provider
392
+ if (payload.repository.name.includes('prod')) {
393
+ return {
394
+ provider: '${productionProvider.node.path}',
395
+ labels: ['custom-runner', 'production', 'modified-via-selector'],
396
+ };
397
+ }
398
+
399
+ // Filter out draft PRs
400
+ if (payload.workflow_job.head_branch?.startsWith('draft/')) {
401
+ return { provider: undefined }; // Skip runner provisioning
402
+ }
403
+
404
+ // Use default for everything else
405
+ return {
406
+ provider: defaultProvider,
407
+ labels: defaultLabels,
408
+ };
409
+ };
410
+ `),
411
+ });
412
+
413
+ new GitHubRunners(this, 'runners', {
414
+ providers: [defaultProvider, productionProvider],
415
+ providerSelector: providerSelector,
416
+ });
417
+ ```
418
+
419
+ **Example: Add dynamic labels based on job metadata**
420
+
421
+ ```typescript
422
+ const providerSelector = new Function(this, 'provider-selector', {
423
+ runtime: Runtime.NODEJS_LATEST,
424
+ handler: 'index.handler',
425
+ code: Code.fromInline(`
426
+ exports.handler = async (event) => {
427
+ const { payload, defaultProvider, defaultLabels } = event;
428
+
429
+ // Add branch name as a label
430
+ const branch = payload.workflow_job.head_branch || 'unknown';
431
+ const labels = [...(defaultLabels || []), 'branch:' + branch];
432
+
433
+ return {
434
+ provider: defaultProvider,
435
+ labels: labels,
436
+ };
437
+ };
438
+ `),
439
+ });
440
+ ```
441
+
442
+ **Important considerations:**
443
+
444
+ * ⚠️ **Label matching responsibility**: You are responsible for ensuring the selected provider's labels match what the job requires. If labels don't match, the runner will be provisioned but GitHub Actions won't assign the job to it.
445
+ * ⚠️ **No guarantee of assignment**: Provider selection only determines which provider will provision a runner. GitHub Actions may still route the job to any available runner with matching labels. For reliable provider assignment, consider repo-level runner registration (the default).
446
+ * ⚡ **Performance**: The selector runs synchronously during webhook processing. Keep it fast and efficient—the webhook has a 30-second timeout total.
447
+
299
448
  ## Examples
300
449
 
301
450
  Beyond the code snippets above, the fullest example available is the [integration test](test/default.integ.ts).
package/SETUP_GITHUB.md CHANGED
@@ -1,6 +1,102 @@
1
1
  # Setup GitHub
2
2
 
3
- Integration with GitHub can be done using an [app](#app-authentication) or [personal access token](#personal-access-token). Using an app allows more fine-grained access control. Using an app is easier with the setup wizard.
3
+ ## Overview of Options
4
+
5
+ You will need to make several decisions during setup. Here's a quick guide to help you choose:
6
+
7
+ ### 1. Authentication Method
8
+
9
+ **Choose between:**
10
+ - **GitHub App** (recommended) - More fine-grained permissions, easier setup with wizard, better security
11
+ - **Personal Access Token (PAT)** - Simpler but less flexible, requires manual webhook setup
12
+
13
+ **When to use App:** Almost always. Use PAT only if you have specific constraints that prevent using an app.
14
+
15
+ **When to use PAT:** If you need a quick setup and don't need fine-grained permissions, or if your organization has policies preventing app creation.
16
+
17
+ > **⚠️ IT/DevOps Help May Be Required:** Installing a GitHub app on repositories or organizations may require repository administrator or organization owner permissions. You may need to coordinate with your IT/devops team to install the app on the desired repositories.
18
+
19
+ ### 2. GitHub Instance
20
+
21
+ **Choose between:**
22
+ - **GitHub.com** - Public GitHub
23
+ - **GitHub Enterprise Server** - Self-hosted GitHub instance
24
+
25
+ **When to use Enterprise Server:** If your organization uses a self-hosted GitHub Enterprise Server instance.
26
+
27
+ > **⚠️ IT/DevOps Help Required:** Setting up with GitHub Enterprise Server requires coordination with your IT/devops team to obtain the correct domain and ensure network connectivity.
28
+
29
+ ### 3. App Scope (if using App)
30
+
31
+ **Choose between:**
32
+ - **User App** - For personal repositories
33
+ - **Organization App** - For organization repositories
34
+
35
+ **When to use User App:** If you only need to run workflows for your personal repositories.
36
+
37
+ **When to use Organization App:** If you need to run workflows for repositories in an organization.
38
+
39
+ > **⚠️ IT/DevOps Approval Required:** Creating an organization app may require organization owner or administrator permissions. You may need to request approval from your IT/devops team or organization administrators.
40
+
41
+ ### 4. Runner Registration Level
42
+
43
+ **Choose between:**
44
+ - **Repository-level** (recommended) - Runners registered to specific repositories
45
+ - **Organization-level** - Runners registered to the entire organization
46
+
47
+ > **Note:** This determines where on-demand runners will be registered dynamically each time they are provisioned. This is independent of where the GitHub app is installed. Organization-level registration makes runners available to **all repositories in the organization**, regardless of app installation.
48
+
49
+ **When to use Repository-level:**
50
+ - You want better isolation between repositories
51
+ - You want to reduce the risk of jobs being accidentally assigned to the wrong runners
52
+ - You're okay with requiring the `administration` permission
53
+
54
+ **When to use Organization-level:**
55
+ - You need to minimize permissions (only requires `organization_self_hosted_runners`)
56
+ - You fully trust all repositories in your organization
57
+ - You want all repositories to share the same pool of runners
58
+
59
+ > **⚠️ IT/DevOps Approval May Be Required:** Organization-level registration affects all repositories in the organization. Your organization may have policies requiring approval before enabling organization-wide runner registration.
60
+
61
+ #### Registration Level Recommendation
62
+
63
+ This determines where on-demand runners will be registered dynamically each time they are provisioned. This is independent of where the GitHub app is installed.
64
+
65
+ Repository-level registration is recommended over organization-level registration for better control and security:
66
+
67
+ * **Reduced risk of accidental job assignment**: Runners are dynamically registered to the repository matching the job that triggered them. This ensures jobs are only assigned to runners intended for that specific repository, reducing the risk of accidental assignment when using multiple runner configurations.
68
+ * **Better isolation**: Each repository only sees runners that are explicitly registered to it, providing better isolation between different projects or teams.
69
+ * **Trade-off**: Repository-level registration requires the `administration` permission, which is broader than the `organization_self_hosted_runners` permission required for organization-level registration. If you need to minimize permissions and fully trust all repositories in your organization, organization-level registration may be acceptable.
70
+
71
+ Organization-level registration registers runners to the entire organization, making them available to **all repositories in the organization**, regardless of whether the app is installed on those repositories. This can lead to jobs being routed to runners that weren't intended for them. Runners may be assigned jobs from repositories where this app isn't even installed.
72
+
73
+ ### 5. Setup Method
74
+
75
+ **Choose between:**
76
+ - **Setup Wizard** (recommended) - Interactive web interface, guides you through the process
77
+ - **Manual Setup** - Step-by-step instructions, more control over each step
78
+
79
+ **When to use Setup Wizard:** If you prefer a guided, interactive experience.
80
+
81
+ **When to use Manual Setup:** If you need more control, want to understand each step in detail, or the wizard isn't available.
82
+
83
+ ### 6. Webhook Scope (if using PAT)
84
+
85
+ **Choose between:**
86
+ - **Organization/Enterprise webhook** - Single webhook for all repositories
87
+ - **Repository-level webhooks** - One webhook per repository
88
+
89
+ **When to use Organization/Enterprise webhook:** If you want to manage a single webhook for multiple repositories.
90
+
91
+ **When to use Repository-level webhooks:** If you only need runners for a few specific repositories.
92
+
93
+ > **⚠️ IT/DevOps Approval Required:** Creating organization or enterprise-level webhooks requires organization owner or enterprise administrator permissions. Repository-level webhooks only require repository admin permissions.
94
+
95
+ ---
96
+
97
+ ## Setup GitHub
98
+
99
+ Integration with GitHub can be done using an [app](#app-authentication) or [personal access token](#personal-access-token-authentication). Using an app allows more fine-grained access control. Using an app is easier with the setup wizard.
4
100
 
5
101
  ## App Authentication
6
102
 
@@ -49,7 +145,7 @@ Integration with GitHub can be done using an [app](#app-authentication) or [pers
49
145
  1. Open the downloaded private key with any text editor
50
146
  2. Copy the text from the private key as-is into the secret
51
147
 
52
- ## Personal Access Token
148
+ ## Personal Access Token Authentication
53
149
 
54
150
  ### Create Token
55
151
 
@@ -79,7 +175,7 @@ Integration with GitHub can be done using an [app](#app-authentication) or [pers
79
175
 
80
176
  1. For organizations go to https://github.com/organizations/MY_ORG/settings/hooks after replacing `MY_ORG` with your GitHub organization name
81
177
  2. For enterprise go to https://github.com/enterprises/MY_ENTERPRISE/settings/hooks after replacing `MY_ENTERPRISE` with your GitHub enterprise name
82
- 3. Otherwise, you can create one per repository in your repository settings under Webhooks
178
+ 3. Otherwise, you can create one webhook per repository in your repository settings under Webhooks
83
179
  4. Configure the webhook:
84
180
  1. For Webhook URL use the value of `github.webhook.url` from `status.json`
85
181
  2. Open the URL in `github.webhook.secretUrl` from `status.json`, retrieve the secret value, and use it for webhook secret