Skip to content
Draft
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
289 changes: 289 additions & 0 deletions docs/api/restish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
# Using Restish as a CLI

The pgEdge Control Plane makes use of a tool called [Restish](https://rest.sh) to get a CLI-like experience against the Control Plane's HTTP API. Restish is a generic, open-source REST client that turns any [OpenAPI](openapi.md)-described API into a set of generated commands, complete with shell completion and readable output.

Jump to what you're trying to do:

- **[Quickstart](#quickstart)** — install Restish and run your first command against a cluster.
- **[Managing Multiple Environments](#managing-multiple-environments)** — one cluster isn't enough, or you want a persistent setup.
- **[Managing Database Configuration as Files](#managing-database-configuration-as-files)** — commit database specs to source control instead of typing JSON inline.

## Quickstart

If you already have a Control Plane cluster running (see the
[installation quickstart](../installation/quickstart.md) if not), you can
be running commands against it in three steps:

First, install Restish:

```sh
brew install restish
```

Then connect Restish to your cluster:

!!! warning

Only connect this way to clusters and databases you're okay with experimenting on. See [Managing Multiple Environments](#managing-multiple-environments)
before connecting Restish to anything production.


```sh
restish api connect pgedge http://localhost:3000
```

Then run your first command:

```sh
restish pgedge list-databases
```

That's enough to start experimenting — `restish pgedge --help` lists every
generated command, and once you have a database config file (see
[Managing Database Configuration as Files](#managing-database-configuration-as-files)
below), `restish pgedge create-database < your-file.json` creates one.

See the [Restish install guide](https://rest.sh/docs/getting-started/install/)
for other install options, including release archives, containers, and
building from source.

## Managing Multiple Environments

Restish doesn't enforce any naming convention for the APIs you connect to.
We recommend using Restish's **profiles** feature: one API registration, `pgedge`, holds a profile per environment, and each profile can override the base URL (and, if you need it later, auth or other per-environment request details).

**Use descriptive cluster names.** Every cluster has its own durable `id`,
set at initialization and returned by `get-cluster`. `init-cluster` takes
an optional `cluster_id` query parameter. Setting it to something descriptive will allow you to keep track of multiple different clusters.

```sh
curl "http://host1.internal:3000/v1/cluster/init?cluster_id=production"
```

Then select that cluster with a matching profile:

```sh
restish -p production pgedge list-databases
restish -p staging pgedge list-databases
```

**Leave the `default` profile pointed at something safe.** Restish falls
back to the `default` profile whenever you don't pass `-p`/`--rsh-profile`
or set `RSH_PROFILE`, so be sure to point `default` at your local or informal cluster.

```sh
restish pgedge list-databases # default profile: local/informal cluster
```

!!! note

A cluster is made up of multiple hosts, each with its own host ID
(e.g. `host-1`). Any of them can serve a request for the whole
cluster, which is why one `base_url` per environment is enough for
routine use — you don't need a profile per host. To target a specific
host directly (retrying against a different one, or comparing behavior
across hosts while debugging), give it its own profile the same way:
`-p production-host-1`.

### Persisting Connections in a Project Config

Register `pgedge` once, with a profile per environment, in a `.restish.json`
file at the root of your infrastructure repo, instead of re-running
`restish api connect` by hand every time you check it out. Restish
discovers this file by walking up from your current directory:

```jsonc
{
"apis": {
"pgedge": {
"base_url": "http://host1.internal:3000",
"profiles": {
"default": {},
"staging": {
"base_url": "http://host1.staging.internal:3000"
},
"production": {
"base_url": "https://host1.prod.internal:3000"
}
}
}
}
}
```

The top-level `base_url` is what the `default` profile falls back to — the
same safe-fallback rule described above, expressed as config instead of
prose. `staging` and `production` each override it, and only take effect
when you pass `-p`.

Commit this file, then run the following once per checkout:

```sh
restish config trust
```

Restish records that trust decision outside the repo, keyed to the file's
contents, so your connections and profiles are ready every time you check
out the repo. If working with a team, everyone ends up with the same
setup without manually running `restish api connect` or keeping a personal
copy in sync.

!!! note

A project config only honors the `apis` and `theme` keys — nothing
else. Keep it secret-free: the Control Plane's dev and local endpoints
don't need auth, so these profiles can stay limited to `base_url` as
shown above. If a cluster you connect to does require auth, reference
the value as `env:NAME` rather than committing it literally.

If a cluster has [mTLS enabled](../installation/mtls.md), add the CA and
client certificate paths to that profile. These are file paths, not the
credentials themselves, so — unlike a token or password — they're fine to
commit as long as everyone using the file also has the actual `ca.crt`,
`client.crt`, and `client.key` in place locally:

```jsonc
"production": {
"base_url": "https://host1.prod.internal:3000",
"ca_cert": "/opt/pgedge/control-plane/ca.crt",
"client_cert": "/opt/pgedge/control-plane/client.crt",
"client_key": "/opt/pgedge/control-plane/client.key"
}
```

Not every connection belongs in that shared file, though. Anything that's
per-machine (eg. a Lima VM IP that's different for every developer, a personal sandbox) should stay out of source control. Restish also
won't let you add a profile to an API that came from trusted project
config, so register a personal one under its own name instead, which
writes to your own local Restish config:

```sh
restish api connect pgedge-sandbox http://192.168.64.3:3000
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

If a cluster uses TLS with a private CA, pass `--rsh-ca-cert` with the CA
file so Restish trusts it; if discovery also fails for a connection set up
this way, add `--spec` with an explicit URL or local file:

```sh
restish api connect pgedge-sandbox https://192.168.64.3:3000 \
--rsh-ca-cert ./ca.crt \
--spec https://192.168.64.3:3000/v1/openapi.json
```

Either way you connect something, the same commands work afterward:

```sh
restish api list # every connection you've configured
restish api inspect pgedge-sandbox # the URL, profiles, and spec Restish resolved
restish api remove pgedge-sandbox # disconnect (personal connections only)
restish pgedge-sandbox --help # every generated command
restish pgedge-sandbox list-databases --help # options for one command
```

## Managing Database Configuration as Files

Keep one file per database, and use it as the source of truth for that
database's configuration:

```sh
mkdir -p databases
cat > databases/example.json <<'EOF'
{
"id": "example",
"spec": {
"database_name": "example",
"database_users": [
{
"username": "admin",
"db_owner": true,
"attributes": ["SUPERUSER", "LOGIN"]
}
],
"port": 5432,
"nodes": [
{ "name": "n1", "host_ids": ["host-1"] },
{ "name": "n2", "host_ids": ["host-2"] },
{ "name": "n3", "host_ids": ["host-3"] }
]
}
}
EOF
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

`databases/example.json` never contains a password, so it's safe to commit
right away. Creating a database still needs a real password the first
time, though, so pass that from a separate file you don't commit instead
of adding it to `databases/example.json`:

```sh
cat > /tmp/example.create.json <<'EOF'
{
"id": "example",
"spec": {
"database_name": "example",
"database_users": [
{
"username": "admin",
"password": "changeme",
"db_owner": true,
"attributes": ["SUPERUSER", "LOGIN"]
}
],
"port": 5432,
"nodes": [
{ "name": "n1", "host_ids": ["host-1"] },
{ "name": "n2", "host_ids": ["host-2"] },
{ "name": "n3", "host_ids": ["host-3"] }
]
}
}
EOF
restish pgedge create-database < /tmp/example.create.json
rm /tmp/example.create.json
Comment on lines +219 to +243

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Create and remove the temporary secret file securely.

cat > /tmp/example.create.json uses a predictable path and does not set restrictive permissions. An interrupted shell can also leave the password in /tmp.

Use umask 077, mktemp, and an EXIT trap. Narrow the later claim to the committed file unless the temporary-file lifecycle is secured.

Proposed secure temporary-file workflow
+umask 077
+secret_file="$(mktemp "${TMPDIR:-/tmp}/example.create.XXXXXX")"
+trap 'rm -f -- "$secret_file"' EXIT
-cat > /tmp/example.create.json <<'EOF'
+cat > "$secret_file" <<'EOF'
...
-restish pgedge create-database < /tmp/example.create.json
-rm /tmp/example.create.json
+restish pgedge create-database < "$secret_file"

Also applies to: 278-289

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/api/restish.md` around lines 219 - 243, The example workflow should
securely manage the temporary JSON file containing the database password: set a
restrictive umask, create the file with mktemp, register an EXIT trap to remove
it, write the payload to that generated path, and pass that path to restish.
Apply the same lifecycle changes to the additional example referenced by the
comment, and limit any cleanup claim to the committed temporary file.

```

Update the same database by editing `databases/example.json` and
re-applying it against the `update-database` command. No password is
needed, since it's omitted from the request entirely:

```sh
restish pgedge update-database example < databases/example.json
```

To apply the same file to a specific environment instead of your default
cluster, add the profile you set up in
[Managing Multiple Environments](#managing-multiple-environments):

```sh
restish -p staging pgedge update-database example < databases/example.json
restish -p production pgedge update-database example < databases/example.json
```

This gives you a directory of database configuration files you can commit
to source control, diff, review in a pull request, and re-apply — the same
workflow you'd use for any other infrastructure-as-code.

### Handling Secrets

The one field in these files that doesn't belong in source control is
`database_users[].password` (and, if you're configuring backups,
credentials like `s3_key_secret`). The Control Plane's update endpoint is
built to make this easy: **secret fields are only required the first time
you create a database. On every `update-database` call after that, you can
leave them out entirely** — the Control Plane keeps whatever value is
already stored unless you explicitly send a new one. See
[Updating a Database](../using/update-db.md) for the full behavior.

In practice, that means the default workflow is the one shown above: keep
`databases/example.json` secret-free from the start, and pass real secret
values only from a separate, uncommitted file for the one `create-database`
call that needs them — deleting that file immediately afterward rather than
editing secrets out of the committed file after the fact. From then on,
`update-database` runs against the secret-free file as-is. If you need to
rotate a password, apply it the same way: a temporary file with the new
value, passed once, then removed.

This keeps `databases/example.json` safe to read, diff, and share at any
point — it's never the file that held the credential, so there's no window
where committing it (or `git add -A`, or a stray backup) could leak one.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ nav:
- API:
- OpenAPI: api/openapi.md
- API Reference: api/reference.md
- Using Restish as a CLI: api/restish.md
- Development:
- Development Process: development/development.md
- Automated End-to-End tests: development/e2e-tests.md
Expand Down