From 071e4beb245b0113950fff5748348e2867a3119d Mon Sep 17 00:00:00 2001 From: Guilherme Caulada Date: Wed, 19 Aug 2026 14:54:40 -0300 Subject: [PATCH 1/2] feat(github-app): use ssm manifest for extra apps Deliver additional GitHub App credentials to the lambdas through a manifest SSM parameter listing the per-app credential parameter names, instead of colon-joined parameter names in the environment. The lambda environment size stays constant regardless of app count, avoiding the 4 KB Lambda environment limit (roughly 15-20 apps with typical paths). The manifest also removes the positional alignment between the id, key, and installation-id lists, which could silently shift installation ids across apps if the lists drifted. The rate-limit metric now reads app ids from the credentials already loaded by the auth module instead of re-reading SSM per app index. Document that additional apps must be installed on the same organizations or repositories as the primary app. --- README.md | 16 +-- docs/rate-limits-and-tuning.md | 11 ++ .../control-plane/src/github/auth.test.ts | 48 ++++--- .../control-plane/src/github/auth.ts | 62 ++++++--- .../src/github/rate-limit.test.ts | 120 ++++-------------- .../control-plane/src/github/rate-limit.ts | 17 +-- .../functions/control-plane/src/modules.d.ts | 1 + main.tf | 27 ++-- modules/multi-runner/README.md | 16 +-- modules/multi-runner/main.tf | 23 ++-- modules/multi-runner/outputs.tf | 28 ++-- modules/multi-runner/termination-watcher.tf | 4 +- modules/runners/README.md | 16 +-- modules/runners/job-retry/README.md | 14 +- modules/runners/job-retry/main.tf | 24 ++-- modules/runners/job-retry/variables.tf | 10 +- modules/runners/pool/README.md | 12 +- modules/runners/pool/main.tf | 80 ++++++------ modules/runners/pool/variables.tf | 10 +- modules/runners/scale-down.tf | 44 +++---- modules/runners/scale-up.tf | 88 ++++++------- modules/runners/tests/pool.tftest.hcl | 5 +- modules/runners/variables.tf | 28 ++-- modules/ssm/README.md | 14 +- modules/ssm/outputs.tf | 7 + modules/ssm/ssm.tf | 29 +++++ outputs.tf | 30 ++--- 27 files changed, 391 insertions(+), 393 deletions(-) diff --git a/README.md b/README.md index f097d3ebd2..b17dfc3b86 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Requirements | Name | Version | -|------|---------| +| ---- | ------- | | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.33 | | [random](#requirement\_random) | ~> 3.0 | @@ -74,14 +74,14 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Providers | Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 6.33 | -| [random](#provider\_random) | ~> 3.0 | +| ---- | ------- | +| [aws](#provider\_aws) | 6.60.0 | +| [random](#provider\_random) | 3.9.0 | ## Modules | Name | Source | Version | -|------|--------|---------| +| ---- | ------ | ------- | | [ami\_housekeeper](#module\_ami\_housekeeper) | ./modules/ami-housekeeper | n/a | | [instance\_termination\_watcher](#module\_instance\_termination\_watcher) | ./modules/termination-watcher | n/a | | [runner\_binaries](#module\_runner\_binaries) | ./modules/runner-binaries-syncer | n/a | @@ -92,7 +92,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Resources | Name | Type | -|------|------| +| ---- | ---- | | [aws_sqs_queue.queued_builds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue.queued_builds_dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue_policy.build_queue_dlq_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | @@ -103,7 +103,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [additional\_github\_apps](#input\_additional\_github\_apps) | Additional GitHub Apps for distributing API rate limit usage. Each must be installed on the same repos/orgs as the primary app. |
list(object({
key_base64 = optional(string)
key_base64_ssm = optional(object({ arn = string, name = string }))
id = optional(string)
id_ssm = optional(object({ arn = string, name = string }))
installation_id = optional(string)
installation_id_ssm = optional(object({ arn = string, name = string }))
}))
| `[]` | no | | [ami](#input\_ami) | AMI configuration for the action runner instances. This object allows you to specify all AMI-related settings in one place.

Parameters:
- `filter`: Map of lists to filter AMIs by various criteria (e.g., { name = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-*"], state = ["available"] })
- `owners`: List of AMI owners to limit the search. Common values: ["amazon"], ["self"], or specific AWS account IDs
- `id_ssm_parameter_arn`: ARN of an SSM parameter containing the AMI ID. If specified, this overrides both AMI filter and parameter name
- `kms_key_arn`: Optional KMS key ARN if the AMI is encrypted with a customer managed key

Defaults to null, in which case the module falls back to individual AMI variables (deprecated). |
object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter_arn = optional(string, null)
kms_key_arn = optional(string, null)
})
| `null` | no | | [ami\_housekeeper\_cleanup\_config](#input\_ami\_housekeeper\_cleanup\_config) | Configuration for AMI cleanup.

`amiFilters` - Filters to use when searching for AMIs to cleanup. Default filter for images owned by the account and that are available.
`dryRun` - If true, no AMIs will be deregistered. Default false.
`launchTemplateNames` - Launch template names to use when searching for AMIs to cleanup. Default no launch templates.
`maxItems` - The maximum number of AMIs that will be queried for cleanup. Default no maximum.
`minimumDaysOld` - Minimum number of days old an AMI must be to be considered for cleanup. Default 30.
`ssmParameterNames` - SSM parameter names to use when searching for AMIs to cleanup. This parameter should be set when using SSM to configure the AMI to use. Default no SSM parameters. |
object({
amiFilters = optional(list(object({
Name = string
Values = list(string)
})),
[{
Name : "state",
Values : ["available"],
},
{
Name : "image-type",
Values : ["machine"],
}]
)
dryRun = optional(bool, false)
launchTemplateNames = optional(list(string))
maxItems = optional(number)
minimumDaysOld = optional(number, 30)
ssmParameterNames = optional(list(string))
})
| `{}` | no | @@ -250,7 +250,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [binaries\_syncer](#output\_binaries\_syncer) | n/a | | [instance\_termination\_handler](#output\_instance\_termination\_handler) | n/a | | [instance\_termination\_watcher](#output\_instance\_termination\_watcher) | n/a | diff --git a/docs/rate-limits-and-tuning.md b/docs/rate-limits-and-tuning.md index 2bed61c9ed..571fd96ff1 100644 --- a/docs/rate-limits-and-tuning.md +++ b/docs/rate-limits-and-tuning.md @@ -50,6 +50,17 @@ Sustained: 10,000/hour ÷ 60 = **~166 runners/minute**. Without a token cache, each runner also costs a `POST /app/installations/{id}/access_tokens` (5 points) against the `core` endpoint. This doesn't directly reduce JIT throughput (different endpoint) but competes with `isJobQueued` for the `core` hourly budget. +### Distributing load across multiple GitHub Apps + +Rate limits are per App installation and cannot be raised. To scale beyond one App's budget, configure extra Apps with `additional_github_apps`. The control-plane lambdas select one App per invocation, making the effective limit N × the per-App limit. + +> [!IMPORTANT] +> Every additional App must be installed on the same organizations or repositories as the primary App. The module cannot verify this. A missing installation surfaces at runtime as installation lookup 404s on the fraction of invocations that select the misconfigured App, which is hard to trace back to the installation. + +Only the primary App needs a webhook configured in GitHub; additional Apps are used for API calls only. Set `installation_id` per additional App to skip one installation lookup per invocation. + +The lambdas receive additional App credentials through a manifest SSM parameter that lists the per-App credential parameter names, so the lambda environment size stays constant regardless of App count. + ### GHES Rate limits are **disabled by default** on GitHub Enterprise Server and must be explicitly enabled by the site admin. When enabled, the same formula applies. diff --git a/lambdas/functions/control-plane/src/github/auth.test.ts b/lambdas/functions/control-plane/src/github/auth.test.ts index dd2cf3b8c2..c2524503b7 100644 --- a/lambdas/functions/control-plane/src/github/auth.test.ts +++ b/lambdas/functions/control-plane/src/github/auth.test.ts @@ -2,7 +2,7 @@ import { createAppAuth } from '@octokit/auth-app'; import { StrategyOptions } from '@octokit/auth-app/dist-types/types'; import { request } from '@octokit/request'; import { RequestInterface, RequestParameters } from '@octokit/types'; -import { getParameters } from '@aws-github-runner/aws-ssm-util'; +import { getParameter, getParameters } from '@aws-github-runner/aws-ssm-util'; import { generateKeyPairSync } from 'node:crypto'; import * as nock from 'nock'; @@ -35,6 +35,7 @@ const PARAMETER_GITHUB_APP_ID_NAME = `/actions-runner/${ENVIRONMENT}/github_app_ const PARAMETER_GITHUB_APP_KEY_BASE64_NAME = `/actions-runner/${ENVIRONMENT}/github_app_key_base64`; const mockedGetParameters = vi.mocked(getParameters); +const mockedGetParameter = vi.mocked(getParameter); beforeEach(() => { vi.resetModules(); @@ -341,23 +342,32 @@ describe('Test getStoredInstallationId', () => { vi.mocked(createAppAuth).mockReturnValue(mockWithHook); }); - it('returns stored installation ID when configured', async () => { - const installationIdParam = `/actions-runner/${ENVIRONMENT}/github_app_installation_id`; - process.env.PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = installationIdParam; + it('returns stored installation ID when configured for an additional app', async () => { + const appIdParam = `/actions-runner/${ENVIRONMENT}/additional_github_app_0_id`; + const appKeyParam = `/actions-runner/${ENVIRONMENT}/additional_github_app_0_key_base64`; + const installationIdParam = `/actions-runner/${ENVIRONMENT}/additional_github_app_0_installation_id`; + process.env.PARAMETER_GITHUB_APPS_MANIFEST_NAME = `/actions-runner/${ENVIRONMENT}/additional_github_apps_manifest`; + mockedGetParameter.mockResolvedValueOnce( + JSON.stringify([ + { idParamName: appIdParam, keyParamName: appKeyParam, installationIdParamName: installationIdParam }, + ]), + ); mockedGetParameters.mockResolvedValueOnce( new Map([ [PARAMETER_GITHUB_APP_ID_NAME, GITHUB_APP_ID], [PARAMETER_GITHUB_APP_KEY_BASE64_NAME, b64], + [appIdParam, '2'], + [appKeyParam, b64], [installationIdParam, '12345'], ]), ); - const result = await getStoredInstallationId(0); + const result = await getStoredInstallationId(1); expect(result).toBe(12345); }); - it('returns undefined when installation ID param is empty', async () => { - process.env.PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = ''; + it('returns undefined when the manifest env var is empty', async () => { + process.env.PARAMETER_GITHUB_APPS_MANIFEST_NAME = ''; mockedGetParameters.mockResolvedValueOnce( new Map([ [PARAMETER_GITHUB_APP_ID_NAME, GITHUB_APP_ID], @@ -369,8 +379,8 @@ describe('Test getStoredInstallationId', () => { expect(result).toBeUndefined(); }); - it('returns undefined when env var is not set', async () => { - delete process.env.PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME; + it('returns undefined when the manifest env var is not set', async () => { + delete process.env.PARAMETER_GITHUB_APPS_MANIFEST_NAME; mockedGetParameters.mockResolvedValueOnce( new Map([ [PARAMETER_GITHUB_APP_ID_NAME, GITHUB_APP_ID], @@ -383,7 +393,7 @@ describe('Test getStoredInstallationId', () => { }); it('returns undefined for out-of-bounds appIndex', async () => { - process.env.PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = ''; + delete process.env.PARAMETER_GITHUB_APPS_MANIFEST_NAME; mockedGetParameters.mockResolvedValueOnce( new Map([ [PARAMETER_GITHUB_APP_ID_NAME, GITHUB_APP_ID], @@ -395,21 +405,21 @@ describe('Test getStoredInstallationId', () => { expect(result).toBeUndefined(); }); - it('loads installation IDs for multi-app setup', async () => { - const app1IdParam = `/actions-runner/${ENVIRONMENT}/github_app_id`; + it('loads installation IDs for multi-app setup from the manifest', async () => { const app2IdParam = `/actions-runner/${ENVIRONMENT}/additional_github_app_0_id`; - const app1KeyParam = `/actions-runner/${ENVIRONMENT}/github_app_key_base64`; const app2KeyParam = `/actions-runner/${ENVIRONMENT}/additional_github_app_0_key_base64`; const app2InstallParam = `/actions-runner/${ENVIRONMENT}/additional_github_app_0_installation_id`; - process.env.PARAMETER_GITHUB_APP_ID_NAME = `${app1IdParam}:${app2IdParam}`; - process.env.PARAMETER_GITHUB_APP_KEY_BASE64_NAME = `${app1KeyParam}:${app2KeyParam}`; - process.env.PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = `:${app2InstallParam}`; - + process.env.PARAMETER_GITHUB_APPS_MANIFEST_NAME = `/actions-runner/${ENVIRONMENT}/additional_github_apps_manifest`; + mockedGetParameter.mockResolvedValueOnce( + JSON.stringify([ + { idParamName: app2IdParam, keyParamName: app2KeyParam, installationIdParamName: app2InstallParam }, + ]), + ); mockedGetParameters.mockResolvedValueOnce( new Map([ - [app1IdParam, '1'], - [app1KeyParam, b64], + [PARAMETER_GITHUB_APP_ID_NAME, '1'], + [PARAMETER_GITHUB_APP_KEY_BASE64_NAME, b64], [app2IdParam, '2'], [app2KeyParam, b64], [app2InstallParam, '67890'], diff --git a/lambdas/functions/control-plane/src/github/auth.ts b/lambdas/functions/control-plane/src/github/auth.ts index f64ac00b30..b40b120bdd 100644 --- a/lambdas/functions/control-plane/src/github/auth.ts +++ b/lambdas/functions/control-plane/src/github/auth.ts @@ -22,7 +22,7 @@ import { Octokit } from '@octokit/rest'; import { retry } from '@octokit/plugin-retry'; import { throttling } from '@octokit/plugin-throttling'; import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { getParameters } from '@aws-github-runner/aws-ssm-util'; +import { getParameter, getParameters } from '@aws-github-runner/aws-ssm-util'; import { EndpointDefaults } from '@octokit/types'; const logger = createChildLogger('gh-auth'); @@ -77,41 +77,64 @@ interface GitHubAppCredential { let appCredentialsPromise: Promise | null = null; +// One entry per additional app in the manifest parameter. The manifest keeps +// the lambda environment size constant regardless of the number of apps: the +// environment carries only the manifest's parameter name, and the manifest +// value lists the per-app credential parameter names. +interface AdditionalAppManifestEntry { + idParamName: string; + keyParamName: string; + installationIdParamName?: string | null; +} + async function loadAppCredentials(): Promise { - if (!process.env.PARAMETER_GITHUB_APP_ID_NAME) { + const idParamName = process.env.PARAMETER_GITHUB_APP_ID_NAME; + const keyParamName = process.env.PARAMETER_GITHUB_APP_KEY_BASE64_NAME; + if (!idParamName) { throw new Error('Environment variable PARAMETER_GITHUB_APP_ID_NAME is not set'); } - if (!process.env.PARAMETER_GITHUB_APP_KEY_BASE64_NAME) { + if (!keyParamName) { throw new Error('Environment variable PARAMETER_GITHUB_APP_KEY_BASE64_NAME is not set'); } - const idParams = process.env.PARAMETER_GITHUB_APP_ID_NAME.split(':').filter(Boolean); - const keyParams = process.env.PARAMETER_GITHUB_APP_KEY_BASE64_NAME.split(':').filter(Boolean); - const installationIdParams = (process.env.PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME || '').split(':'); - if (idParams.length !== keyParams.length) { - throw new Error(`GitHub App parameter count mismatch: ${idParams.length} IDs vs ${keyParams.length} keys`); + + const entries: { id: string; key: string; installationId?: string }[] = [{ id: idParamName, key: keyParamName }]; + + const manifestParamName = process.env.PARAMETER_GITHUB_APPS_MANIFEST_NAME; + if (manifestParamName) { + const manifest = JSON.parse(await getParameter(manifestParamName)) as AdditionalAppManifestEntry[]; + entries.push( + ...manifest.map((entry) => ({ + id: entry.idParamName, + key: entry.keyParamName, + installationId: entry.installationIdParamName ?? undefined, + })), + ); } + // Batch fetch all SSM parameters in a single call to reduce API calls - const allParamNames = [...idParams, ...keyParams, ...installationIdParams.filter((p) => p.length > 0)]; + const allParamNames = entries.flatMap((entry) => [ + entry.id, + entry.key, + ...(entry.installationId ? [entry.installationId] : []), + ]); const params = await getParameters(allParamNames); const credentials: GitHubAppCredential[] = []; - for (let i = 0; i < idParams.length; i++) { - const appIdValue = params.get(idParams[i]); + for (const entry of entries) { + const appIdValue = params.get(entry.id); if (!appIdValue) { - throw new Error(`Parameter ${idParams[i]} not found`); + throw new Error(`Parameter ${entry.id} not found`); } const appId = parseInt(appIdValue, 10); - const privateKeyBase64 = params.get(keyParams[i]); + const privateKeyBase64 = params.get(entry.key); if (!privateKeyBase64) { - throw new Error(`Parameter ${keyParams[i]} not found`); + throw new Error(`Parameter ${entry.key} not found`); } // replace literal \n characters with new lines to allow the key to be stored as a // single line variable. This logic should match how the GitHub Terraform provider // processes private keys to retain compatibility between the projects const privateKey = Buffer.from(privateKeyBase64, 'base64').toString().replace(/\\n/g, '\n'); - const installationIdParam = installationIdParams[i]; - const installationIdValue = - installationIdParam && installationIdParam.length > 0 ? params.get(installationIdParam) : undefined; + const installationIdValue = entry.installationId ? params.get(entry.installationId) : undefined; const installationId = installationIdValue ? parseInt(installationIdValue, 10) : undefined; credentials.push({ appId, privateKey, installationId }); } @@ -119,6 +142,11 @@ async function loadAppCredentials(): Promise { return credentials; } +export async function getLoadedAppId(appIndex: number): Promise { + const credentials = await getAppCredentials(); + return credentials[appIndex]?.appId; +} + function getAppCredentials(): Promise { if (!appCredentialsPromise) appCredentialsPromise = loadAppCredentials(); return appCredentialsPromise; diff --git a/lambdas/functions/control-plane/src/github/rate-limit.test.ts b/lambdas/functions/control-plane/src/github/rate-limit.test.ts index d9d18c5921..457a367c89 100644 --- a/lambdas/functions/control-plane/src/github/rate-limit.test.ts +++ b/lambdas/functions/control-plane/src/github/rate-limit.test.ts @@ -2,22 +2,13 @@ import { ResponseHeaders } from '@octokit/types'; import { createSingleMetric } from '@aws-github-runner/aws-powertools-util'; import { MetricUnit } from '@aws-lambda-powertools/metrics'; import { metricGitHubAppRateLimit } from './rate-limit'; -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { getParameter } from '@aws-github-runner/aws-ssm-util'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { getLoadedAppId } from './auth'; -process.env.PARAMETER_GITHUB_APP_ID_NAME = 'test'; -vi.mock('@aws-github-runner/aws-ssm-util', async () => { - // Return only what we need without spreading actual - return { - getParameter: vi.fn((name: string) => { - if (name === process.env.PARAMETER_GITHUB_APP_ID_NAME) { - return '1234'; - } else { - return ''; - } - }), - }; -}); +vi.mock('./auth', async () => ({ + // App ids per index, as loaded by the auth module from SSM. + getLoadedAppId: vi.fn(async (appIndex: number) => [1234, 5678][appIndex]), +})); vi.mock('@aws-github-runner/aws-powertools-util', async () => { // Provide only what's needed without spreading actual @@ -44,7 +35,6 @@ describe('metricGitHubAppRateLimit', () => { }); it('should update rate limit metric', async () => { - // set process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT to true process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = 'true'; const headers: ResponseHeaders = { 'x-ratelimit-remaining': '10', @@ -59,7 +49,6 @@ describe('metricGitHubAppRateLimit', () => { }); it('should not update rate limit metric', async () => { - // set process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT to false process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = 'false'; const headers: ResponseHeaders = { 'x-ratelimit-remaining': '10', @@ -72,7 +61,6 @@ describe('metricGitHubAppRateLimit', () => { }); it('should not update rate limit metric if headers are undefined', async () => { - // set process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT to true process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = 'true'; await metricGitHubAppRateLimit(undefined as unknown as ResponseHeaders); @@ -80,95 +68,37 @@ describe('metricGitHubAppRateLimit', () => { expect(createSingleMetric).not.toHaveBeenCalled(); }); - it('should cache GitHub App ID and only call getParameter once', async () => { - // Reset modules to clear the appIdPromises Map cache - vi.resetModules(); - const { metricGitHubAppRateLimit: freshMetricFunction } = await import('./rate-limit'); - + it('should label metric with correct appId for index 1 (additional app)', async () => { process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = 'true'; - const headers: ResponseHeaders = { - 'x-ratelimit-remaining': '10', - 'x-ratelimit-limit': '60', - }; - - const mockGetParameter = vi.mocked(getParameter); - mockGetParameter.mockClear(); - - await freshMetricFunction(headers); - await freshMetricFunction(headers); - await freshMetricFunction(headers); - - // getParameter should only be called once due to caching (index 0 cached after first call) - expect(mockGetParameter).toHaveBeenCalledTimes(1); - // split(':')[0] of 'test' is still 'test' - expect(mockGetParameter).toHaveBeenCalledWith(process.env.PARAMETER_GITHUB_APP_ID_NAME); - }); -}); - -describe('metricGitHubAppRateLimit multi-app', () => { - let freshMetricFunction: typeof metricGitHubAppRateLimit; - let mockGetParam: ReturnType; - - beforeEach(async () => { - // Reset modules to get a clean appIdPromises Map for each test - vi.resetModules(); + const headers: ResponseHeaders = { 'x-ratelimit-remaining': '100', 'x-ratelimit-limit': '5000' }; - process.env.PARAMETER_GITHUB_APP_ID_NAME = 'app0:app1'; - process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = 'true'; + await metricGitHubAppRateLimit(headers, 1); - mockGetParam = vi.fn((name: string) => { - if (name === 'app0') return Promise.resolve('1234'); - if (name === 'app1') return Promise.resolve('5678'); - return Promise.resolve(''); + expect(createSingleMetric).toHaveBeenCalledWith('GitHubAppRateLimitRemaining', MetricUnit.Count, 100, { + AppId: '5678', }); - - vi.doMock('@aws-github-runner/aws-ssm-util', () => ({ getParameter: mockGetParam })); - vi.doMock('@aws-github-runner/aws-powertools-util', () => ({ - logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }, - createSingleMetric: vi.fn(() => ({ addMetadata: vi.fn() })), - })); - - const mod = await import('./rate-limit'); - freshMetricFunction = mod.metricGitHubAppRateLimit; }); - afterEach(() => { - vi.resetModules(); - process.env.PARAMETER_GITHUB_APP_ID_NAME = 'test'; - }); + it('should default to index 0 when no appIndex is passed', async () => { + process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = 'true'; + const headers: ResponseHeaders = { 'x-ratelimit-remaining': '75', 'x-ratelimit-limit': '5000' }; - it('should label metric with correct appId for index 0 (primary app)', async () => { - const { createSingleMetric: mockMetric } = await import('@aws-github-runner/aws-powertools-util'); - const headers: ResponseHeaders = { 'x-ratelimit-remaining': '50', 'x-ratelimit-limit': '5000' }; - await freshMetricFunction(headers, 0); - expect(mockMetric).toHaveBeenCalledWith('GitHubAppRateLimitRemaining', MetricUnit.Count, 50, { AppId: '1234' }); - }); + await metricGitHubAppRateLimit(headers); - it('should label metric with correct appId for index 1 (additional app)', async () => { - const { createSingleMetric: mockMetric } = await import('@aws-github-runner/aws-powertools-util'); - const headers: ResponseHeaders = { 'x-ratelimit-remaining': '100', 'x-ratelimit-limit': '5000' }; - await freshMetricFunction(headers, 1); - expect(mockMetric).toHaveBeenCalledWith('GitHubAppRateLimitRemaining', MetricUnit.Count, 100, { AppId: '5678' }); + expect(getLoadedAppId).toHaveBeenCalledWith(0); + expect(createSingleMetric).toHaveBeenCalledWith('GitHubAppRateLimitRemaining', MetricUnit.Count, 75, { + AppId: '1234', + }); }); - it('should default to index 0 when no appIndex is passed', async () => { - const { createSingleMetric: mockMetric } = await import('@aws-github-runner/aws-powertools-util'); + it('should label metric with an empty AppId when the appIndex is unknown', async () => { + process.env.ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = 'true'; const headers: ResponseHeaders = { 'x-ratelimit-remaining': '75', 'x-ratelimit-limit': '5000' }; - await freshMetricFunction(headers); - expect(mockMetric).toHaveBeenCalledWith('GitHubAppRateLimitRemaining', MetricUnit.Count, 75, { AppId: '1234' }); - }); - it('should cache per index and call getParameter separately for each index', async () => { - const headers: ResponseHeaders = { 'x-ratelimit-remaining': '10', 'x-ratelimit-limit': '5000' }; + await metricGitHubAppRateLimit(headers, 99); - // Two calls with index 1, then one with index 0 - await freshMetricFunction(headers, 1); - await freshMetricFunction(headers, 1); - await freshMetricFunction(headers, 0); - - // getParameter should be called exactly once per distinct index - expect(mockGetParam).toHaveBeenCalledTimes(2); - expect(mockGetParam).toHaveBeenCalledWith('app1'); - expect(mockGetParam).toHaveBeenCalledWith('app0'); + expect(createSingleMetric).toHaveBeenCalledWith('GitHubAppRateLimitRemaining', MetricUnit.Count, 75, { + AppId: '', + }); }); }); diff --git a/lambdas/functions/control-plane/src/github/rate-limit.ts b/lambdas/functions/control-plane/src/github/rate-limit.ts index df2372a255..2cd194fbeb 100644 --- a/lambdas/functions/control-plane/src/github/rate-limit.ts +++ b/lambdas/functions/control-plane/src/github/rate-limit.ts @@ -2,21 +2,14 @@ import { ResponseHeaders } from '@octokit/types'; import { createSingleMetric, logger } from '@aws-github-runner/aws-powertools-util'; import { MetricUnit } from '@aws-lambda-powertools/metrics'; import yn from 'yn'; -import { getParameter } from '@aws-github-runner/aws-ssm-util'; -// Cache the app ID per app index to avoid repeated SSM calls across Lambda invocations. -// In multi-app mode PARAMETER_GITHUB_APP_ID_NAME is a ':'-joined list of SSM param names, -// one per app in app-index order; index 0 is the primary app. -const appIdPromises = new Map>(); +import { getLoadedAppId } from './auth'; +// App ids come from the credentials already loaded by the auth module, so no +// additional SSM reads are needed here. Index 0 is the primary app. async function getAppId(appIndex = 0): Promise { - let cached = appIdPromises.get(appIndex); - if (!cached) { - const paramName = process.env.PARAMETER_GITHUB_APP_ID_NAME.split(':')[appIndex]; - cached = getParameter(paramName); - appIdPromises.set(appIndex, cached); - } - return cached; + const appId = await getLoadedAppId(appIndex); + return appId !== undefined ? String(appId) : ''; } export async function metricGitHubAppRateLimit(headers: ResponseHeaders, appIndex?: number): Promise { diff --git a/lambdas/functions/control-plane/src/modules.d.ts b/lambdas/functions/control-plane/src/modules.d.ts index d5157ccb37..c28714f149 100644 --- a/lambdas/functions/control-plane/src/modules.d.ts +++ b/lambdas/functions/control-plane/src/modules.d.ts @@ -15,6 +15,7 @@ declare namespace NodeJS { PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME: string; PARAMETER_GITHUB_APP_ID_NAME: string; PARAMETER_GITHUB_APP_KEY_BASE64_NAME: string; + PARAMETER_GITHUB_APPS_MANIFEST_NAME?: string; RUNNER_OWNER: string; COMPUTE_PROVIDER_TYPE?: string; SCALE_DOWN_CONFIG: string; diff --git a/main.tf b/main.tf index cad9b66c58..7cb7c1026c 100644 --- a/main.tf +++ b/main.tf @@ -7,19 +7,18 @@ locals { primary_app_key_base64 = coalesce(var.github_app.key_base64_ssm, module.ssm.parameters.github_app_key_base64) github_app_parameters = { - id = concat( - [local.primary_app_id], - [for p in module.ssm.additional_app_parameters : p.id] - ) - key_base64 = concat( - [local.primary_app_key_base64], - [for p in module.ssm.additional_app_parameters : p.key_base64] - ) - installation_id = concat( - [null], - [for p in module.ssm.additional_app_parameters : p.installation_id] - ) + id = local.primary_app_id + key_base64 = local.primary_app_key_base64 webhook_secret = coalesce(var.github_app.webhook_secret_ssm, module.ssm.parameters.github_app_webhook_secret) + # Additional apps flow to the lambdas through the manifest parameter so + # the lambda environment size stays constant regardless of app count. + additional_apps_manifest = module.ssm.additional_apps_manifest + additional_app_parameter_arns = flatten([ + for p in module.ssm.additional_app_parameters : concat( + [p.id.arn, p.key_base64.arn], + p.installation_id != null ? [p.installation_id.arn] : [] + ) + ]) } default_runner_labels = distinct(concat(["self-hosted", var.runner_os, var.runner_architecture])) @@ -406,8 +405,8 @@ locals { metrics = var.metrics enable_runner_deregistration = var.instance_termination_watcher.enable_runner_deregistration github_app_parameters = var.instance_termination_watcher.enable_runner_deregistration ? { - id = local.github_app_parameters.id[0] - key_base64 = local.github_app_parameters.key_base64[0] + id = local.github_app_parameters.id + key_base64 = local.github_app_parameters.key_base64 } : null ghes_url = var.ghes_url } diff --git a/modules/multi-runner/README.md b/modules/multi-runner/README.md index 21fc8f441b..e9835cc686 100644 --- a/modules/multi-runner/README.md +++ b/modules/multi-runner/README.md @@ -98,7 +98,7 @@ module "multi-runner" { ## Requirements | Name | Version | -|------|---------| +| ---- | ------- | | [terraform](#requirement\_terraform) | >= 1.3 | | [aws](#requirement\_aws) | >= 6.33 | | [random](#requirement\_random) | ~> 3.0 | @@ -106,14 +106,14 @@ module "multi-runner" { ## Providers | Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 6.33 | -| [random](#provider\_random) | ~> 3.0 | +| ---- | ------- | +| [aws](#provider\_aws) | 6.60.0 | +| [random](#provider\_random) | 3.9.0 | ## Modules | Name | Source | Version | -|------|--------|---------| +| ---- | ------ | ------- | | [ami\_housekeeper](#module\_ami\_housekeeper) | ../ami-housekeeper | n/a | | [instance\_termination\_watcher](#module\_instance\_termination\_watcher) | ../termination-watcher | n/a | | [runner\_binaries](#module\_runner\_binaries) | ../runner-binaries-syncer | n/a | @@ -124,7 +124,7 @@ module "multi-runner" { ## Resources | Name | Type | -|------|------| +| ---- | ---- | | [aws_sqs_queue.queued_builds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue.queued_builds_dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue_policy.build_queue_dlq_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | @@ -135,7 +135,7 @@ module "multi-runner" { ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [additional\_github\_apps](#input\_additional\_github\_apps) | Additional GitHub Apps for random API rate limit distribution.

The primary app (var.github\_app) is always included and is the one whose
webhook secret is used for incoming webhook signature validation. Only the
primary app needs a webhook configured in GitHub.

Additional apps listed here are used exclusively by the control-plane
lambdas (scale-up, scale-down, pool, job-retry) which randomly select an
app for each GitHub API call. Each additional app must be installed on the
same repositories/organizations as the primary app. |
list(object({
key_base64 = optional(string)
key_base64_ssm = optional(object({ arn = string, name = string }))
id = optional(string)
id_ssm = optional(object({ arn = string, name = string }))
installation_id = optional(string)
installation_id_ssm = optional(object({ arn = string, name = string }))
}))
| `[]` | no | | [ami\_housekeeper\_cleanup\_config](#input\_ami\_housekeeper\_cleanup\_config) | Configuration for AMI cleanup. |
object({
maxItems = optional(number)
minimumDaysOld = optional(number)
amiFilters = optional(list(object({
Name = string
Values = list(string)
})))
launchTemplateNames = optional(list(string))
ssmParameterNames = optional(list(string))
dryRun = optional(bool)
})
| `{}` | no | | [ami\_housekeeper\_lambda\_memory\_size](#input\_ami\_housekeeper\_lambda\_memory\_size) | Memory size limit in MB of the lambda. | `number` | `256` | no | @@ -219,7 +219,7 @@ module "multi-runner" { ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [binaries\_syncer\_map](#output\_binaries\_syncer\_map) | n/a | | [instance\_termination\_handler](#output\_instance\_termination\_handler) | n/a | | [instance\_termination\_watcher](#output\_instance\_termination\_watcher) | n/a | diff --git a/modules/multi-runner/main.tf b/modules/multi-runner/main.tf index bd96e30847..8efcac04d8 100644 --- a/modules/multi-runner/main.tf +++ b/modules/multi-runner/main.tf @@ -7,19 +7,18 @@ locals { primary_app_key_base64 = coalesce(var.github_app.key_base64_ssm, module.ssm.parameters.github_app_key_base64) github_app_parameters = { - id = concat( - [local.primary_app_id], - [for p in module.ssm.additional_app_parameters : p.id] - ) - key_base64 = concat( - [local.primary_app_key_base64], - [for p in module.ssm.additional_app_parameters : p.key_base64] - ) - installation_id = concat( - [null], - [for p in module.ssm.additional_app_parameters : p.installation_id] - ) + id = local.primary_app_id + key_base64 = local.primary_app_key_base64 webhook_secret = coalesce(var.github_app.webhook_secret_ssm, module.ssm.parameters.github_app_webhook_secret) + # Additional apps flow to the lambdas through the manifest parameter so + # the lambda environment size stays constant regardless of app count. + additional_apps_manifest = module.ssm.additional_apps_manifest + additional_app_parameter_arns = flatten([ + for p in module.ssm.additional_app_parameters : concat( + [p.id.arn, p.key_base64.arn], + p.installation_id != null ? [p.installation_id.arn] : [] + ) + ]) } runner_extra_labels = { for k, v in var.multi_runner_config : k => sort(setunion(flatten(v.matcherConfig.labelMatchers), compact(v.runner_config.runner_extra_labels))) } diff --git a/modules/multi-runner/outputs.tf b/modules/multi-runner/outputs.tf index 50adb7fe46..b0e9000a2d 100644 --- a/modules/multi-runner/outputs.tf +++ b/modules/multi-runner/outputs.tf @@ -45,25 +45,15 @@ output "webhook" { } output "ssm_parameters" { - value = merge( - { - id = { name = local.github_app_parameters.id[0].name, arn = local.github_app_parameters.id[0].arn } - key_base64 = { name = local.github_app_parameters.key_base64[0].name, arn = local.github_app_parameters.key_base64[0].arn } - webhook_secret = { name = local.github_app_parameters.webhook_secret.name, arn = local.github_app_parameters.webhook_secret.arn } - }, - { for idx, v in local.github_app_parameters.id : "github_app_id_${idx}" => { - name = v.name - arn = v.arn - } }, - { for idx, v in local.github_app_parameters.key_base64 : "github_app_key_base64_${idx}" => { - name = v.name - arn = v.arn - } }, - { "github_app_webhook_secret" = { - name = local.github_app_parameters.webhook_secret.name - arn = local.github_app_parameters.webhook_secret.arn - } }, - ) + value = { + id = { name = local.github_app_parameters.id.name, arn = local.github_app_parameters.id.arn } + key_base64 = { name = local.github_app_parameters.key_base64.name, arn = local.github_app_parameters.key_base64.arn } + webhook_secret = { name = local.github_app_parameters.webhook_secret.name, arn = local.github_app_parameters.webhook_secret.arn } + additional_apps_manifest = local.github_app_parameters.additional_apps_manifest != null ? { + name = local.github_app_parameters.additional_apps_manifest.name + arn = local.github_app_parameters.additional_apps_manifest.arn + } : null + } } output "instance_termination_watcher" { diff --git a/modules/multi-runner/termination-watcher.tf b/modules/multi-runner/termination-watcher.tf index 750db361bf..31e51cd216 100644 --- a/modules/multi-runner/termination-watcher.tf +++ b/modules/multi-runner/termination-watcher.tf @@ -20,8 +20,8 @@ locals { metrics = var.metrics enable_runner_deregistration = var.instance_termination_watcher.enable_runner_deregistration github_app_parameters = var.instance_termination_watcher.enable_runner_deregistration ? { - id = local.github_app_parameters.id[0] - key_base64 = local.github_app_parameters.key_base64[0] + id = local.github_app_parameters.id + key_base64 = local.github_app_parameters.key_base64 } : null ghes_url = var.ghes_url environment_variables = var.instance_termination_watcher.environment_variables diff --git a/modules/runners/README.md b/modules/runners/README.md index d228615edd..f5da55e112 100644 --- a/modules/runners/README.md +++ b/modules/runners/README.md @@ -51,27 +51,27 @@ yarn run dist ## Requirements | Name | Version | -|------|---------| +| ---- | ------- | | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.33 | ## Providers | Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 6.33 | +| ---- | ------- | +| [aws](#provider\_aws) | 6.60.0 | ## Modules | Name | Source | Version | -|------|--------|---------| +| ---- | ------ | ------- | | [job\_retry](#module\_job\_retry) | ./job-retry | n/a | | [pool](#module\_pool) | ./pool | n/a | ## Resources | Name | Type | -|------|------| +| ---- | ---- | | [aws_cloudwatch_event_rule.scale_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | | [aws_cloudwatch_event_rule.ssm_housekeeper](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | | [aws_cloudwatch_event_target.scale_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource | @@ -135,7 +135,7 @@ yarn run dist ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [ami](#input\_ami) | AMI configuration for the action runner instances. This object allows you to specify all AMI-related settings in one place.

Parameters:
- `filter`: Map of lists to filter AMIs by various criteria (e.g., { name = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-*"], state = ["available"] })
- `owners`: List of AMI owners to limit the search. Common values: ["amazon"], ["self"], or specific AWS account IDs
- `id_ssm_parameter_name`: Name of an SSM parameter containing the AMI ID. If specified, this overrides the AMI filter
- `id_ssm_parameter_arn`: ARN of an SSM parameter containing the AMI ID. If specified, this overrides both AMI filter and parameter name
- `kms_key_arn`: Optional KMS key ARN if the AMI is encrypted with a customer managed key

Defaults to null, in which case the module falls back to individual AMI variables (deprecated). |
object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter_arn = optional(string, null)
kms_key_arn = optional(string, null)
})
| `null` | no | | [associate\_public\_ipv4\_address](#input\_associate\_public\_ipv4\_address) | Associate public IPv4 with the runner. Only tested with IPv4 | `bool` | `false` | no | | [aws\_partition](#input\_aws\_partition) | (optional) partition for the base arn if not 'aws' | `string` | `"aws"` | no | @@ -162,7 +162,7 @@ yarn run dist | [enable\_userdata](#input\_enable\_userdata) | Should the userdata script be enabled for the runner. Set this to false if you are using your own prebuilt AMI | `bool` | `true` | no | | [ghes\_ssl\_verify](#input\_ghes\_ssl\_verify) | GitHub Enterprise SSL verification. Set to 'false' when custom certificate (chains) is used for GitHub Enterprise Server (insecure). | `bool` | `true` | no | | [ghes\_url](#input\_ghes\_url) | GitHub Enterprise Server URL. DO NOT SET IF USING PUBLIC GITHUB..However if you are using GitHub Enterprise Cloud with data-residency (ghe.com), set the endpoint here. Example - https://companyname.ghe.com\| | `string` | `null` | no | -| [github\_app\_parameters](#input\_github\_app\_parameters) | Parameter Store for GitHub App Parameters.

Supports multiple GitHub Apps for random API rate limit distribution.
Each list element corresponds to one GitHub App and is a map containing
`name` and `arn` keys referencing SSM parameters. The first element is the
primary app (the one whose webhook secret is used for incoming webhook
validation). All apps must be installed on the same repositories/organizations.

The control-plane lambdas (scale-up, scale-down, pool, job-retry) randomly
select an app from the list for each GitHub API call, distributing rate
limit consumption across all configured apps. |
object({
key_base64 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
| n/a | yes | +| [github\_app\_parameters](#input\_github\_app\_parameters) | Parameter Store for GitHub App Parameters.

Supports multiple GitHub Apps for API rate limit distribution. `id` and
`key_base64` reference the primary app (the one whose webhook secret is
used for incoming webhook validation). Additional apps are delivered to
the lambdas via `additional_apps_manifest`, an SSM parameter whose value
lists the per-app credential parameter names, keeping the lambda
environment size constant regardless of app count.
`additional_app_parameter_arns` carries the ARNs of every additional app
credential parameter for the lambda IAM policies. All apps must be
installed on the same repositories/organizations as the primary app. |
object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
| n/a | yes | | [iam\_overrides](#input\_iam\_overrides) | This map provides the possibility to override some IAM defaults. The following attributes are supported: `instance_profile_name` overrides the instance profile name used in the launch template. `runner_role_arn` overrides the IAM role ARN used for the runner instances. |
object({
override_instance_profile = optional(bool, null)
instance_profile_name = optional(string, null)
override_runner_role = optional(bool, null)
runner_role_arn = optional(string, null)
})
|
{
"instance_profile_name": null,
"override_instance_profile": false,
"override_runner_role": false,
"runner_role_arn": null
}
| no | | [idle\_config](#input\_idle\_config) | List of time period that can be defined as cron expression to keep a minimum amount of runners active instead of scaling down to 0. By defining this list you can ensure that in time periods that match the cron expression within 5 seconds a runner is kept idle. |
list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = optional(string, "oldest_first")
}))
| `[]` | no | | [instance\_allocation\_strategy](#input\_instance\_allocation\_strategy) | The allocation strategy for creating instances. For spot, AWS recommends `price-capacity-optimized`; for on-demand, use `lowest-price` or `prioritized`. The AWS default is `lowest-price`. | `string` | `"lowest-price"` | no | @@ -246,7 +246,7 @@ yarn run dist ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [lambda\_pool](#output\_lambda\_pool) | n/a | | [lambda\_pool\_log\_group](#output\_lambda\_pool\_log\_group) | n/a | | [lambda\_scale\_down](#output\_lambda\_scale\_down) | n/a | diff --git a/modules/runners/job-retry/README.md b/modules/runners/job-retry/README.md index 1eb06dab26..267516fbbb 100644 --- a/modules/runners/job-retry/README.md +++ b/modules/runners/job-retry/README.md @@ -11,26 +11,26 @@ The module is an inner module and used by the runner module when the opt-in feat ## Requirements | Name | Version | -|------|---------| +| ---- | ------- | | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.21 | ## Providers | Name | Version | -|------|---------| +| ---- | ------- | | [aws](#provider\_aws) | >= 6.21 | ## Modules | Name | Source | Version | -|------|--------|---------| +| ---- | ------ | ------- | | [job\_retry](#module\_job\_retry) | ../../lambda | n/a | ## Resources | Name | Type | -|------|------| +| ---- | ---- | | [aws_iam_role_policy.job_retry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | | [aws_lambda_event_source_mapping.job_retry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource | | [aws_lambda_permission.job_retry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | @@ -41,13 +41,13 @@ The module is an inner module and used by the runner module when the opt-in feat ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [config](#input\_config) | Configuration for the spot termination watcher lambda function.

`aws_partition`: Partition for the base arn if not 'aws'
`architecture`: AWS Lambda architecture. Lambda functions using Graviton processors ('arm64') tend to have better price/performance than 'x86\_64' functions.
`environment_variables`: Environment variables for the lambda.
`enable_organization_runners`: Enable organization runners.
`enable_metric`: Enable metric for the lambda. If `spot_warning` is set to true, the lambda will emit a metric when it detects a spot termination warning.
'ghes\_url': Optional GitHub Enterprise Server URL.
'user\_agent': Optional User-Agent header for GitHub API requests.
'github\_app\_parameters': Parameter Store for GitHub App Parameters.
'kms\_key\_arn': Optional CMK Key ARN instead of using the default AWS managed key.
`lambda_event_source_mapping_batch_size`: Maximum number of records to pass to the lambda function in a single batch for the event source mapping. When not set, the AWS default will be used.
`lambda_event_source_mapping_maximum_batching_window_in_seconds`: Maximum amount of time to gather records before invoking the lambda function, in seconds. AWS requires this to be greater than 0 if batch\_size is greater than 10.
`lambda_principals`: Add extra principals to the role created for execution of the lambda, e.g. for local testing.
`lambda_tags`: Map of tags that will be added to created resources. By default resources will be tagged with name and environment.
`log_level`: Logging level for lambda logging. Valid values are 'silly', 'trace', 'debug', 'info', 'warn', 'error', 'fatal'.
`logging_kms_key_id`: Specifies the kms key id to encrypt the logs with
`logging_retention_in_days`: Specifies the number of days you want to retain log events for the lambda log group. Possible values are: 0, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653.
`memory_size`: Memory size limit in MB of the lambda.
`metrics`: Configuration to enable metrics creation by the lambda.
`prefix`: The prefix used for naming resources.
`role_path`: The path that will be added to the role, if not set the environment name will be used.
`role_permissions_boundary`: Permissions boundary that will be added to the created role for the lambda.
`runtime`: AWS Lambda runtime.
`s3_bucket`: S3 bucket from which to specify lambda functions. This is an alternative to providing local files directly.
`s3_key`: S3 key for syncer lambda function. Required if using S3 bucket to specify lambdas.
`s3_object_version`: S3 object version for syncer lambda function. Useful if S3 versioning is enabled on source bucket.
`security_group_ids`: List of security group IDs associated with the Lambda function.
'sqs\_build\_queue': SQS queue for build events to re-publish job request.
`subnet_ids`: List of subnets in which the action runners will be launched, the subnets needs to be subnets in the `vpc_id`.
`tag_filters`: Map of tags that will be used to filter the resources to be tracked. Only for which all tags are present and starting with the same value as the value in the map will be tracked.
`tags`: Map of tags that will be added to created resources. By default resources will be tagged with name and environment.
`timeout`: Time out of the lambda in seconds.
`tracing_config`: Configuration for lambda tracing.
`zip`: File location of the lambda zip file. |
object({
aws_partition = optional(string, null)
architecture = optional(string, null)
enable_organization_runners = bool
environment_variables = optional(map(string), {})
ghes_url = optional(string, null)
user_agent = optional(string, null)
github_app_parameters = object({
key_base64 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
kms_key_arn = optional(string, null)
lambda_event_source_mapping_batch_size = optional(number, 10)
lambda_event_source_mapping_maximum_batching_window_in_seconds = optional(number, 0)
lambda_tags = optional(map(string), {})
log_level = optional(string, null)
logging_kms_key_id = optional(string, null)
logging_retention_in_days = optional(number, null)
memory_size = optional(number, null)
metrics = optional(object({
enable = optional(bool, false)
namespace = optional(string, null)
metric = optional(object({
enable_github_app_rate_limit = optional(bool, true)
enable_job_retry = optional(bool, true)
}), {})
}), {})
prefix = optional(string, null)
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
queue_encryption = optional(object({
kms_data_key_reuse_period_seconds = optional(number, null)
kms_master_key_id = optional(string, null)
sqs_managed_sse_enabled = optional(bool, true)
}), {})
role_path = optional(string, null)
role_permissions_boundary = optional(string, null)
runtime = optional(string, null)
security_group_ids = optional(list(string), [])
subnet_ids = optional(list(string), [])
s3_bucket = optional(string, null)
s3_key = optional(string, null)
s3_object_version = optional(string, null)
sqs_build_queue = object({
url = string
arn = string
})
tags = optional(map(string), {})
timeout = optional(number, 30)
tracing_config = optional(object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
}), {})
zip = optional(string, null)
})
| n/a | yes | +| ---- | ----------- | ---- | ------- | :------: | +| [config](#input\_config) | Configuration for the spot termination watcher lambda function.

`aws_partition`: Partition for the base arn if not 'aws'
`architecture`: AWS Lambda architecture. Lambda functions using Graviton processors ('arm64') tend to have better price/performance than 'x86\_64' functions.
`environment_variables`: Environment variables for the lambda.
`enable_organization_runners`: Enable organization runners.
`enable_metric`: Enable metric for the lambda. If `spot_warning` is set to true, the lambda will emit a metric when it detects a spot termination warning.
'ghes\_url': Optional GitHub Enterprise Server URL.
'user\_agent': Optional User-Agent header for GitHub API requests.
'github\_app\_parameters': Parameter Store for GitHub App Parameters.
'kms\_key\_arn': Optional CMK Key ARN instead of using the default AWS managed key.
`lambda_event_source_mapping_batch_size`: Maximum number of records to pass to the lambda function in a single batch for the event source mapping. When not set, the AWS default will be used.
`lambda_event_source_mapping_maximum_batching_window_in_seconds`: Maximum amount of time to gather records before invoking the lambda function, in seconds. AWS requires this to be greater than 0 if batch\_size is greater than 10.
`lambda_principals`: Add extra principals to the role created for execution of the lambda, e.g. for local testing.
`lambda_tags`: Map of tags that will be added to created resources. By default resources will be tagged with name and environment.
`log_level`: Logging level for lambda logging. Valid values are 'silly', 'trace', 'debug', 'info', 'warn', 'error', 'fatal'.
`logging_kms_key_id`: Specifies the kms key id to encrypt the logs with
`logging_retention_in_days`: Specifies the number of days you want to retain log events for the lambda log group. Possible values are: 0, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653.
`memory_size`: Memory size limit in MB of the lambda.
`metrics`: Configuration to enable metrics creation by the lambda.
`prefix`: The prefix used for naming resources.
`role_path`: The path that will be added to the role, if not set the environment name will be used.
`role_permissions_boundary`: Permissions boundary that will be added to the created role for the lambda.
`runtime`: AWS Lambda runtime.
`s3_bucket`: S3 bucket from which to specify lambda functions. This is an alternative to providing local files directly.
`s3_key`: S3 key for syncer lambda function. Required if using S3 bucket to specify lambdas.
`s3_object_version`: S3 object version for syncer lambda function. Useful if S3 versioning is enabled on source bucket.
`security_group_ids`: List of security group IDs associated with the Lambda function.
'sqs\_build\_queue': SQS queue for build events to re-publish job request.
`subnet_ids`: List of subnets in which the action runners will be launched, the subnets needs to be subnets in the `vpc_id`.
`tag_filters`: Map of tags that will be used to filter the resources to be tracked. Only for which all tags are present and starting with the same value as the value in the map will be tracked.
`tags`: Map of tags that will be added to created resources. By default resources will be tagged with name and environment.
`timeout`: Time out of the lambda in seconds.
`tracing_config`: Configuration for lambda tracing.
`zip`: File location of the lambda zip file. |
object({
aws_partition = optional(string, null)
architecture = optional(string, null)
enable_organization_runners = bool
environment_variables = optional(map(string), {})
ghes_url = optional(string, null)
user_agent = optional(string, null)
github_app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
kms_key_arn = optional(string, null)
lambda_event_source_mapping_batch_size = optional(number, 10)
lambda_event_source_mapping_maximum_batching_window_in_seconds = optional(number, 0)
lambda_tags = optional(map(string), {})
log_level = optional(string, null)
logging_kms_key_id = optional(string, null)
logging_retention_in_days = optional(number, null)
memory_size = optional(number, null)
metrics = optional(object({
enable = optional(bool, false)
namespace = optional(string, null)
metric = optional(object({
enable_github_app_rate_limit = optional(bool, true)
enable_job_retry = optional(bool, true)
}), {})
}), {})
prefix = optional(string, null)
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
queue_encryption = optional(object({
kms_data_key_reuse_period_seconds = optional(number, null)
kms_master_key_id = optional(string, null)
sqs_managed_sse_enabled = optional(bool, true)
}), {})
role_path = optional(string, null)
role_permissions_boundary = optional(string, null)
runtime = optional(string, null)
security_group_ids = optional(list(string), [])
subnet_ids = optional(list(string), [])
s3_bucket = optional(string, null)
s3_key = optional(string, null)
s3_object_version = optional(string, null)
sqs_build_queue = object({
url = string
arn = string
})
tags = optional(map(string), {})
timeout = optional(number, 30)
tracing_config = optional(object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
}), {})
zip = optional(string, null)
})
| n/a | yes | ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [job\_retry\_check\_queue](#output\_job\_retry\_check\_queue) | n/a | | [lambda](#output\_lambda) | n/a | diff --git a/modules/runners/job-retry/main.tf b/modules/runners/job-retry/main.tf index d5455951d0..287d63d571 100644 --- a/modules/runners/job-retry/main.tf +++ b/modules/runners/job-retry/main.tf @@ -3,15 +3,15 @@ locals { name = "job-retry" environment_variables = { - ENABLE_ORGANIZATION_RUNNERS = var.config.enable_organization_runners - ENABLE_METRIC_JOB_RETRY = var.config.metrics.enable && var.config.metrics.metric.enable_job_retry - ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.metrics.enable && var.config.metrics.metric.enable_github_app_rate_limit - GHES_URL = var.config.ghes_url - USER_AGENT = var.config.user_agent - JOB_QUEUE_SCALE_UP_URL = var.config.sqs_build_queue.url - PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.config.github_app_parameters.id : p.name]) - PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.config.github_app_parameters.key_base64 : p.name]) - PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.config.github_app_parameters.installation_id : p != null ? p.name : ""]) + ENABLE_ORGANIZATION_RUNNERS = var.config.enable_organization_runners + ENABLE_METRIC_JOB_RETRY = var.config.metrics.enable && var.config.metrics.metric.enable_job_retry + ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.metrics.enable && var.config.metrics.metric.enable_github_app_rate_limit + GHES_URL = var.config.ghes_url + USER_AGENT = var.config.user_agent + JOB_QUEUE_SCALE_UP_URL = var.config.sqs_build_queue.url + PARAMETER_GITHUB_APP_ID_NAME = var.config.github_app_parameters.id.name + PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.config.github_app_parameters.key_base64.name + PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.config.github_app_parameters.additional_apps_manifest != null ? var.config.github_app_parameters.additional_apps_manifest.name : "" } config = merge(var.config, { @@ -67,9 +67,9 @@ resource "aws_iam_role_policy" "job_retry" { sqs_build_queue_arn = var.config.sqs_build_queue.arn sqs_job_retry_queue_arn = aws_sqs_queue.job_retry_check_queue.arn github_app_parameter_arns = jsonencode(concat( - [for p in var.config.github_app_parameters.id : p.arn], - [for p in var.config.github_app_parameters.key_base64 : p.arn], - [for p in var.config.github_app_parameters.installation_id : p.arn if p != null], + [var.config.github_app_parameters.id.arn, var.config.github_app_parameters.key_base64.arn], + var.config.github_app_parameters.additional_app_parameter_arns, + var.config.github_app_parameters.additional_apps_manifest != null ? [var.config.github_app_parameters.additional_apps_manifest.arn] : [], )) }) } diff --git a/modules/runners/job-retry/variables.tf b/modules/runners/job-retry/variables.tf index cb010d7552..1a2fff1dc1 100644 --- a/modules/runners/job-retry/variables.tf +++ b/modules/runners/job-retry/variables.tf @@ -44,9 +44,13 @@ variable "config" { ghes_url = optional(string, null) user_agent = optional(string, null) github_app_parameters = object({ - key_base64 = list(map(string)) - id = list(map(string)) - installation_id = list(object({ name = string, arn = string })) + key_base64 = map(string) + id = map(string) + additional_apps_manifest = optional(object({ + name = string + arn = string + }), null) + additional_app_parameter_arns = optional(list(string), []) }) kms_key_arn = optional(string, null) lambda_event_source_mapping_batch_size = optional(number, 10) diff --git a/modules/runners/pool/README.md b/modules/runners/pool/README.md index c613bab02b..5a1ff2edcf 100644 --- a/modules/runners/pool/README.md +++ b/modules/runners/pool/README.md @@ -9,14 +9,14 @@ The pool is an opt-in feature. To be able to use the count on a module level to ## Requirements | Name | Version | -|------|---------| +| ---- | ------- | | [terraform](#requirement\_terraform) | >= 0.14.1 | | [aws](#requirement\_aws) | >= 6.21 | ## Providers | Name | Version | -|------|---------| +| ---- | ------- | | [aws](#provider\_aws) | >= 6.21 | ## Modules @@ -26,7 +26,7 @@ No modules. ## Resources | Name | Type | -|------|------| +| ---- | ---- | | [aws_cloudwatch_log_group.pool](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_iam_role.pool](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.scheduler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | @@ -47,15 +47,15 @@ No modules. ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [aws\_partition](#input\_aws\_partition) | (optional) partition for the arn if not 'aws' | `string` | `"aws"` | no | -| [config](#input\_config) | Lookup details in parent module. |
object({
lambda = object({
log_level = string
logging_retention_in_days = number
logging_kms_key_id = string
log_class = string
reserved_concurrent_executions = number
s3_bucket = string
s3_key = string
s3_object_version = string
security_group_ids = list(string)
runtime = string
architecture = string
memory_size = number
timeout = number
zip = string
subnet_ids = list(string)
parameter_store_tags = string
})
tags = map(string)
ghes = object({
url = string
ssl_verify = string
})
github_app_parameters = object({
key_base64 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
subnet_ids = list(string)
runner = object({
disable_runner_autoupdate = bool
ephemeral = bool
enable_jit_config = bool
enable_on_demand_failover_for_errors = list(string)
scale_errors = list(string)
boot_time_in_minutes = number
labels = list(string)
launch_template = object({
name = string
})
group_name = string
name_prefix = string
pool_owner = string
role = object({
arn = string
})
use_dedicated_host = bool
})
runners_maximum_count = number
instance_types = list(string)
instance_type_priorities = optional(map(number))
instance_target_capacity_type = string
instance_allocation_strategy = string
instance_max_spot_price = string
prefix = string
pool = list(object({
schedule_expression = string
schedule_expression_timezone = string
size = number
}))
include_busy_runners = bool
role_permissions_boundary = string
kms_key_arn = string
ami_kms_key_arn = string
ami_id_ssm_parameter_arn = string
role_path = string
ssm_token_path = string
ssm_config_path = string
ami_id_ssm_parameter_name = string
ami_id_ssm_parameter_read_policy_arn = string
arn_ssm_parameters_path_config = string
lambda_tags = map(string)
user_agent = string
})
| n/a | yes | +| [config](#input\_config) | Lookup details in parent module. |
object({
lambda = object({
log_level = string
logging_retention_in_days = number
logging_kms_key_id = string
log_class = string
reserved_concurrent_executions = number
s3_bucket = string
s3_key = string
s3_object_version = string
security_group_ids = list(string)
runtime = string
architecture = string
memory_size = number
timeout = number
zip = string
subnet_ids = list(string)
parameter_store_tags = string
})
tags = map(string)
ghes = object({
url = string
ssl_verify = string
})
github_app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
subnet_ids = list(string)
runner = object({
disable_runner_autoupdate = bool
ephemeral = bool
enable_jit_config = bool
enable_on_demand_failover_for_errors = list(string)
scale_errors = list(string)
boot_time_in_minutes = number
labels = list(string)
launch_template = object({
name = string
})
group_name = string
name_prefix = string
pool_owner = string
role = object({
arn = string
})
use_dedicated_host = bool
})
runners_maximum_count = number
instance_types = list(string)
instance_type_priorities = optional(map(number))
instance_target_capacity_type = string
instance_allocation_strategy = string
instance_max_spot_price = string
prefix = string
pool = list(object({
schedule_expression = string
schedule_expression_timezone = string
size = number
}))
include_busy_runners = bool
role_permissions_boundary = string
kms_key_arn = string
ami_kms_key_arn = string
ami_id_ssm_parameter_arn = string
role_path = string
ssm_token_path = string
ssm_config_path = string
ami_id_ssm_parameter_name = string
ami_id_ssm_parameter_read_policy_arn = string
arn_ssm_parameters_path_config = string
lambda_tags = map(string)
user_agent = string
})
| n/a | yes | | [tracing\_config](#input\_tracing\_config) | Configuration for lambda tracing. |
object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
})
| `{}` | no | ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [lambda](#output\_lambda) | n/a | | [lambda\_log\_group](#output\_lambda\_log\_group) | n/a | | [role\_pool](#output\_role\_pool) | n/a | diff --git a/modules/runners/pool/main.tf b/modules/runners/pool/main.tf index 3be3fe41ef..c6e9a9a2f8 100644 --- a/modules/runners/pool/main.tf +++ b/modules/runners/pool/main.tf @@ -25,43 +25,43 @@ resource "aws_lambda_function" "pool" { environment { variables = { - AMI_ID_SSM_PARAMETER_NAME = var.config.ami_id_ssm_parameter_name - DISABLE_RUNNER_AUTOUPDATE = var.config.runner.disable_runner_autoupdate - ENABLE_EPHEMERAL_RUNNERS = var.config.runner.ephemeral - ENABLE_JIT_CONFIG = var.config.runner.enable_jit_config - ENVIRONMENT = var.config.prefix - GHES_URL = var.config.ghes.url - USER_AGENT = var.config.user_agent - INSTANCE_ALLOCATION_STRATEGY = var.config.instance_allocation_strategy - INSTANCE_MAX_SPOT_PRICE = var.config.instance_max_spot_price - INSTANCE_TARGET_CAPACITY_TYPE = var.config.instance_target_capacity_type - INSTANCE_TYPE_PRIORITIES = var.config.instance_type_priorities != null ? jsonencode(var.config.instance_type_priorities) : "" - INSTANCE_TYPES = join(",", var.config.instance_types) - LAUNCH_TEMPLATE_NAME = var.config.runner.launch_template.name - LOG_LEVEL = upper(var.config.lambda.log_level) - NODE_TLS_REJECT_UNAUTHORIZED = var.config.ghes.url != null && !var.config.ghes.ssl_verify ? 0 : 1 - PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.config.github_app_parameters.id : p.name]) - PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.config.github_app_parameters.key_base64 : p.name]) - PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.config.github_app_parameters.installation_id : p != null ? p.name : ""]) - POWERTOOLS_LOGGER_LOG_EVENT = var.config.lambda.log_level == "debug" ? "true" : "false" - RUNNER_BOOT_TIME_IN_MINUTES = var.config.runner.boot_time_in_minutes - RUNNER_LABELS = lower(join(",", var.config.runner.labels)) - RUNNER_GROUP_NAME = var.config.runner.group_name - RUNNER_NAME_PREFIX = var.config.runner.name_prefix - RUNNER_OWNER = var.config.runner.pool_owner - RUNNERS_MAXIMUM_COUNT = var.config.runners_maximum_count - SSM_TOKEN_PATH = var.config.ssm_token_path - SSM_CONFIG_PATH = var.config.ssm_config_path - SUBNET_IDS = join(",", var.config.subnet_ids) - POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-pool" - POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false - POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests - POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error - ENABLE_ON_DEMAND_FAILOVER_FOR_ERRORS = jsonencode(var.config.runner.enable_on_demand_failover_for_errors) - SSM_PARAMETER_STORE_TAGS = var.config.lambda.parameter_store_tags - SCALE_ERRORS = jsonencode(var.config.runner.scale_errors) - USE_DEDICATED_HOST = var.config.runner.use_dedicated_host - INCLUDE_BUSY_RUNNERS = var.config.include_busy_runners + AMI_ID_SSM_PARAMETER_NAME = var.config.ami_id_ssm_parameter_name + DISABLE_RUNNER_AUTOUPDATE = var.config.runner.disable_runner_autoupdate + ENABLE_EPHEMERAL_RUNNERS = var.config.runner.ephemeral + ENABLE_JIT_CONFIG = var.config.runner.enable_jit_config + ENVIRONMENT = var.config.prefix + GHES_URL = var.config.ghes.url + USER_AGENT = var.config.user_agent + INSTANCE_ALLOCATION_STRATEGY = var.config.instance_allocation_strategy + INSTANCE_MAX_SPOT_PRICE = var.config.instance_max_spot_price + INSTANCE_TARGET_CAPACITY_TYPE = var.config.instance_target_capacity_type + INSTANCE_TYPE_PRIORITIES = var.config.instance_type_priorities != null ? jsonencode(var.config.instance_type_priorities) : "" + INSTANCE_TYPES = join(",", var.config.instance_types) + LAUNCH_TEMPLATE_NAME = var.config.runner.launch_template.name + LOG_LEVEL = upper(var.config.lambda.log_level) + NODE_TLS_REJECT_UNAUTHORIZED = var.config.ghes.url != null && !var.config.ghes.ssl_verify ? 0 : 1 + PARAMETER_GITHUB_APP_ID_NAME = var.config.github_app_parameters.id.name + PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.config.github_app_parameters.key_base64.name + PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.config.github_app_parameters.additional_apps_manifest != null ? var.config.github_app_parameters.additional_apps_manifest.name : "" + POWERTOOLS_LOGGER_LOG_EVENT = var.config.lambda.log_level == "debug" ? "true" : "false" + RUNNER_BOOT_TIME_IN_MINUTES = var.config.runner.boot_time_in_minutes + RUNNER_LABELS = lower(join(",", var.config.runner.labels)) + RUNNER_GROUP_NAME = var.config.runner.group_name + RUNNER_NAME_PREFIX = var.config.runner.name_prefix + RUNNER_OWNER = var.config.runner.pool_owner + RUNNERS_MAXIMUM_COUNT = var.config.runners_maximum_count + SSM_TOKEN_PATH = var.config.ssm_token_path + SSM_CONFIG_PATH = var.config.ssm_config_path + SUBNET_IDS = join(",", var.config.subnet_ids) + POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-pool" + POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false + POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests + POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error + ENABLE_ON_DEMAND_FAILOVER_FOR_ERRORS = jsonencode(var.config.runner.enable_on_demand_failover_for_errors) + SSM_PARAMETER_STORE_TAGS = var.config.lambda.parameter_store_tags + SCALE_ERRORS = jsonencode(var.config.runner.scale_errors) + USE_DEDICATED_HOST = var.config.runner.use_dedicated_host + INCLUDE_BUSY_RUNNERS = var.config.include_busy_runners } } @@ -104,9 +104,9 @@ resource "aws_iam_role_policy" "pool" { arn_ssm_parameters_path_config = var.config.arn_ssm_parameters_path_config arn_runner_instance_role = var.config.runner.role.arn github_app_parameter_arns = jsonencode(concat( - [for p in var.config.github_app_parameters.id : p.arn], - [for p in var.config.github_app_parameters.key_base64 : p.arn], - [for p in var.config.github_app_parameters.installation_id : p.arn if p != null], + [var.config.github_app_parameters.id.arn, var.config.github_app_parameters.key_base64.arn], + var.config.github_app_parameters.additional_app_parameter_arns, + var.config.github_app_parameters.additional_apps_manifest != null ? [var.config.github_app_parameters.additional_apps_manifest.arn] : [], )) kms_key_arn = var.config.kms_key_arn ami_kms_key_arn = var.config.ami_kms_key_arn diff --git a/modules/runners/pool/variables.tf b/modules/runners/pool/variables.tf index 4c577e86c7..4c3551c4c1 100644 --- a/modules/runners/pool/variables.tf +++ b/modules/runners/pool/variables.tf @@ -25,9 +25,13 @@ variable "config" { ssl_verify = string }) github_app_parameters = object({ - key_base64 = list(map(string)) - id = list(map(string)) - installation_id = list(object({ name = string, arn = string })) + key_base64 = map(string) + id = map(string) + additional_apps_manifest = optional(object({ + name = string + arn = string + }), null) + additional_app_parameter_arns = optional(list(string), []) }) subnet_ids = list(string) runner = object({ diff --git a/modules/runners/scale-down.tf b/modules/runners/scale-down.tf index f2879a0b2a..5ebee165e0 100644 --- a/modules/runners/scale-down.tf +++ b/modules/runners/scale-down.tf @@ -25,25 +25,25 @@ resource "aws_lambda_function" "scale_down" { environment { variables = { - ENVIRONMENT = var.prefix - ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.metrics.enable && var.metrics.metric.enable_github_app_rate_limit - GHES_URL = var.ghes_url - USER_AGENT = var.user_agent - LOG_LEVEL = upper(var.log_level) - MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.minimum_running_time_in_minutes, local.min_runtime_defaults[var.runner_os]) - NODE_TLS_REJECT_UNAUTHORIZED = var.ghes_url != null && !var.ghes_ssl_verify ? 0 : 1 - PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.github_app_parameters.id : p.name]) - PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.github_app_parameters.key_base64 : p.name]) - PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.github_app_parameters.installation_id : p != null ? p.name : ""]) - POWERTOOLS_LOGGER_LOG_EVENT = var.log_level == "debug" ? "true" : "false" - RUNNER_BOOT_TIME_IN_MINUTES = var.runner_boot_time_in_minutes - SCALE_DOWN_CONFIG = jsonencode(var.idle_config) - POWERTOOLS_SERVICE_NAME = "${var.prefix}-scale-down" - POWERTOOLS_METRICS_NAMESPACE = var.metrics.namespace - POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false - POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests - POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error - COMPUTE_PROVIDER_TYPE = "ec2" + ENVIRONMENT = var.prefix + ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.metrics.enable && var.metrics.metric.enable_github_app_rate_limit + GHES_URL = var.ghes_url + USER_AGENT = var.user_agent + LOG_LEVEL = upper(var.log_level) + MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.minimum_running_time_in_minutes, local.min_runtime_defaults[var.runner_os]) + NODE_TLS_REJECT_UNAUTHORIZED = var.ghes_url != null && !var.ghes_ssl_verify ? 0 : 1 + PARAMETER_GITHUB_APP_ID_NAME = var.github_app_parameters.id.name + PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.github_app_parameters.key_base64.name + PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.github_app_parameters.additional_apps_manifest != null ? var.github_app_parameters.additional_apps_manifest.name : "" + POWERTOOLS_LOGGER_LOG_EVENT = var.log_level == "debug" ? "true" : "false" + RUNNER_BOOT_TIME_IN_MINUTES = var.runner_boot_time_in_minutes + SCALE_DOWN_CONFIG = jsonencode(var.idle_config) + POWERTOOLS_SERVICE_NAME = "${var.prefix}-scale-down" + POWERTOOLS_METRICS_NAMESPACE = var.metrics.namespace + POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false + POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests + POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error + COMPUTE_PROVIDER_TYPE = "ec2" } } @@ -104,9 +104,9 @@ resource "aws_iam_role_policy" "scale_down" { policy = templatefile("${path.module}/policies/lambda-scale-down.json", { environment = var.prefix github_app_parameter_arns = jsonencode(concat( - [for p in var.github_app_parameters.id : p.arn], - [for p in var.github_app_parameters.key_base64 : p.arn], - [for p in var.github_app_parameters.installation_id : p.arn if p != null], + [var.github_app_parameters.id.arn, var.github_app_parameters.key_base64.arn], + var.github_app_parameters.additional_app_parameter_arns, + var.github_app_parameters.additional_apps_manifest != null ? [var.github_app_parameters.additional_apps_manifest.arn] : [], )) kms_key_arn = local.kms_key_arn }) diff --git a/modules/runners/scale-up.tf b/modules/runners/scale-up.tf index 2e045345a6..c16eeacc5c 100644 --- a/modules/runners/scale-up.tf +++ b/modules/runners/scale-up.tf @@ -25,47 +25,47 @@ resource "aws_lambda_function" "scale_up" { architectures = [var.lambda_architecture] environment { variables = { - AMI_ID_SSM_PARAMETER_NAME = local.ami_id_ssm_parameter_name - DISABLE_RUNNER_AUTOUPDATE = var.disable_runner_autoupdate - ENABLE_EPHEMERAL_RUNNERS = var.enable_ephemeral_runners - ENABLE_JIT_CONFIG = var.enable_jit_config - ENABLE_JOB_QUEUED_CHECK = local.enable_job_queued_check - ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.metrics.enable && var.metrics.metric.enable_github_app_rate_limit - ENABLE_ORGANIZATION_RUNNERS = var.enable_organization_runners - ENVIRONMENT = var.prefix - GHES_URL = var.ghes_url - USER_AGENT = var.user_agent - INSTANCE_ALLOCATION_STRATEGY = var.instance_allocation_strategy - INSTANCE_MAX_SPOT_PRICE = var.instance_max_spot_price - INSTANCE_TARGET_CAPACITY_TYPE = var.instance_target_capacity_type - INSTANCE_TYPE_PRIORITIES = var.instance_type_priorities != null ? jsonencode(var.instance_type_priorities) : "" - INSTANCE_TYPES = join(",", var.instance_types) - LAUNCH_TEMPLATE_NAME = aws_launch_template.runner.name - LOG_LEVEL = upper(var.log_level) - MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.minimum_running_time_in_minutes, local.min_runtime_defaults[var.runner_os]) - NODE_TLS_REJECT_UNAUTHORIZED = var.ghes_url != null && !var.ghes_ssl_verify ? 0 : 1 - PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.github_app_parameters.id : p.name]) - PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.github_app_parameters.key_base64 : p.name]) - PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.github_app_parameters.installation_id : p != null ? p.name : ""]) - POWERTOOLS_LOGGER_LOG_EVENT = var.log_level == "debug" ? "true" : "false" - POWERTOOLS_METRICS_NAMESPACE = var.metrics.namespace - POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false - POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests - POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error - RUNNER_LABELS = lower(join(",", var.runner_labels)) - RUNNER_GROUP_NAME = var.runner_group_name - RUNNER_NAME_PREFIX = var.runner_name_prefix - COMPUTE_PROVIDER_TYPE = "ec2" - RUNNERS_MAXIMUM_COUNT = var.runners_maximum_count - POWERTOOLS_SERVICE_NAME = "${var.prefix}-scale-up" - SSM_TOKEN_PATH = local.token_path - SSM_CONFIG_PATH = "${var.ssm_paths.root}/${var.ssm_paths.config}" - SSM_PARAMETER_STORE_TAGS = local.parameter_store_tags - SUBNET_IDS = join(",", var.subnet_ids) - ENABLE_ON_DEMAND_FAILOVER_FOR_ERRORS = jsonencode(var.enable_on_demand_failover_for_errors) - SCALE_ERRORS = jsonencode(var.scale_errors) - JOB_RETRY_CONFIG = jsonencode(local.job_retry_config) - USE_DEDICATED_HOST = var.use_dedicated_host + AMI_ID_SSM_PARAMETER_NAME = local.ami_id_ssm_parameter_name + DISABLE_RUNNER_AUTOUPDATE = var.disable_runner_autoupdate + ENABLE_EPHEMERAL_RUNNERS = var.enable_ephemeral_runners + ENABLE_JIT_CONFIG = var.enable_jit_config + ENABLE_JOB_QUEUED_CHECK = local.enable_job_queued_check + ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.metrics.enable && var.metrics.metric.enable_github_app_rate_limit + ENABLE_ORGANIZATION_RUNNERS = var.enable_organization_runners + ENVIRONMENT = var.prefix + GHES_URL = var.ghes_url + USER_AGENT = var.user_agent + INSTANCE_ALLOCATION_STRATEGY = var.instance_allocation_strategy + INSTANCE_MAX_SPOT_PRICE = var.instance_max_spot_price + INSTANCE_TARGET_CAPACITY_TYPE = var.instance_target_capacity_type + INSTANCE_TYPE_PRIORITIES = var.instance_type_priorities != null ? jsonencode(var.instance_type_priorities) : "" + INSTANCE_TYPES = join(",", var.instance_types) + LAUNCH_TEMPLATE_NAME = aws_launch_template.runner.name + LOG_LEVEL = upper(var.log_level) + MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.minimum_running_time_in_minutes, local.min_runtime_defaults[var.runner_os]) + NODE_TLS_REJECT_UNAUTHORIZED = var.ghes_url != null && !var.ghes_ssl_verify ? 0 : 1 + PARAMETER_GITHUB_APP_ID_NAME = var.github_app_parameters.id.name + PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.github_app_parameters.key_base64.name + PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.github_app_parameters.additional_apps_manifest != null ? var.github_app_parameters.additional_apps_manifest.name : "" + POWERTOOLS_LOGGER_LOG_EVENT = var.log_level == "debug" ? "true" : "false" + POWERTOOLS_METRICS_NAMESPACE = var.metrics.namespace + POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false + POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests + POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error + RUNNER_LABELS = lower(join(",", var.runner_labels)) + RUNNER_GROUP_NAME = var.runner_group_name + RUNNER_NAME_PREFIX = var.runner_name_prefix + COMPUTE_PROVIDER_TYPE = "ec2" + RUNNERS_MAXIMUM_COUNT = var.runners_maximum_count + POWERTOOLS_SERVICE_NAME = "${var.prefix}-scale-up" + SSM_TOKEN_PATH = local.token_path + SSM_CONFIG_PATH = "${var.ssm_paths.root}/${var.ssm_paths.config}" + SSM_PARAMETER_STORE_TAGS = local.parameter_store_tags + SUBNET_IDS = join(",", var.subnet_ids) + ENABLE_ON_DEMAND_FAILOVER_FOR_ERRORS = jsonencode(var.enable_on_demand_failover_for_errors) + SCALE_ERRORS = jsonencode(var.scale_errors) + JOB_RETRY_CONFIG = jsonencode(local.job_retry_config) + USE_DEDICATED_HOST = var.use_dedicated_host } } @@ -126,9 +126,9 @@ resource "aws_iam_role_policy" "scale_up" { environment = var.prefix sqs_arn = var.sqs_build_queue.arn github_app_parameter_arns = jsonencode(concat( - [for p in var.github_app_parameters.id : p.arn], - [for p in var.github_app_parameters.key_base64 : p.arn], - [for p in var.github_app_parameters.installation_id : p.arn if p != null], + [var.github_app_parameters.id.arn, var.github_app_parameters.key_base64.arn], + var.github_app_parameters.additional_app_parameter_arns, + var.github_app_parameters.additional_apps_manifest != null ? [var.github_app_parameters.additional_apps_manifest.arn] : [], ["arn:${var.aws_partition}:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter${var.ssm_paths.root}/${var.ssm_paths.config}/*"] )) kms_key_arn = local.kms_key_arn diff --git a/modules/runners/tests/pool.tftest.hcl b/modules/runners/tests/pool.tftest.hcl index d6d327c598..c2282a7685 100644 --- a/modules/runners/tests/pool.tftest.hcl +++ b/modules/runners/tests/pool.tftest.hcl @@ -33,9 +33,8 @@ variables { runners_lambda_s3_key = "runners.zip" github_app_parameters = { - key_base64 = [{ name = "/github-runner/key-base64", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64" }] - id = [{ name = "/github-runner/app-id", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id" }] - installation_id = [null] + key_base64 = { name = "/github-runner/key-base64", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64" } + id = { name = "/github-runner/app-id", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id" } } ssm_paths = { diff --git a/modules/runners/variables.tf b/modules/runners/variables.tf index 946f9abf30..807a79bc93 100644 --- a/modules/runners/variables.tf +++ b/modules/runners/variables.tf @@ -229,20 +229,24 @@ variable "github_app_parameters" { description = <<-EOF Parameter Store for GitHub App Parameters. - Supports multiple GitHub Apps for random API rate limit distribution. - Each list element corresponds to one GitHub App and is a map containing - `name` and `arn` keys referencing SSM parameters. The first element is the - primary app (the one whose webhook secret is used for incoming webhook - validation). All apps must be installed on the same repositories/organizations. - - The control-plane lambdas (scale-up, scale-down, pool, job-retry) randomly - select an app from the list for each GitHub API call, distributing rate - limit consumption across all configured apps. + Supports multiple GitHub Apps for API rate limit distribution. `id` and + `key_base64` reference the primary app (the one whose webhook secret is + used for incoming webhook validation). Additional apps are delivered to + the lambdas via `additional_apps_manifest`, an SSM parameter whose value + lists the per-app credential parameter names, keeping the lambda + environment size constant regardless of app count. + `additional_app_parameter_arns` carries the ARNs of every additional app + credential parameter for the lambda IAM policies. All apps must be + installed on the same repositories/organizations as the primary app. EOF type = object({ - key_base64 = list(map(string)) - id = list(map(string)) - installation_id = list(object({ name = string, arn = string })) + key_base64 = map(string) + id = map(string) + additional_apps_manifest = optional(object({ + name = string + arn = string + }), null) + additional_app_parameter_arns = optional(list(string), []) }) } diff --git a/modules/ssm/README.md b/modules/ssm/README.md index 9f95dd2d45..73ba9a2d09 100644 --- a/modules/ssm/README.md +++ b/modules/ssm/README.md @@ -8,15 +8,15 @@ This module is used for storing configuration of runners, registration tokens an ## Requirements | Name | Version | -|------|---------| +| ---- | ------- | | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.21 | ## Providers | Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 6.21 | +| ---- | ------- | +| [aws](#provider\_aws) | 6.60.0 | ## Modules @@ -25,10 +25,11 @@ No modules. ## Resources | Name | Type | -|------|------| +| ---- | ---- | | [aws_ssm_parameter.additional_github_app_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.additional_github_app_installation_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.additional_github_app_key_base64](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.additional_github_apps_manifest](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.github_app_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.github_app_key_base64](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.github_app_webhook_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | @@ -36,7 +37,7 @@ No modules. ## Inputs | Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| +| ---- | ----------- | ---- | ------- | :------: | | [additional\_github\_apps](#input\_additional\_github\_apps) | Additional GitHub Apps for distributing API rate limit usage. |
list(object({
key_base64 = optional(string)
key_base64_ssm = optional(object({ arn = string, name = string }))
id = optional(string)
id_ssm = optional(object({ arn = string, name = string }))
installation_id = optional(string)
installation_id_ssm = optional(object({ arn = string, name = string }))
}))
| `[]` | no | | [github\_app](#input\_github\_app) | GitHub app parameters, see your github app.
You can optionally create the SSM parameters yourself and provide the ARN and name here, through the `*_ssm` attributes.
If you chose to provide the configuration values directly here,
please ensure the key is the base64-encoded `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem`).
Note: the provided SSM parameters arn and name have a precedence over the actual value (i.e `key_base64_ssm` has a precedence over `key_base64` etc). |
object({
key_base64 = optional(string)
key_base64_ssm = optional(object({
arn = string
name = string
}))
id = optional(string)
id_ssm = optional(object({
arn = string
name = string
}))
webhook_secret = optional(string)
webhook_secret_ssm = optional(object({
arn = string
name = string
}))
})
| n/a | yes | | [kms\_key\_arn](#input\_kms\_key\_arn) | Optional CMK Key ARN to be used for Parameter Store. | `string` | `null` | no | @@ -46,7 +47,8 @@ No modules. ## Outputs | Name | Description | -|------|-------------| +| ---- | ----------- | | [additional\_app\_parameters](#output\_additional\_app\_parameters) | n/a | +| [additional\_apps\_manifest](#output\_additional\_apps\_manifest) | n/a | | [parameters](#output\_parameters) | n/a | diff --git a/modules/ssm/outputs.tf b/modules/ssm/outputs.tf index 6032913520..e1afaf990e 100644 --- a/modules/ssm/outputs.tf +++ b/modules/ssm/outputs.tf @@ -33,3 +33,10 @@ output "additional_app_parameters" { } ] } + +output "additional_apps_manifest" { + value = length(var.additional_github_apps) > 0 ? { + name = aws_ssm_parameter.additional_github_apps_manifest[0].name + arn = aws_ssm_parameter.additional_github_apps_manifest[0].arn + } : null +} diff --git a/modules/ssm/ssm.tf b/modules/ssm/ssm.tf index 26ae6ea790..9467a136e5 100644 --- a/modules/ssm/ssm.tf +++ b/modules/ssm/ssm.tf @@ -51,3 +51,32 @@ resource "aws_ssm_parameter" "additional_github_app_installation_id" { key_id = local.kms_key_arn tags = var.tags } + +locals { + # Parameter names of every additional app credential, in app order. The + # manifest keeps the lambda environment size constant regardless of the + # number of configured apps: the lambdas receive only the manifest's + # parameter name and resolve the per-app parameter names from its value. + additional_apps_manifest = [ + for idx, app in var.additional_github_apps : { + idParamName = app.id_ssm != null ? app.id_ssm.name : aws_ssm_parameter.additional_github_app_id[tostring(idx)].name + keyParamName = app.key_base64_ssm != null ? app.key_base64_ssm.name : aws_ssm_parameter.additional_github_app_key_base64[tostring(idx)].name + installationIdParamName = ( + app.installation_id_ssm != null ? app.installation_id_ssm.name : + app.installation_id != null ? aws_ssm_parameter.additional_github_app_installation_id[tostring(idx)].name : + null + ) + } + ] +} + +resource "aws_ssm_parameter" "additional_github_apps_manifest" { + count = length(var.additional_github_apps) > 0 ? 1 : 0 + name = "${var.path_prefix}/additional_github_apps_manifest" + type = "String" + # Intelligent-Tiering upgrades the parameter to the advanced tier when the + # manifest outgrows the 4KB standard tier value limit (roughly 15 apps). + tier = "Intelligent-Tiering" + value = jsonencode(local.additional_apps_manifest) + tags = var.tags +} diff --git a/outputs.tf b/outputs.tf index 8e560e07f7..937714ff63 100644 --- a/outputs.tf +++ b/outputs.tf @@ -44,27 +44,15 @@ output "webhook" { } output "ssm_parameters" { - value = merge( - { - id = { name = local.github_app_parameters.id[0].name, arn = local.github_app_parameters.id[0].arn } - key_base64 = { name = local.github_app_parameters.key_base64[0].name, arn = local.github_app_parameters.key_base64[0].arn } - webhook_secret = { name = local.github_app_parameters.webhook_secret.name, arn = local.github_app_parameters.webhook_secret.arn } - }, - { for idx, v in local.github_app_parameters.id : "github_app_id_${idx}" => { - name = v.name - arn = v.arn - } }, - { for idx, v in local.github_app_parameters.key_base64 : "github_app_key_base64_${idx}" => { - name = v.name - arn = v.arn - } }, - { - github_app_webhook_secret = { - name = local.github_app_parameters.webhook_secret.name - arn = local.github_app_parameters.webhook_secret.arn - } - }, - ) + value = { + id = { name = local.github_app_parameters.id.name, arn = local.github_app_parameters.id.arn } + key_base64 = { name = local.github_app_parameters.key_base64.name, arn = local.github_app_parameters.key_base64.arn } + webhook_secret = { name = local.github_app_parameters.webhook_secret.name, arn = local.github_app_parameters.webhook_secret.arn } + additional_apps_manifest = local.github_app_parameters.additional_apps_manifest != null ? { + name = local.github_app_parameters.additional_apps_manifest.name + arn = local.github_app_parameters.additional_apps_manifest.arn + } : null + } } From 70c81ad22d3280b783fd54f43e63c70a7bc9dee2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:55:14 +0000 Subject: [PATCH 2/2] docs: auto update terraform docs --- README.md | 16 ++++++++-------- modules/multi-runner/README.md | 16 ++++++++-------- modules/runners/README.md | 14 +++++++------- modules/runners/job-retry/README.md | 12 ++++++------ modules/runners/pool/README.md | 10 +++++----- modules/ssm/README.md | 12 ++++++------ 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index b17dfc3b86..f097d3ebd2 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Requirements | Name | Version | -| ---- | ------- | +|------|---------| | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.33 | | [random](#requirement\_random) | ~> 3.0 | @@ -74,14 +74,14 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Providers | Name | Version | -| ---- | ------- | -| [aws](#provider\_aws) | 6.60.0 | -| [random](#provider\_random) | 3.9.0 | +|------|---------| +| [aws](#provider\_aws) | >= 6.33 | +| [random](#provider\_random) | ~> 3.0 | ## Modules | Name | Source | Version | -| ---- | ------ | ------- | +|------|--------|---------| | [ami\_housekeeper](#module\_ami\_housekeeper) | ./modules/ami-housekeeper | n/a | | [instance\_termination\_watcher](#module\_instance\_termination\_watcher) | ./modules/termination-watcher | n/a | | [runner\_binaries](#module\_runner\_binaries) | ./modules/runner-binaries-syncer | n/a | @@ -92,7 +92,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Resources | Name | Type | -| ---- | ---- | +|------|------| | [aws_sqs_queue.queued_builds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue.queued_builds_dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue_policy.build_queue_dlq_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | @@ -103,7 +103,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Inputs | Name | Description | Type | Default | Required | -| ---- | ----------- | ---- | ------- | :------: | +|------|-------------|------|---------|:--------:| | [additional\_github\_apps](#input\_additional\_github\_apps) | Additional GitHub Apps for distributing API rate limit usage. Each must be installed on the same repos/orgs as the primary app. |
list(object({
key_base64 = optional(string)
key_base64_ssm = optional(object({ arn = string, name = string }))
id = optional(string)
id_ssm = optional(object({ arn = string, name = string }))
installation_id = optional(string)
installation_id_ssm = optional(object({ arn = string, name = string }))
}))
| `[]` | no | | [ami](#input\_ami) | AMI configuration for the action runner instances. This object allows you to specify all AMI-related settings in one place.

Parameters:
- `filter`: Map of lists to filter AMIs by various criteria (e.g., { name = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-*"], state = ["available"] })
- `owners`: List of AMI owners to limit the search. Common values: ["amazon"], ["self"], or specific AWS account IDs
- `id_ssm_parameter_arn`: ARN of an SSM parameter containing the AMI ID. If specified, this overrides both AMI filter and parameter name
- `kms_key_arn`: Optional KMS key ARN if the AMI is encrypted with a customer managed key

Defaults to null, in which case the module falls back to individual AMI variables (deprecated). |
object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter_arn = optional(string, null)
kms_key_arn = optional(string, null)
})
| `null` | no | | [ami\_housekeeper\_cleanup\_config](#input\_ami\_housekeeper\_cleanup\_config) | Configuration for AMI cleanup.

`amiFilters` - Filters to use when searching for AMIs to cleanup. Default filter for images owned by the account and that are available.
`dryRun` - If true, no AMIs will be deregistered. Default false.
`launchTemplateNames` - Launch template names to use when searching for AMIs to cleanup. Default no launch templates.
`maxItems` - The maximum number of AMIs that will be queried for cleanup. Default no maximum.
`minimumDaysOld` - Minimum number of days old an AMI must be to be considered for cleanup. Default 30.
`ssmParameterNames` - SSM parameter names to use when searching for AMIs to cleanup. This parameter should be set when using SSM to configure the AMI to use. Default no SSM parameters. |
object({
amiFilters = optional(list(object({
Name = string
Values = list(string)
})),
[{
Name : "state",
Values : ["available"],
},
{
Name : "image-type",
Values : ["machine"],
}]
)
dryRun = optional(bool, false)
launchTemplateNames = optional(list(string))
maxItems = optional(number)
minimumDaysOld = optional(number, 30)
ssmParameterNames = optional(list(string))
})
| `{}` | no | @@ -250,7 +250,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh) ## Outputs | Name | Description | -| ---- | ----------- | +|------|-------------| | [binaries\_syncer](#output\_binaries\_syncer) | n/a | | [instance\_termination\_handler](#output\_instance\_termination\_handler) | n/a | | [instance\_termination\_watcher](#output\_instance\_termination\_watcher) | n/a | diff --git a/modules/multi-runner/README.md b/modules/multi-runner/README.md index e9835cc686..21fc8f441b 100644 --- a/modules/multi-runner/README.md +++ b/modules/multi-runner/README.md @@ -98,7 +98,7 @@ module "multi-runner" { ## Requirements | Name | Version | -| ---- | ------- | +|------|---------| | [terraform](#requirement\_terraform) | >= 1.3 | | [aws](#requirement\_aws) | >= 6.33 | | [random](#requirement\_random) | ~> 3.0 | @@ -106,14 +106,14 @@ module "multi-runner" { ## Providers | Name | Version | -| ---- | ------- | -| [aws](#provider\_aws) | 6.60.0 | -| [random](#provider\_random) | 3.9.0 | +|------|---------| +| [aws](#provider\_aws) | >= 6.33 | +| [random](#provider\_random) | ~> 3.0 | ## Modules | Name | Source | Version | -| ---- | ------ | ------- | +|------|--------|---------| | [ami\_housekeeper](#module\_ami\_housekeeper) | ../ami-housekeeper | n/a | | [instance\_termination\_watcher](#module\_instance\_termination\_watcher) | ../termination-watcher | n/a | | [runner\_binaries](#module\_runner\_binaries) | ../runner-binaries-syncer | n/a | @@ -124,7 +124,7 @@ module "multi-runner" { ## Resources | Name | Type | -| ---- | ---- | +|------|------| | [aws_sqs_queue.queued_builds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue.queued_builds_dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | | [aws_sqs_queue_policy.build_queue_dlq_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | @@ -135,7 +135,7 @@ module "multi-runner" { ## Inputs | Name | Description | Type | Default | Required | -| ---- | ----------- | ---- | ------- | :------: | +|------|-------------|------|---------|:--------:| | [additional\_github\_apps](#input\_additional\_github\_apps) | Additional GitHub Apps for random API rate limit distribution.

The primary app (var.github\_app) is always included and is the one whose
webhook secret is used for incoming webhook signature validation. Only the
primary app needs a webhook configured in GitHub.

Additional apps listed here are used exclusively by the control-plane
lambdas (scale-up, scale-down, pool, job-retry) which randomly select an
app for each GitHub API call. Each additional app must be installed on the
same repositories/organizations as the primary app. |
list(object({
key_base64 = optional(string)
key_base64_ssm = optional(object({ arn = string, name = string }))
id = optional(string)
id_ssm = optional(object({ arn = string, name = string }))
installation_id = optional(string)
installation_id_ssm = optional(object({ arn = string, name = string }))
}))
| `[]` | no | | [ami\_housekeeper\_cleanup\_config](#input\_ami\_housekeeper\_cleanup\_config) | Configuration for AMI cleanup. |
object({
maxItems = optional(number)
minimumDaysOld = optional(number)
amiFilters = optional(list(object({
Name = string
Values = list(string)
})))
launchTemplateNames = optional(list(string))
ssmParameterNames = optional(list(string))
dryRun = optional(bool)
})
| `{}` | no | | [ami\_housekeeper\_lambda\_memory\_size](#input\_ami\_housekeeper\_lambda\_memory\_size) | Memory size limit in MB of the lambda. | `number` | `256` | no | @@ -219,7 +219,7 @@ module "multi-runner" { ## Outputs | Name | Description | -| ---- | ----------- | +|------|-------------| | [binaries\_syncer\_map](#output\_binaries\_syncer\_map) | n/a | | [instance\_termination\_handler](#output\_instance\_termination\_handler) | n/a | | [instance\_termination\_watcher](#output\_instance\_termination\_watcher) | n/a | diff --git a/modules/runners/README.md b/modules/runners/README.md index f5da55e112..05c6ea5620 100644 --- a/modules/runners/README.md +++ b/modules/runners/README.md @@ -51,27 +51,27 @@ yarn run dist ## Requirements | Name | Version | -| ---- | ------- | +|------|---------| | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.33 | ## Providers | Name | Version | -| ---- | ------- | -| [aws](#provider\_aws) | 6.60.0 | +|------|---------| +| [aws](#provider\_aws) | >= 6.33 | ## Modules | Name | Source | Version | -| ---- | ------ | ------- | +|------|--------|---------| | [job\_retry](#module\_job\_retry) | ./job-retry | n/a | | [pool](#module\_pool) | ./pool | n/a | ## Resources | Name | Type | -| ---- | ---- | +|------|------| | [aws_cloudwatch_event_rule.scale_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | | [aws_cloudwatch_event_rule.ssm_housekeeper](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource | | [aws_cloudwatch_event_target.scale_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource | @@ -135,7 +135,7 @@ yarn run dist ## Inputs | Name | Description | Type | Default | Required | -| ---- | ----------- | ---- | ------- | :------: | +|------|-------------|------|---------|:--------:| | [ami](#input\_ami) | AMI configuration for the action runner instances. This object allows you to specify all AMI-related settings in one place.

Parameters:
- `filter`: Map of lists to filter AMIs by various criteria (e.g., { name = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-*"], state = ["available"] })
- `owners`: List of AMI owners to limit the search. Common values: ["amazon"], ["self"], or specific AWS account IDs
- `id_ssm_parameter_name`: Name of an SSM parameter containing the AMI ID. If specified, this overrides the AMI filter
- `id_ssm_parameter_arn`: ARN of an SSM parameter containing the AMI ID. If specified, this overrides both AMI filter and parameter name
- `kms_key_arn`: Optional KMS key ARN if the AMI is encrypted with a customer managed key

Defaults to null, in which case the module falls back to individual AMI variables (deprecated). |
object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter_arn = optional(string, null)
kms_key_arn = optional(string, null)
})
| `null` | no | | [associate\_public\_ipv4\_address](#input\_associate\_public\_ipv4\_address) | Associate public IPv4 with the runner. Only tested with IPv4 | `bool` | `false` | no | | [aws\_partition](#input\_aws\_partition) | (optional) partition for the base arn if not 'aws' | `string` | `"aws"` | no | @@ -246,7 +246,7 @@ yarn run dist ## Outputs | Name | Description | -| ---- | ----------- | +|------|-------------| | [lambda\_pool](#output\_lambda\_pool) | n/a | | [lambda\_pool\_log\_group](#output\_lambda\_pool\_log\_group) | n/a | | [lambda\_scale\_down](#output\_lambda\_scale\_down) | n/a | diff --git a/modules/runners/job-retry/README.md b/modules/runners/job-retry/README.md index 267516fbbb..57c6d9dc91 100644 --- a/modules/runners/job-retry/README.md +++ b/modules/runners/job-retry/README.md @@ -11,26 +11,26 @@ The module is an inner module and used by the runner module when the opt-in feat ## Requirements | Name | Version | -| ---- | ------- | +|------|---------| | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.21 | ## Providers | Name | Version | -| ---- | ------- | +|------|---------| | [aws](#provider\_aws) | >= 6.21 | ## Modules | Name | Source | Version | -| ---- | ------ | ------- | +|------|--------|---------| | [job\_retry](#module\_job\_retry) | ../../lambda | n/a | ## Resources | Name | Type | -| ---- | ---- | +|------|------| | [aws_iam_role_policy.job_retry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | | [aws_lambda_event_source_mapping.job_retry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource | | [aws_lambda_permission.job_retry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | @@ -41,13 +41,13 @@ The module is an inner module and used by the runner module when the opt-in feat ## Inputs | Name | Description | Type | Default | Required | -| ---- | ----------- | ---- | ------- | :------: | +|------|-------------|------|---------|:--------:| | [config](#input\_config) | Configuration for the spot termination watcher lambda function.

`aws_partition`: Partition for the base arn if not 'aws'
`architecture`: AWS Lambda architecture. Lambda functions using Graviton processors ('arm64') tend to have better price/performance than 'x86\_64' functions.
`environment_variables`: Environment variables for the lambda.
`enable_organization_runners`: Enable organization runners.
`enable_metric`: Enable metric for the lambda. If `spot_warning` is set to true, the lambda will emit a metric when it detects a spot termination warning.
'ghes\_url': Optional GitHub Enterprise Server URL.
'user\_agent': Optional User-Agent header for GitHub API requests.
'github\_app\_parameters': Parameter Store for GitHub App Parameters.
'kms\_key\_arn': Optional CMK Key ARN instead of using the default AWS managed key.
`lambda_event_source_mapping_batch_size`: Maximum number of records to pass to the lambda function in a single batch for the event source mapping. When not set, the AWS default will be used.
`lambda_event_source_mapping_maximum_batching_window_in_seconds`: Maximum amount of time to gather records before invoking the lambda function, in seconds. AWS requires this to be greater than 0 if batch\_size is greater than 10.
`lambda_principals`: Add extra principals to the role created for execution of the lambda, e.g. for local testing.
`lambda_tags`: Map of tags that will be added to created resources. By default resources will be tagged with name and environment.
`log_level`: Logging level for lambda logging. Valid values are 'silly', 'trace', 'debug', 'info', 'warn', 'error', 'fatal'.
`logging_kms_key_id`: Specifies the kms key id to encrypt the logs with
`logging_retention_in_days`: Specifies the number of days you want to retain log events for the lambda log group. Possible values are: 0, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653.
`memory_size`: Memory size limit in MB of the lambda.
`metrics`: Configuration to enable metrics creation by the lambda.
`prefix`: The prefix used for naming resources.
`role_path`: The path that will be added to the role, if not set the environment name will be used.
`role_permissions_boundary`: Permissions boundary that will be added to the created role for the lambda.
`runtime`: AWS Lambda runtime.
`s3_bucket`: S3 bucket from which to specify lambda functions. This is an alternative to providing local files directly.
`s3_key`: S3 key for syncer lambda function. Required if using S3 bucket to specify lambdas.
`s3_object_version`: S3 object version for syncer lambda function. Useful if S3 versioning is enabled on source bucket.
`security_group_ids`: List of security group IDs associated with the Lambda function.
'sqs\_build\_queue': SQS queue for build events to re-publish job request.
`subnet_ids`: List of subnets in which the action runners will be launched, the subnets needs to be subnets in the `vpc_id`.
`tag_filters`: Map of tags that will be used to filter the resources to be tracked. Only for which all tags are present and starting with the same value as the value in the map will be tracked.
`tags`: Map of tags that will be added to created resources. By default resources will be tagged with name and environment.
`timeout`: Time out of the lambda in seconds.
`tracing_config`: Configuration for lambda tracing.
`zip`: File location of the lambda zip file. |
object({
aws_partition = optional(string, null)
architecture = optional(string, null)
enable_organization_runners = bool
environment_variables = optional(map(string), {})
ghes_url = optional(string, null)
user_agent = optional(string, null)
github_app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
kms_key_arn = optional(string, null)
lambda_event_source_mapping_batch_size = optional(number, 10)
lambda_event_source_mapping_maximum_batching_window_in_seconds = optional(number, 0)
lambda_tags = optional(map(string), {})
log_level = optional(string, null)
logging_kms_key_id = optional(string, null)
logging_retention_in_days = optional(number, null)
memory_size = optional(number, null)
metrics = optional(object({
enable = optional(bool, false)
namespace = optional(string, null)
metric = optional(object({
enable_github_app_rate_limit = optional(bool, true)
enable_job_retry = optional(bool, true)
}), {})
}), {})
prefix = optional(string, null)
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
queue_encryption = optional(object({
kms_data_key_reuse_period_seconds = optional(number, null)
kms_master_key_id = optional(string, null)
sqs_managed_sse_enabled = optional(bool, true)
}), {})
role_path = optional(string, null)
role_permissions_boundary = optional(string, null)
runtime = optional(string, null)
security_group_ids = optional(list(string), [])
subnet_ids = optional(list(string), [])
s3_bucket = optional(string, null)
s3_key = optional(string, null)
s3_object_version = optional(string, null)
sqs_build_queue = object({
url = string
arn = string
})
tags = optional(map(string), {})
timeout = optional(number, 30)
tracing_config = optional(object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
}), {})
zip = optional(string, null)
})
| n/a | yes | ## Outputs | Name | Description | -| ---- | ----------- | +|------|-------------| | [job\_retry\_check\_queue](#output\_job\_retry\_check\_queue) | n/a | | [lambda](#output\_lambda) | n/a | diff --git a/modules/runners/pool/README.md b/modules/runners/pool/README.md index 5a1ff2edcf..54b85d968e 100644 --- a/modules/runners/pool/README.md +++ b/modules/runners/pool/README.md @@ -9,14 +9,14 @@ The pool is an opt-in feature. To be able to use the count on a module level to ## Requirements | Name | Version | -| ---- | ------- | +|------|---------| | [terraform](#requirement\_terraform) | >= 0.14.1 | | [aws](#requirement\_aws) | >= 6.21 | ## Providers | Name | Version | -| ---- | ------- | +|------|---------| | [aws](#provider\_aws) | >= 6.21 | ## Modules @@ -26,7 +26,7 @@ No modules. ## Resources | Name | Type | -| ---- | ---- | +|------|------| | [aws_cloudwatch_log_group.pool](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_iam_role.pool](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.scheduler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | @@ -47,7 +47,7 @@ No modules. ## Inputs | Name | Description | Type | Default | Required | -| ---- | ----------- | ---- | ------- | :------: | +|------|-------------|------|---------|:--------:| | [aws\_partition](#input\_aws\_partition) | (optional) partition for the arn if not 'aws' | `string` | `"aws"` | no | | [config](#input\_config) | Lookup details in parent module. |
object({
lambda = object({
log_level = string
logging_retention_in_days = number
logging_kms_key_id = string
log_class = string
reserved_concurrent_executions = number
s3_bucket = string
s3_key = string
s3_object_version = string
security_group_ids = list(string)
runtime = string
architecture = string
memory_size = number
timeout = number
zip = string
subnet_ids = list(string)
parameter_store_tags = string
})
tags = map(string)
ghes = object({
url = string
ssl_verify = string
})
github_app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
subnet_ids = list(string)
runner = object({
disable_runner_autoupdate = bool
ephemeral = bool
enable_jit_config = bool
enable_on_demand_failover_for_errors = list(string)
scale_errors = list(string)
boot_time_in_minutes = number
labels = list(string)
launch_template = object({
name = string
})
group_name = string
name_prefix = string
pool_owner = string
role = object({
arn = string
})
use_dedicated_host = bool
})
runners_maximum_count = number
instance_types = list(string)
instance_type_priorities = optional(map(number))
instance_target_capacity_type = string
instance_allocation_strategy = string
instance_max_spot_price = string
prefix = string
pool = list(object({
schedule_expression = string
schedule_expression_timezone = string
size = number
}))
include_busy_runners = bool
role_permissions_boundary = string
kms_key_arn = string
ami_kms_key_arn = string
ami_id_ssm_parameter_arn = string
role_path = string
ssm_token_path = string
ssm_config_path = string
ami_id_ssm_parameter_name = string
ami_id_ssm_parameter_read_policy_arn = string
arn_ssm_parameters_path_config = string
lambda_tags = map(string)
user_agent = string
})
| n/a | yes | | [tracing\_config](#input\_tracing\_config) | Configuration for lambda tracing. |
object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
})
| `{}` | no | @@ -55,7 +55,7 @@ No modules. ## Outputs | Name | Description | -| ---- | ----------- | +|------|-------------| | [lambda](#output\_lambda) | n/a | | [lambda\_log\_group](#output\_lambda\_log\_group) | n/a | | [role\_pool](#output\_role\_pool) | n/a | diff --git a/modules/ssm/README.md b/modules/ssm/README.md index 73ba9a2d09..66bce354e2 100644 --- a/modules/ssm/README.md +++ b/modules/ssm/README.md @@ -8,15 +8,15 @@ This module is used for storing configuration of runners, registration tokens an ## Requirements | Name | Version | -| ---- | ------- | +|------|---------| | [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 6.21 | ## Providers | Name | Version | -| ---- | ------- | -| [aws](#provider\_aws) | 6.60.0 | +|------|---------| +| [aws](#provider\_aws) | >= 6.21 | ## Modules @@ -25,7 +25,7 @@ No modules. ## Resources | Name | Type | -| ---- | ---- | +|------|------| | [aws_ssm_parameter.additional_github_app_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.additional_github_app_installation_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_ssm_parameter.additional_github_app_key_base64](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | @@ -37,7 +37,7 @@ No modules. ## Inputs | Name | Description | Type | Default | Required | -| ---- | ----------- | ---- | ------- | :------: | +|------|-------------|------|---------|:--------:| | [additional\_github\_apps](#input\_additional\_github\_apps) | Additional GitHub Apps for distributing API rate limit usage. |
list(object({
key_base64 = optional(string)
key_base64_ssm = optional(object({ arn = string, name = string }))
id = optional(string)
id_ssm = optional(object({ arn = string, name = string }))
installation_id = optional(string)
installation_id_ssm = optional(object({ arn = string, name = string }))
}))
| `[]` | no | | [github\_app](#input\_github\_app) | GitHub app parameters, see your github app.
You can optionally create the SSM parameters yourself and provide the ARN and name here, through the `*_ssm` attributes.
If you chose to provide the configuration values directly here,
please ensure the key is the base64-encoded `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem`).
Note: the provided SSM parameters arn and name have a precedence over the actual value (i.e `key_base64_ssm` has a precedence over `key_base64` etc). |
object({
key_base64 = optional(string)
key_base64_ssm = optional(object({
arn = string
name = string
}))
id = optional(string)
id_ssm = optional(object({
arn = string
name = string
}))
webhook_secret = optional(string)
webhook_secret_ssm = optional(object({
arn = string
name = string
}))
})
| n/a | yes | | [kms\_key\_arn](#input\_kms\_key\_arn) | Optional CMK Key ARN to be used for Parameter Store. | `string` | `null` | no | @@ -47,7 +47,7 @@ No modules. ## Outputs | Name | Description | -| ---- | ----------- | +|------|-------------| | [additional\_app\_parameters](#output\_additional\_app\_parameters) | n/a | | [additional\_apps\_manifest](#output\_additional\_apps\_manifest) | n/a | | [parameters](#output\_parameters) | n/a |