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
81 changes: 81 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Manual Cloud Deploy Workflows

This repo uses three manual deployment workflows and one reusable validation workflow:

- `deploy-aws-rds.yml`
- `deploy-gcp-postgres.yml`
- `azure-postgres-opentofu.yml`
- `managed-db-validate.yml` (reusable via `workflow_call`)

Deploy workflows are run manually from the Actions tab.

AWS and GCP also support trusted PR comment triggers:

- AWS: `/deploy-aws-rds [target|command] [command]`
- GCP: `/deploy-gcp-pg [target|command] [command]`

## Deploy Inputs

AWS and GCP workflows support `target` (`pg15`-`pg18` or `all`) and `command` (`plan`, `apply`, `destroy`).

Azure workflow supports:

- `action`: `plan`, `apply`, `destroy`
- `postgres_version`: `pg15`, `pg16`, `pg17`, `pg18`
- `personal_ip`: optional (falls back to secret)

## Secrets

### AWS

- `AWS_ACCESS_KEY_ID`
- `AWS_SECRET_ACCESS_KEY`
- `AWS_ALLOWED_CIDR_BLOCK`
- `AWS_DB_PASSWORD`

### GCP

- `GCP_SA_KEY`
- `DEPLOY_PERSONAL_IP_CIDR` (unless provided as workflow input)
- `GCP_DB_PASSWORD`

### Azure

