feat(deployer): separate kubernetes and standalone code paths #647

Merged
fuzzy merged 1 commit from feat/deployer-selection into main 2026-08-13 13:57:02 +00:00
Owner

What

Separate the Kubernetes and standalone deployment code paths behind a single Deployer interface, and allow selecting the backend at startup.

  • Deployer interface (#333)site.KubernetesClient renamed to site.Deployer; SiteService now holds a deployer instead of a kubernetes client, making it agnostic to the runtime.
  • KubernetesDeployer (#334)kubernetes.Client renamed to KubernetesDeployer (NewNewKubernetesDeployer); the redundant producer-side KubernetesService interface is removed (superseded by site.Deployer).
  • StandaloneDeployer (#335) — new internal/docker/deployer.go implementing site.Deployer, verifying Docker socket access at construction (CheckAccess); SiteURL/PreviewURL computed from base domain; lifecycle operations return clear not-implemented errors until Phase 2 (container lifecycle).
  • Deployer selection (#336)config.Deployer (default kubernetes); --deployer flag, bare DEPLOYER env var, and config resolution in cmd/thwap-pagesd/main.go; buildRouter constructs the selected deployer and matching site repository (standalone uses in-memory persistence until Phase 6 SQLite). Unknown values (e.g. swarm) rejected until Phase 5.

Why

Phase 1 requires the deployer seam before standalone container operations (Phase 2), config extensions (#343), and site operations (Phase 4) can be layered on. This makes the deployment backend a single, selectable point of configuration.

Testing

  • internal/dockerStandaloneDeployer tests: nil-client, socket-access failure at construction, URL helpers (default host + custom domain), lifecycle not-implemented errors
  • cmd/thwap-pagesdresolveDeployer precedence (flag > env > config > default) and validateDeployer rejection tests
  • internal/config — default deployer assertion; YAML/TOML fixtures updated
  • go vet ./... clean
  • go test -race -count=1 ./... — 37 packages pass, no failures
  • Pre-commit hooks pass (golangci-lint, go fmt, go mod tidy, go test, gitleaks)

Breaking Changes

None externally. Internal renames (KubernetesClientDeployer, kubernetes.ClientKubernetesDeployer, kubernetes.NewNewKubernetesDeployer) affect internal callers only.

Notes

Standalone mode's container lifecycle is intentionally deferred: CreateSiteIngress/PublishSite/etc. return not-implemented errors until Phase 2 (#349) fills them in. Site persistence for standalone is in-memory until Phase 6 (#432) introduces the local database.

Closes #333
Closes #334
Closes #335
Closes #336
Closes #337

## What Separate the Kubernetes and standalone deployment code paths behind a single `Deployer` interface, and allow selecting the backend at startup. - **Deployer interface (#333)** — `site.KubernetesClient` renamed to `site.Deployer`; `SiteService` now holds a `deployer` instead of a kubernetes client, making it agnostic to the runtime. - **KubernetesDeployer (#334)** — `kubernetes.Client` renamed to `KubernetesDeployer` (`New` → `NewKubernetesDeployer`); the redundant producer-side `KubernetesService` interface is removed (superseded by `site.Deployer`). - **StandaloneDeployer (#335)** — new `internal/docker/deployer.go` implementing `site.Deployer`, verifying Docker socket access at construction (`CheckAccess`); `SiteURL`/`PreviewURL` computed from base domain; lifecycle operations return clear not-implemented errors until Phase 2 (container lifecycle). - **Deployer selection (#336)** — `config.Deployer` (default `kubernetes`); `--deployer` flag, bare `DEPLOYER` env var, and config resolution in `cmd/thwap-pagesd/main.go`; `buildRouter` constructs the selected deployer and matching site repository (standalone uses in-memory persistence until Phase 6 SQLite). Unknown values (e.g. `swarm`) rejected until Phase 5. ## Why Phase 1 requires the deployer seam before standalone container operations (Phase 2), config extensions (#343), and site operations (Phase 4) can be layered on. This makes the deployment backend a single, selectable point of configuration. ## Testing - [x] `internal/docker` — `StandaloneDeployer` tests: nil-client, socket-access failure at construction, URL helpers (default host + custom domain), lifecycle not-implemented errors - [x] `cmd/thwap-pagesd` — `resolveDeployer` precedence (flag > env > config > default) and `validateDeployer` rejection tests - [x] `internal/config` — default deployer assertion; YAML/TOML fixtures updated - [x] `go vet ./...` clean - [x] `go test -race -count=1 ./...` — 37 packages pass, no failures - [x] Pre-commit hooks pass (golangci-lint, go fmt, go mod tidy, go test, gitleaks) ## Breaking Changes None externally. Internal renames (`KubernetesClient` → `Deployer`, `kubernetes.Client` → `KubernetesDeployer`, `kubernetes.New` → `NewKubernetesDeployer`) affect internal callers only. ## Notes Standalone mode's container lifecycle is intentionally deferred: `CreateSiteIngress`/`PublishSite`/etc. return not-implemented errors until Phase 2 (#349) fills them in. Site persistence for standalone is in-memory until Phase 6 (#432) introduces the local database. Closes #333 Closes #334 Closes #335 Closes #336 Closes #337
feat(deployer): separate kubernetes and standalone code paths
Some checks failed
CI / test (pull_request) Successful in 4m2s
CI / docker (pull_request) Successful in 2m53s
CI / build (pull_request) Successful in 4m14s
CI / lint (pull_request) Successful in 7m3s
CI / docker (push) Failing after 57s
CI / test (push) Successful in 2m38s
CI / build (push) Successful in 6m48s
CI / lint (push) Successful in 14m15s
d97176f5fe
Introduce the Deployer seam so SiteService is agnostic to the deployment
runtime, and allow selecting the backend at startup.

- site: rename KubernetesClient to Deployer interface; Service now holds
  a deployer instead of a kubernetes client
- kubernetes: rename Client to KubernetesDeployer and New to
  NewKubernetesDeployer; drop the redundant KubernetesService interface
- docker: add StandaloneDeployer implementing site.Deployer, verifying
  socket access at construction; lifecycle operations return
  not-implemented until Phase 2
- config: add deployer setting (default kubernetes)
- main: resolve backend via --deployer flag, DEPLOYER env var, or config;
  buildRouter constructs the selected deployer and its site repository
- DOCKER-ROADMAP.md: mark #337 and subtasks complete

Closes #333
Closes #334
Closes #335
Closes #336
Closes #337
the.auditor left a comment

Clean seam. The Deployer interface rename is complete and mechanical (no leftover KubernetesClient/kubernetes.New callers), the redundant producer-side KubernetesService interface is correctly removed, resolveDeployer precedence (flag > DEPLOYER env > config incl. THWAP_PAGESD_DEPLOYER > default) is sound and well tested, and the standalone deployer fails fast on socket access. Referenced issues #333–#337 match the roadmap checkboxes.

Suggestions

  1. cmd/thwap-pagesd/main.go (newDeployer) — standalone's base domain comes from cfg.Kubernetes.BaseDomain, so standalone mode still reads the kubernetes config section. Consider a dedicated standalone config value (ties to #338) or document the reuse. Filed as #648.
  2. internal/docker/deployer.go — the package docker doc comment is duplicated verbatim from docker.go; single package comment per package. Filed as #649.
  3. cmd/thwap-pagesd/main.gonewDeployer branching (kubernetes/standalone) is untested; consider injectable constructor funcs or a fake-socket test. Filed as #650.
  4. internal/docker/deployer.go — deferred lifecycle ops return ad-hoc errors; a shared ErrNotImplemented sentinel would let the API return 501 vs 500 until Phase 2. Filed as #651.

Questions

  • Standalone mode boots and validates Docker access, but every site operation (Register/Publish/Preview) returns not-implemented, so a standalone daemon is inert until Phase 2. Understood as intended per the notes.
  • Note: #646 (bound CheckAccess with a timeout) is now relevant at daemon startup, since NewStandaloneDeployer performs the check at construction.

No blocking issues. Approving.

Clean seam. The `Deployer` interface rename is complete and mechanical (no leftover `KubernetesClient`/`kubernetes.New` callers), the redundant producer-side `KubernetesService` interface is correctly removed, `resolveDeployer` precedence (flag > DEPLOYER env > config incl. `THWAP_PAGESD_DEPLOYER` > default) is sound and well tested, and the standalone deployer fails fast on socket access. Referenced issues #333–#337 match the roadmap checkboxes. ## Suggestions 1. `cmd/thwap-pagesd/main.go` (`newDeployer`) — standalone's base domain comes from `cfg.Kubernetes.BaseDomain`, so standalone mode still reads the `kubernetes` config section. Consider a dedicated standalone config value (ties to #338) or document the reuse. Filed as #648. 2. `internal/docker/deployer.go` — the `package docker` doc comment is duplicated verbatim from `docker.go`; single package comment per package. Filed as #649. 3. `cmd/thwap-pagesd/main.go` — `newDeployer` branching (kubernetes/standalone) is untested; consider injectable constructor funcs or a fake-socket test. Filed as #650. 4. `internal/docker/deployer.go` — deferred lifecycle ops return ad-hoc errors; a shared `ErrNotImplemented` sentinel would let the API return 501 vs 500 until Phase 2. Filed as #651. ## Questions - Standalone mode boots and validates Docker access, but every site operation (Register/Publish/Preview) returns not-implemented, so a standalone daemon is inert until Phase 2. Understood as intended per the notes. - Note: #646 (bound `CheckAccess` with a timeout) is now relevant at daemon startup, since `NewStandaloneDeployer` performs the check at construction. No blocking issues. Approving.
fuzzy merged commit d97176f5fe into main 2026-08-13 13:57:02 +00:00
fuzzy deleted branch feat/deployer-selection 2026-08-13 13:57:02 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thwap/thwap-pagesd!647
No description provided.