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

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)
    })

}