Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4cd4433
Refactor Terraform configuration to use variables instead of local co…
jesbinjoseph May 11, 2026
6bad773
Add LOCAL_TFVARS_FILE support in Makefiles and update README instruct…
jesbinjoseph May 11, 2026
d9420bd
Add sample.tfvars configuration for environment setup
jesbinjoseph May 11, 2026
9672371
Enhance helm-values.tf with checksums for secrets and config maps
jesbinjoseph May 12, 2026
8898394
Add validation for service_account_email variable in variables.tf
jesbinjoseph May 13, 2026
f5017f8
Add validation for dicom_domain_name when enable_dicom is true
jesbinjoseph May 13, 2026
8de9120
Refactor tfvars-pull.sh to ensure secure file permissions before writ…
jesbinjoseph May 13, 2026
0010891
Update secret manager role description for clarity in github-wif.tf
jesbinjoseph May 13, 2026
f7ba415
Remove -auto-approve flag from tofu apply commands in Makefiles for s…
jesbinjoseph May 13, 2026
31c2e56
Refactor documentation for clarity and consistency across Helm and Te…
jesbinjoseph May 13, 2026
9f2ea83
Refactor Helm and Terraform instructions to clarify value injection p…
jesbinjoseph May 13, 2026
6a01e8a
Fix indentation in tfvars-pull.sh for improved readability
jesbinjoseph May 13, 2026
621d25d
Add maintenance window configuration for GKE cluster
jesbinjoseph May 13, 2026
a58d817
Add update_on_creation_fail to cloudsql_psa resource for improved err…
jesbinjoseph May 13, 2026
bb5d95c
Update maintenance window times for GKE cluster to IST
jesbinjoseph May 13, 2026
392db88
Implement external TLS support with cert-manager integration
jesbinjoseph May 13, 2026
2907396
Enhance Helm linting workflow to target the pull request's base branch
jesbinjoseph May 14, 2026
7cc4ee1
Remove Helm linting workflow and chart-testing configuration
jesbinjoseph May 14, 2026
6ad177c
Add auto-approve flag to Terraform commands in Makefiles
jesbinjoseph May 15, 2026
f8e54b8
Update secret checksums to use nonsensitive function for enhanced sec…
jesbinjoseph May 18, 2026
297d6f5
Enhance documentation for KMS module and TLS integration, including e…
jesbinjoseph May 18, 2026
43c6e10
Refactor local variable for service account roles in GitHub WIF confi…
jesbinjoseph May 18, 2026
f97334c
Remove commented-out local_file resources for generated Helm values
jesbinjoseph May 18, 2026
5962884
Remove unnecessary blank lines in variables.tf
jesbinjoseph May 18, 2026
b8237bf
Refactor SHA-256 checksum functions for improved error handling and c…
jesbinjoseph May 18, 2026
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
85 changes: 85 additions & 0 deletions .github/instructions/helm.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
applyTo: "helm_charts/**"
description: "Helm chart conventions for CARE application Kubernetes deployments"
---

# Helm Chart Conventions

## Chart Structure

Each chart under `helm_charts/` contains:

| File | Purpose |
|------|---------|
| `Chart.yaml` | Chart metadata |
| `values.yaml` | Default values (overridden by Terraform-generated values) |
| `templates/_helpers.tpl` | Shared naming and label helpers |
| `templates/*.yaml` | Workload, service, and routing templates |

Current charts: `gateway`, `redis`, `metabase`, `care_be`, `care_fe`, `dcm4chee`.

## Common Helpers

All charts implement an identical set of helper templates in `_helpers.tpl`:

| Helper | Purpose |
|--------|---------|
| `CHART.name` | Chart name; respects `nameOverride`; truncated to 63 characters |
| `CHART.fullname` | Includes release name; respects `fullnameOverride` |
| `CHART.chart` | `{Chart.Name}-{Chart.Version}` with `+` replaced by `_` |
| `CHART.labels` | Standard Kubernetes labels (chart, selector, version, managed-by) |
| `CHART.selectorLabels` | `app.kubernetes.io/name` and `app.kubernetes.io/instance` |
| `CHART.serviceAccountName` | Conditional on `serviceAccount.create`; falls back to `default` |

