Skip to content

feat(api): catch the HTTP API up with the CLI (secret get/set/create-tls, pvcSize) - #12

Merged
anak10thn merged 2 commits into
mainfrom
feat/api-secret-parity
Aug 4, 2026
Merged

feat(api): catch the HTTP API up with the CLI (secret get/set/create-tls, pvcSize)#12
anak10thn merged 2 commits into
mainfrom
feat/api-secret-parity

Conversation

@anak10thn

Copy link
Copy Markdown
Member

Problem

The HTTP API had drifted behind the CLI. Probed against a running server:

GET   /api/v1/secrets/{name}   -> 405
PATCH /api/v1/secrets/{name}   -> 405
POST  /api/v1/secrets/tls      -> 405

secret get and secret set shipped in v2.10.0 with no endpoints, and secret create-tls never had one — only the generic and docker-registry writers existed.

Change

# keys listed, values withheld
$ curl .../api/v1/secrets/app-config
{"keys":["APP_MODE","DB_PASS"],"managedBy":true,"name":"app-config",
 "namespace":"default","redacted":true,"type":"Opaque"}

# merge one key
$ curl -X PATCH -d '{"data":{"DB_PASS":"rotated"}}' .../api/v1/secrets/app-config
{"keys":2,"name":"app-config","namespace":"default","set":1,
 "note":"running pods keep the old values until restarted"}
  • GET /api/v1/secrets/{name} — keys listed, values only with ?showValues=true; 404 for a missing Secret rather than a blanket 500.
  • PATCH /api/v1/secrets/{name} — merges, the counterpart to POST which replaces and drops keys it was not given. Creates when absent. The response carries the restart caveat.
  • POST /api/v1/secrets/tls — BYO cert from inline PEM (the CLI reads files; over HTTP the contents come inline).

openapi.yaml and docs/API.md updated with all three.

Two things that needed nothing

  • x-orcinus-env-from-secret already works over HTTP — conversion runs inside the server process. Confirmed via /convert: the rendered output contains the envFrom/secretRef.
  • GET /api/v1/secrets already returned KeyNames, since it serializes the shared type.

Deliberately not changed

SecretInfo keeps its capitalised JSON fields (Name, Keys, KeyNames). They are what openapi.yaml already publishes, so renaming to match the camelCase used elsewhere would break existing consumers. The inconsistency is now noted in the schema description; worth a deliberate breaking change later, not a drive-by one.

Limitation documented, not fixed

Compose keys that point at local files cannot work over HTTP — only the compose text is uploaded, so there is nothing to resolve paths against:

POST /api/v1/convert  with  env_file: .env
{"error":"...: service \"app\": env_file \".env\" not found: /var/.../orcinus-engine-XXX/.env"}

Same for configs:/secrets: with file: and bind mounts. Now called out in API.md, pointing at environment: or x-orcinus-env-from-secret instead. Fixing it properly means multi-file upload, which is a separate design question.

Test harness hazard — please read

Config{Kubeconfig: ""} falls through to $KUBECONFIG, then ~/.orcinus/kubeconfig. So an api test that reached applier() wrote to whatever cluster the machine points at. My new route tests did exactly that and created a kubernetes.io/tls Secret named c on a live cluster (tls.crt: eA== — base64 of the literal "x" from the test body) before I caught it. It has been deleted.

testServer now pins an unresolvable kubeconfig so those routes fail with 503, plus TestTestServerCannotReachACluster fails if that isolation ever breaks. Existing tests never tripped this because they only exercised convert and a plugin 404.

Verification

Live against a real 3-node k3s cluster, through a running orcinus api: create → get (redacted) → get ?showValues=true → patch (merge; the untouched key survived) → create-tls → 404 on a missing name → delete. All test Secrets cleaned up; the cluster's own 7 secrets untouched.

Offline: 151 tests pass, including 4 new api tests (routes wired, writes validate before touching a cluster, openapi covers the new surface, and the isolation guard).

…g to a real cluster

The HTTP API had drifted behind the CLI: `secret get` and `secret set` (v2.10.0)
had no endpoints, and `secret create-tls` never had one at all. Verified against
a running server — GET/PATCH /api/v1/secrets/{name} and POST
/api/v1/secrets/tls all answered 405.

- GET /api/v1/secrets/{name} lists the keys, withholding values unless
  ?showValues=true, and answers 404 rather than 500 for a missing Secret.
- PATCH /api/v1/secrets/{name} merges keys, the counterpart to POST, which
  replaces the data and drops what it was not given. Its response carries the
  note that running pods keep the old values until restarted.
- POST /api/v1/secrets/tls creates a BYO-cert Secret from inline PEM, the
  parity gap for `secret create-tls`.

SecretInfo keeps its capitalised JSON field names: they are what openapi.yaml
already publishes, so renaming them to match the rest of the API would break
existing consumers. Noted in the schema description instead.

Two things this does NOT need: x-orcinus-env-from-secret already works over
HTTP (conversion runs in the server process), and GET /api/v1/secrets already
returned KeyNames.

Documents a limitation found while testing: compose keys that point at local
files — env_file, configs/secrets with file:, bind mounts — cannot work over
HTTP, because only the compose text is uploaded and there is nothing to resolve
paths against. The request fails naming the missing path.

Fixes a hazard in the api test harness. Config{Kubeconfig: ""} falls through to
$KUBECONFIG and then ~/.orcinus/kubeconfig, so a test that reached applier()
wrote to whatever cluster the machine pointed at — the new route tests created a
TLS Secret on a live cluster before this was caught. testServer now pins an
unresolvable kubeconfig so those routes fail with 503, with a guard test that
fails if isolation ever breaks. Existing tests never hit this because they only
exercised convert and a 404.
The raw-body deploy path builds its DeployRequest from query params and read
every field except pvcSize, so `POST /api/v1/deploy?pvcSize=10Gi` with a YAML
body silently produced a 1Gi claim. Silent is the bad part: a PVC cannot be
resized after the fact unless the StorageClass allows expansion, so the only
way out is deleting the claim and its data.

openapi.yaml described the raw-body path as "options as query params" without
listing any, which is why the gap went unnoticed. Both /convert and /deploy now
enumerate them.

The JSON body path was always correct; every other CLI deploy flag already had
an equivalent on both paths.
@anak10thn anak10thn changed the title feat(api): secret get/set/create-tls over HTTP feat(api): catch the HTTP API up with the CLI (secret get/set/create-tls, pvcSize) Aug 3, 2026
@anak10thn
anak10thn merged commit 93d3287 into main Aug 4, 2026
2 checks passed
@anak10thn
anak10thn deleted the feat/api-secret-parity branch August 4, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant