Skip to content
Open
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
37 changes: 27 additions & 10 deletions gh-actions/infra/backup-database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@ authenticate to JAAS beforehand.

## Inputs

| Input | Required | Default | Description |
| ------------- | -------- | --------------- | -------------------------------------------------- |
| `model` | yes | | Juju model name |
| `model-owner` | no | | Juju model owner, for models owned by another user |
| `application` | yes | | Application whose units will be considered |
| `action` | no | `create-backup` | Juju action name |
| `parameters` | no | `{}` | JSON object containing action parameters |
| `unit-role` | no | `non-primary` | `non-primary`, `primary`, or `any` |
| `timeout` | no | `6h` | Duration passed to Juju's `--wait`, e.g. `30m` |
| `dry-run` | no | `false` | Skip backup creation but still list backups |
| Input | Required | Default | Description |
| ------------------------- | -------- | --------------- | -------------------------------------------------- |
| `model` | yes | | Juju model name |
| `model-owner` | no | | Juju model owner, for models owned by another user |
| `application` | yes | | Application whose units will be considered |
| `action` | no | `create-backup` | Juju action name |
| `parameters` | no | `{}` | JSON object containing action parameters |
| `unit-role` | no | `non-primary` | `non-primary`, `primary`, or `any` |
| `timeout` | no | `6h` | Duration passed to Juju's `--wait`, e.g. `30m` |
| `credential-usernames` | no | | Comma-separated database usernames to back up |
| `credentials-secret-path` | no | see below | Vault KV path under the `secret` mount |
| `vault-addr` | no | | Vault URL; required for credential backup |
| `vault-token` | no | | Vault token; required for credential backup |
| `dry-run` | no | `false` | Skip backup creation but still list backups |

Only `model` and `application` are strictly required. `model-owner` is only
needed when targeting a model owned by another user.
Expand All @@ -32,6 +36,16 @@ eligible non-primary unit is selected, falling back to the primary with a
degraded warning when no eligible replica remains. `any` skips role discovery.
Excluded units and degraded fallbacks are noted in the job summary.

## Credential backup

When `credential-usernames` is set, the action also retrieves each listed
user's password with the charm's `get-password` action and stores it in Vault,
updating the secret only when a value has changed. Credentials are stored at
`secret/services/{model}/{application}-credentials`, overridable with
`credentials-secret-path`. Retrieved passwords are masked in the logs and never
printed. During a dry run, credentials are retrieved and compared but Vault is
not written.

## Usage

```yaml
Expand All @@ -52,6 +66,9 @@ steps:
model: example-model
model-owner: ${{ vars.JUJU_MODEL_OWNER }}
application: database
credential-usernames: charmed-replication
vault-addr: ${{ vars.VAULT_ADDR }}
vault-token: ${{ steps.credentials.outputs.vault_token }}
dry-run: "true"
```

Expand Down
74 changes: 72 additions & 2 deletions gh-actions/infra/backup-database/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ inputs:
description: Duration passed to Juju's --wait option, e.g. 30m or 6h
required: false
default: 6h
credential-usernames:
description: Comma-separated database usernames whose credentials should be backed up
required: false
default: ""
credentials-secret-path:
description: Vault KV path under the secret mount for database credentials
required: false
default: ""
vault-addr:
description: Vault server address, required when credential-usernames is set
required: false
default: ""
vault-token:
description: Vault token, required when credential-usernames is set
required: false
default: ""
dry-run:
description: Validate model access and unit selection without running the action
required: false
Expand All @@ -35,6 +51,41 @@ inputs:
runs:
using: composite
steps:
- name: Install Vault
if: ${{ inputs.credential-usernames != '' }}
shell: bash
run: |
set -eo pipefail
if ! snap list vault >/dev/null 2>&1; then
sudo snap install vault
fi
vault version

- name: Prepare credential path
id: prepare
if: ${{ inputs.credential-usernames != '' }}
shell: bash
env:
APPLICATION: ${{ inputs.application }}
CREDENTIALS_SECRET_PATH: ${{ inputs.credentials-secret-path }}
MODEL: ${{ inputs.model }}
run: |
set -eo pipefail
credentials_path="${CREDENTIALS_SECRET_PATH:-services/${MODEL}/${APPLICATION}-credentials}"
echo "credentials-path=${credentials_path}" >> "${GITHUB_OUTPUT}"

- name: Fetch current cluster credentials
id: cluster-credentials
if: ${{ inputs.credential-usernames != '' }}
uses: hashicorp/vault-action@v4
with:
url: ${{ inputs.vault-addr }}
method: token
token: ${{ inputs.vault-token }}
exportEnv: false
ignoreNotFound: true
secrets: secret/data/${{ steps.prepare.outputs.credentials-path }} ** | CURRENT_

- name: Run database backup
id: database-backup
shell: bash
Expand All @@ -49,18 +100,37 @@ runs:
DRY_RUN: ${{ inputs.dry-run }}
run: python3 "${{ github.action_path }}/backup.py"

- name: Back up database credentials
id: database-credentials
if: ${{ always() && inputs.credential-usernames != '' }}
shell: bash
env:
MODEL: ${{ inputs.model }}
MODEL_OWNER: ${{ inputs.model-owner }}
APPLICATION: ${{ inputs.application }}
TIMEOUT: ${{ inputs.timeout }}
CREDENTIAL_USERNAMES: ${{ inputs.credential-usernames }}
CREDENTIALS_SECRET_PATH: ${{ inputs.credentials-secret-path }}
DRY_RUN: ${{ inputs.dry-run }}
VAULT_ADDR: ${{ inputs.vault-addr }}
VAULT_TOKEN: ${{ inputs.vault-token }}
VAULT_CREDENTIAL_OUTPUTS_JSON: ${{ toJSON(steps.cluster-credentials.outputs) }}
run: python3 "${{ github.action_path }}/backup_credentials.py"

- name: Write job summary
if: always()
shell: bash
env:
BACKUP_RESULT: ${{ steps.database-backup.outputs.result }}
CREDENTIAL_RESULT: ${{ steps.database-credentials.outputs.result }}
run: |
set -eo pipefail
[[ -n "${BACKUP_RESULT}" ]] || exit 0
[[ -n "${BACKUP_RESULT}${CREDENTIAL_RESULT}" ]] || exit 0
{
echo "## Database backup"
echo
echo "| Operation | Target | Unit | Result | Notes |"
echo "| --------- | ------ | ---- | ------ | ----- |"
jq -Rr 'select(length > 0) | fromjson | [.operation, "`\(.target)`", (.unit // "—" | "`\(.)`"), .result, (.notes // "—")] | "| " + join(" | ") + " |"' <<< "${BACKUP_RESULT}"
printf '%s\n%s\n' "${BACKUP_RESULT}" "${CREDENTIAL_RESULT}" |
jq -Rr 'select(length > 0) | fromjson | [.operation, "`\(.target)`", (.unit // "—" | "`\(.)`"), .result, (.notes // "—")] | "| " + join(" | ") + " |"'
} >> "${GITHUB_STEP_SUMMARY}"
Loading
Loading