-
Notifications
You must be signed in to change notification settings - Fork 2
docs: Restish cli guide #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
GabriellePoncey
wants to merge
9
commits into
main
Choose a base branch
from
docs/PLAT-671/restish-cli-guide
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+279
−0
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
92dabb6
docs: Restish cli guide
GabriellePoncey edb357e
docs: fix secrets and TLS gaps in Restish guide
GabriellePoncey 44e159b
docs: harden temp secrets file example in Restish guide
GabriellePoncey ab9a30e
docs: phrasing, scope secret cleanup to subshell
GabriellePoncey 170db7d
docs: prompt for password instead of hardcoding it
GabriellePoncey 8ea5d5d
docs: refactor secrets and setup
GabriellePoncey 7ec2e26
docs: revisit Restish CLI examples
GabriellePoncey ae9fe5b
docs: drop jq empty check
GabriellePoncey 144def2
addressing review comments
tsivaprasad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,278 @@ | ||
| # Using Restish as a CLI | ||
|
|
||
| The pgEdge Control Plane can be used with 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: | ||
|
|
||
|
|
||
| ### 1. Installation | ||
|
|
||
| First, install Restish [via Restish's official website](https://rest.sh/docs/getting-started/install/). 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](#managing-multiple-environments) | ||
| if you need to connect to multiple Control Plane clusters from one machine. | ||
|
|
||
| Connect Restish to your cluster: | ||
|
|
||
| <!-- markdownlint-disable-next-line MD046 --> | ||
| ```sh | ||
| restish api connect pgedge http://localhost:3000 | ||
| ``` | ||
| ### 3. Verification | ||
|
|
||
| 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. | ||
|
|
||
| ## 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. | ||
|
|
||
| ```sh | ||
| restish pgedge init-cluster --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`. | ||
|
|
||
| Add a profile per environment to the same `pgedge` registration with | ||
| `restish api set`: | ||
|
|
||
| ```sh | ||
| 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: | ||
|
|
||
| ```sh | ||
| 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: | ||
|
|
||
| ```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 | ||
| ``` | ||
|
|
||
| If the cluster also requires a client certificate for mTLS, see | ||
| [Connecting Over mTLS](#connecting-over-mtls). | ||
|
|
||
| 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 | ||
| ``` | ||
|
|
||
| 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 | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| [project configuration](https://rest.sh/docs/reference/config/). | ||
|
|
||
| ### Connecting Over mTLS | ||
|
|
||
| If a cluster has [mTLS enabled](../installation/mtls.md), pass the CA | ||
| certificate plus a client certificate and key when you connect: | ||
|
|
||
| ```sh | ||
| 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`: | ||
|
|
||
| ```sh | ||
| 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 | ||
|
jason-lynch marked this conversation as resolved.
|
||
|
|
||
| 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 | ||
|
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`: | ||
|
|
||
| ```bash | ||
| ( | ||
| set -e | ||
| umask 077 | ||
| tmpfile=$(mktemp) | ||
| trap 'rm -f "$tmpfile"' EXIT | ||
| read -rsp "Password: " DB_PASS; echo | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| 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: | ||
|
|
||
| ```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 | ||
|
|
||
| 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](../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 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: | ||
|
|
||
| ```bash | ||
| ( | ||
| 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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.