1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package ociclient
16
17 import (
18 "context"
19 "encoding/json"
20 "fmt"
21 "io"
22 "net/http"
23 "strings"
24
25 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
26
27 "cuelabs.dev/go/oci/ociregistry"
28 "cuelabs.dev/go/oci/ociregistry/internal/ocirequest"
29 )
30
31 func (c *client) Repositories(ctx context.Context, startAfter string) ociregistry.Seq[string] {
32 return c.pager(ctx, &ocirequest.Request{
33 Kind: ocirequest.ReqCatalogList,
34 ListN: c.listPageSize,
35 ListLast: startAfter,
36 }, func(resp *http.Response) ([]string, error) {
37 data, err := io.ReadAll(resp.Body)
38 if err != nil {
39 return nil, err
40 }
41 var catalog struct {
42 Repos []string `json:"repositories"`
43 }
44 if err := json.Unmarshal(data, &catalog); err != nil {
45 return nil, fmt.Errorf("cannot unmarshal catalog response: %v", err)
46 }
47 return catalog.Repos, nil
48 })
49 }
50
51 func (c *client) Tags(ctx context.Context, repoName, startAfter string) ociregistry.Seq[string] {
52 return c.pager(ctx, &ocirequest.Request{
53 Kind: ocirequest.ReqTagsList,
54 Repo: repoName,
55 ListN: c.listPageSize,
56 ListLast: startAfter,
57 }, func(resp *http.Response) ([]string, error) {
58 data, err := io.ReadAll(resp.Body)
59 if err != nil {
60 return nil, err
61 }
62 var tagsResponse struct {
63 Repo string `json:"name"`
64 Tags []string `json:"tags"`
65 }
66 if err := json.Unmarshal(data, &tagsResponse); err != nil {
67 return nil, fmt.Errorf("cannot unmarshal tags list response: %v", err)
68 }
69 return tagsResponse.Tags, nil
70 })
71 }
72
73 func (c *client) Referrers(ctx context.Context, repoName string, digest ociregistry.Digest, artifactType string) ociregistry.Seq[ociregistry.Descriptor] {
74
75 resp, err := c.doRequest(ctx, &ocirequest.Request{
76 Kind: ocirequest.ReqReferrersList,
77 Repo: repoName,
78 Digest: string(digest),
79 ListN: c.listPageSize,
80 })
81 if err != nil {
82 return ociregistry.ErrorIter[ociregistry.Descriptor](err)
83 }
84
85 data, err := io.ReadAll(resp.Body)
86 resp.Body.Close()
87 if err != nil {
88 return ociregistry.ErrorIter[ociregistry.Descriptor](err)
89 }
90 var referrersResponse ocispec.Index
91 if err := json.Unmarshal(data, &referrersResponse); err != nil {
92 return ociregistry.ErrorIter[ociregistry.Descriptor](fmt.Errorf("cannot unmarshal referrers response: %v", err))
93 }
94 return ociregistry.SliceIter(referrersResponse.Manifests)
95 }
96
97
98
99
100
101 func (c *client) pager(ctx context.Context, initialReq *ocirequest.Request, parseResponse func(*http.Response) ([]string, error)) ociregistry.Seq[string] {
102 return func(yield func(string, error) bool) {
103
104 req, err := newRequest(ctx, initialReq, nil)
105 if err != nil {
106 yield("", err)
107 return
108 }
109 for {
110 resp, err := c.do(req)
111 if err != nil {
112 yield("", err)
113 return
114 }
115 items, err := parseResponse(resp)
116 resp.Body.Close()
117 if err != nil {
118 yield("", err)
119 return
120 }
121
122 for _, item := range items {
123 if !yield(item, nil) {
124 return
125 }
126 }
127 if len(items) < initialReq.ListN {
128
129
130
131
132 return
133 }
134 req, err = nextLink(ctx, resp, initialReq, items[len(items)-1])
135 if err != nil {
136 yield("", fmt.Errorf("invalid Link header in response: %v", err))
137 return
138 }
139 }
140 }
141 }
142
143
144
145
146
147
148 func nextLink(ctx context.Context, resp *http.Response, initialReq *ocirequest.Request, last string) (*http.Request, error) {
149 link0 := resp.Header.Get("Link")
150 if link0 == "" {
151
152
153
154 rreq := *initialReq
155 rreq.ListLast = last
156 req, err := newRequest(ctx, &rreq, nil)
157 if err != nil {
158
159
160 return nil, fmt.Errorf("cannot form next request: %v", err)
161 }
162 return req, nil
163 }
164
165
166 link, ok := strings.CutPrefix(link0, "<")
167 if !ok {
168 return nil, fmt.Errorf("no initial < character in Link=%q", link0)
169 }
170 link, _, ok = strings.Cut(link, ">")
171 if !ok {
172 return nil, fmt.Errorf("no > character in Link=%q", link0)
173 }
174
175 linkURL, err := resp.Request.URL.Parse(link)
176 if err != nil {
177 return nil, fmt.Errorf("invalid URL in Link=%q", link0)
178 }
179 return http.NewRequestWithContext(ctx, "GET", linkURL.String(), nil)
180 }
181
View as plain text