pgrator

module
v0.0.0-...-a06bb16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: MIT

README

pgrator

Kubernetes operator for the nais platform that manages Postgres, Valkey, and OpenSearch resources. It reconciles opinionated nais CRDs into the resources needed to run these services on GCP.

Managed resources

CRD API group Backend Creates
Postgres nais.io/v1 CloudNativePG Logical database; creates the default PostgresBranch
PostgresBranch nais.io/v1 CloudNativePG Independent CNPG Cluster, Pooler, network and optional WAL archive/backup resources
PostgresBinding nais.io/v1 CloudNativePG Workload database roles, connection/certificate Secrets and NetworkPolicies
PostgresAccess nais.io/v1 CloudNativePG Time-limited personal role, credentials, relay mapping and database ingress policy
Valkey nais.io/v1 Aiven Aiven Valkey instance + ServiceIntegration (metrics)
OpenSearch nais.io/v1 Aiven Aiven OpenSearch instance + ServiceIntegration (metrics)

Getting started

Prerequisites
  • mise — manages all tool versions and tasks
  • Docker — for building images
  • A Kubernetes cluster (for e2e tests)
Setup
# Install all tools (Go, golangci-lint, controller-gen, etc.)
mise install

# Run all checks and tests
mise run all

# Run unit and integration tests in both Go modules
mise run test

# Run only linting
mise run check:lint
Available tasks

Use mise tasks to get a list of available tasks, with descriptions

Git hooks

Lefthook is configured for pre-commit (fmt, lint, vet, generate check) and pre-push (tests). Install hooks with:

lefthook install

Architecture

See ARCHITECTURE.md for detailed information about project structure, conventions, and design patterns.

Postgres

Postgres is the logical database. Pgrator creates its default main PostgresBranch; each branch is an independent, writable CNPG cluster with its own data history. A recovery branch uses an immutable bootstrap.recovery.sourceBranch (local name) and UTC targetTime to restore from another branch's archive. Recovery does not merge data or switch the active branch.

Branch names are local to a Postgres: Postgres.spec.activeBranch, status.activeBranch and recovery sourceBranch use local names. Kubernetes PostgresBranch.metadata.name is a deterministic, hashed object name from v1.PostgresBranchObjectName(postgres, branch); spec.postgres and spec.branchName must match and are immutable. PostgresAccess.spec.postgresBranch refers to this object name, not the local name. See ADR 0007.

spec.activeBranch requests a selection; status.activeBranch records the branch pgrator has selected. Without an explicit request, main is selected initially and an existing selection is retained. For an explicit request, pgrator checks that the branch identity matches and neither it nor its CNPG cluster is terminating, and that the cluster is initialized and has a Ready condition. An unready request fails reconciliation instead of updating observed status. Do not treat that status gate as an atomic cutover guarantee: binding reconciliation also reads the requested spec, and admission and reconciliation are not atomic with branch deletion. Verify dependent bindings and connections during activation.

PostgresBinding supplies workloads with a stable logical connection Secret for the selected branch. PostgresAccess selects one branch independently of the active workload branch. It creates a short-lived password, a CNPG DatabaseRole (PostgreSQL role name is the authenticated email), an owned RelayAccess and token Secret, and a database-ingress policy. Its Ready condition requires the role applied at the current generation, a persisted token and the owned relay mapping's published endpoint; Ready does not establish SQL connectivity. The relay operator publishes the public endpoint after persisting its egress policy, and pgrator copies that endpoint into PostgresAccess.status.relayEndpoint for the API's owner-only connection response. Expiry removes access resources while CNPG's ReclaimPolicy: Retain preserves the PostgreSQL role and its objects. Existing roles created under older naming are not migrated or re-owned automatically. See ADR 0005 for identity history and ADR 0006 for relay transport.

The generated CRD documentation describes public fields; PostgresBranch is currently an internal API kind, not a user-authored manifest.

CI/CD

GitHub Actions runs the repository's mise checks and tests and publishes image and Helm chart artifacts for non-Dependabot branch pushes. Deployment via Fasit remains restricted to main.

Pull requests also run E2E tests in a kind cluster using Chainsaw, via the mise run test:ci task.

Contributing

Development workflow
  1. Install tools — mise install sets up Go, golangci-lint, controller-gen, helm, and all other dependencies at pinned versions.
  2. Make changes — edit code, CRD types, or Helm chart.
  3. Regenerate — if you changed types in pkg/api/, run mise run generate to update CRDs and DeepCopy methods.
  4. Test locally — mise run test runs standard Go tests in both the root and pkg/api modules. Controller integration tests use envtest (a real API server + etcd, no cluster needed).
  5. Commit — lefthook pre-commit hooks run fmt, lint, vet, and generate-check automatically.
  6. Push — pre-push hook runs tests. CI runs the full matrix in parallel.
Running operator in local cluster
  • mise run dev:setup-cluster to start a local cluster.
  • mise run dev:tilt to use tilt to install dependencies into the cluster, and build and install the operator.
  • mise run test:e2e to run E2E tests in the local cluster.
  • mise run dev:stop-cluster to stop the cluster.