When creating a new chart, copy `_helpers.tpl` from an existing chart and update the template name prefix.

## Deployment Patterns

- Use checksum annotations for ConfigMap/Secret-driven rolling updates:
```yaml
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
```
- Define resource requests and limits explicitly in `values.yaml`.
- Define liveness and readiness probes explicitly.

## Global Values Contract

Terraform injects a `global` block via `common_helm_values` in `deploy/locals.tf`:

```yaml
global:
cloudProvider: gcp
namespace: <namespace>
gateway:
name: care-regional-gateway
namespace: <namespace>
gatewayClassName: gke-l7-regional-external-managed
backendPolicy:
enabled: true
securityPolicy: <cloud-armor-policy-name>
httpRoute:
enabled: true
```

All charts should reference `global.*` for gateway routing and backend policy configuration.

## Value Injection from Terraform

- Chart-specific values are defined as locals in `deploy/helm-values.tf` and passed directly to `helm_release` resources via `yamlencode()`. File-based generation under `deploy/generated_values/` is currently disabled.
- Image configuration originates from `var.helm_config.<chart_key>` with `repository` and `tag` keys.
- Domain hostnames for HTTPRoute come from `var.web_domain_name`, `var.api_domain_name`, etc.
- Secret references use `envFromSecret` pointing to Kubernetes secrets created in `deploy/secrets.tf`.

## Routing

- Gateway API resources (HTTPRoute, Gateway) are the default routing mechanism.
- Legacy GCE Ingress is optional, controlled by the `enable_legacy_ingress` feature flag in Terraform.
- Each chart with external access includes `httproute.yaml` and `backend-policy.yaml` templates.

## Adding a New Chart

1. Create `helm_charts/<name>/` with `Chart.yaml`, `values.yaml`, and `templates/`.
2. Copy `_helpers.tpl` from an existing chart and update the template name prefix.
3. Define chart-specific values as a local in `deploy/helm-values.tf`.
4. Add a `helm_release` resource in `deploy/helm.tf`, merging `common_helm_values` via `yamlencode()`.
5. Add image configuration to the `helm_config` variable and `environments/sample.tfvars`.
107 changes: 107 additions & 0 deletions .github/instructions/terraform.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
applyTo: "**/*.tf"
description: "Terraform/OpenTofu conventions for this GCP infrastructure project"
---

# Terraform Conventions

## Module Structure

Modules are applied in the following order:

| Module | Purpose |
|--------|---------|
| `pre-infra/` | Bootstrap (APIs, optional DNS zone) |
| `infra/` | VPC, GKE, Cloud SQL, GCS, Cloud Armor, GitHub WIF |
| `KMS/` | Key ring and encryption keys |
| `deploy/` | Namespace, secrets, Helm releases |

Each module typically contains:

| File | Purpose |
|------|---------|
| `init.tf` | Providers, backend, remote state data sources |
| `variables.tf` | Symlink to root shared variable contract (edit root only) |
| `locals.tf` | Derived values, naming, secret composition |
| `outputs.tf` | Exported values for downstream modules |
| Resource files | Grouped by domain (`network.tf`, `cloud-sql.tf`, `helm.tf`, etc.) |

## Provider Versions

All modules pin: `google`/`google-beta` `~> 6.33`, `random ~> 3.7`, OpenTofu `~> 1.11`.

The `deploy/` module additionally requires: `kubernetes ~> 2.0`, `helm ~> 2.0`, `tls ~> 4.0`, `local ~> 2.0`.

## Configuration Pattern

- Modules consume tfvars values directly via `var.*`.
- Runtime tfvars file path: `../environments/<env>.tfvars`.
- Secret Manager naming convention: `tofu-tfvars-<env>`.
- Do not introduce `local.cfg` or JSON decode flows for configuration.

## Naming Overrides

Use the coalesce pattern for optional naming overrides:

```hcl
name = coalesce(var.cluster_name, "${var.org}-${var.app}-${var.environment}")
```

## Feature Flags

Gate resources using boolean variables with `count` or `for_each`:

```hcl
count = var.enable_dicom ? 1 : 0
```

Current flags: `enable_dicom`, `enable_cloud_armor`, `enable_github_wif`, `enable_legacy_ingress`, `enable_dns_zone`.

## Cross-Module References

