Skip to content

Commit 9b790bd

Browse files
refactor(forge): create submodule for github features (#180)
* refactor: create submodule github_global_lock * refactor: create submodule github_webhook_relay * refactor: create submodule github_app_runner_group * refactor: delete old files
1 parent f70ba04 commit 9b790bd

37 files changed

+1249
-160
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module "github_app_runner_group" {
2+
source = "./github_app_runner_group"
3+
4+
providers = {
5+
aws = aws
6+
}
7+
8+
prefix = var.deployment_config.prefix
9+
secrets_prefix = local.cicd_secrets_prefix
10+
logging_retention_in_days = var.logging_retention_in_days
11+
tags = local.all_security_tags
12+
github_api = local.github_api
13+
ghes_org = var.ghes_org
14+
runner_group_name = var.runner_group_name
15+
repository_selection = var.repository_selection
16+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data "aws_region" "current" {}

modules/platform/forge_runners/lambda/github_app_runner_group.py renamed to modules/platform/forge_runners/github_app_runner_group/lambda/github_app_runner_group.py

File renamed without changes.

modules/platform/forge_runners/register_repo_runner_group.tf renamed to modules/platform/forge_runners/github_app_runner_group/main.tf

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ module "register_github_app_runner_group_lambda" {
22
source = "terraform-aws-modules/lambda/aws"
33
version = "8.1.0"
44

5-
function_name = "${var.deployment_config.prefix}-register-github-app-runner-group"
5+
function_name = "${var.prefix}-register-github-app-runner-group"
66
handler = "github_app_runner_group.lambda_handler"
77
runtime = "python3.12"
88
timeout = 900
99
architectures = ["x86_64"]
1010

1111
source_path = [{
12-
path = "${path.module}/lambda"
13-
pip_requirements = "${path.module}/lambda/requirements.txt"
12+
path = "${path.module}/lambda"
1413
}]
1514

1615
layers = [
17-
"arn:aws:lambda:${var.aws_region}:770693421928:layer:Klayers-p312-cryptography:17"
16+
"arn:aws:lambda:${data.aws_region.current.region}:770693421928:layer:Klayers-p312-cryptography:17",
17+
"arn:aws:lambda:${data.aws_region.current.region}:770693421928:layer:Klayers-p312-requests:17",
18+
"arn:aws:lambda:${data.aws_region.current.region}:770693421928:layer:Klayers-p312-PyJWT:1",
1819
]
1920

2021
logging_log_group = aws_cloudwatch_log_group.register_github_app_runner_group_lambda.name
@@ -23,22 +24,22 @@ module "register_github_app_runner_group_lambda" {
2324
trigger_on_package_timestamp = false
2425

2526
environment_variables = {
26-
GITHUB_API = local.github_api
27+
GITHUB_API = var.github_api
2728
ORGANIZATION = var.ghes_org
2829
RUNNER_GROUP_NAME = var.runner_group_name
2930
REPOSITORY_SELECTION = var.repository_selection
30-
SECRET_NAME_APP_ID = "${local.cicd_secrets_prefix}github_actions_runners_app_id"
31-
SECRET_NAME_PRIVATE_KEY = "${local.cicd_secrets_prefix}github_actions_runners_app_key"
32-
SECRET_NAME_INSTALLATION_ID = "${local.cicd_secrets_prefix}github_actions_runners_app_installation_id"
31+
SECRET_NAME_APP_ID = local.secrets.github_actions_runners_app_id.name
32+
SECRET_NAME_PRIVATE_KEY = local.secrets.github_actions_runners_app_key.name
33+
SECRET_NAME_INSTALLATION_ID = local.secrets.github_actions_runners_app_installation_id.name
3334
}
3435

3536
attach_policy_json = true
3637

3738
policy_json = data.aws_iam_policy_document.register_github_app_runner_group_lambda.json
3839

39-
function_tags = local.all_security_tags
40-
role_tags = local.all_security_tags
41-
tags = local.all_security_tags
40+
function_tags = var.tags
41+
role_tags = var.tags
42+
tags = var.tags
4243

4344
depends_on = [aws_cloudwatch_log_group.register_github_app_runner_group_lambda]
4445
}
@@ -51,27 +52,27 @@ data "aws_iam_policy_document" "register_github_app_runner_group_lambda" {
5152
]
5253
effect = "Allow"
5354
resources = [
54-
data.aws_secretsmanager_secret_version.data_cicd_secrets["${local.cicd_secrets_prefix}github_actions_runners_app_key"].arn,
55-
data.aws_secretsmanager_secret_version.data_cicd_secrets["${local.cicd_secrets_prefix}github_actions_runners_app_id"].arn,
56-
data.aws_secretsmanager_secret_version.data_cicd_secrets["${local.cicd_secrets_prefix}github_actions_runners_app_installation_id"].arn,
55+
data.aws_secretsmanager_secret_version.secrets["github_actions_runners_app_key"].arn,
56+
data.aws_secretsmanager_secret_version.secrets["github_actions_runners_app_id"].arn,
57+
data.aws_secretsmanager_secret_version.secrets["github_actions_runners_app_installation_id"].arn,
5758
]
5859
}
5960
}
6061

6162
resource "aws_cloudwatch_log_group" "register_github_app_runner_group_lambda" {
62-
name = "/aws/lambda/${var.deployment_config.prefix}-register-github-app-runner-group"
63+
name = "/aws/lambda/${var.prefix}-register-github-app-runner-group"
6364
retention_in_days = var.logging_retention_in_days
64-
tags = local.all_security_tags
65-
tags_all = local.all_security_tags
65+
tags = var.tags
66+
tags_all = var.tags
6667
}
6768

6869
resource "aws_cloudwatch_event_rule" "register_github_app_runner_group_lambda" {
69-
name = "${var.deployment_config.prefix}-register-github-app-runner-group"
70+
name = "${var.prefix}-register-github-app-runner-group"
7071
description = "Trigger Lambda every 10 minutes"
7172
schedule_expression = "cron(*/10 * * * ? *)"
7273

73-
tags = local.all_security_tags
74-
tags_all = local.all_security_tags
74+
tags = var.tags
75+
tags_all = var.tags
7576

7677
depends_on = [module.register_github_app_runner_group_lambda]
7778
}
@@ -85,7 +86,7 @@ resource "aws_cloudwatch_event_target" "register_github_app_runner_group_lambda"
8586

8687
resource "aws_lambda_permission" "register_github_app_runner_group_lambda" {
8788
action = "lambda:InvokeFunction"
88-
function_name = "${var.deployment_config.prefix}-register-github-app-runner-group"
89+
function_name = "${var.prefix}-register-github-app-runner-group"
8990
principal = "events.amazonaws.com"
9091
statement_id = "AllowExecutionFromCloudWatch"
9192
source_arn = aws_cloudwatch_event_rule.register_github_app_runner_group_lambda.arn
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
locals {
2+
secrets = {
3+
github_actions_runners_app_key = {
4+
name = "${var.secrets_prefix}github_actions_runners_app_key"
5+
}
6+
github_actions_runners_app_id = {
7+
name = "${var.secrets_prefix}github_actions_runners_app_id"
8+
}
9+
github_actions_runners_app_installation_id = {
10+
name = "${var.secrets_prefix}github_actions_runners_app_installation_id"
11+
}
12+
}
13+
}
14+
15+
data "aws_secretsmanager_secret" "secrets" {
16+
for_each = local.secrets
17+
name = each.value.name
18+
}
19+
20+
data "aws_secretsmanager_secret_version" "secrets" {
21+
for_each = data.aws_secretsmanager_secret.secrets
22+
secret_id = each.value.id
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
variable "prefix" {
2+
description = "Prefix for all resources"
3+
type = string
4+
}
5+
6+
variable "tags" {
7+
description = "Tags to apply to created resources."
8+
type = map(string)
9+
default = {}
10+
}
11+
12+
variable "secrets_prefix" {
13+
description = "Prefix for all secrets"
14+
type = string
15+
}
16+
17+
variable "logging_retention_in_days" {
18+
description = "Retention in days for CloudWatch Log Group for the Lambdas."
19+
type = number
20+
default = 30
21+
}
22+
23+
variable "github_api" {
24+
description = "Base URL for the GitHub API (set to GHES API endpoint if using Enterprise)."
25+
type = string
26+
default = "https://api.github.com"
27+
}
28+
29+
variable "ghes_org" {
30+
description = "GitHub organization (GHES or GitHub.com)."
31+
type = string
32+
}
33+
34+
variable "runner_group_name" {
35+
description = "Name of the GitHub Actions runner group to create/update and attach repositories to."
36+
type = string
37+
}
38+
39+
variable "repository_selection" {
40+
description = "Repository selection type: 'all' or 'selected'."
41+
type = string
42+
validation {
43+
condition = contains(["all", "selected"], var.repository_selection)
44+
error_message = "repository_selection must be 'all' or 'selected'."
45+
}
46+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
terraform {
2+
required_providers {
3+
aws = {
4+
source = "hashicorp/aws"
5+
version = ">= 5.27"
6+
}
7+
}
8+
9+
# OpenTofu version.
10+
required_version = ">= 1.9.1"
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module "github_global_lock" {
2+
source = "./github_global_lock"
3+
4+
providers = {
5+
aws = aws
6+
}
7+
8+
prefix = var.deployment_config.prefix
9+
secrets_prefix = local.cicd_secrets_prefix
10+
logging_retention_in_days = var.logging_retention_in_days
11+
tags = local.all_security_tags
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data "aws_region" "current" {}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# GitHub Actions Job Log Archiver Module
2+
3+
Archives completed GitHub Actions workflow job logs into per-tenant S3 buckets for audit, troubleshooting, and retention requirements.
4+
5+
## Features
6+
- Per-tenant buckets (auto-created or externally provided)
7+
- Two processing modes:
8+
- Direct (legacy): EventBridge -> downloader Lambda -> S3
9+
- Queue pipeline (default): EventBridge -> dispatcher Lambda -> SQS -> downloader Lambda (better isolation, retry & backpressure)
10+
- GitHub App authentication (JWT + installation token) to fetch job logs
11+
- EventBridge rule listening for `workflow_job.completed` events (via existing webhook relay -> EventBridge integration)
12+
- Optional KMS encryption
13+
- Shared read/list access for platform/observability roles
14+
- Versioning & basic lifecycle management
15+
16+
## How It Works
17+
### Queue Pipeline (default)
18+
1. GitHub webhook (workflow_job events) reaches your existing relay (not part of this module) which forwards events onto EventBridge as detail-type `GitHub Webhook`.
19+
2. EventBridge rule filters for `workflow_job` with `action=completed`.
20+
3. Dispatcher Lambda validates mapping & completion status, then enqueues a concise message to SQS.
21+
4. Downloader Lambda (SQS trigger) consumes batches, performs GitHub API log download, and writes to S3.
22+
5. Object stored at: `s3://<bucket>/<workflow_name>/<run_attempt>/<job_id>.zip`.
23+
24+
### Direct Mode (fallback)
25+
Set `enable_queue_pipeline = false` to revert to the original single-Lambda flow (less durable, fewer moving parts for very low volume environments).
26+
27+
## Inputs
28+
See `variables.tf` for full list. Key inputs:
29+
- `repo_tenant_map` (map) : Maps `org/repo` -> tenant id.
30+
- `github_app_id`, `github_app_private_key_secret_arn` : GitHub App credentials.
31+
- `github_app_installation_id` : Optional fixed installation id (skip lookup per repo).
32+
- `kms_key_arn` : Optional SSE-KMS key for encryption.
33+
- `shared_role_arns` : Optional map of role ARNs granted read-list across all buckets.
34+
- `enable_queue_pipeline` : Toggle queue pipeline (default true).
35+
- `sqs_visibility_timeout_seconds`, `sqs_max_receive_count` : Queue configuration.
36+
- `downloader_batch_size`, `downloader_max_concurrency` : SQS batch processing tuning.
37+
38+
## Outputs
39+
- `bucket_names` : tenant -> bucket mapping.
40+
- `lambda_function_name` / `lambda_function_arn`.
41+
42+
## Bucket Naming
43+
Uses `bucket_name_format` replacing `{{tenant}}` with tenant id. Provide pre-created buckets by setting `create_buckets = false` and ensuring the names exist.
44+
45+
## Example (Queue Pipeline)
46+
```hcl
47+
module "gha_job_log_archiver" {
48+
source = "../modules/integrations/github_actions_job_log_archiver"
49+
50+
repo_tenant_map = {
51+
"myorg/app-service" = "tenant-a"
52+
"myorg/api-gateway" = "tenant-b"
53+
}
54+
55+
github_app_id = var.github_app_id
56+
github_app_private_key_secret_arn = var.github_app_private_key_secret_arn
57+
github_app_installation_id = var.github_app_installation_id # optional
58+
kms_key_arn = var.logs_kms_key_arn # optional
59+
shared_role_arns = {
60+
observability = aws_iam_role.observability.arn
61+
}
62+
tags = var.tags
63+
}
64+
```
65+
66+
## Example (Direct Mode)
67+
```hcl
68+
module "gha_job_log_archiver" {
69+
source = "../modules/integrations/github_actions_job_log_archiver"
70+
enable_queue_pipeline = false
71+
repo_tenant_map = { "myorg/app-service" = "tenant-a" }
72+
github_app_id = var.github_app_id
73+
github_app_private_key_secret_arn = var.github_app_private_key_secret_arn
74+
}
75+
```
76+
77+
## Required GitHub App Permissions
78+
- Actions: Read
79+
- Metadata: Read
80+
81+
## IAM / Security Notes
82+
- Ensure the provided KMS key allows the Lambda role to `Encrypt/Decrypt/GenerateDataKey`.
83+
- Bucket policies (shared access) are not yet explicitly created; consider future enhancement if central read roles need cross-account access.
84+
85+
## Future Enhancements
86+
- Add optional CloudWatch metric/log filters for download errors.
87+
- Retry & backoff for GitHub API secondary rate limits / 403 abuse detection.
88+
- Support artifact / log size tagging in object metadata.
89+
- Add SQS FIFO option for strict ordering per repository.
90+
91+
## Event Shape Assumption
92+
Expecting EventBridge detail of form (subset):
93+
```json
94+
{
95+
"detail": {
96+
"repository": { "full_name": "org/repo" },
97+
"action": "completed",
98+
"workflow_job": {
99+
"id": 123456789,
100+
"run_id": 987654321,
101+
"run_attempt": 1,
102+
"name": "build",
103+
"workflow_name": "CI",
104+
"status": "completed",
105+
"conclusion": "success"
106+
}
107+
}
108+
}
109+
```
110+
111+
## Local Development
112+
Package the Lambda:
113+
```bash
114+
(cd modules/integrations/github_actions_job_log_archiver/lambda && zip job_log_archiver.zip job_log_archiver.py)
115+
```
116+
Then re-run Terraform apply so the updated `source_code_hash` triggers deployment.
117+
118+
## License
119+
See parent repository license.

0 commit comments

Comments
 (0)