Skip to content

Using Restish as a CLI

The pgEdge Control Plane can be used with a tool called Restish 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-described API into a set of generated commands, complete with shell completion and readable output.

Jump to what you're trying to do:

Quickstart

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

1. Installation

First, install Restish via Restish's official website. Restish supports many installation methods, including Homebrew (macOS), GitHub Releases, and OCI images; select the option that most aligns with your organization's preferences and practices.

2. Connection

Note

See Managing Multiple Environments if you need to connect to multiple Control Plane clusters from one machine.

Connect Restish to your cluster:

restish api connect pgedge http://localhost:3000

3. Verification

Then run your first command:

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 below), restish pgedge create-database < your-file.json creates one.

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 an immutable 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.

restish pgedge init-cluster --cluster-id production

Then select that cluster with a matching profile:

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.

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.

Add a profile per environment to the same pgedge registration with restish api set:

restish api set pgedge 'profiles.staging.base_url: https://host1.staging.internal:3000'
restish api set pgedge 'profiles.production.base_url: https://host1.prod.internal:3000'

Not every connection belongs on your everyday pgedge registration, though. Anything that's per-machine (eg. a Lima VM IP that's different for every developer, a personal sandbox) is better off registered under its own name so it doesn't collide with your regular setup:

restish api connect pgedge-sandbox http://192.168.64.3:3000

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:

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

If the cluster also requires a client certificate for mTLS, see Connecting Over mTLS.

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

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

Restish also supports registering connections in a .restish.json project config file so a whole team shares the same setup automatically. Restish won't use a discovered project config until you review it and run restish config trust; re-run that command after any change to the file. See Restish's own docs on project configuration.

Connecting Over mTLS

If a cluster has mTLS enabled, pass the CA certificate plus a client certificate and key when you connect:

restish api connect pgedge-sandbox https://192.168.64.3:3000 \
    --rsh-ca-cert ./ca.crt \
    --rsh-client-cert ./client.crt \
    --rsh-client-key ./client.key

For a shared registration like pgedge, set the same paths per profile with restish api set:

restish api set pgedge \
    'profiles.production.ca_cert: /opt/pgedge/control-plane/ca.crt' \
    'profiles.production.client_cert: /opt/pgedge/control-plane/client.crt' \
    'profiles.production.client_key: /opt/pgedge/control-plane/client.key'

Managing Database Configuration as Files

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

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

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:

(
  set -e
  umask 077
  tmpfile=$(mktemp)
  trap 'rm -f "$tmpfile"' EXIT
  read -rsp "Password: " DB_PASS; echo
  jq --rawfile pw <(printf '%s' "$DB_PASS") \
    '.spec.database_users[0].password = $pw' \
    databases/example.json > "$tmpfile"
  restish pgedge create-database < "$tmpfile"
)

read -rsp prompts for the password without echo, so it never appears in your terminal output or shell history. jq reads the value from a file descriptor (--rawfile pw <(printf '%s' "$DB_PASS")) rather than a command-line argument, so the password never appears in the command text or in the process list. umask 077 keeps the tmpfile unreadable by anyone else on the machine while it exists, and the subshell (...) limits the trap's scope: when create-database returns (or fails), the subshell exits and the trap fires immediately, removing the file before control returns to your interactive shell.

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:

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:

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

Secret fields, such as database_users[].password or s3_key_secret, should be excluded from any files committed to source control. 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 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 tmpfile for the one create-database call that needs them — prompted interactively with echo disabled (so the value never enters shell history), injected via jq at runtime, and written to a file created with a restrictive umask inside a subshell so it's removed as soon as create-database returns (even if it fails). 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: read -rsp for the new value, jq to inject it, passed once.

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.

Tip

The Control Plane excludes every secret field from its responses, you can skip manual redaction entirely: create the database from a one-off request that includes all of its secrets, then pull the sanitized spec back into your file:

(
  set -e
  set -o pipefail
  tmpfile=$(mktemp)
  trap 'rm -f "$tmpfile"' EXIT
  restish pgedge get-database example | jq '{ id, spec }' > "$tmpfile"
  mv "$tmpfile" databases/example.json
)

The redirect writes to a tmpfile first; databases/example.json is replaced only after get-database and jq both succeed (set -e plus pipefail), and the mv swaps it in atomically.