Skip to content

Commit 2deb962

Browse files
authored
✨ feat: Discover mixtapes dynamically via registry catalog (#51)
## Summary - Replaces the hardcoded `knownMixtapes` list in `pkg/mixtapes/registry.go` with a live `/v2/_catalog` query via `go-containerregistry`'s `remote.Catalog`, filtering to the `mixtapes/` prefix - Falls back to a minimal offline list (`coder`) if the registry is unreachable, with a 10s timeout on the catalog query - Sorts `coder` first via a `priorityMixtapes` map since it's currently the only mixtape with a working multi-arch `:latest` manifest; remaining repos sort alphabetically ## Why The hardcoded list was stale (`coder-arm64`, `coder-x86`) and didn't reflect what's actually published at `download.stereos.ai` (`coder`, `coder-arm64`, `opencode-mixtape`). New mixtapes required a code change + release to surface in `mb mixtapes list`. This depends on the upstream `/v2/_catalog` pagination fix in speaker-wire (Link header termination), which is already deployed. ## Test plan - [x] `go build ./pkg/mixtapes/` clean - [x] `mb mixtapes list` against live `download.stereos.ai` returns all 3 published repos with `coder` first - [x] `mb mixtapes list coder` walks tags successfully (`latest`, `dev-0f0941e`) - [x] Fallback path returns `coder` when registry is unreachable Signed-off-by: Matt Yeazel <matt@papercompute.com>
1 parent 0583118 commit 2deb962

1 file changed

Lines changed: 80 additions & 9 deletions

File tree

pkg/mixtapes/registry.go

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
11
package mixtapes
22

33
import (
4+
"context"
45
"fmt"
56
"os"
7+
"sort"
68
"strings"
9+
"time"
710

811
"github.com/google/go-containerregistry/pkg/name"
912
"github.com/google/go-containerregistry/pkg/v1/remote"
1013

1114
"github.com/papercomputeco/masterblaster/pkg/ui"
1215
)
1316

14-
// knownMixtapes is the list of mixtape names published in the default registry.
15-
// Update this list when new mixtapes are added to download.stereos.ai.
16-
var knownMixtapes = []string{
17-
"coder-arm64",
18-
"coder-x86",
19-
}
17+
// catalogTimeout bounds how long ListCatalog will wait on the registry's
18+
// /v2/_catalog endpoint before falling back to the offline list.
19+
const catalogTimeout = 10 * time.Second
20+
21+
// fallbackMixtapes is used only when the registry catalog query fails
22+
// (offline, DNS, registry down). Keep this list minimal — it should only
23+
// contain names known to have a working :latest manifest.
24+
var fallbackMixtapes = []string{"coder"}
25+
26+
// priorityMixtapes are surfaced first in catalog listings. Lower number =
27+
// higher priority; entries not in the map sort after all priority entries
28+
// and are then ordered alphabetically. Currently only "coder" has a fully
29+
// working multi-arch :latest manifest; remove this bias once the other
30+
// mixtapes are republished.
31+
var priorityMixtapes = map[string]int{"coder": 0}
2032

2133
// CatalogEntry describes a mixtape repository in the remote registry.
2234
type CatalogEntry struct {
@@ -30,16 +42,75 @@ type TagEntry struct {
3042
Tag string // Tag string (e.g. "latest", "0.1.0")
3143
}
3244

33-
// ListCatalog returns the known mixtape repositories in the default registry.
45+
// ListCatalog queries the OCI registry's /v2/_catalog endpoint for the
46+
// repositories under the mixtapes/ prefix and returns them as CatalogEntries.
47+
// On network failure or an empty result it falls back to a small hardcoded
48+
// list of names known to have a working :latest manifest.
3449
func ListCatalog() ([]CatalogEntry, error) {
50+
reg, err := name.NewRegistry(DefaultDownloadRegistry)
51+
if err != nil {
52+
return nil, fmt.Errorf("parsing registry %q: %w", DefaultDownloadRegistry, err)
53+
}
54+
55+
ctx, cancel := context.WithTimeout(context.Background(), catalogTimeout)
56+
defer cancel()
57+
58+
repos, err := remote.Catalog(ctx, reg)
59+
if err != nil {
60+
ui.Warn("registry catalog query failed (%v); using fallback list", err)
61+
return fallbackCatalog(), nil
62+
}
63+
64+
prefix := defaultRepoPrefix + "/"
3565
var entries []CatalogEntry
36-
for _, m := range knownMixtapes {
66+
for _, r := range repos {
67+
short := strings.TrimPrefix(r, prefix)
68+
if short == "" || short == r {
69+
// Either empty after trimming, or the prefix wasn't there at all.
70+
continue
71+
}
3772
entries = append(entries, CatalogEntry{
73+
Name: short,
74+
Repo: DefaultDownloadRegistry + "/" + r,
75+
})
76+
}
77+
78+
if len(entries) == 0 {
79+
return fallbackCatalog(), nil
80+
}
81+
82+
sortCatalog(entries)
83+
return entries, nil
84+
}
85+
86+
// sortCatalog orders entries by priorityMixtapes first, then alphabetically
87+
// by short name. Stable so equal-priority entries keep input order.
88+
func sortCatalog(entries []CatalogEntry) {
89+
sort.SliceStable(entries, func(i, j int) bool {
90+
pi, oki := priorityMixtapes[entries[i].Name]
91+
pj, okj := priorityMixtapes[entries[j].Name]
92+
switch {
93+
case oki && !okj:
94+
return true
95+
case !oki && okj:
96+
return false
97+
case oki && okj && pi != pj:
98+
return pi < pj
99+
default:
100+
return entries[i].Name < entries[j].Name
101+
}
102+
})
103+
}
104+
105+
func fallbackCatalog() []CatalogEntry {
106+
out := make([]CatalogEntry, 0, len(fallbackMixtapes))
107+
for _, m := range fallbackMixtapes {
108+
out = append(out, CatalogEntry{
38109
Name: m,
39110
Repo: DefaultDownloadRegistry + "/" + defaultRepoPrefix + "/" + m,
40111
})
41112
}
42-
return entries, nil
113+
return out
43114
}
44115

45116
// ListTags queries the OCI registry for all tags of a given mixtape.

0 commit comments

Comments
 (0)