The `deploy/init.tf` file reads remote state from:

| Source | Prefix | Contents |
|--------|--------|----------|
| `infra` | `infra` | Network, cluster, database, platform outputs |
| `keys` | `keys` | KMS key outputs |

Access pattern: `data.terraform_remote_state.infra.outputs.<key>`.

## State Backend

GCS backend with the following prefixes:

| Module | Prefix |
|--------|--------|
| `pre-infra/` | `pre-infra` |
| `infra/` | `infra` |
| `KMS/` | `keys` |
| `deploy/` | `deploy-backend` |

The `deploy/` module runs `tofu plan` with `-lock=false`. All other modules lock normally.

## Adding Secrets

1. Add the key-value pair to `local.secret_data` (or `local.metabase_secret_data` / `local.dicom_secret_data`) in `deploy/locals.tf`.
2. The corresponding `kubernetes_secret` in `deploy/secrets.tf` reads from that map automatically.
3. If the secret value originates from infrastructure, ensure the upstream module exports it in `outputs.tf`.

## Adding Helm Charts

1. Create a chart under `helm_charts/<name>/` following existing chart patterns.
2. Define chart-specific values as a local in `deploy/helm-values.tf`.
3. Add a `helm_release` resource in `deploy/helm.tf`, merging `common_helm_values` with chart-specific values via `yamlencode()`.
4. Image configuration comes from `var.helm_config.<chart_key>` (add to `helm_config` variable and `sample.tfvars`).

## Deploy-Specific Variables

All variables — including deploy-focused ones such as `helm_config`, `additional_secrets`, `additional_config_map_data`, `enable_legacy_ingress`, `jwks_base64`, `namespace_name`, and resource name overrides — are defined in the root `variables.tf` (symlinked into every module). Refer to `environments/sample.tfvars` for the complete shape.

## tfvars Workflow

All modules support Makefile targets: `pull-tfvars`, `push-tfvars`, `plan`, `deploy`.

Helper scripts:
- `scripts/tfvars-pull.sh` — Pulls tfvars from Secret Manager.
- `scripts/tfvars-push.sh` — Pushes tfvars and verifies SHA256 integrity.
63 changes: 0 additions & 63 deletions .github/workflows/helm-lint-test.yml

This file was deleted.

126 changes: 126 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Project Guidelines

## Overview

This repository contains GCP infrastructure-as-code for [CARE](README.md), built with OpenTofu and Helm on Google Kubernetes Engine (GKE).

## Architecture

Modules must be applied in the following order:

| Order | Module | Purpose |
|-------|--------|---------|
| 1 | `pre-infra/` | Project bootstrap: API enablement, optional DNS zone |
| 2 | `infra/` | VPC, GKE, Cloud SQL, GCS buckets, Cloud Armor, GitHub WIF |
| 3 | `KMS/` | Key ring and encryption keys |
| 4 | `deploy/` | Kubernetes namespace, secrets, Helm releases |

The `deploy/` module reads remote state from `infra` (prefix `infra`) and `KMS` (prefix `keys`) via `terraform_remote_state` data sources in `deploy/init.tf`.

## Build and Deploy

Each module directory contains a Makefile with the following targets:

| Target | Description |
|--------|-------------|
| `make init` | Initialize OpenTofu with GCS backend |
| `make pull-tfvars` | Pull tfvars from Secret Manager |
| `make plan` | Generate an execution plan |
| `make deploy` | Apply infrastructure changes |
| `make destroy` | Tear down resources |
| `make lint` | Format files recursively |
| `make push-tfvars` | Push local tfvars to Secret Manager |

### Required Environment Variables

Set the following before running any target:

- `PROJECT_ID` (or `TF_VAR_project_id`)
- `ENV_NAME` (or `TF_VAR_environment` / `TF_VAR_env_name`)
- `BACKEND_BUCKET`

### State Backend Prefixes

| Module | Prefix |
|--------|--------|
| `pre-infra/` | `pre-infra` |
| `infra/` | `infra` |
| `KMS/` | `keys` |
| `deploy/` | `deploy-backend` |

> The `deploy/` module runs `tofu plan` with `-lock=false`. All other modules use normal locking.

## Configuration

