Skip to content

Commit 246de86

Browse files
committed
Support chainguard tag history extension
1 parent 4f31616 commit 246de86

2 files changed

Lines changed: 133 additions & 18 deletions

File tree

internal/explore/explore.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ func (h *handler) errHandler(hfe HandleFuncE) http.HandlerFunc {
178178
func (h *handler) renderResponse(w http.ResponseWriter, r *http.Request) error {
179179
qs := r.URL.Query()
180180

181+
if image := qs.Get("history"); image != "" {
182+
return h.renderHistory(w, r, strings.TrimPrefix(strings.TrimSpace(image), "https://"))
183+
}
181184
if image := qs.Get("image"); image != "" {
182185
return h.renderManifest(w, r, strings.TrimPrefix(strings.TrimSpace(image), "https://"))
183186
}
@@ -572,6 +575,105 @@ func (h *handler) renderManifest(w http.ResponseWriter, r *http.Request, image s
572575
return nil
573576
}
574577

578+
func (h *handler) tagHistory(w http.ResponseWriter, r *http.Request, ref name.Reference, u string) ([]byte, error) {
579+
repo := ref.Context().String()
580+
581+
auth := authn.Anonymous
582+
if h.keychain != nil {
583+
ref, err := name.NewRepository(repo)
584+
if err == nil {
585+
maybeAuth, err := h.keychain.Resolve(ref)
586+
if err == nil {
587+
auth = maybeAuth
588+
} else {
589+
logs.Debug.Printf("Resolve(%q) = %v", repo, err)
590+
}
591+
} else {
592+
logs.Debug.Printf("NewRepository(%q) = %v", repo, err)
593+
}
594+
}
595+
tr, err := h.transportFromCookie(w, r, repo, auth)
596+
if err != nil {
597+
return nil, err
598+
}
599+
600+
req, err := http.NewRequest(http.MethodGet, u, nil)
601+
if err != nil {
602+
return nil, err
603+
}
604+
605+
req.Header.Set("User-Agent", h.userAgent)
606+
607+
resp, err := tr.RoundTrip(req)
608+
if err != nil {
609+
return nil, err
610+
}
611+
defer resp.Body.Close()
612+
613+
if resp.StatusCode != http.StatusOK {
614+
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
615+
}
616+
617+
return io.ReadAll(resp.Body)
618+
}
619+
620+
// Render CGR history.
621+
func (h *handler) renderHistory(w http.ResponseWriter, r *http.Request, image string) error {
622+
ref, err := name.ParseReference(image, name.WeakValidation)
623+
if err != nil {
624+
return err
625+
}
626+
627+
if ref.Context().RegistryStr() != "cgr.dev" {
628+
return fmt.Errorf("not a cgr.dev image: %s", image)
629+
}
630+
631+
u := fmt.Sprintf("https://%s/v2/%s/_chainguard/history/%s", ref.Context().Registry, ref.Context().RepositoryStr(), ref.Identifier())
632+
633+
// TODO: Do we need to cache this?
634+
th, err := h.tagHistory(w, r, ref, u)
635+
if err != nil {
636+
return err
637+
}
638+
639+
header := HeaderData{
640+
Repo: ref.Context().String(),
641+
Reference: ref.String(),
642+
JQ: `curl -H "$(crane auth token -H ` + ref.Context().String() + `)" ` + u,
643+
}
644+
645+
if err := headerTmpl.Execute(w, TitleData{image}); err != nil {
646+
return fmt.Errorf("headerTmpl: %w", err)
647+
}
648+
649+
copied := *r.URL
650+
output := &jsonOutputter{
651+
w: w,
652+
u: &copied,
653+
fresh: []bool{},
654+
repo: ref.Context().String(),
655+
mt: r.URL.Query().Get("mt"),
656+
}
657+
658+
// Mutates header for bodyTmpl.
659+
b, err := h.jq(output, th, r, &header)
660+
if err != nil {
661+
return fmt.Errorf("h.jq: %w", err)
662+
}
663+
664+
if err := bodyTmpl.Execute(w, header); err != nil {
665+
return fmt.Errorf("bodyTmpl: %w", err)
666+
}
667+
668+
if err := h.renderContent(w, r, ref, b, output, copied); err != nil {
669+
return err
670+
}
671+
672+
fmt.Fprintf(w, footer)
673+
674+
return nil
675+
}
676+
575677
func (h *handler) renderReferrers(w http.ResponseWriter, r *http.Request, src string) error {
576678
ref, err := name.NewDigest(src)
577679
if err != nil {
@@ -1444,6 +1546,12 @@ func headerData(ref name.Reference, desc v1.Descriptor) *HeaderData {
14441546
if strings.Contains(handler, "?") {
14451547
sep = "&"
14461548
}
1549+
1550+
if _, ok := ref.(name.Tag); ok {
1551+
if ref.Context().RegistryStr() == "cgr.dev" {
1552+
handler = "?history="
1553+
}
1554+
}
14471555
return &HeaderData{
14481556
Repo: ref.Context().String(),
14491557
Reference: ref.String(),

internal/explore/render.go

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -545,27 +545,34 @@ func renderMap(w *jsonOutputter, o map[string]interface{}, raw *json.RawMessage)
545545
}
546546
}
547547
case "digest":
548-
if mt, ok := o["mediaType"]; ok {
549-
if s, ok := mt.(string); ok {
550-
h := v1.Hash{}
551-
if err := json.Unmarshal(v, &h); err != nil {
552-
log.Printf("Unmarshal digest %q: %v", string(v), err)
553-
} else {
554-
size := int64(0)
555-
if sz, ok := o["size"]; ok {
556-
if i64, ok := sz.(int64); ok {
557-
size = i64
558-
} else if f64, ok := sz.(float64); ok {
559-
size = int64(f64)
560-
}
561-
}
562-
563-
w.Linkify(s, h, size)
548+
h := v1.Hash{}
549+
if err := json.Unmarshal(v, &h); err != nil {
550+
log.Printf("Unmarshal digest %q: %v", string(v), err)
551+
} else {
552+
size := int64(0)
553+
if sz, ok := o["size"]; ok {
554+
if i64, ok := sz.(int64); ok {
555+
size = i64
556+
} else if f64, ok := sz.(float64); ok {
557+
size = int64(f64)
558+
}
559+
}
564560

565-
// Don't fall through to renderRaw.
566-
continue
561+
mediaType := w.u.Query().Get("mt")
562+
if mt, ok := o["mediaType"]; ok {
563+
if s, ok := mt.(string); ok {
564+
mediaType = s
567565
}
568566
}
567+
568+
if mediaType == "" && w.u.Query().Get("history") != "" {
569+
mediaType = string(types.OCIImageIndex)
570+
}
571+
572+
w.Linkify(mediaType, h, size)
573+
574+
// Don't fall through to renderRaw.
575+
continue
569576
}
570577
if name, ok := o["name"]; ok {
571578
// Set this for DSSE digest.name

0 commit comments

Comments
 (0)