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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
component: [vpc, postgres-instance, app-alb, dummy, github]
component: [network, compute-engine, github]
steps:
- uses: actions/checkout@v6

Expand Down
45 changes: 38 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,51 @@ pins that tag, so this file is the human-readable answer to "what's in v0.2.0?".

## [Unreleased]

> **Cloud pivot:** the org is moving to **GCP**. New components target `hashicorp/google`; the
> AWS modules have been removed (see **Removed** below).

### Added
- `compute-engine` — first GCP compute module (`google_compute_instance`). **Bootstrap-agnostic**:
runs a caller-supplied `startup_script` (userdata) on first boot, `""` = none. The Docker
bootstrap and its on/off switch live in the consuming environment (`infra-environments-dev`),
not in the module (no Ansible, no COS). **No external IP** by default — access is "SSM-like":
**OS Login + IAP TCP forwarding**
(`gcloud compute ssh --tunnel-through-iap`), governed by IAM. Grants `roles/compute.osLogin`
and `roles/iap.tunnelResourceAccessor` to `access_members`. Opts into VPC firewall rules via
`network_tags` (e.g. `[network.ssh_tag]`); empty default = no tag-scoped inbound. Consumes
`network`/`subnetwork` from the `network` component. Built for cheap teardown/redeploy (no deletion protection, boot disk
auto-deletes, `allow_stopping_for_update`), so the VM can be destroyed when idle to save credits.
Outputs `instance_name`, `internal_ip`, `ssh_command`.
- `github` — repository factory component (`integrations/github` provider). Manages GitHub
repos as code via a `repositories` map: visibility, description, topics, default branch, and
optional branch protection. First component requiring a credential (`GITHUB_TOKEN`); intended
to be owned by `infra-environments-dev` only, since repos are org-scoped.

### Changed
- **`network` (replaces AWS `vpc`) — GCP network foundation built on registry modules.** The old
AWS `vpc` (IGW, public/private subnets per AZ) is gone; the new `network` component is a thin
wrapper over the verified CFT modules `terraform-google-modules/network/google` (`~> 18.0`) and
`terraform-google-modules/cloud-router/google` (`~> 9.0`). It creates a custom-mode VPC network
+ one **regional** subnetwork (Private Google Access on), optional **Cloud Router + NAT**
(`enable_cloud_nat`, default `true`) for private-instance egress, and an **allow-IAP-SSH** rule
from `35.235.240.0/20` (`enable_iap_ssh`, default `true`) **scoped by `target_tags` to VMs
wearing the exported `ssh_tag`** (multi-VM ready). Inputs `project_id`, `subnet_cidr`. Outputs
`network_self_link`, `subnetwork_self_link`, `network_name`, `subnetwork_name`, `region`,
`ssh_tag`. Pulls in the `google-beta` provider (required by the network module). Replaces the
hand-written `vpc` rewrite that previously lived on this branch.
- `github` — added per-repo `delete_branch_on_merge` (auto-deletes the head branch on merge,
default `true`) and an org-wide default-team grant: `default_team` (default `engineers`)
is granted `default_team_permission` (default `push`) on every managed repo via
`github_team_repository`; set `default_team = ""` to opt out. Adds a `team_grants` output.
The team grant requires `GITHUB_TOKEN` with `admin:org`.

## [0.3.0] - 2026-06-08

### Added
- `github` — repository factory component (`integrations/github` provider). Manages GitHub
repos as code via a `repositories` map: visibility, description, topics, default branch, and
optional branch protection. First component requiring a credential (`GITHUB_TOKEN`); intended
to be owned by `infra-environments-dev` only, since repos are org-scoped.
### Removed
- **`app-alb` and `postgres-instance` (AWS) — deleted.** Both consumed the old AWS `vpc`
outputs and are not used by any environment (already dropped from `infra-environments-dev`).
Removed as part of the GCP pivot rather than left as dead AWS modules. Recoverable from git
history; will be replaced by GCP equivalents (Cloud Load Balancing / Cloud SQL) if needed.
- **`dummy` — deleted.** The credential-free pipeline-test stub (random/local/null) has served
its purpose now that real GCP components (`vpc`, `compute-engine`) exercise the pipeline.

