1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package ociserver
16
17 import (
18 "bytes"
19 "context"
20 "encoding/json"
21 "fmt"
22 "io"
23 "net/http"
24 "net/url"
25 "strconv"
26
27 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
28
29 "cuelabs.dev/go/oci/ociregistry"
30 "cuelabs.dev/go/oci/ociregistry/internal/ocirequest"
31 )
32
33 const maxPageSize = 10000
34
35 type catalog struct {
36 Repos []string `json:"repositories"`
37 }
38
39 type listTags struct {
40 Name string `json:"name"`
41 Tags []string `json:"tags"`
42 }
43
44 func (r *registry) handleTagsList(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) error {
45 tags, link, err := r.nextListResults(req, rreq, r.backend.Tags(ctx, rreq.Repo, rreq.ListLast))
46 if err != nil {
47 return err
48 }
49 msg, _ := json.Marshal(listTags{
50 Name: rreq.Repo,
51 Tags: tags,
52 })
53 if link != "" {
54 resp.Header().Set("Link", link)
55 }
56 resp.Header().Set("Content-Length", strconv.Itoa(len(msg)))
57 resp.WriteHeader(http.StatusOK)
58 resp.Write(msg)
59 return nil
60 }
61
62 func (r *registry) handleCatalogList(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) (_err error) {
63 repos, link, err := r.nextListResults(req, rreq, r.backend.Repositories(ctx, rreq.ListLast))
64 if err != nil {
65 return err
66 }
67 msg, err := json.Marshal(catalog{
68 Repos: repos,
69 })
70 if err != nil {
71 return err
72 }
73 if link != "" {
74 resp.Header().Set("Link", link)
75 }
76 resp.Header().Set("Content-Length", strconv.Itoa(len(msg)))
77 resp.WriteHeader(http.StatusOK)
78 io.Copy(resp, bytes.NewReader([]byte(msg)))
79 return nil
80 }
81
82
83 func (r *registry) handleReferrersList(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) (_err error) {
84 if r.opts.DisableReferrersAPI {
85 return withHTTPCode(http.StatusNotFound, fmt.Errorf("referrers API has been disabled"))
86 }
87
88 im := &ocispec.Index{
89 Versioned: v2,
90 MediaType: mediaTypeOCIImageIndex,
91 }
92
93
94 it := r.backend.Referrers(ctx, rreq.Repo, ociregistry.Digest(rreq.Digest), "")
95
96 it(func(desc ociregistry.Descriptor, err error) bool {
97 if err != nil {
98 _err = err
99 return false
100 }
101 im.Manifests = append(im.Manifests, desc)
102 return true
103 })
104 if _err != nil {
105 return _err
106 }
107 msg, err := json.Marshal(im)
108 if err != nil {
109 return err
110 }
111 resp.Header().Set("Content-Length", strconv.Itoa(len(msg)))
112 resp.Header().Set("Content-Type", "application/vnd.oci.image.index.v1+json")
113 resp.WriteHeader(http.StatusOK)
114 resp.Write(msg)
115 return nil
116 }
117
118 func (r *registry) nextListResults(req *http.Request, rreq *ocirequest.Request, itemsIter ociregistry.Seq[string]) (items []string, link string, _err error) {
119 if r.opts.MaxListPageSize > 0 && rreq.ListN > r.opts.MaxListPageSize {
120 return nil, "", ociregistry.NewError(fmt.Sprintf("query parameter n is too large (n=%d, max=%d)", rreq.ListN, r.opts.MaxListPageSize), ociregistry.ErrUnsupported.Code(), nil)
121 }
122 n := rreq.ListN
123 if n <= 0 {
124 n = maxPageSize
125 }
126 truncated := false
127
128 itemsIter(func(item string, err error) bool {
129 if err != nil {
130 _err = err
131 return false
132 }
133 if rreq.ListN > 0 && len(items) >= rreq.ListN {
134 truncated = true
135 return false
136 }
137
138
139 items = append(items, item)
140
141 return true
142 })
143 if _err != nil {
144 return nil, "", _err
145 }
146 if truncated && !r.opts.OmitLinkHeaderFromResponses {
147 link = r.makeNextLink(req, items[len(items)-1])
148 }
149 return items, link, nil
150 }
151
152
153
154
155
156
157
158
159
160 func (r *registry) makeNextLink(req *http.Request, startAfter string) string {
161
162
163 query := req.URL.Query()
164 query.Set("last", startAfter)
165 u := &url.URL{
166 Path: req.URL.Path,
167 RawQuery: query.Encode(),
168 }
169 return fmt.Sprintf(`<%v>;rel="next"`, u)
170 }
171
View as plain text