Merge pull request #1676 from entireio/paul/mirror-get-cluster-routing · Entire
Merge pull request #1676 from entireio/paul/mirror-get-cluster-routing
e980d1b→main·
Soph·1w ago·3 files·+169 added/-10 removed
fix(repo): resolve mirror get clone URLs via that cluster's login server
Changes
3
cmd/entire/cli
Mcorecmd.go+25/-3
Mrepo_mirror.go+35/-7
Mrepo_mirror_test.go+109
214 unmodified lines
// field/value list (default) or raw JSON (--json), reusing the same column
// definition as the matching list view.
func runCoreObject[T any](cmd *cobra.Command, headers []string, row func(T) []string, fn func(ctx context.Context, c *coreapi.Client) (*T, error)) error {
return runCore(cmd, func(ctx context.Context, c *coreapi.Client) error {
return runCore(cmd, renderCoreObject(cmd, headers, row, fn))
})
}
// runCoreObjectForCluster is runCoreObject for a resource-provider command (see
// runCoreForCluster): identical field/JSON rendering, but dialing the core that
// fronts clusterHost rather than the active context.
func runCoreObjectForCluster[T any](cmd *cobra.Command, clusterHost string, headers []string, row func(T) []string, fn func(ctx context.Context, c *coreapi.Client) (*T, error)) error {
return runCoreForCluster(cmd, clusterHost, renderCoreObject(cmd, headers, row, fn))
}
// renderCoreObject builds the run-function shared by runCoreObject and
// runCoreObjectForCluster: fetch via fn, then render as a field/value list
// (default) or raw JSON (--json). Kept separate from the client-selection so
// the two object variants differ only in which core they dial (mirroring
// renderCoreList).
func renderCoreObject[T any](cmd *cobra.Command, headers []string, row func(T) []string, fn func(ctx context.Context, c *coreapi.Client) (*T, error)) func(context.Context, *coreapi.Client) error {
return func(ctx context.Context, c *coreapi.Client) error {
item, err := fn(ctx, c)
if err != nil {
return err
}
return printFields(cmd.OutOrStdout(), headers, row(*item))
}
}
// TestRepoMirrorGet_Routing pins which core `mirror get <ref>` dials. A clone
// URL names its cluster, so it must be resolved on the core fronting that
// cluster (clusterCoreClient), not the active context — the original bug:
// `mirror get entire://<cluster>/…` for a cluster in a federation other than
// the active login failed with "no mirror matching" until the user switched
// contexts. A ULID carries no cluster coordinate and stays on the active
// context; an unparseable ref must error before dialing anything.
func TestRepoMirrorGet_Routing(t *testing.T) {
const mirrorULID = "0123456789ABCDEFGHJKMNPQRS"
const clusterHost = "eukanuba.partial.to"
const cloneURL = "entire://" + clusterHost + "/gh/entirehq/librarian"
// mirrorServer answers both the list (clone-URL resolution) and the
// GetMirror-by-ULID calls for the librarian mirror.
mirrorServer := func(t *testing.T) *httptest.Server {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.URL.Path {
case mirrorsAPIPath:
assert.NoError(t, printJSON(w, &coreapi.ListMirrorsOutputBody{Mirrors: []coreapi.Mirror{
{MirrorId: mirrorULID, Owner: "entirehq", Repo: "librarian", ClusterHost: clusterHost},
}}))
case mirrorsAPIPath + "/" + mirrorULID:
assert.NoError(t, printJSON(w, &coreapi.Mirror{
MirrorId: mirrorULID, Owner: "entirehq", Repo: "librarian", ClusterHost: clusterHost,
IsPrivate: coreapi.NewOptBool(true),
}))
default:
t.Errorf("unexpected request path %q", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
}
}))
t.Cleanup(srv.Close)
return srv
}
t.Run("clone URL dials the cluster's core, not the active context", func(t *testing.T) {
srv := mirrorServer(t)
seamActive(t, func(context.Context) (*coreapi.Client, error) {
t.Error("clone-URL get dialed the active context's core")
return nil, errors.New("wrong core")
})
var gotHost string
seamCluster(t, func(_ context.Context, host string) (*coreapi.Client, error) {
gotHost = host
return coreapi.NewWithBearer(srv.URL, "tok")
})
out, err := runGet(t, cloneURL)
require.NoError(t, err)
require.Equal(t, clusterHost, gotHost, "must resolve on the clone URL's cluster")
require.Contains(t, out, "entirehq/librarian")
require.Contains(t, out, cloneURL)
})
}