feat(config): add config-as-code YAML import/export
Add declarative config-as-code support via YAML files. Monitors and alerts can be exported, version controlled, and applied across instances. - goupkeep export [-o file.yaml] dumps current state - goupkeep apply -f file.yaml creates/updates to match desired state - --dry-run shows planned changes without applying - --prune deletes monitors/alerts not in the YAML - Matching by name, alert references by name, nested group children - CLI refactored to subcommands (apply, export, serve) with backward compat - 24 tests covering apply, export, validation, round-trip idempotency
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
# Config as Code
|
||||
|
||||
Define your monitors and alerts in a YAML file. Version control them, copy them between instances, or spin up a fresh setup in one command.
|
||||
|
||||
## Quick start
|
||||
|
||||
Export what you already have:
|
||||
|
||||
```bash
|
||||
goupkeep export -o monitors.yaml
|
||||
```
|
||||
|
||||
That gives you a working file you can edit and re-apply:
|
||||
|
||||
```bash
|
||||
goupkeep apply -f monitors.yaml
|
||||
```
|
||||
|
||||
That's it. Apply only creates or updates — it won't delete anything unless you tell it to.
|
||||
|
||||
## The YAML file
|
||||
|
||||
Two top-level sections: `alerts` and `monitors`. Alerts go first because monitors reference them by name.
|
||||
|
||||
```yaml
|
||||
alerts:
|
||||
- name: Discord Ops
|
||||
type: discord
|
||||
settings:
|
||||
url: https://discord.com/api/webhooks/your/token
|
||||
|
||||
- name: PagerDuty Critical
|
||||
type: pagerduty
|
||||
settings:
|
||||
routing_key: your-integration-key
|
||||
severity: critical
|
||||
|
||||
monitors:
|
||||
- name: API
|
||||
type: http
|
||||
url: https://api.example.com/health
|
||||
interval: 30
|
||||
alert: Discord Ops
|
||||
|
||||
- name: Production
|
||||
type: group
|
||||
alert: PagerDuty Critical
|
||||
monitors:
|
||||
- name: Prod Web
|
||||
type: http
|
||||
url: https://prod.example.com
|
||||
interval: 15
|
||||
- name: Prod DB
|
||||
type: port
|
||||
hostname: db.internal
|
||||
port: 5432
|
||||
interval: 30
|
||||
```
|
||||
|
||||
## Monitor types
|
||||
|
||||
Each type has required fields. Everything else is optional with sensible defaults.
|
||||
|
||||
**http** — polls a URL
|
||||
```yaml
|
||||
- name: My API
|
||||
type: http
|
||||
url: https://api.example.com/health
|
||||
interval: 30
|
||||
```
|
||||
|
||||
Optional: `method` (default GET), `accepted_codes` (default 200-299), `timeout`, `check_ssl`, `expiry_threshold` (default 7 days), `max_retries`, `ignore_tls`, `description`, `paused`.
|
||||
|
||||
**ping** — ICMP ping a host
|
||||
```yaml
|
||||
- name: Gateway
|
||||
type: ping
|
||||
hostname: 10.0.0.1
|
||||
interval: 30
|
||||
```
|
||||
|
||||
**port** — check if a port is open
|
||||
```yaml
|
||||
- name: SSH Server
|
||||
type: port
|
||||
hostname: 10.0.0.1
|
||||
port: 22
|
||||
interval: 60
|
||||
```
|
||||
|
||||
**dns** — resolve a hostname
|
||||
```yaml
|
||||
- name: DNS Check
|
||||
type: dns
|
||||
hostname: example.com
|
||||
dns_resolve_type: A
|
||||
dns_server: 1.1.1.1
|
||||
interval: 60
|
||||
```
|
||||
|
||||
**push** — heartbeat endpoint for cron jobs
|
||||
```yaml
|
||||
- name: Nightly Backup
|
||||
type: push
|
||||
interval: 86400
|
||||
```
|
||||
|
||||
Push monitors get a token assigned automatically. Hit the push endpoint before the interval expires or it alerts.
|
||||
|
||||
**group** — organize monitors together
|
||||
```yaml
|
||||
- name: Production
|
||||
type: group
|
||||
monitors:
|
||||
- name: Web
|
||||
type: http
|
||||
url: https://prod.example.com
|
||||
interval: 15
|
||||
```
|
||||
|
||||
Groups can't nest inside other groups. A group is healthy when all its children are healthy.
|
||||
|
||||
## Alert types
|
||||
|
||||
All 9 providers work in the YAML. The `settings` map is different per type.
|
||||
|
||||
```yaml
|
||||
# Discord / Slack / Generic Webhook — just a URL
|
||||
- name: Discord Ops
|
||||
type: discord
|
||||
settings:
|
||||
url: https://discord.com/api/webhooks/your/token
|
||||
|
||||
# Email
|
||||
- name: Email Oncall
|
||||
type: email
|
||||
settings:
|
||||
host: smtp.example.com
|
||||
port: "587"
|
||||
user: oncall@example.com
|
||||
pass: your-password
|
||||
from: oncall@example.com
|
||||
to: team@example.com
|
||||
|
||||
# Ntfy
|
||||
- name: Ntfy Alerts
|
||||
type: ntfy
|
||||
settings:
|
||||
url: https://ntfy.sh
|
||||
topic: my-alerts
|
||||
priority: "4"
|
||||
|
||||
# Telegram
|
||||
- name: Telegram Ops
|
||||
type: telegram
|
||||
settings:
|
||||
token: "123456:ABC-DEF..."
|
||||
chat_id: "-1001234567890"
|
||||
|
||||
# PagerDuty
|
||||
- name: PD Critical
|
||||
type: pagerduty
|
||||
settings:
|
||||
routing_key: your-integration-key
|
||||
severity: critical
|
||||
|
||||
# Pushover
|
||||
- name: Pushover
|
||||
type: pushover
|
||||
settings:
|
||||
token: app-token
|
||||
user: user-key
|
||||
|
||||
# Gotify
|
||||
- name: Gotify
|
||||
type: gotify
|
||||
settings:
|
||||
url: https://gotify.example.com
|
||||
token: app-token
|
||||
priority: "8"
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
**Export current state:**
|
||||
```bash
|
||||
goupkeep export -o monitors.yaml # to a file
|
||||
goupkeep export # to stdout
|
||||
```
|
||||
|
||||
**Apply a config:**
|
||||
```bash
|
||||
goupkeep apply -f monitors.yaml
|
||||
```
|
||||
|
||||
**See what would change first:**
|
||||
```bash
|
||||
goupkeep apply -f monitors.yaml --dry-run
|
||||
```
|
||||
|
||||
**Delete monitors not in the YAML:**
|
||||
```bash
|
||||
goupkeep apply -f monitors.yaml --prune
|
||||
```
|
||||
|
||||
Without `--prune`, apply never deletes anything. It only creates and updates.
|
||||
|
||||
**Pointing at a different database:**
|
||||
```bash
|
||||
goupkeep export -db-type postgres -dsn "host=localhost dbname=upkeep sslmode=disable"
|
||||
goupkeep apply -f monitors.yaml -db-type postgres -dsn "..."
|
||||
```
|
||||
|
||||
Both commands respect the `UPKEEP_DB_TYPE` and `UPKEEP_DB_DSN` environment variables too.
|
||||
|
||||
## How apply works
|
||||
|
||||
Monitors and alerts are matched by **name**. Names must be unique across the entire file.
|
||||
|
||||
1. Alerts are resolved first (created or updated)
|
||||
2. Groups are created next (so children can reference them)
|
||||
3. Everything else is created or updated
|
||||
4. If `--prune` is set, anything in the database that's not in the YAML gets deleted
|
||||
|
||||
Apply is idempotent. Run it twice with the same file, second run changes nothing.
|
||||
|
||||
If something fails mid-apply, just fix the issue and run it again. It picks up where it left off.
|
||||
|
||||
## Typical workflow
|
||||
|
||||
```bash
|
||||
# set up your monitors in the TUI first, then export
|
||||
goupkeep export -o monitors.yaml
|
||||
|
||||
# commit it
|
||||
git add monitors.yaml && git commit -m "add monitor config"
|
||||
|
||||
# deploy to another instance
|
||||
scp monitors.yaml prod-server:
|
||||
ssh prod-server goupkeep apply -f monitors.yaml
|
||||
|
||||
# or just keep it as a backup you can restore from
|
||||
goupkeep apply -f monitors.yaml
|
||||
```
|
||||
Reference in New Issue
Block a user