Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,24 @@ locals {

team_members = {
"akuzminsky" = {
role = "member"
role = "member"
team_role = "member"
teams = [
github_team.dev.name,
github_team.admins.name,
]
}
"infrahouse8" = {
role = "admin"
role = "admin"
team_role = "maintainer"
teams = [
github_team.dev.name,
github_team.admins.name,
]
}
"naumenko" = {
role = "member"
role = "member"
team_role = "member"
teams = [
github_team.dev.name,
]
Expand Down
28 changes: 27 additions & 1 deletion modules/service-repo/files.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@ resource "github_repository_file" "gitignore" {
}
}

resource "github_repository_file" "renovate" {
count = var.archived ? 0 : 1
depends_on = [
github_repository_ruleset.main
]
repository = github_repository.this.name
file = "renovate.json"
content = file("${path.module}/templates/renovate.json")
commit_message = "Add renovate.json"
overwrite_on_create = true
}

# Consumer-facing docs for the managed workflows + renovate.json. Placed under .github/ so it does
# not shadow a hand-written root README.md.
resource "github_repository_file" "repo_readme" {
count = var.archived ? 0 : 1
depends_on = [
github_repository_ruleset.main
]
repository = github_repository.this.name
file = ".github/README.md"
content = file("${path.module}/templates/repo-README.md")
commit_message = "Add .github/README.md documenting managed CI/CD"
overwrite_on_create = true
}

resource "github_repository_file" "makefile_root" {
count = var.archived ? 0 : 1
depends_on = [
Expand All @@ -33,7 +59,7 @@ resource "github_repository_file" "makefile_fragment" {
repository = github_repository.this.name
file = "makefiles/Makefile"
content = file("${path.module}/templates/makefile.mk")
commit_message = "Add makefiles/Makefile"
commit_message = "Update makefiles/Makefile"
overwrite_on_create = true
}

Expand Down
42 changes: 42 additions & 0 deletions modules/service-repo/templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!--
Maintainer notes for github-control. This file is NOT injected into managed repos — only files
referenced by a github_repository_file resource in ../files.tf or ../workflows.tf are pushed.
-->

# service-repo templates

Files in this directory are pushed verbatim (or rendered from `.tftpl`) into every repository managed
by the `service-repo` module. Downstream repos must **not** edit them; they are overwritten on the
next `terraform apply`. To change behavior, edit the template here and open a PR against
[github-control](https://github.com/infrahouse/github-control).

The consumer-facing explanation of what each workflow and `renovate.json` does is itself shipped to
the repos as `.github/README.md` (from `repo-README.md`) — read that file for the prose.

## Resource map

| Template | Injected as | Defined in |
|----------|-------------|------------|
| `repo-README.md` | `.github/README.md` | `../files.tf` |
| `renovate.json` | `renovate.json` | `../files.tf` |
| `terraform-CI.yml.tftpl` | `.github/workflows/terraform-CI.yml` | `../workflows.tf` |
| `terraform-CD.yml.tftpl` | `.github/workflows/terraform-CD.yml` | `../workflows.tf` |
| `terraform-drift.yml` | `.github/workflows/terraform-drift.yml` | `../workflows.tf` |
| `terraform-drift-wrapper.yml.tftpl` | `.github/workflows/terraform-drift-wrapper.yml` | `../workflows.tf` |
| `secrets-scanner.yml` | `.github/workflows/secrets-scanner.yml` | `../workflows.tf` |
| `vuln-scanner-pr.yml` | `.github/workflows/vuln-scanner-pr.yml` | `../workflows.tf` |

Scaffolding templates (`gitignore`, `Makefile.root`, `makefile.mk`, `Makefile.env`,
`requirements.txt`, `terraform.tf.tftpl`, `terraform.tfvars.tftpl`, `releases.auto.tfvars.tftpl`)
build the Terraform root modules and tooling per environment — see `../files.tf` and
`../environments.tf`.

## Conventions

- **Action pinning.** Every third-party action is pinned to a full 40-char commit SHA with a trailing
`# vN` comment, satisfying the Semgrep `github-actions-mutable-action-tag` rule. Same-repo
reusable-workflow calls (`./.github/workflows/...`) stay unpinned. Renovate keeps the SHAs current.
- **Matrix rendering.** Environment matrices render as YAML block sequences (`- "env"`), not
`jsonencode(...)` flow arrays, so the output passes yamllint's `commas` rule.
- **`renovate.json` has no banner.** JSON has no comments, so it cannot carry the managed-by header
the other templates use. It is managed the same way regardless.
16 changes: 16 additions & 0 deletions modules/service-repo/templates/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"helpers:pinGitHubActionDigests"
],
"dependencyDashboardApproval": true,
"prConcurrentLimit": 1,
"rebaseWhen": "conflicted",
"packageRules": [
{
"matchManagers": ["github-actions"],
"automerge": false
}
]
}
58 changes: 58 additions & 0 deletions modules/service-repo/templates/repo-README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!--
This file is managed by Terraform in the github-control repository.
Do not edit it here — changes are overwritten on the next apply.
To change it, open a PR against https://github.com/infrahouse/github-control
(modules/service-repo/templates/repo-README.md).
-->

# CI/CD & automation (managed)

This repository's `.github/workflows/` and `renovate.json` are generated and kept in sync by
[github-control](https://github.com/infrahouse/github-control) through the `service-repo` Terraform
module. **Do not edit those files here** — any change is reverted on the next `terraform apply`. To
change how CI/CD behaves, open a PR against github-control.

## Workflows

### `terraform-CI.yml` — plan on every PR

Runs on `pull_request`. For each environment it runs `terraform plan`, posts the plan as a PR comment
(`ih-plan publish`), and uploads the plan file to the state bucket (`ih-plan upload`) so the exact
reviewed plan can be applied after merge.

### `terraform-CD.yml` — apply on merge

Runs when a PR is merged. For each environment it downloads the plan CI stored, applies it, removes
the stored plan, then runs `make smoke`. Environments deploy in the order set by each environment's
`deploy_order`.

### `terraform-drift.yml` — reusable drift check

A `workflow_call` reusable workflow. Runs `terraform plan -detailed-exitcode` for one environment; if
drift is detected it opens a `config-drift`-labeled PR to reconcile.

### `terraform-drift-wrapper.yml` — scheduled drift sweep

Runs daily (and on demand via `workflow_dispatch`). Calls `terraform-drift.yml` for every environment
and posts to the `#infra` Slack channel on failure.

### `secrets-scanner.yml` — leaked-secret scan

Runs on PRs and the merge queue. Uses TruffleHog to scan the diff for verified secrets.

### `vuln-scanner-pr.yml` — dependency + SAST scan

Runs on PRs and the merge queue. One job runs OSV-Scanner for dependency CVEs; another runs Semgrep
SAST (`semgrep scan --error`), which blocks the PR on findings.

## `renovate.json`

Configures [Renovate](https://docs.renovatebot.com/) for this repo. Every GitHub Action in the
workflows above is pinned to a full commit SHA (with a trailing `# vN` comment) to prevent
supply-chain attacks from silently repointed tags or branches. Renovate's
`helpers:pinGitHubActionDigests` preset keeps those pins current — it opens a PR bumping the SHA and
comment when a new version ships. Updates are queued on the Dependency Dashboard for approval
(`dependencyDashboardApproval`) and never automerge.

`renovate.json` is strict JSON, so it carries no "managed by Terraform" banner comment, but it is
managed exactly like the workflows — edit it in github-control, not here.
4 changes: 2 additions & 2 deletions modules/service-repo/templates/secrets-scanner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
uses: trufflesecurity/trufflehog@00155c9dc586f34d189adc83d3ac2698c2ec551f # v3.95.8
with:
path: ./
base: ${{ github.event.repository.default_branch }}
Expand Down
13 changes: 8 additions & 5 deletions modules/service-repo/templates/terraform-CD.yml.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ jobs:
strategy:
fail-fast: false
matrix:
env: ${jsonencode(environments_by_order[order])}
env:
%{ for env in environments_by_order[order] ~}
- "${env}"
%{ endfor ~}
name: "Terraform Apply $${{ matrix.env }}"
runs-on: ubuntu-24.04
environment: "live-$${{ matrix.env }}"
Expand All @@ -42,7 +45,7 @@ jobs:

steps:
- name: "Checkout"
uses: "actions/checkout@v6"
uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6

- name: "Extract Variables"
id: "extract_vars"
Expand All @@ -55,7 +58,7 @@ jobs:
echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"

- name: "Configure AWS Credentials"
uses: "aws-actions/configure-aws-credentials@v6"
uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b" # v6
with:
role-to-assume: "$${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
role-session-name: "github-actions-$${{ matrix.env }}"
Expand All @@ -67,12 +70,12 @@ jobs:
echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"

- name: "Setup Terraform"
uses: "hashicorp/setup-terraform@v4"
uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e" # v4
with:
terraform_version: "$${{ steps.terraform_version.outputs.IH_TF_VERSION }}"

- name: "Set up Python"
uses: "actions/setup-python@v6"
uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" # v6
with:
python-version: "3.14"

Expand Down
13 changes: 8 additions & 5 deletions modules/service-repo/templates/terraform-CI.yml.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ jobs:
strategy:
fail-fast: false
matrix:
env: ${jsonencode(environments)}
env:
%{ for env in environments ~}
- "${env}"
%{ endfor ~}
name: "Terraform Plan $${{ matrix.env }}"
runs-on: ubuntu-24.04
environment: "continuous-integration-$${{ matrix.env }}"
Expand All @@ -36,7 +39,7 @@ jobs:

steps:
- name: "Checkout"
uses: "actions/checkout@v6"
uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6

- name: "Extract Variables"
id: "extract_vars"
Expand All @@ -49,7 +52,7 @@ jobs:
echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"

- name: "Configure AWS Credentials"
uses: "aws-actions/configure-aws-credentials@v6"
uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b" # v6
with:
role-to-assume: "$${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
role-session-name: "github-actions-$${{ matrix.env }}"
Expand All @@ -61,12 +64,12 @@ jobs:
echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"

- name: "Setup Terraform"
uses: "hashicorp/setup-terraform@v4"
uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e" # v4
with:
terraform_version: "$${{ steps.terraform_version.outputs.IH_TF_VERSION }}"

- name: "Set up Python"
uses: "actions/setup-python@v6"
uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" # v6
with:
python-version: "3.14"

Expand Down
10 changes: 6 additions & 4 deletions modules/service-repo/templates/terraform-drift-wrapper.yml.tftpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: "[Meta] Terraform Drift Detection"

on:
on: # yamllint disable-line rule:truthy
workflow_dispatch:
schedule:
# Runs daily at 10:XX AM UTC
Expand All @@ -11,11 +11,13 @@ jobs:
config-drift-check:
strategy:
matrix:
env: ${jsonencode(environments)}
env:
%{ for env in environments ~}
- "${env}"
%{ endfor ~}
uses: "./.github/workflows/terraform-drift.yml"
with:
env: "$${{ matrix.env }}"
secrets: "inherit"

notify_on_failure:
name: "Notify #infra slack on failure"
Expand All @@ -24,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Post to Slack
uses: "slackapi/slack-github-action@v2.1.1"
uses: "slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a" # v2.1.1
with:
method: "chat.postMessage"
token: $${{ secrets.SLACK_BOT_TOKEN }}
Expand Down
10 changes: 5 additions & 5 deletions modules/service-repo/templates/terraform-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:

steps:
- name: "Checkout"
uses: "actions/checkout@v6"
uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6

- name: "Extract Variables"
id: "extract_vars"
Expand All @@ -47,7 +47,7 @@ jobs:
echo "REGION=$REGION" >> "$GITHUB_OUTPUT"

- name: "Configure AWS Credentials"
uses: "aws-actions/configure-aws-credentials@v6"
uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b" # v6
with:
role-to-assume: "${{ vars.ROLE_GITHUB }}"
role-session-name: "github-actions-${{ inputs.env }}"
Expand All @@ -59,12 +59,12 @@ jobs:
echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"

- name: "Setup Terraform"
uses: "hashicorp/setup-terraform@v4"
uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e" # v4
with:
terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"

- name: "Set up Python"
uses: "actions/setup-python@v6"
uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" # v6
with:
python-version: "3.14"

Expand Down Expand Up @@ -108,7 +108,7 @@ jobs:

- name: "Create Pull Request"
if: "steps.check_label.outputs.label_exists == 'false'"
uses: "peter-evans/create-pull-request@v8"
uses: "peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1" # v8
with:
token: "${{ steps.app-token.outputs.token }}"
base: "main"
Expand Down
Loading
Loading