mirror of
https://github.com/hanzoai/deploy.git
synced 2026-10-07 08:44:50 +00:00
House rule: /v1/ is the API prefix, everywhere, and /api/ is never added on top of it. The api.* subdomain names the API; a path prefix restating it is noise. This applies it to the whole route surface: /api/v1/* -> /v1/* 106 proto annotations, 109 gateway patterns /api/version -> /v1/version /api/badge -> /v1/badge /api/webhook -> /v1/webhook (server + applicationset controller) /api/dex/* -> /v1/dex/* (constants; dex is built, not deployed) The grpc-gateway .pb.gw.go files are generated, and the plugin toolchain is not on this machine, so they were rewritten by a script that applies exactly the transform codegen would: for /api/v1 paths, drop the leading "api" literal-push opcode pair and decrement every LitPush(2,N)/Capture(5,N) pool index; for /api/version, rename the segment. ConcatN(4,N) operands are counts, not pool indexes, and are untouched. The .proto annotations moved in the same commit, so the next real codegen run is a no-op against these files. MustPattern panics at package init on a malformed pattern, so every rewritten pattern is validated by any import of pkg/apiclient — those tests pass. Also moved: assets/swagger.json (82 paths), the UI's two roots (apiRoot() '/v1', version-service '/v1/version') and its two relative composers (app logs download, badge panel), the five audit-pattern regexes in server.go, and the tests that asserted the old paths. terminal_test's request URL claimed /api/v1/terminal, a route that never existed — the handler is mounted at /terminal; the fixture now says so. NOT touched, deliberately: /api/v3 in GitHub Enterprise base-url examples (GitHub's URL shape, not ours), util/vendored/kubeclientmetrics (parses the KUBERNETES API server's /api/v1 paths), and util/http's cookie-path fixture (arbitrary string exercising slash normalization). go build ./... clean. Pre-existing failures in badge/session/webhook/account tests reproduce identically on the parent commit (rebrand fallout — badge colors, cookie name length, dex issuer) and are not made worse here. External consumers checked: universe has zero references to cd.hanzo.ai/api; the CLI speaks gRPC, not the REST gateway; UI and server ship in one image so they move together. Co-authored-by: Hanzo Dev <[email protected]>
242 lines
5.8 KiB
Go
242 lines
5.8 KiB
Go
package webhook
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/sirupsen/logrus/hooks/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/hanzoai/cd/pkg/apis/application/v1alpha1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
kubetesting "k8s.io/client-go/testing"
|
|
)
|
|
|
|
func TestNormalizeOCI(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
url string
|
|
expected string
|
|
}{
|
|
{"strips oci:// prefix and lowercases", "oci://GHCR.IO/USER/REPO", "ghcr.io/user/repo"},
|
|
{"strips oci:// prefix and trailing slash", "oci://ghcr.io/user/repo/", "ghcr.io/user/repo"},
|
|
{"already normalized with prefix", "oci://ghcr.io/user/repo", "ghcr.io/user/repo"},
|
|
{"without oci:// prefix", "ghcr.io/user/repo", "ghcr.io/user/repo"},
|
|
{"uppercase without prefix", "GHCR.IO/USER/REPO", "ghcr.io/user/repo"},
|
|
{"empty", "", ""},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got := normalizeOCI(tt.url)
|
|
assert.Equal(t, tt.expected, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGHCRHandlerCanHandle(t *testing.T) {
|
|
t.Parallel()
|
|
h := newGHCRParser("")
|
|
|
|
tests := []struct {
|
|
name string
|
|
event string
|
|
expected bool
|
|
}{
|
|
{"package event", "package", true},
|
|
{"registry package event", "registry_package", false},
|
|
{"push event", "push", false},
|
|
{"empty", "", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/", http.NoBody)
|
|
req.Header.Set("X-GitHub-Event", tt.event)
|
|
assert.Equal(t, tt.expected, h.CanHandle(req))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryPackageEvent(t *testing.T) {
|
|
hook := test.NewGlobal()
|
|
h := NewMockHandler(nil, []string{})
|
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/v1/webhook", http.NoBody)
|
|
req.Header.Set("X-GitHub-Event", "package")
|
|
payload, err := os.ReadFile("testdata/ghcr-package-event.json")
|
|
require.NoError(t, err)
|
|
req.Body = io.NopCloser(bytes.NewReader(payload))
|
|
|
|
w := httptest.NewRecorder()
|
|
h.Handler(w, req)
|
|
h.Shutdown()
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assertLogContains(t, hook, "Received registry webhook event")
|
|
}
|
|
|
|
func TestHandleRegistryEvent_RefreshMatchingApp(t *testing.T) {
|
|
hook := test.NewGlobal()
|
|
|
|
patchedApps := []string{}
|
|
|
|
reaction := func(action kubetesting.Action) (bool, runtime.Object, error) {
|
|
patch := action.(kubetesting.PatchAction)
|
|
patchedApps = append(patchedApps, patch.GetName())
|
|
return true, nil, nil
|
|
}
|
|
|
|
h := NewMockHandler(
|
|
&reactorDef{"patch", "applications", reaction},
|
|
[]string{},
|
|
&v1alpha1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "oci-app",
|
|
Namespace: "cd",
|
|
},
|
|
Spec: v1alpha1.ApplicationSpec{
|
|
Sources: v1alpha1.ApplicationSources{
|
|
{
|
|
RepoURL: "oci://ghcr.io/user/repo",
|
|
TargetRevision: "1.0.0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
event := &RegistryEvent{
|
|
RegistryURL: "ghcr.io",
|
|
Repository: "user/repo",
|
|
Tag: "1.0.0",
|
|
}
|
|
|
|
h.HandleRegistryEvent(event)
|
|
|
|
assert.Contains(t, patchedApps, "oci-app")
|
|
assert.Contains(t, hook.LastEntry().Message, "Requested app 'oci-app' refresh")
|
|
}
|
|
|
|
func TestHandleRegistryEvent_RepoMismatch(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
hook := test.NewGlobal()
|
|
|
|
h := NewMockHandler(nil, []string{},
|
|
&v1alpha1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "oci-app",
|
|
Namespace: "cd",
|
|
},
|
|
Spec: v1alpha1.ApplicationSpec{
|
|
Sources: v1alpha1.ApplicationSources{
|
|
{
|
|
RepoURL: "oci://ghcr.io/other/repo",
|
|
TargetRevision: "1.0.0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
event := &RegistryEvent{
|
|
RegistryURL: "ghcr.io",
|
|
Repository: "user/repo",
|
|
Tag: "1.0.0",
|
|
}
|
|
|
|
h.HandleRegistryEvent(event)
|
|
assert.Contains(t, hook.LastEntry().Message, "Skipping app: OCI repository URLs do not match")
|
|
}
|
|
|
|
func TestHandleRegistryEvent_RevisionMismatch(t *testing.T) {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
hook := test.NewGlobal()
|
|
|
|
h := NewMockHandler(
|
|
nil,
|
|
[]string{},
|
|
&v1alpha1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "oci-app",
|
|
Namespace: "cd",
|
|
},
|
|
Spec: v1alpha1.ApplicationSpec{
|
|
Sources: v1alpha1.ApplicationSources{
|
|
{
|
|
RepoURL: "oci://ghcr.io/user/repo",
|
|
TargetRevision: "2.0.0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
event := &RegistryEvent{
|
|
RegistryURL: "ghcr.io",
|
|
Repository: "user/repo",
|
|
Tag: "1.0.0",
|
|
}
|
|
|
|
h.HandleRegistryEvent(event)
|
|
assert.Contains(t, hook.LastEntry().Message, "Skipping app: revision does not match targetRevision")
|
|
}
|
|
|
|
func TestHandleRegistryEvent_NamespaceFiltering(t *testing.T) {
|
|
t.Parallel()
|
|
patched := []string{}
|
|
|
|
reaction := func(action kubetesting.Action) (bool, runtime.Object, error) {
|
|
patch := action.(kubetesting.PatchAction)
|
|
patched = append(patched, patch.GetNamespace())
|
|
return true, nil, nil
|
|
}
|
|
|
|
h := NewMockHandler(
|
|
&reactorDef{"patch", "applications", reaction},
|
|
[]string{"team-*"},
|
|
&v1alpha1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "team-a",
|
|
},
|
|
Spec: v1alpha1.ApplicationSpec{
|
|
Sources: v1alpha1.ApplicationSources{
|
|
{RepoURL: "oci://ghcr.io/user/repo", TargetRevision: "1.0.0"},
|
|
},
|
|
},
|
|
},
|
|
&v1alpha1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app2",
|
|
Namespace: "kube-system",
|
|
},
|
|
Spec: v1alpha1.ApplicationSpec{
|
|
Sources: v1alpha1.ApplicationSources{
|
|
{RepoURL: "oci://ghcr.io/user/repo", TargetRevision: "1.0.0"},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
event := &RegistryEvent{
|
|
RegistryURL: "ghcr.io",
|
|
Repository: "user/repo",
|
|
Tag: "1.0.0",
|
|
}
|
|
|
|
h.HandleRegistryEvent(event)
|
|
|
|
assert.Contains(t, patched, "team-a")
|
|
assert.NotContains(t, patched, "kube-system")
|
|
}
|