feat(site): implement rollback capability #322

Merged
fuzzy merged 1 commit from feat/rollback into main 2026-08-03 23:39:31 +00:00
Owner

What

Add rollback when a version tag is deleted:

  • Detect tag deletion (#89) — the webhook receiver routes tag refs with an all-zero after SHA to SiteService.Rollback (previously tag deletion fell through to Publish).
  • Reselect highest remaining tag (#90)Rollback lists the repository tags and selects the highest remaining v* via pkg/version.Highest.
  • Revert Kubernetes deployment (#91)Rollback updates the Kubernetes resources via KubernetesService.PublishSite and records an active Deployment at the reselected version.
  • Audit log (#92) — new AuditEvent model + AuditRepository (in-memory). Publish, preview, and rollback actions record events (AuditPublish, AuditPreview, AuditRollback). Wired through NewService.

Why

Phase 7 task #93 — rolling back a site when its latest version tag is removed.

Testing

  • go build ./...
  • go vet ./...
  • go test -race ./...Rollback (happy path, no tags, unknown site), audit recording on publish + rollback, audit repository, receiver tag-deletion routing; full suite passes
  • gofmt -l ./cmd ./internal ./pkg clean
  • golangci-lint run — 0 issues
  • pre-commit hooks all pass

Breaking Changes

None.

Notes

  • Rollback reuses PublishSite; content population into the serving volume remains deferred (same limitation as #73).
  • Issues #89–#93 were closed via the Forgejo API as part of this task per the workflow.

Closes #89
Closes #90
Closes #91
Closes #92
Closes #93

## What Add rollback when a version tag is deleted: - **Detect tag deletion (#89)** — the webhook receiver routes tag refs with an all-zero `after` SHA to `SiteService.Rollback` (previously tag deletion fell through to `Publish`). - **Reselect highest remaining tag (#90)** — `Rollback` lists the repository tags and selects the highest remaining `v*` via `pkg/version.Highest`. - **Revert Kubernetes deployment (#91)** — `Rollback` updates the Kubernetes resources via `KubernetesService.PublishSite` and records an active `Deployment` at the reselected version. - **Audit log (#92)** — new `AuditEvent` model + `AuditRepository` (in-memory). Publish, preview, and rollback actions record events (`AuditPublish`, `AuditPreview`, `AuditRollback`). Wired through `NewService`. ## Why Phase 7 task #93 — rolling back a site when its latest version tag is removed. ## Testing - [x] `go build ./...` - [x] `go vet ./...` - [x] `go test -race ./...` — `Rollback` (happy path, no tags, unknown site), audit recording on publish + rollback, audit repository, receiver tag-deletion routing; full suite passes - [x] `gofmt -l ./cmd ./internal ./pkg` clean - [x] `golangci-lint run` — 0 issues - [x] pre-commit hooks all pass ## Breaking Changes None. ## Notes - `Rollback` reuses `PublishSite`; content population into the serving volume remains deferred (same limitation as #73). - Issues #89–#93 were closed via the Forgejo API as part of this task per the workflow. Closes #89 Closes #90 Closes #91 Closes #92 Closes #93
feat(site): implement rollback capability
All checks were successful
Test and Release / lint (pull_request) Successful in 4m24s
Test and Release / test (pull_request) Successful in 23m15s
58750c9f41
Add rollback when a version tag is deleted:

- webhook receiver detects tag deletion (IsTag ref with an all-zero
  after SHA) and routes to SiteService.Rollback instead of Publish
- SiteService.Rollback reselects the highest remaining v* tag, updates
  the Kubernetes resources via PublishSite, records an active
  Deployment, and writes an audit event
- Audit log: AuditEvent model and AuditRepository (in-memory), wired
  into the service; publish, preview, and rollback actions record
  events
- New config-independent audit wiring in main

Add tests for Rollback (happy path, no tags, unknown site), audit
recording on publish and rollback, the audit repository, and receiver
tag-deletion routing. Mark task done in ROADMAP.md.

closes #89
closes #90
closes #91
closes #92
closes #93
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-03 23:16:48 +00:00
the.auditor left a comment

Review Summary

Verified locally on the PR head (58750c9): go build, go vet, gofmt, golangci-lint run all clean; full suite passes with -race (Rollback happy/no-tags/unknown-site, audit recording, audit repo, receiver tag-deletion routing).

Clean, correct implementation. The receiver now routes tag deletions (IsTag + IsDeleted) to Rollback — closing the gap I flagged in #142/#144 where tag deletions fell through to a redundant Publish. Rollback reselects the highest remaining v* (the deleted tag is absent from ListTags), republishes, and records an active deployment plus a rollback audit event. The AuditRepository (in-memory, newest-first) and logAudit wiring across publish/preview/rollback are tidy, and all NewService call sites were updated consistently.

No blocking issues.

Non-blocking observations:

  1. logAudit silently drops errors (_ = s.audit.Create(...)) — deliberate best-effort design so audit failure can't fail a publish, but worth documenting once a persistent audit store lands.
  2. Rollback and Publish are nearly identical apart from the audit event and error message — the shared "select highest tag + PublishSite + record deployment" path could be factored out once content population (#73's deferred follow-on) lands.
  3. As with #73, the reselected version isn't plumbed into the Kubernetes deployment (SITE_VERSION stays hardcoded) — acknowledged deferral; the "rollback" currently re-upserts identical resources.

Approving.

## Review Summary Verified locally on the PR head (`58750c9`): `go build`, `go vet`, `gofmt`, `golangci-lint run` all clean; full suite passes with `-race` (Rollback happy/no-tags/unknown-site, audit recording, audit repo, receiver tag-deletion routing). Clean, correct implementation. The receiver now routes tag deletions (`IsTag` + `IsDeleted`) to `Rollback` — closing the gap I flagged in #142/#144 where tag deletions fell through to a redundant `Publish`. `Rollback` reselects the highest remaining `v*` (the deleted tag is absent from `ListTags`), republishes, and records an active deployment plus a `rollback` audit event. The `AuditRepository` (in-memory, newest-first) and `logAudit` wiring across publish/preview/rollback are tidy, and all `NewService` call sites were updated consistently. **No blocking issues.** Non-blocking observations: 1. `logAudit` silently drops errors (`_ = s.audit.Create(...)`) — deliberate best-effort design so audit failure can't fail a publish, but worth documenting once a persistent audit store lands. 2. `Rollback` and `Publish` are nearly identical apart from the audit event and error message — the shared "select highest tag + PublishSite + record deployment" path could be factored out once content population (#73's deferred follow-on) lands. 3. As with #73, the reselected version isn't plumbed into the Kubernetes deployment (`SITE_VERSION` stays hardcoded) — acknowledged deferral; the "rollback" currently re-upserts identical resources. Approving.
fuzzy merged commit 58750c9f41 into main 2026-08-03 23:39:31 +00:00
fuzzy deleted branch feat/rollback 2026-08-03 23:39:31 +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!322
No description provided.