Make Grafana/Metabase/Tailscale name errors terminal

The source-name worker keeps a generic name and marks the source
synced only when a resolver reports ErrTerminalNameResolution. The
Grafana and Metabase name resolvers, plus Tailscale via its shared
fetchUsers, returned a plain error on any non-2xx, so a revoked
credential (401/403) never reached the terminal path and the worker
re-claimed the row every drain cycle with no delay.

Route their non-2xx through nameStatusError so permanent 4xx wrap
ErrTerminalNameResolution while 5xx stay retryable. The sentinel is
inert on the ListAccounts sync path, which does not inspect it.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-07-22 14:36:36 +02:00
parent aeb7a1c8e6
commit 94552dbf11
4 changed files with 45 additions and 3 deletions

View File

@@ -61,6 +61,44 @@ func TestNameStatusError(t *testing.T) {
}
}
// TestNameResolversTerminalOnClientError guards the fix that makes the
// grafana, metabase and tailscale name resolvers surface a permanent 4xx as
// ErrTerminalNameResolution. Without it a revoked-credential source hot-loops
// the source-name worker, which never marks such a source synced.
func TestNameResolversTerminalOnClientError(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"message":"unauthorized"}`))
}),
)
t.Cleanup(srv.Close)
cases := []struct {
name string
resolver NameResolver
}{
{name: "grafana", resolver: NewGrafanaNameResolver(srv.Client(), srv.URL)},
{name: "metabase", resolver: NewMetabaseNameResolver(srv.Client(), srv.URL)},
{name: "tailscale", resolver: NewTailscaleNameResolver(&http.Client{Transport: &hostRewriter{target: srv.URL}})},
}
for _, tc := range cases {
t.Run(
tc.name,
func(t *testing.T) {
t.Parallel()
_, err := tc.resolver.ResolveInstanceName(context.Background())
require.Error(t, err)
assert.ErrorIs(t, err, ErrTerminalNameResolution)
},
)
}
}
// hostRewriter redirects requests to the configured target host so that
// resolvers with hardcoded production URLs (api.notion.com, etc.) can be
// pointed at an httptest server.