mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 06:36:16 +00:00
Compare commits
7 Commits
v0.1.3
..
b749a6d3b4
| Author | SHA1 | Date | |
|---|---|---|---|
| b749a6d3b4 | |||
| 05724edde2 | |||
| b39a60af46 | |||
| fcf2dcb1eb | |||
| 31586c3058 | |||
| 41541629ec | |||
| 744041ca1e |
@@ -1,6 +1,26 @@
|
||||
<!-- xlate:verbatim-fences -->
|
||||
# Changelog
|
||||
|
||||
## 0.1.5 — 2026-09-07
|
||||
|
||||
- **`${VAR}` inside comment lines is not a reference.** The renderer's `.env.template` header literally says
|
||||
"substitutes every ${VAR}", which the refusal check counted as an unresolved variable
|
||||
(`ENV_INCOMPLETE: unresolved: VAR`) — the first bundle on env-qa-02 was refused for it.
|
||||
|
||||
- **config: `tenancy.port` is the service's intercept port (443), not the in-pod 8081.** With 8081 the SDK
|
||||
found no intercept and the dial failed (`service not available`, then a bare `TypeError` from the SDK's
|
||||
fallback). `install.sh` now writes 443; `config.example.yaml` updated.
|
||||
- **transport sdk: clear error instead of a TypeError** when an address has no intercept or the identity
|
||||
has no dial policy for the service (`TransportError` names the host:port and what to check).
|
||||
|
||||
## 0.1.4 — 2026-09-07
|
||||
|
||||
- **install.sh: `ziti-edge-tunnel.service` failed to start after enrolment** (`203/EXEC`: the package's
|
||||
`ExecStartPre` script is not executable by user `ziti` on ziti-edge-tunnel 1.18.x) and, because the
|
||||
unit was enabled with `--now` under `set -e`, the script aborted BEFORE writing config/ACLs/grant/timer —
|
||||
a second kit was needed. The run-host drop-in now clears `ExecStartPre=`; a tunneler start failure is a
|
||||
WARNING until everything else is in place, then a hard error at the end.
|
||||
|
||||
## 0.1.3 — 2026-09-06
|
||||
|
||||
- **install.sh: `--version` was clobbered by `/etc/os-release`** (it defines `VERSION`), so the kit tried to download
|
||||
|
||||
@@ -23,9 +23,9 @@ you the enrolment JWT, a one-time **bootstrap deploy grant** and the read-only *
|
||||
|
||||
```sh
|
||||
T=<deploy token> # read-only GitLab deploy token (read_package_registry); the kit carries it
|
||||
curl -sSf -H "DEPLOY-TOKEN: $T" https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.3/install.sh \
|
||||
curl -sSf -H "DEPLOY-TOKEN: $T" https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.5/install.sh \
|
||||
| sudo bash -s -- --env env-qa-02 --site cbs --token "$T" --bootstrap-file bootstrap.jwt --enrol-jwt ./monky-host.env-qa-02.jwt
|
||||
# [--transport sdk|proxy|system] [--version 0.1.3] [--docker-data-root /home/docker-data] [--laptop] [--bao-ca openbao-ca.pem] [--source gitlab|gitea]
|
||||
# [--transport sdk|proxy|system] [--version 0.1.5] [--docker-data-root /home/docker-data] [--laptop] [--bao-ca openbao-ca.pem] [--source gitlab|gitea]
|
||||
```
|
||||
|
||||
`install.sh` installs `ziti-edge-tunnel` (OpenZiti `jammy` suite) and `docker-compose-plugin` if
|
||||
|
||||
@@ -16,7 +16,7 @@ template → optional openbao-ca PEM → optional `monky-deployd-proxy.service`
|
||||
|
||||
| var | note |
|
||||
|---|---|
|
||||
| `monky_deployd_version` | pinned release, e.g. `0.1.3` |
|
||||
| `monky_deployd_version` | pinned release, e.g. `0.1.5` |
|
||||
| `monky_deployd_download_token` | **vaulted**: GitLab deploy token, scope `read_package_registry` only (revocable) — the registry is private; seeded in OpenBao at `monky/monky-tenancy/deployd-download` key `token` (path/key are the operator's choice). Empty = no header (only works with the Gitea `base_url`) |
|
||||
| `monky_deployd_env_id` / `_site` / `_transport` | per host (`env-dev-06`, `cbs`, `sdk`) |
|
||||
| `monky_deployd_bootstrap_grant` | tenancy-minted deploy grant (1 h) — `ansible-vault` or a lookup at play time; empty keeps the existing token |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
# monky_deployd — install and configure the Monky backend pull agent (MONKY-ADR-0028 §D).
|
||||
# Copy this role into osg1-07 (roles/monky_deployd) and roll to env-dev-06..09 after the pilot.
|
||||
monky_deployd_version: "0.1.3"
|
||||
monky_deployd_version: "0.1.5"
|
||||
monky_deployd_deb: "monky-deployd_{{ monky_deployd_version }}_amd64.deb"
|
||||
# PRIMARY download = the GitLab project's generic package registry on scm.tikali.ai. Inside the estate
|
||||
# gitea.cbs.tikali.net is split-horizon to jump1's RED EIP (10.10.0.175, no HTTP ingress), so backend
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# /etc/monky-deployd/config.yaml — monky-deployd v0.1.3 (MONKY-ADR-0028 §D)
|
||||
# /etc/monky-deployd/config.yaml — monky-deployd v0.1.5 (MONKY-ADR-0028 §D)
|
||||
# Written by packaging/install.sh (or the ansible role monky_deployd). YAML *subset*: maps, scalars,
|
||||
# simple lists, comments. Keys not listed here are a config error.
|
||||
|
||||
@@ -10,7 +10,7 @@ identity: /opt/openziti/etc/identities/monky-host.env-qa-02.json # the box's h
|
||||
tenancy:
|
||||
service: monky.tenancy.deploy # ziti service bound by the tenancy sidecar -> 127.0.0.1:8081 (agent entrypoint)
|
||||
host: monky.tenancy.deploy # intercept host (sdk/system); defaults to `service`
|
||||
port: 8081
|
||||
port: 443 # intercept port of monky.tenancy.deploy (in-pod 8081)
|
||||
scheme: http # plain HTTP inside the mesh; the mesh is the transport security
|
||||
proxy_addr: 127.0.0.1:18443 # transport: proxy
|
||||
timeout_s: 30
|
||||
|
||||
+19
-12
@@ -70,7 +70,8 @@ same tick, otherwise it exits 1 and says "re-run the install kit".
|
||||
```json
|
||||
{"env_id": "env-qa-02", "desired_sha": "7a10…", "action": "apply", "purge_volumes": false,
|
||||
"bundle_url": "/v1/agent/bundle/env-qa-02/7a10…", "checkin_interval_s": 60,
|
||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky", "prefix": "env-qa-02/see"}}
|
||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky", "prefix": "env-qa-02/see",
|
||||
"auth_mount": "jwt-tenancy", "auth_role": "see-env"}}
|
||||
```
|
||||
`action`: `apply` (desired ≠ applied), `none` (converged → heartbeat), `down` (retire; `purge_volumes`
|
||||
is only meaningful here). `vault.mount` is the **KV** mount; the agent adopts it if it differs from
|
||||
@@ -109,12 +110,16 @@ address, pull, renderer, images_policy, secrets_provider, agent{…}, files[]`.
|
||||
### `POST /v1/agent/lease`
|
||||
|
||||
```json
|
||||
{"env_id": "env-qa-02", "reason": "apply"} // reason: apply | renew
|
||||
{"env_id": "env-qa-02"}
|
||||
```
|
||||
(The agent also sends `"reason": "apply" | "renew"` for its own logs; tenancy's `AgentLease` is
|
||||
`{env_id}` and ignores unknown fields.)
|
||||
```json
|
||||
{"env_id": "env-qa-02", "login_jwt": "eyJ…", "ttl_s": 3600, "mount": "jwt-tenancy", "role": "see-env",
|
||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky"}}
|
||||
"addr": "https://bao.cbs.tikali.net:8200"}
|
||||
```
|
||||
`addr` is the OpenBao address for the login below, top-level (tenancy `AgentLeaseOut`); the KV
|
||||
mount/prefix come from `checkin`'s `vault`, not from the lease.
|
||||
Then `POST /v1/auth/{mount}/login {"role": "{role}", "jwt": "{login_jwt}"}` on OpenBao. A body with
|
||||
`wrapping_token` / `role_id` (the pre-Gate-1 AppRole lease) is refused with `LEASE_SHAPE` → report
|
||||
`failed`. `429 LEASE_RATE_LIMITED` → exit 75.
|
||||
@@ -133,15 +138,17 @@ first 2 KiB land in the audit log — it has been through the redactor.
|
||||
|
||||
## Divergences (2026-09-05)
|
||||
|
||||
- **monky-tenancy `main` (MR !15) still implements the AppRole lease and install kit**
|
||||
(`AgentLeaseOut{wrapping_token, role_id}`, `bootstrap.wrap`, `bao.approle` in the kit's config). The
|
||||
binding design is the plan's Gate 1 RESULT / ADR-0028 amendment: `{login_jwt, ttl_s, mount, role}`
|
||||
and `POST /v1/auth/jwt-tenancy/login`. This agent implements the latter; against an un-migrated
|
||||
tenancy it reports `failed` with `LEASE_SHAPE` and refuses `bao.approle` in its config. The tenancy
|
||||
follow-up (deploy-grant signer, JWKS, `lease` shape, kit → `bootstrap.jwt`) is tracked on
|
||||
monky-tenancy.
|
||||
- The kit's generated config uses `tenancy.base_url: http://monky.tenancy.deploy:8081` — accepted as
|
||||
an alias for `tenancy.{scheme,host,port}`.
|
||||
- **Resolved 2026-09-05 (before v0.1.0 was tagged):** monky-tenancy !17 (`288df791`) landed the
|
||||
tenancy side of the Gate 1 RESULT / ADR-0028 amendment — `AgentLeaseOut{env_id, login_jwt, ttl_s,
|
||||
mount, role, addr}`, the ES256 deploy-grant signer (`app/agent_keys.py`), `GET
|
||||
/.well-known/agent-jwks.json`, no AppRole and no response wrapping anywhere in tenancy. The
|
||||
`LEASE_SHAPE` refusal of a `wrapping_token`/`role_id` body stays in this agent as a guard against a
|
||||
stale tenancy, not as a description of `main`.
|
||||
- **Resolved 2026-09-05:** the kit no longer generates a config file. Since monky-tenancy !22
|
||||
(`61bd0281`) the one-time install script stages the bootstrap grant and runs `install.sh --env …
|
||||
--site … --version … --bootstrap-file …`, and `install.sh` writes `/etc/monky-deployd/config.yaml`
|
||||
(tenancy service name, OpenBao address and `jwt-tenancy`/`see-env`). `tenancy.base_url:` remains an
|
||||
accepted alias for `tenancy.{scheme,host,port}` (`config.py`) for hand-written configs.
|
||||
- `report` gains an optional `detail` (doc 24 §3.3); tenancy's `AgentReport` ignores unknown fields
|
||||
today — if `strict` bodies land, `detail` folds into `log_tail`.
|
||||
- Bundle sha header: tenancy sends `X-Bundle-Sha`, doc 24 says `X-Bundle-Sha256`; the agent reads
|
||||
|
||||
@@ -4,4 +4,4 @@ Dials monky-tenancy over the mesh with the box's host identity, fetches the rend
|
||||
leases a deploy grant, logs in to OpenBao, reads its own secrets, runs `docker compose`,
|
||||
reports. Stdlib only; the optional `openziti` SDK is the `sdk` transport."""
|
||||
|
||||
__version__ = "0.1.3"
|
||||
__version__ = "0.1.5"
|
||||
|
||||
@@ -144,12 +144,18 @@ def parse(data: bytes, *, max_bytes: int = 4 * 1024 * 1024) -> Bundle:
|
||||
# --- refusal checks (pure; names only, never values) -----------------------------------------
|
||||
|
||||
|
||||
def _code_lines(text: str) -> str:
|
||||
"""Drop comment lines: a `# … ${VAR} …` remark in .env.template (the renderer writes one)
|
||||
is not a reference. Compose/dotenv comments start with `#` after optional whitespace."""
|
||||
return "\n".join(ln for ln in text.splitlines() if not ln.lstrip().startswith("#"))
|
||||
|
||||
|
||||
def referenced_vars(text: str) -> set[str]:
|
||||
return {m.group(1) for m in _VAR_RE.finditer(text)}
|
||||
return {m.group(1) for m in _VAR_RE.finditer(_code_lines(text))}
|
||||
|
||||
|
||||
def defaulted_vars(text: str) -> set[str]:
|
||||
return {m.group(1) for m in _VAR_DEFAULTED_RE.finditer(text)}
|
||||
return {m.group(1) for m in _VAR_DEFAULTED_RE.finditer(_code_lines(text))}
|
||||
|
||||
|
||||
def unresolved_vars(bundle: Bundle, provided: set[str]) -> list[str]:
|
||||
|
||||
@@ -4,7 +4,7 @@ Bearer = the agent's OpenBao token (from the `jwt-tenancy` login). Tenancy pins
|
||||
the token's `meta.env_id` (403 AGENT_ENV_MISMATCH -> exit 78, never retried) and refuses a token
|
||||
whose deploy grant was superseded (401 AGENT_UNAUTHENTICATED -> re-bootstrap or re-run the kit).
|
||||
|
||||
Lease shape of record (Gate 1 v2, 2026-09-05): `{login_jwt, ttl_s, mount, role, vault}`. An
|
||||
Lease shape of record (Gate 1 v2, 2026-09-05): `{env_id, login_jwt, ttl_s, mount, role, addr}`. An
|
||||
AppRole-era body (`wrapping_token`, `role_id`) is refused loudly — there is nothing to unwrap."""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -158,13 +158,15 @@ class TenancyClient:
|
||||
"(ADR-0028 amendment 2026-09-05)",
|
||||
)
|
||||
raise TenancyError(200, "LEASE_SHAPE", "lease response carries no login_jwt")
|
||||
# shape of record (tenancy AgentLeaseOut): `addr` is top-level; a pre-0.1.x `vault{}` object
|
||||
# is still read as a fallback so an older fake or tenancy does not break the lease
|
||||
v = js.get("vault") or {}
|
||||
return Lease(
|
||||
login_jwt=str(js["login_jwt"]),
|
||||
ttl_s=int(js.get("ttl_s") or 3600),
|
||||
mount=str(js.get("mount") or "jwt-tenancy"),
|
||||
role=str(js.get("role") or "see-env"),
|
||||
vault=Vault(addr=v.get("addr"), mount=v.get("mount"), prefix=v.get("prefix")),
|
||||
vault=Vault(addr=js.get("addr") or v.get("addr"), mount=v.get("mount"), prefix=v.get("prefix")),
|
||||
)
|
||||
|
||||
def report(
|
||||
|
||||
@@ -104,6 +104,14 @@ class SdkTransport(Transport):
|
||||
return socket.create_connection((host, port), timeout=timeout)
|
||||
except OSError as exc:
|
||||
raise TransportError(f"transport sdk: dial {host}:{port} failed: {exc}") from exc
|
||||
except Exception as exc: # noqa: BLE001 - the SDK raises bare Exception((code, msg)) and TypeError
|
||||
# openziti-sdk-py: an address with NO matching intercept falls through to
|
||||
# PySocket.connect(tuple) → TypeError; a matching intercept the identity may not dial
|
||||
# raises Exception((-18, 'service not available')) — env-qa-02 pilot, 2026-09-07.
|
||||
raise TransportError(
|
||||
f"transport sdk: dial {host}:{port} failed: {exc} — no intercept for that host:port, or this "
|
||||
"identity has no dial policy for the service (check the intercept port and the identity's attrs)"
|
||||
) from exc
|
||||
|
||||
def describe(self) -> str:
|
||||
return f"sdk(identity={self.identity_path})"
|
||||
|
||||
+19
-8
@@ -3,9 +3,9 @@
|
||||
#
|
||||
# T=<read-only GitLab deploy token, scope read_package_registry> # from the install kit / OpenBao
|
||||
# curl -sSf -H "DEPLOY-TOKEN: $T" \
|
||||
# https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.3/install.sh \
|
||||
# https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.5/install.sh \
|
||||
# | sudo bash -s -- --env env-qa-02 --site cbs --token "$T" --bootstrap-file bootstrap.jwt \
|
||||
# [--transport sdk|proxy|system] [--version 0.1.3] [--enrol-jwt /path/monky-host.env-qa-02.jwt] \
|
||||
# [--transport sdk|proxy|system] [--version 0.1.5] [--enrol-jwt /path/monky-host.env-qa-02.jwt] \
|
||||
# [--laptop] [--source gitlab|gitea] [--docker-data-root /home/docker-data]
|
||||
#
|
||||
# --token / MONKY_DEPLOYD_TOKEN: the GitLab project is PRIVATE (its parent groups are private, so it
|
||||
@@ -29,7 +29,7 @@
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
|
||||
DEFAULT_VERSION="0.1.3"
|
||||
DEFAULT_VERSION="0.1.5"
|
||||
# Download source. PRIMARY is the GitLab project's generic package registry on scm.tikali.ai: inside
|
||||
# the estate gitea.cbs.tikali.net is split-horizon to jump1's RED EIP (10.10.0.175), which has no HTTP
|
||||
# ingress, so backend boxes cannot reach the Gitea mirror (cbs/iac#102); scm.tikali.ai they can. The
|
||||
@@ -45,7 +45,7 @@ TOKEN="${MONKY_DEPLOYD_TOKEN:-}"
|
||||
OPENZITI_SUITE="${OPENZITI_SUITE:-jammy}"
|
||||
IDENTITY_DIR="/opt/openziti/etc/identities"
|
||||
ETC="/etc/monky-deployd"
|
||||
ENV_ID="" SITE="" TRANSPORT="sdk" DOCKER_DATA_ROOT="" DEPLOYD_VERSION="$DEFAULT_VERSION" ENROL_JWT="" BOOTSTRAP_FILE="" NO_RUN="" FORCE_CONFIG="" BAO_CA="" LAPTOP="false"
|
||||
ENV_ID="" SITE="" TRANSPORT="sdk" DOCKER_DATA_ROOT="" TUNNEL_FAILED="" DEPLOYD_VERSION="$DEFAULT_VERSION" ENROL_JWT="" BOOTSTRAP_FILE="" NO_RUN="" FORCE_CONFIG="" BAO_CA="" LAPTOP="false"
|
||||
|
||||
usage() { if [ -f "$0" ]; then sed -n '2,28p' "$0"; else echo "monky-deployd install.sh — see README.md (Install)"; fi; exit "${1:-0}"; }
|
||||
die() { echo "install.sh: $*" >&2; exit 1; }
|
||||
@@ -194,14 +194,21 @@ chmod 0600 "$IDENTITY"
|
||||
# ziti-edge-tunnel as a HOST (bind side, no tun/DNS) — the agent dials with the SDK or the proxy
|
||||
install -d /etc/systemd/system/ziti-edge-tunnel.service.d
|
||||
cat > /etc/systemd/system/ziti-edge-tunnel.service.d/run-host.conf <<'DROPIN'
|
||||
# monky-deployd: run-host mode (no tun, no DNS); identities from the standard directory
|
||||
# monky-deployd: run-host mode (no tun, no DNS); identities from the standard directory.
|
||||
# ExecStartPre is cleared: the package's ziti-edge-tunnel.sh (auto-enrol *.jwt in the identity dir)
|
||||
# is not executable by user ziti on 1.18.x (203/EXEC, env-qa-02 pilot) and run-host does not need it.
|
||||
[Service]
|
||||
ExecStartPre=
|
||||
ExecStart=
|
||||
ExecStart=/opt/openziti/bin/ziti-edge-tunnel run-host --identity-dir=/opt/openziti/etc/identities
|
||||
DROPIN
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now ziti-edge-tunnel.service
|
||||
systemctl restart ziti-edge-tunnel.service || true
|
||||
systemctl enable ziti-edge-tunnel.service >/dev/null 2>&1 || true
|
||||
if ! systemctl restart ziti-edge-tunnel.service; then
|
||||
# do NOT abort here: config, ACLs, grant and timer below must land so a re-run needs no new kit
|
||||
echo "WARNING: ziti-edge-tunnel.service failed to start — see: journalctl -u ziti-edge-tunnel; continuing" >&2
|
||||
TUNNEL_FAILED=1
|
||||
fi
|
||||
|
||||
# --- 4. config, ACLs, bootstrap grant -----------------------------------------------------------------
|
||||
install -d -m 0750 -o root -g monky-deployd "$ETC"
|
||||
@@ -226,7 +233,7 @@ identity: $IDENTITY
|
||||
tenancy:
|
||||
service: monky.tenancy.deploy
|
||||
host: monky.tenancy.deploy
|
||||
port: 8081
|
||||
port: 443 # the service's INTERCEPT port (host.v1 forwards to 8081 inside the pod); plain HTTP inside the mesh
|
||||
scheme: http
|
||||
proxy_addr: 127.0.0.1:18443
|
||||
bao:
|
||||
@@ -292,3 +299,7 @@ monky-deployd $DEPLOYD_VERSION installed for $ENV_ID ($SITE, transport $TRANSPOR
|
||||
CHECK
|
||||
CHECK_STATUS="$(monky-deployd status 2>&1 || true)"
|
||||
echo "$CHECK_STATUS" | sed 's/^/ | /'
|
||||
if [ -n "$TUNNEL_FAILED" ] && ! systemctl is-active --quiet ziti-edge-tunnel.service; then
|
||||
echo "ERROR: ziti-edge-tunnel.service is not running; the agent cannot reach the mesh until it is. Everything else is installed — fix the unit and 'systemctl restart ziti-edge-tunnel monky-deployd'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "monky-deployd"
|
||||
version = "0.1.3"
|
||||
version = "0.1.5"
|
||||
description = "Monky backend pull agent: checkin -> bundle -> lease -> OpenBao -> docker compose -> report, over the ziti mesh (MONKY-ADR-0028)"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
+2
-1
@@ -354,7 +354,8 @@ class FakeTenancy:
|
||||
"ttl_s": 3600,
|
||||
"mount": self.bao.mount,
|
||||
"role": self.bao.role,
|
||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": self.kv_mount},
|
||||
# tenancy AgentLeaseOut: `addr` is top-level; KV mount/prefix come from checkin
|
||||
"addr": "https://bao.cbs.tikali.net:8200",
|
||||
},
|
||||
headers={"Cache-Control": "no-store"},
|
||||
)
|
||||
|
||||
@@ -79,3 +79,16 @@ def test_disk_need_bytes_spellings():
|
||||
assert b.parse(tar_bytes(make_files(meta={"agent": {"disk_need_bytes": 5}}))).disk_need_bytes == 5
|
||||
assert b.parse(tar_bytes(make_files(meta={"disk": {"need_bytes": 7}}))).disk_need_bytes == 7
|
||||
assert b.parse(tar_bytes(make_files())).disk_need_bytes == 0
|
||||
|
||||
|
||||
def test_placeholders_in_comment_lines_are_not_references():
|
||||
"""The renderer's .env.template header says '... substitutes every ${VAR} ...' — that must not
|
||||
become an unresolved 'VAR' (env-qa-02 pilot: ENV_INCOMPLETE: unresolved: VAR)."""
|
||||
from monky_deployd.bundle import defaulted_vars, referenced_vars
|
||||
|
||||
text = (
|
||||
"# The on-box agent substitutes every ${VAR} from OpenBao per secrets.manifest.json.\n"
|
||||
" # ${ALSO_COMMENT}\nGEMINI_API_KEY=${GEMINI_API_KEY}\nPG=${PGPASSWORD:-x}\n"
|
||||
)
|
||||
assert referenced_vars(text) == {"GEMINI_API_KEY", "PGPASSWORD"}
|
||||
assert defaulted_vars(text) == {"PGPASSWORD"}
|
||||
|
||||
Reference in New Issue
Block a user