Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ Versioning after its first public release.
and once more on the bytes about to reach PyPI.
- `twine check --strict` runs on the wheel and sdist before a tag is spent, because PyPI
rejects an unrenderable long description only after the version number is consumed.
- The Helm chart can point the Control Plane at a PostgreSQL you operate instead
of the StatefulSet it ships. `postgresql.embedded.enabled: false` already
dropped that StatefulSet, its Service and its ingress NetworkPolicy, but the
Control Plane kept a hardcoded in-cluster host and no port of its own, so the
release dialled a Service the same render had declined to create.
`postgresql.external.host` and `postgresql.external.port` now supply
`SANDBOX_DB_HOST` and `SANDBOX_DB_PORT`; the credential contract is unchanged
- `postgresql.authSecret` is mounted in either mode - so an external server
only needs a Secret carrying its own `database`, `username` and `password`,
and the database and role must already exist there. An empty
`postgresql.external.host` fails `helm template` rather than falling back to
`sandbox-postgres`, which is the process default and would have produced a
release that starts, reports ready, and answers from a Service that is gone.

### Changed

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ operation fails; it never falls back to running on the host.

```bash
make bootstrap # create .venv and install SDK + test dependencies
make test # 878 unit and contract tests, no network, no cluster
make test # 880 unit and contract tests, no network, no cluster
make verify # complete Python, Console, manifest, Helm, wheel gate
make help # every Make target with its one-line description
```
Expand Down
4 changes: 3 additions & 1 deletion charts/sandbox/templates/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,9 @@ spec:
- name: SANDBOX_STORE_BACKEND
value: postgresql
- name: SANDBOX_DB_HOST
value: sandbox-postgres
value: {{ if .Values.postgresql.embedded.enabled }}sandbox-postgres{{ else }}{{ required "postgresql.external.host is required when postgresql.embedded.enabled is false" .Values.postgresql.external.host }}{{ end }}
- name: SANDBOX_DB_PORT
value: {{ .Values.postgresql.external.port | default 5432 | quote }}
- name: SANDBOX_DB_NAME
valueFrom:
secretKeyRef:
Expand Down
11 changes: 10 additions & 1 deletion charts/sandbox/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,18 @@
"postgresql": {
"type": "object",
"additionalProperties": false,
"required": ["embedded", "authSecret", "storageClass", "storageSize"],
"required": ["embedded", "external", "authSecret", "storageClass", "storageSize"],
"properties": {
"embedded": {"$ref": "#/$defs/feature"},
"external": {
"type": "object",
"additionalProperties": false,
"required": ["host", "port"],
"properties": {
"host": {"type": "string"},
"port": {"type": "integer", "minimum": 1, "maximum": 65535}
}
},
"authSecret": {"type": "string", "minLength": 1},
"storageClass": {"type": "string"},
"storageSize": {"type": "string", "minLength": 1}
Expand Down
11 changes: 11 additions & 0 deletions charts/sandbox/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ workspace:
postgresql:
embedded:
enabled: true
# Read only when embedded.enabled is false: the Control Plane dials this
# server instead of the Service the chart creates for it. The credential
# contract does not change with the mode - `authSecret` is mounted either way,
# so an external server needs a Secret carrying its own `database`,
# `username` and `password`, and the database and role must already exist
# there. The chart never carries credentials in values.
external:
# Required when embedded.enabled is false. The render fails on an empty
# value rather than falling back to the in-cluster Service name.
host: ""
port: 5432
authSecret: sandbox-postgres-auth
storageClass: ""
storageSize: 4Gi
Expand Down
34 changes: 34 additions & 0 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,40 @@ replace these values. Release `package-metadata.json` follows the shared schema
and carries the OCI Chart digest plus all four runtime-image digests and their
Helm value paths.

### Embedded or external PostgreSQL

`postgresql.embedded.enabled` decides whether the release carries its own
database. The default is the single-instance StatefulSet, Service and ingress
NetworkPolicy this chart ships; setting it to `false` drops all three and points
the Control Plane at a server you operate:

```yaml
postgresql:
embedded:
enabled: false
external:
host: postgres.example.net
port: 5432
```

| Values path | Rendered variable | Read by |
| --- | --- | --- |
| `postgresql.embedded.enabled` | `SANDBOX_DB_HOST` (the in-cluster Service, or `external.host`) | `control_plane/core.py` |
| `postgresql.external.port` | `SANDBOX_DB_PORT` | the same reader |

The credential contract does not change with the mode: `postgresql.authSecret`
is mounted either way and supplies `database`, `username` and `password`, so an
external server only needs a Secret carrying its own values. The database and
role must already exist there - the bundled StatefulSet is what creates the two
it is handed. An empty `postgresql.external.host` fails `helm template` instead
of falling back to the in-cluster Service name, because the Control Plane's own
default is `sandbox-postgres`: the release would start, report ready, and answer
every request from a Service the same render declined to create.

The Kustomize base keeps its embedded server and the example
`overlays/external-deps/control-plane-external.yaml` remains the source-deployment
path for an out-of-cluster database.

## Local integration

Lima provides the Linux VM in the standalone integration environment; kubeadm
Expand Down
60 changes: 60 additions & 0 deletions tests/test_helm_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,66 @@ def test_postgres_secret_override_reaches_every_consumer(self) -> None:
self.assertEqual(rendered.count(f"name: {secret}"), 5)
self.assertIn(f"secretName: {secret}", rendered)

def test_external_postgres_replaces_the_embedded_database(self) -> None:
"""Switching off the embedded server must move the address too.

The chart already omitted the Service and the StatefulSet, but the
Control Plane kept a hardcoded in-cluster host and no port of its own:
a release built for an external server dialled a Service the same
render had declined to create.
"""
if not shutil.which("helm"):
self.skipTest("helm is not installed")
host = "postgres.example.net"
rendered = subprocess.run(
[
"helm", "template", "sandbox", str(CHART),
"--set", "postgresql.embedded.enabled=false",
"--set-string", f"postgresql.external.host={host}",
"--set", "postgresql.external.port=6432",
],
check=True,
capture_output=True,
text=True,
).stdout
documents = [item for item in yaml.safe_load_all(rendered) if item]
named = {(item.get("kind"), item["metadata"]["name"]) for item in documents}
for absent in ("Service", "StatefulSet", "NetworkPolicy"):
self.assertNotIn((absent, "sandbox-postgres"), named)
control_plane = next(
item for item in documents
if item.get("kind") == "Deployment"
and item["metadata"]["name"] == "sandbox-control-plane"
)
environment = {
item["name"]: item.get("value")
for item in control_plane["spec"]["template"]["spec"]["containers"][0]["env"]
}
self.assertEqual(host, environment["SANDBOX_DB_HOST"])
# A string, not an integer: the API server refuses a non-string env value.
self.assertEqual("6432", environment["SANDBOX_DB_PORT"])

def test_external_postgres_without_a_host_is_refused_at_render(self) -> None:
"""The Control Plane's own default is the reason this cannot be silent.

`SANDBOX_DB_HOST` falls back to `sandbox-postgres` in the process, so an
empty external host would ship a release that starts, reports ready, and
answers every request from a Service that does not exist.
"""
if not shutil.which("helm"):
self.skipTest("helm is not installed")
result = subprocess.run(
[
"helm", "template", "sandbox", str(CHART),
"--set", "postgresql.embedded.enabled=false",
],
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(0, result.returncode)
self.assertIn("postgresql.external.host", result.stderr)

def test_otlp_endpoint_is_opt_in_and_reaches_both_traced_roles(self) -> None:
if not shutil.which("helm"):
self.skipTest("helm is not installed")
Expand Down
Loading