All configuration is driven by tfvars files. See [environments/sample.tfvars](environments/sample.tfvars) for the complete variable shape.

- Real tfvars are stored in Secret Manager under the name `tofu-tfvars-<env>`.
- The `make pull-tfvars` target retrieves them to `../environments/<env>.tfvars`.
- Real tfvars must never be committed to the repository.

## Conventions

### Naming

Resource names follow the pattern `{org}-{app}-{environment}` with resource-specific suffixes. Any derived name can be overridden using the `coalesce(var.override, derived_default)` pattern.

### Shared Variables

The root `variables.tf` is symlinked into each module directory. Do not create separate copies. All variables, including deploy-specific ones (`helm_config`, `additional_secrets`, `additional_config_map_data`, `enable_legacy_ingress`), are defined in this single file.

### Naming Overrides

The following optional variables override auto-derived resource names. All default to `null`:

`cluster_name`, `namespace_name`, `vpc_network_name`, `database_subnet_name`, `gke_subnet_name`, `pods_range_name`, `services_range_name`, `gateway_ip_name`, `legacy_ingress_ip_name`, `legacy_fe_ip_name`, `flow_logs_bucket`, `cloudsql_private_ip_name`, `nat_ip_address_name`

### Feature Flags

Boolean variables control optional infrastructure with `count` or `for_each`:

| Flag | Controls |
|------|----------|
| `enable_dicom` | DICOM stack (bucket, database, dcm4chee chart) |
| `enable_cloud_armor` | Cloud Armor security policies |
| `enable_github_wif` | GitHub Actions Workload Identity Federation |
| `enable_legacy_ingress` | Legacy GCE Ingress resources |
| `enable_dns_zone` | Cloud DNS managed zone |

### Provider Versions

All modules pin: `google`/`google-beta` `~> 6.33`, `random ~> 3.7`, OpenTofu `~> 1.11`.

The `deploy/` module additionally requires: `kubernetes ~> 2.0`, `helm ~> 2.0`, `tls ~> 4.0`, `local ~> 2.0`.

### Helm Value Injection

Helm values are defined as locals in `deploy/helm-values.tf` and passed directly to `helm_release` resources in `deploy/helm.tf` via `yamlencode()`. Chart-specific values are merged with `common_helm_values` (defined in `deploy/locals.tf`) at release time. File-based value generation under `deploy/generated_values/` is currently disabled.

Local charts: `gateway`, `redis`, `metabase`, `care_be`, `care_fe`, `dcm4chee`.

Additionally, `cert-manager` is installed from the Jetstack Helm repository as a dependency for TLS and Gateway API integration.

### Helm Charts

Charts are located under `helm_charts/`. Refer to [.github/instructions/helm.instructions.md](.github/instructions/helm.instructions.md) for detailed conventions. All charts share an identical `_helpers.tpl` pattern for naming, labels, and service account helpers.

## Infrastructure Components

| Component | Description |
|-----------|-------------|
| **GKE** | Regional cluster with Gateway API, Workload Identity (`terraform-google-modules/kubernetes-engine/google` ~> 36.3) |
| **Cloud SQL** | Two PostgreSQL 17 Enterprise instances (primary + Metabase), private IP, optional read replicas |
| **GCS Buckets** | Three CMEK-encrypted buckets (patient, facility, DICOM) with HMAC access |
| **Cloud Armor** | Regional security policy with OWASP rules and geo-blocking |
| **Jumphost** | Debian 13 VM with OpenTofu pre-installed (`infra/jumphost.tf`) |
| **GitHub WIF** | Workload Identity Federation for GitHub Actions CI/CD |

## Pitfalls

- Module apply order is strict. Applying out of order will fail.
- Never commit real tfvars files. Store them in Secret Manager.
- Ensure correct value types in tfvars: numbers as numbers, booleans as booleans.
- The `variables.tf` files in module directories are symlinks. Edit only the root copy.
- The `deploy/` module authenticates via `data.google_client_config` access token. Valid GCP credentials are required.
- To add new secrets, update `local.secret_data` in `deploy/locals.tf`. The `kubernetes_secret` in `deploy/secrets.tf` reads from that map automatically.
- `additional_config_map_data` injects entries into the backend ConfigMap. `additional_secrets` injects entries into the Kubernetes Secret.
Loading