## [0.2.0] - 2026-06-03

Expand Down
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ and **[CHANGELOG.md](./CHANGELOG.md)** for what changed in each tagged version.

## Components

| Component | Purpose | Key outputs |
| ------------------- | ------------------------------------------------ | --------------------------------------------- |
| `vpc` | Network foundation (VPC, subnets) | `vpc_id`, `subnet_ids_list_by_name` |
| `postgres-instance` | RDS PostgreSQL instance | `database_address`, `database_arn` (+secrets) |
| `app-alb` | Public Application Load Balancer | `alb_dns_name`, `alb_arn`, `target_group_arn` |
| `dummy` | Credential-free CI/CD test (random/local/null) | `pet_name`, `artifact_path` |
| `github` | GitHub repositories as code (repo factory) | `repository_names`, `repository_urls` |

`vpc`, `postgres-instance`, and `app-alb` form a dependency chain:
**`vpc` → `postgres-instance` / `app-alb`**. `dummy` has no dependencies — it exists only to
exercise the pipeline (plan → PR comment → gated apply) without a cloud account, and can be
removed once real components are flowing.
> **Cloud:** GCP (`hashicorp/google`). The original AWS modules have been removed in the GCP
> pivot — see [CHANGELOG.md](./CHANGELOG.md).

| Component | Cloud | Purpose | Key outputs |
| ---------------- | ------ | ------------------------------------------------ | ----------------------------------------------- |
| `network` | GCP | Network foundation — wraps CFT network + cloud-router modules | `network_self_link`, `subnetwork_self_link`, `ssh_tag` |
| `compute-engine` | GCP | VM (bootstrap-agnostic); OS Login + IAP access, no public IP | `instance_name`, `internal_ip`, `ssh_command` |
| `github` | GitHub | GitHub repositories as code (repo factory) | `repository_names`, `repository_urls` |

`network` and `compute-engine` form a dependency chain:
**`network` → `compute-engine`** (the VM attaches to the network/subnetwork the `network` outputs).
`github` is standalone (org-scoped, no network).

## Anatomy of a component