The cluster uses kind by default, but can be changed to any cluster engine supported by ctlptl. To select a different engine, create a local mise config, overriding the env-variable DEV_CLUSTER_ENGINE with a ctlptl product name.

Adding a new golden test case

Golden tests are data-driven: each test case is a directory under internal/controller/testdata/{resource}/{case-name}/:

my-test-case/
├── object.yaml           # Input CRD spec to reconcile
├── prepared_data.yaml    # Optional: set engine, projectID, etc.
├── related_objects/      # Optional: pre-existing objects in cluster
├── contains/             # Assert actions contain at least these (use for partial checks)
│   └── cluster.yaml
└── consists_of/          # Assert actions match exactly these (use for full coverage)
    └── cluster.yaml

Each expected file in contains/ or consists_of/ specifies:

  • action: the concrete action type, for example create, createOrUpdate, or exclusiveCreateOrUpdate
  • matcher: Equal (exact match) or Subset (only specified fields must match)
  • object: the expected Kubernetes resource
Writing Go tests

Use the standard library testing package. Prefer table-driven tests with t.Run when several cases exercise the same contract; use a focused TestXxx function when setup or behavior differs materially. Test observable behavior, not implementation branches. Golden fixtures are the preferred contract tests for reconciler output.

Running tests
mise run test          # Root + pkg/api Go tests; sets up envtest automatically
mise run test:e2e      # E2E tests (requires running mise run dev:tilt in a separate terminal)
mise run test:ci       # Starts a cluster, runs E2E tests

go test ./... can be used for a single module when KUBEBUILDER_ASSETS is already configured. The mise task is the authoritative full test command because Go does not traverse into the nested pkg/api module automatically.

Code generation

After modifying types in pkg/api/ or RBAC markers (+kubebuilder:rbac):

mise run generate          # Regenerate CRDs + DeepCopy + copy to Helm chart
mise run generate-check    # Verify nothing is out of date (CI runs this)

Some code generated with GitHub Copilot

This repository occasionally uses GitHub Copilot to generate code.

Directories

Path Synopsis
cmd
docgen command
internal
resourcecreator/access
Package access builds resources for personal Postgres access.
Package access builds resources for personal Postgres access.
resourcecreator/binding
Package binding builds pgrator-owned workload binding resources.
Package binding builds pgrator-owned workload binding resources.
resourcecreator/cnpg
Package cnpg builds CloudNativePG resources (Cluster, DatabaseRole, Pooler, ScheduledBackup) for the nais.io/v1 Postgres type.
Package cnpg builds CloudNativePG resources (Cluster, DatabaseRole, Pooler, ScheduledBackup) for the nais.io/v1 Postgres type.
resourcecreator/fqdnpolicy
Package fqdnpolicy builds the FQDNNetworkPolicy needed by CNPG's WAL archiver.
Package fqdnpolicy builds the FQDNNetworkPolicy needed by CNPG's WAL archiver.
resourcecreator/iam
Package iam builds the Google IAM resources (via Config Connector) that let a CloudNativePG cluster authenticate to its WAL bucket through GKE Workload Identity.
Package iam builds the Google IAM resources (via Config Connector) that let a CloudNativePG cluster authenticate to its WAL bucket through GKE Workload Identity.
resourcecreator/netpol
Package netpol builds the NetworkPolicy that isolates a CNPG Postgres cluster, allowing intra-cluster traffic, the CloudNativePG operator, and metrics scraping.
Package netpol builds the NetworkPolicy that isolates a CNPG Postgres cluster, allowing intra-cluster traffic, the CloudNativePG operator, and metrics scraping.
resourcecreator/storage
Package storage builds the WAL archive storage for a CloudNativePG cluster: the Google Cloud Storage bucket (via Config Connector) and the barman-cloud ObjectStore that CloudNativePG's WAL archiver plugin points at.
Package storage builds the WAL archive storage for a CloudNativePG cluster: the Google Cloud Storage bucket (via Config Connector) and the barman-cloud ObjectStore that CloudNativePG's WAL archiver plugin points at.
thirdparty/aiven/v1alpha1
Package aiven_v1alpha1 contains API Schema definitions for the aiven.io v1alpha1 API group +kubebuilder:object:generate=true +kubebuilder:skip
Package aiven_v1alpha1 contains API Schema definitions for the aiven.io v1alpha1 API group +kubebuilder:object:generate=true +kubebuilder:skip
thirdparty/google/iam/v1beta1
Package v1beta1 contains API Schema definitions for the iam.cnrm.cloud.google.com v1beta1 API group +kubebuilder:object:generate=true +kubebuilder:skip
Package v1beta1 contains API Schema definitions for the iam.cnrm.cloud.google.com v1beta1 API group +kubebuilder:object:generate=true +kubebuilder:skip
thirdparty/google/networking/v1alpha3
Package v1alpha3 contains the API types used by Google's FQDNNetworkPolicy.
Package v1alpha3 contains the API types used by Google's FQDNNetworkPolicy.
thirdparty/google/storage/v1beta1
+kubebuilder:object:generate=true +groupName=storage.cnrm.cloud.google.com +versionName=v1beta1
+kubebuilder:object:generate=true +groupName=storage.cnrm.cloud.google.com +versionName=v1beta1
pkg
api module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL