mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-13 12:17:34 +00:00
feat(stacks): per-stack environment inventory and secret-safe guardrails (#1397)
* feat(stacks): per-stack environment inventory and secret-safe guardrails
Add an Environment tab to Stack Anatomy that derives a per-stack inventory
of environment variables from the compose files and env files. Each variable
shows its source, whether Compose interpolates it or injects it into a
container, and a status (present, missing, unused, duplicate, or shell-only),
plus likely-secret classification. The inventory works from variable names
only: a value is never read, returned, or logged, and a likely secret shows
presence only. A copy env checklist action exports names and status without
values.
Surface a missing required env_file as a Compose Doctor preflight finding,
and add an opt-in node setting that refuses a deploy or update when a
required ${VAR:?...} variable is unset or empty, before any backup, pull, or
up runs. Default off.
The Environment tab is capability-gated so it hides on older remote nodes.
* fix(stacks): harden env-file reader against a stat-then-open race
Open the env-file handle first and fstat the open handle instead of
stat-ing the path before opening, removing the check-then-use window in
readEnvFileKeys. Use a secure mkdtemp directory for the out-of-base test
path instead of a predictable name in the temp root.
* fix(stacks): resolve nested env_file paths per compose file, reconcile inline keys per service
Resolve each env_file relative to the directory of the compose file that
declared it, so a nested multi-file Git override (infra/prod.yml referencing
./prod.env) lands next to that file instead of the stack root. The root
compose file is unaffected, since its directory is the stack directory.
Reconcile inline environment provenance per service, so a key an override
removed from one service's effective env is not labeled compose-inline just
because another service injects the same name from a different source.
This commit is contained in:
@@ -820,6 +820,104 @@ paths:
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/stacks/{stackName}/env-inventory:
|
||||
get:
|
||||
operationId: getStackEnvInventory
|
||||
tags: [Stacks]
|
||||
summary: Get environment inventory
|
||||
description: >-
|
||||
Returns a per-stack inventory of environment variables: each variable's
|
||||
source, whether Compose interpolates it or injects it into a container,
|
||||
and a status (present, missing, unused, duplicate, or unpersisted), plus
|
||||
likely-secret classification. Names only: a variable value is never read
|
||||
or returned. When the effective model cannot be rendered, `renderable` is
|
||||
false and the inventory is derived from the authored source alone.
|
||||
Requires `stack:read` permission.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
responses:
|
||||
"200":
|
||||
description: The environment inventory.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [stackName, renderable, items, envFiles, summary]
|
||||
properties:
|
||||
stackName:
|
||||
type: string
|
||||
renderable:
|
||||
type: boolean
|
||||
description: False when the effective model could not be rendered; the inventory is then partial.
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [key, sources, usedForInterpolation, injectedIntoService, required, hasDefault, likelySecret, status]
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [compose-inline, env-file, dotenv, process-env, compose-ref]
|
||||
usedForInterpolation:
|
||||
type: boolean
|
||||
injectedIntoService:
|
||||
type: boolean
|
||||
required:
|
||||
type: boolean
|
||||
hasDefault:
|
||||
type: boolean
|
||||
likelySecret:
|
||||
type: boolean
|
||||
status:
|
||||
type: string
|
||||
enum: [present, missing, unused, duplicate, unpersisted]
|
||||
envFiles:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
rawPaths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
existence:
|
||||
type: string
|
||||
enum: [present, missing, unverifiable]
|
||||
required:
|
||||
type: boolean
|
||||
isInterpolationSource:
|
||||
type: boolean
|
||||
isInjectionSource:
|
||||
type: boolean
|
||||
declaringServices:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
summary:
|
||||
type: object
|
||||
properties:
|
||||
total: { type: integer }
|
||||
missing: { type: integer }
|
||||
unused: { type: integer }
|
||||
duplicate: { type: integer }
|
||||
unpersisted: { type: integer }
|
||||
likelySecret: { type: integer }
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
description: Stack not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/stacks/{stackName}/env:
|
||||
get:
|
||||
operationId: getStackEnv
|
||||
|
||||
Reference in New Issue
Block a user