Expand Down Expand Up @@ -77,6 +77,7 @@ process is in [CONVENTIONS.md](./CONVENTIONS.md#versioning--releasing).

## Notes

- All values are placeholders — no real AWS account IDs, credentials, or hostnames.
- All values are placeholders — no real GCP project IDs, credentials, or hostnames.
- Modules are minimal but **valid and applyable** (real resource blocks), so you can grow them.
- A real apply requires AWS credentials and a state backend, both configured in the env repos.
- A real apply requires GCP credentials (a project + enabled APIs) and a state backend, both
configured in the env repos.
26 changes: 0 additions & 26 deletions app-alb/README.md

This file was deleted.

76 changes: 0 additions & 76 deletions app-alb/terraform/main.tf

This file was deleted.

19 changes: 0 additions & 19 deletions app-alb/terraform/outputs.tf

This file was deleted.

36 changes: 0 additions & 36 deletions app-alb/terraform/variables.tf

This file was deleted.

103 changes: 103 additions & 0 deletions compute-engine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# compute-engine

A **GCP Compute Engine VM with Docker** installed on first boot. The VM has **no external IP** —
access follows the same model as AWS SSM Session Manager: **OS Login + IAP TCP forwarding**, where
you connect through Google's infrastructure with your own identity and short-lived,
IAM-governed keys (no public SSH port, no key files to manage).

## What it creates

- `google_compute_instance` — the VM. No external IP by default; `enable-oslogin = TRUE` so SSH
access is governed by IAM, not project metadata keys. Runs the caller-supplied `startup_script`
on first boot (empty string = no bootstrap). **This module is bootstrap-agnostic** — the actual
first-boot script (e.g. installing Docker) is **userdata owned by the consuming environment**,
not baked into the module. See "Bootstrap / userdata" below.
- `google_compute_instance_iam_member` (per member) — grants **`roles/compute.osLogin`** to each
`access_members` principal, letting them log in over SSH.
- `google_iap_tunnel_instance_iam_member` (per member) — grants **`roles/iap.tunnelResourceAccessor`**
to each `access_members` principal, letting them open an IAP tunnel to the VM.

Inbound SSH from the IAP range is allowed by the **`network` component's** `allow-iap-ssh` firewall rule,
and outbound internet (for the Docker install) comes from the `network`'s Cloud NAT — so this module
assumes it is attached to a `network` created by that component (or an equivalent network).

## Access model ("SSM-like")

```
you / CI service account
│ (identity + IAM: compute.osLogin + iap.tunnelResourceAccessor)
IAP ──tunnel──▶ VM:22 (no external IP; firewall allows only 35.235.240.0/20)
```

Connect with:

```bash
gcloud compute ssh <instance-name> \
--zone <zone> --tunnel-through-iap
```

The `ssh_command` output prints this for you. To grant someone access, add their principal to
`access_members` (e.g. `user:alice@officialdad.com`, `serviceAccount:ci@<project>.iam.gserviceaccount.com`)
and re-apply — no keys to distribute, and access is revoked the moment IAM is removed.

## Bootstrap / userdata

The module does **not** know what to install — it just runs whatever `startup_script` string the
caller passes, on first boot, as the instance's `metadata_startup_script`. Pass `""` (the default)
for a plain VM.

The **consuming environment owns the bootstrap**. In `infra-environments-dev` the Docker install
lives as a script file (e.g. `compute-engine/userdata/docker-bootstrap.sh`) and a switch in the
unit's `terragrunt.hcl` decides whether to pass it:

```hcl
locals {
install_docker = true
}
inputs = {
startup_script = local.install_docker ? file("${get_terragrunt_dir()}/userdata/docker-bootstrap.sh") : ""
}
```

This keeps the cookbook module generic (any VM, any bootstrap) and puts the "what runs on boot"
decision where environment-specific choices belong. A Docker bootstrap needs outbound internet
(apt + docker.com), which the `network`'s Cloud NAT provides to this otherwise-private VM.

## Auth

Provider needs `project_id` + credentials (ADC / `GOOGLE_APPLICATION_CREDENTIALS` / CI service
account); region from `var.global.deploy_region`. The principal running `apply` needs rights to
create instances and set IAM on them, and the **OS Login API**, **Compute API**, and **IAP API**
must be enabled on the project.

## Inputs

| Name | Type | Default | Description |
| ------------------- | ------------ | ------------------------ | ------------------------------------------------------------------------ |
| `global` | object | — | Env-wide context (`environment_name`, `deploy_region`, `tags`). |
| `project_id` | string | — | GCP project the VM is created in. |
| `network` | string | — | Network self link / name (from `network.network_self_link`). |
| `subnetwork` | string | — | Subnetwork self link / name (from `network.subnetwork_self_link`). |
| `zone` | string | `""` | Zone for the VM. Empty → `"<deploy_region>-a"`. |
| `machine_type` | string | `e2-micro` | Machine type. |
| `boot_image` | string | `debian-cloud/debian-12` | Boot image (`project/family` or full self link). |
| `boot_disk_size_gb` | number | `20` | Boot disk size in GB. |
| `startup_script` | string | `""` | First-boot script (userdata). Empty = no bootstrap. Supplied by the env. |
| `assign_public_ip` | bool | `false` | Attach an ephemeral external IP. Leave `false` for the IAP-only model. |
| `access_members` | list(string) | `[]` | IAM principals granted OS Login + IAP tunnel access (see access model). |

## Outputs

| Name | Description |
| --------------- | ---------------------------------------------------------------- |
| `instance_name` | The VM name (`<environment_name>-compute-engine`). |
| `instance_id` | The instance ID. |
| `internal_ip` | The VM's internal IP. |
| `zone` | The zone the VM runs in. |
| `ssh_command` | Ready-to-run `gcloud compute ssh ... --tunnel-through-iap` line. |

## Dependencies

Consumes `network` and `subnetwork` from the `network` component. Relies on the `network`'s `allow-iap-ssh`
firewall rule (inbound SSH from IAP) and Cloud NAT (outbound, for the Docker install).
Loading