feat(tpagectl): implement deployment rollback subcommand #570

Merged
fuzzy merged 2 commits from feat/cli-deployment-rollback into main 2026-08-06 10:35:05 +00:00
Owner

What

Implements deployment rollback end to end: the daemon's missing POST /api/v1/sites/{name}/rollback endpoint plus the tpagectl deployment rollback subcommand. This completes the Phase 3 deployment command group.

Daemon

  • SiteService.Rollback(ctx, siteName, version) now accepts an optional version: empty selects the highest remaining v* tag (preserving the webhook tag-deletion path, which passes ""); a non-empty version must be an existing tag, else a validation error (→ 400).
  • New POST /api/v1/sites/{name}/rollback handler (#211): owner-scoped (403), 200 with the new Deployment, 400 on invalid body / unknown tag / no tags, 404 when the site is missing.
  • Tests: service (specific version, auto-highest, unknown tag, no tags, unknown site) and handler (200/400/404).

CLI

  • Flags (#210): --site (required), --to-version (optional; latest if omitted), --force.
  • Confirm (#212): prompts Roll back site "X"? [y/N] via cmd.Root().Reader unless --force; aborts on no/empty input.
  • POST (#211): /api/v1/sites/{name}/rollback body {"to_version": ...}.
  • Display (#213): fetches the site status for the previous active version, then prints Rolled back site "X": vPrev -> vNew.
  • Friendly 404s; missing --site error.
  • Tests: --force happy path (path + body + prev/new output), confirm-yes, confirm-no abort (no POST), default version, 404, missing --site.

Why

Phase 3 roadmap task #214; the final deployment command.

Testing

  • Daemon: service + handler tests for versioned/auto rollback, unknown tag, no tags, 404, 400
  • CLI: force/confirm-yes/confirm-no, default version, 404, missing --site
  • go test -race ./... passes (18 packages)
  • golangci-lint run clean
  • pre-commit hooks pass (gofmt, go mod tidy, go test)

Breaking Changes

SiteService.Rollback signature gains a version parameter; the webhook receiver and test fakes were updated accordingly (empty string preserves existing behavior).

Notes

With all four subcommands implemented, the deployment package's errNotImplemented stub sentinel and its test are removed.

Closes #210
Closes #211
Closes #212
Closes #213
Closes #214

## What Implements `deployment rollback` end to end: the daemon's missing `POST /api/v1/sites/{name}/rollback` endpoint plus the tpagectl `deployment rollback` subcommand. This completes the Phase 3 deployment command group. **Daemon** - `SiteService.Rollback(ctx, siteName, version)` now accepts an optional version: empty selects the highest remaining `v*` tag (preserving the webhook tag-deletion path, which passes `""`); a non-empty version must be an existing tag, else a validation error (→ 400). - New `POST /api/v1/sites/{name}/rollback` handler (#211): owner-scoped (403), 200 with the new `Deployment`, 400 on invalid body / unknown tag / no tags, 404 when the site is missing. - Tests: service (specific version, auto-highest, unknown tag, no tags, unknown site) and handler (200/400/404). **CLI** - **Flags** (#210): `--site` (required), `--to-version` (optional; latest if omitted), `--force`. - **Confirm** (#212): prompts `Roll back site "X"? [y/N]` via `cmd.Root().Reader` unless `--force`; aborts on no/empty input. - **POST** (#211): `/api/v1/sites/{name}/rollback` body `{"to_version": ...}`. - **Display** (#213): fetches the site status for the previous active version, then prints `Rolled back site "X": vPrev -> vNew`. - Friendly 404s; missing `--site` error. - Tests: `--force` happy path (path + body + prev/new output), confirm-yes, confirm-no abort (no POST), default version, 404, missing `--site`. ## Why Phase 3 roadmap task #214; the final deployment command. ## Testing - [x] Daemon: service + handler tests for versioned/auto rollback, unknown tag, no tags, 404, 400 - [x] CLI: force/confirm-yes/confirm-no, default version, 404, missing `--site` - [x] `go test -race ./...` passes (18 packages) - [x] `golangci-lint run` clean - [x] pre-commit hooks pass (gofmt, go mod tidy, go test) ## Breaking Changes `SiteService.Rollback` signature gains a `version` parameter; the webhook receiver and test fakes were updated accordingly (empty string preserves existing behavior). ## Notes With all four subcommands implemented, the deployment package's `errNotImplemented` stub sentinel and its test are removed. Closes #210 Closes #211 Closes #212 Closes #213 Closes #214
Add the daemon POST /api/v1/sites/{name}/rollback endpoint and the
tpagectl deployment rollback subcommand.

Daemon: SiteService.Rollback now takes an optional version - empty selects
the highest remaining v* tag (preserving webhook behavior, which passes
empty), otherwise the version must be an existing tag. The handler is
owner-scoped and returns 200 with the new deployment, 400 on unknown tags
or no tags, 404 when the site is missing.

CLI: deployment rollback takes --site (required), optional --to-version
(latest if omitted), and --force to skip confirmation. It prompts for
confirmation, posts to the rollback endpoint, and prints the previous and
new active versions.
docs(roadmap): mark deployment rollback subcommand complete
All checks were successful
Test and Release / lint (pull_request) Successful in 4m27s
Test and Release / test (pull_request) Successful in 23m47s
aba5f3b7ce
Record completion of the tpagectl deployment rollback subcommand and its
daemon endpoint: --site with optional --to-version, confirmation with
--force, POST /api/v1/sites/{name}/rollback, and display of the previous
and new active versions. This completes the Phase 3 deployment command
group.

closes #210
closes #211
closes #212
closes #213
closes #214
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-06 10:33:32 +00:00
the.auditor left a comment

Summary

deployment rollback: daemon POST /api/v1/sites/{name}/rollback + tpagectl subcommand, with SiteService.Rollback gaining a version parameter. Verified: go test -race ./... passes (18 packages) and golangci-lint run ./... is clean. Closes #210-214, completing the Phase 3 deployment group.

Verified correctness: selectVersion reuse keeps version semantics consistent with promote (empty → highest v*, explicit → must exist, else 400); handler maps ErrNotFound→404 and "has no "→400; rollback response is the daemon Deployment; the webhook tag-deletion path is preserved via Rollback(..., "").

No blocking issues.

Suggestions (filed as issues)

  1. internal/tpagectl/deployment/rollback_test.go – non-404 error passthrough (500/403) untested; recurring gap (see #554, #557, #566). Filed as #571

References (covered by existing issues)

  1. internal/tpagectl/deployment/rollback.go:52 – previous-version status GET is best-effort and its error is dropped (err == nil && status.CurrentDeployment != nil → "unknown" fallback); same pattern as #569.
  2. confirmRollback duplicates confirmDelete; the inline 404 mapping is now a sixth copy; deploymentSiteStatus near-duplicates promote's siteStatus — extends #565/#561.
  3. The non-v-tag rollback classification question raised in #568 applies equally here via selectVersion.

Praise

  • Consistent selectVersion reuse between promote and rollback
  • Breaking Rollback signature handled cleanly across the webhook receiver and all test fakes
  • Confirmation UX mirrors site delete (y/N, abort on no/empty, --force)
  • errNotImplemented removal tidily completes the group
  • Strong daemon + CLI coverage (force/confirm/decline/default/404/missing-site, service + handler tests)
## Summary `deployment rollback`: daemon `POST /api/v1/sites/{name}/rollback` + tpagectl subcommand, with `SiteService.Rollback` gaining a version parameter. Verified: `go test -race ./...` passes (18 packages) and `golangci-lint run ./...` is clean. Closes #210-214, completing the Phase 3 deployment group. Verified correctness: `selectVersion` reuse keeps version semantics consistent with promote (empty → highest `v*`, explicit → must exist, else 400); handler maps ErrNotFound→404 and "has no "→400; rollback response is the daemon `Deployment`; the webhook tag-deletion path is preserved via `Rollback(..., "")`. No blocking issues. ## Suggestions (filed as issues) 1. `internal/tpagectl/deployment/rollback_test.go` – non-404 error passthrough (500/403) untested; recurring gap (see #554, #557, #566). Filed as #571 ## References (covered by existing issues) 1. `internal/tpagectl/deployment/rollback.go:52` – previous-version status GET is best-effort and its error is dropped (`err == nil && status.CurrentDeployment != nil` → "unknown" fallback); same pattern as #569. 2. `confirmRollback` duplicates `confirmDelete`; the inline 404 mapping is now a sixth copy; `deploymentSiteStatus` near-duplicates promote's `siteStatus` — extends #565/#561. 3. The non-v-tag rollback classification question raised in #568 applies equally here via `selectVersion`. ## Praise - Consistent `selectVersion` reuse between promote and rollback - Breaking `Rollback` signature handled cleanly across the webhook receiver and all test fakes - Confirmation UX mirrors `site delete` (y/N, abort on no/empty, `--force`) - `errNotImplemented` removal tidily completes the group - Strong daemon + CLI coverage (force/confirm/decline/default/404/missing-site, service + handler tests)
fuzzy merged commit aba5f3b7ce into main 2026-08-06 10:35:05 +00:00
fuzzy deleted branch feat/cli-deployment-rollback 2026-08-06 10:35:05 +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!570
No description provided.