repo create: stamp a usable entire:// remote in the JSON output · Entire
repo create: stamp a usable entire:// remote in the JSON output
7de7dce→main·
stiak·1mo ago·2 files·+210 added/-1 removed
entire repo create echoed the raw Repo wire JSON, which has clusterHost
and path but no ready-to-use clone/remote URL. Synthesize a remote field
of the form entire://
The path the API returns carries a leading slash, so trim it before
joining to avoid a doubled separator. Omit the field entirely when either
coordinate is unresolved (e.g. a still-provisioning repo) rather than
emit a half-formed URL. The synthesis only fills a gap: if the wire object
already carries a remote (a future first-class field, or one arriving via
additional properties) it's left untouched so the server value wins.
Repo carries a custom marshaler plus additional properties, so the repo is round-tripped through its own encoder and the remote merged in rather than embedded in a wrapper struct.
Sessions
Changes
2
cmd/entire/cli
Mrepo.go+54/-1
Arepo_test.go+156
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/spf13/cobra"
)
// repoRemoteURL synthesizes the entire:// clone/remote URL for a repo from
// its resolved cluster host and path — the form `git clone` and
// `git remote add` accept, which git-remote-entire reads back as the repo
// slug from the URL path. Returns "" when either coordinate is missing (a
// still-provisioning repo may not have them yet); a half-formed URL is worse
// than none.
func repoRemoteURL(r coreapi.Repo) string {
host := strings.TrimSpace(r.ClusterHost.Or(""))
path := strings.TrimSpace(r.Path.Or(""))
if host == "" || path == "" {
return ""
}
return "entire://" + host + "/" + strings.TrimPrefix(path, "/")
}
// repoCreateOutput renders a created repo as JSON with a synthesized `remote`
// field merged in — the entire:// URL callers paste into `git clone` or
// `git remote add`. The repo carries a custom marshaler plus arbitrary
// additional properties, so it can't simply be embedded in a wrapper struct;
// instead it's round-tripped through its own encoder and the remote is merged
// into the resulting object. The synthesis only fills a gap: if the wire
// object already carries a `remote` (a future first-class field, or one
// arriving via additional properties) it's left untouched, so the
// server-provided value always wins. The field is omitted when the clone
// coordinates aren't resolvable yet rather than emitted half-formed.
func repoCreateOutput(r *coreapi.Repo) (any, error) {
raw, err := json.Marshal(r)
if err != nil {
return nil, fmt.Errorf("encode repo: %w", err)
}
var obj map[string]json.RawMessage
if err := json.Unmarshal(raw, &obj); err != nil {
return nil, fmt.Errorf("decode repo: %w", err)
}
if _, ok := obj["remote"]; !ok {
if remote := repoRemoteURL(*r); remote != "" {
encoded, err := json.Marshal(remote)
if err != nil {
return nil, fmt.Errorf("encode remote: %w", err)
}
obj["remote"] = encoded
}
}
return obj, nil
}
func TestRepoRemoteURL(t *testing.T) {
t.Parallel()
tests := []struct {
name string
repo coreapi.Repo
want string
}{
{
name: "host and path produce an entire:// URL",
repo: coreapi.Repo{
ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io"),
Path: coreapi.NewOptString("acme/web"),
},
want: "entire://aws-us-east-2.entire.io/acme/web",
},
{
name: "leading slash on path is not doubled",
repo: coreapi.Repo{
ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io"),
Path: coreapi.NewOptString("/acme/web"),
},
want: "entire://aws-us-east-2.entire.io/acme/web",
},
{
name: "missing host yields no URL",
repo: coreapi.Repo{Path: coreapi.NewOptString("acme/web")},
want: "",
},
{
name: "missing path yields no URL",
repo: coreapi.Repo{ClusterHost: coreapi.NewOptString("aws-us-east-2.entire.io")},
want: "",
},
{
name: "blank coordinates yield no URL",
repo: coreapi.Repo{
ClusterHost: coreapi.NewOptString(" "),
Path: coreapi.NewOptString(""),
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := repoRemoteURL(tt.repo); got != tt.want {
t.Errorf("repoRemoteURL() = %q, want %q", got, tt.want)
}
})
}
}