Close push→build→image→CR: a proven, clean-semver image rolls live by patching the matching operator hanzo.ai/v1 Service CR's spec.image directly, so the operator reconciles the Deployment. This is the in-cluster, direct-CR replacement for universe's image-update.yml GitOps hop (repository_dispatch → PR → ArgoCD), with the same determinism (clean-semver only; resolve CR by metadata.name) and no git round-trip. - build.go: RegisterServiceReleaser / OnServiceRelease / ServiceReleaserRegistered inversion seam (mirrors RegisterPushBuilder), so a build-completion path rolls a proven image with no cloud⇄paas import cycle. - clients/paas/release.go: releaseService — resolve CR by name (main-first), clean-semver gate (IsSemverTag), idempotent merge-patch of spec.image; registered at Mount as the releaser impl (paas owns the first-party Service CR plane). - clients/platform/release.go: rolloutRelease — native CR patch primary, universe image-update dispatch kept as an additive GitOps mirror during cutover. Tests: split/gate, patch, idempotent, reject-floating, unknown-service, main-first, fail-closed, seam dispatch/no-op. go build + go test green (paas, platform, root).
44 lines
1.5 KiB
Go
44 lines
1.5 KiB
Go
package cloud
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// TestOnServiceReleaseNoop proves the dispatch seam is a safe no-op when no
|
|
// releaser is registered (a binary without the paas control plane co-resident) —
|
|
// mirroring OnGitPush's contract.
|
|
func TestOnServiceReleaseNoop(t *testing.T) {
|
|
RegisterServiceReleaser(nil)
|
|
if ServiceReleaserRegistered() {
|
|
t.Fatal("ServiceReleaserRegistered() = true with no releaser registered")
|
|
}
|
|
if err := OnServiceRelease(context.Background(), ServiceReleaseEvent{Service: "cloud", Image: "ghcr.io/hanzoai/cloud:v1.0.0"}); err != nil {
|
|
t.Fatalf("OnServiceRelease with no releaser = %v, want nil no-op", err)
|
|
}
|
|
}
|
|
|
|
// TestOnServiceReleaseDispatch proves a registered releaser receives the exact
|
|
// event and its error propagates — the one inversion point paas installs.
|
|
func TestOnServiceReleaseDispatch(t *testing.T) {
|
|
var got ServiceReleaseEvent
|
|
sentinel := errors.New("boom")
|
|
RegisterServiceReleaser(func(_ context.Context, ev ServiceReleaseEvent) error {
|
|
got = ev
|
|
return sentinel
|
|
})
|
|
t.Cleanup(func() { RegisterServiceReleaser(nil) })
|
|
|
|
if !ServiceReleaserRegistered() {
|
|
t.Fatal("ServiceReleaserRegistered() = false after registration")
|
|
}
|
|
want := ServiceReleaseEvent{Service: "hanzo-app", Image: "ghcr.io/hanzoai/hanzo-app:v1.42.15", SHA: "abc1234"}
|
|
if err := OnServiceRelease(context.Background(), want); !errors.Is(err, sentinel) {
|
|
t.Fatalf("OnServiceRelease error = %v, want sentinel", err)
|
|
}
|
|
if got != want {
|
|
t.Fatalf("releaser received %+v, want %+v", got, want)
|
|
}
|
|
}
|