1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package remote
16
17 import (
18 "context"
19 "encoding/json"
20 "fmt"
21 "net/http"
22 "net/url"
23 "strings"
24
25 "github.com/google/go-containerregistry/pkg/name"
26 "github.com/google/go-containerregistry/pkg/v1/remote/transport"
27 )
28
29
30
31
32 func ListWithContext(ctx context.Context, repo name.Repository, options ...Option) ([]string, error) {
33 return List(repo, append(options, WithContext(ctx))...)
34 }
35
36
37
38 func List(repo name.Repository, options ...Option) ([]string, error) {
39 o, err := makeOptions(options...)
40 if err != nil {
41 return nil, err
42 }
43 return newPuller(o).List(o.context, repo)
44 }
45
46 type Tags struct {
47 Name string `json:"name"`
48 Tags []string `json:"tags"`
49 Next string `json:"next,omitempty"`
50 }
51
52 func (f *fetcher) listPage(ctx context.Context, repo name.Repository, next string, pageSize int) (*Tags, error) {
53 if next == "" {
54 uri := &url.URL{
55 Scheme: repo.Scheme(),
56 Host: repo.RegistryStr(),
57 Path: fmt.Sprintf("/v2/%s/tags/list", repo.RepositoryStr()),
58 }
59 if pageSize > 0 {
60 uri.RawQuery = fmt.Sprintf("n=%d", pageSize)
61 }
62 next = uri.String()
63 }
64
65 req, err := http.NewRequestWithContext(ctx, "GET", next, nil)
66 if err != nil {
67 return nil, err
68 }
69
70 resp, err := f.client.Do(req)
71 if err != nil {
72 return nil, err
73 }
74
75 if err := transport.CheckError(resp, http.StatusOK); err != nil {
76 return nil, err
77 }
78
79 parsed := Tags{}
80 if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil {
81 return nil, err
82 }
83
84 if err := resp.Body.Close(); err != nil {
85 return nil, err
86 }
87
88 uri, err := getNextPageURL(resp)
89 if err != nil {
90 return nil, err
91 }
92
93 if uri != nil {
94 parsed.Next = uri.String()
95 }
96
97 return &parsed, nil
98 }
99
100
101
102
103 func getNextPageURL(resp *http.Response) (*url.URL, error) {
104 link := resp.Header.Get("Link")
105 if link == "" {
106 return nil, nil
107 }
108
109 if link[0] != '<' {
110 return nil, fmt.Errorf("failed to parse link header: missing '<' in: %s", link)
111 }
112
113 end := strings.Index(link, ">")
114 if end == -1 {
115 return nil, fmt.Errorf("failed to parse link header: missing '>' in: %s", link)
116 }
117 link = link[1:end]
118
119 linkURL, err := url.Parse(link)
120 if err != nil {
121 return nil, err
122 }
123 if resp.Request == nil || resp.Request.URL == nil {
124 return nil, nil
125 }
126 linkURL = resp.Request.URL.ResolveReference(linkURL)
127 return linkURL, nil
128 }
129
130 type Lister struct {
131 f *fetcher
132 repo name.Repository
133 pageSize int
134
135 page *Tags
136 err error
137
138 needMore bool
139 }
140
141 func (l *Lister) Next(ctx context.Context) (*Tags, error) {
142 if l.needMore {
143 l.page, l.err = l.f.listPage(ctx, l.repo, l.page.Next, l.pageSize)
144 } else {
145 l.needMore = true
146 }
147 return l.page, l.err
148 }
149
150 func (l *Lister) HasNext() bool {
151 return l.page != nil && (!l.needMore || l.page.Next != "")
152 }
153
View as plain text