- OIDC: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID`
- or service principal JSON: `AZURE_CREDENTIALS`
- `AZURE_PERSONAL_IP` (unless provided as workflow input)
- `AZURE_DB_PASSWORD`

### Shared deploy controls

- `DEPLOY_TRIGGER_USER` (used by AWS/GCP manual and comment-triggered deploy checks)

## Validation Workflow

`managed-db-validate.yml` installs `pgFirstAid.sql`, recreates `view_pgFirstAid_managed.sql`, and runs integration tests (including pgTAP coverage through the integration test harness).

It supports three connection modes:

- `direct`: caller passes `pg_host`
- `aws`: resolves host from `aws_db_identifier`
- `gcp`: resolves host from `gcp_project_id` + `gcp_instance_name`

Current wiring:

- Azure apply calls `managed-db-validate.yml` automatically after deploy.
- AWS apply calls `managed-db-validate.yml` for each selected version after deploy.
- GCP apply calls `managed-db-validate.yml` for each selected version after deploy.

## Secret Handling

- DB passwords are passed to OpenTofu as `TF_VAR_db_password`.
- Password variables in the OpenTofu stacks are marked `sensitive = true`.
- Workflows use step-level environment variables and masking for secret values used in shell steps.
- Avoid printing secret values in custom debug statements.

## Recommended Run Order

1. Run `plan`
2. Run `apply`
3. Confirm validation results
4. Run `destroy` when done with test resources
154 changes: 154 additions & 0 deletions .github/workflows/azure-postgres-opentofu.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
name: Azure PostgreSQL OpenTofu

on:
workflow_dispatch:
inputs:
action:
description: "OpenTofu action"
required: true
type: choice
default: plan
options:
- plan
- apply
- destroy
postgres_version:
description: "Target PostgreSQL version"
required: true
type: choice
default: pg18
options:
- pg15
- pg16
- pg17
- pg18
personal_ip:
description: "IP allowed to connect (example: 203.0.113.10). Leave blank to use AZURE_PERSONAL_IP secret."
required: false
type: string

concurrency:
group: azure-postgres-${{ inputs.postgres_version }}
cancel-in-progress: false

jobs:
opentofu:
name: ${{ inputs.action }} ${{ inputs.postgres_version }}
runs-on: [self-hosted, linux, pgfirstaid-ci]
outputs:
pg_host: ${{ steps.capture_connection.outputs.pg_host }}
pg_port: ${{ steps.capture_connection.outputs.pg_port }}
pg_user: ${{ steps.capture_connection.outputs.pg_user }}
pg_database: ${{ steps.capture_connection.outputs.pg_database }}
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: testing/azure/deploy/${{ inputs.postgres_version }}

env:
TF_IN_AUTOMATION: "true"

steps:
- name: Checkout
uses: actions/checkout@v4
with:
clean: false

- name: Azure login (OIDC)
if: ${{ secrets.AZURE_CLIENT_ID != '' && secrets.AZURE_TENANT_ID != '' && secrets.AZURE_SUBSCRIPTION_ID != '' }}
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Azure login (service principal JSON)
if: ${{ !(secrets.AZURE_CLIENT_ID != '' && secrets.AZURE_TENANT_ID != '' && secrets.AZURE_SUBSCRIPTION_ID != '') }}
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Setup OpenTofu
uses: opentofu/setup-opentofu@v1

- name: Resolve personal IP
shell: bash
env:
AZURE_PERSONAL_IP: ${{ secrets.AZURE_PERSONAL_IP }}
run: |
PERSONAL_IP="${{ inputs.personal_ip }}"
if [ -z "$PERSONAL_IP" ]; then
PERSONAL_IP="$AZURE_PERSONAL_IP"
fi

if [ -z "$PERSONAL_IP" ]; then
echo "::error::No personal IP provided. Set input 'personal_ip' or secret 'AZURE_PERSONAL_IP'."
exit 1
fi

echo "TF_VAR_personal_ip=$PERSONAL_IP" >> "$GITHUB_ENV"

- name: Resolve DB password
shell: bash
env:
AZURE_DB_PASSWORD: ${{ secrets.AZURE_DB_PASSWORD }}
run: |
DB_PASSWORD="$AZURE_DB_PASSWORD"

if [ -z "$DB_PASSWORD" ]; then
echo "::error::Missing secret 'AZURE_DB_PASSWORD'."
exit 1
fi

echo "::add-mask::$DB_PASSWORD"
echo "TF_VAR_db_password=$DB_PASSWORD" >> "$GITHUB_ENV"

- name: OpenTofu init
run: tofu init -input=false

- name: OpenTofu validate
run: tofu validate

- name: OpenTofu plan
if: ${{ inputs.action == 'plan' || inputs.action == 'apply' }}
run: tofu plan -input=false -out=tfplan

- name: OpenTofu apply
if: ${{ inputs.action == 'apply' }}
run: tofu apply -input=false -auto-approve tfplan

- name: Show connection details
if: ${{ inputs.action == 'apply' }}
run: |
echo "Server: $(tofu output -raw server_name)"
echo "FQDN: $(tofu output -raw server_fqdn)"
echo "Database: $(tofu output -raw database_name)"

- name: Capture connection outputs
id: capture_connection
if: ${{ inputs.action == 'apply' }}
run: |
echo "pg_host=$(tofu output -raw server_fqdn)" >> "$GITHUB_OUTPUT"
echo "pg_port=5432" >> "$GITHUB_OUTPUT"
echo "pg_user=$(tofu output -raw db_user)" >> "$GITHUB_OUTPUT"
echo "pg_database=$(tofu output -raw database_name)" >> "$GITHUB_OUTPUT"

- name: OpenTofu destroy
if: ${{ inputs.action == 'destroy' }}
run: tofu destroy -input=false -auto-approve

validate:
if: ${{ inputs.action == 'apply' }}
needs: opentofu
uses: ./.github/workflows/managed-db-validate.yml
with:
pg_host: ${{ needs.opentofu.outputs.pg_host }}
pg_port: ${{ needs.opentofu.outputs.pg_port }}
pg_user: ${{ needs.opentofu.outputs.pg_user }}
pg_database: ${{ needs.opentofu.outputs.pg_database }}
pg_sslmode: require
test_view_mode: managed
secrets:
pg_password: ${{ secrets.AZURE_DB_PASSWORD }}
Loading