github.com/nikkelma/oras-project_oras-go@v1.1.1-0.20220201001104-a75f6a419090/pkg/registry/remote/repository.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 package remote 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "net/http" 22 "strconv" 23 24 errdef "oras.land/oras-go/pkg/content" 25 "oras.land/oras-go/pkg/registry" 26 "oras.land/oras-go/pkg/registry/remote/auth" 27 "oras.land/oras-go/pkg/registry/remote/internal/errutil" 28 ) 29 30 // Client is an interface for a HTTP client. 31 type Client interface { 32 // Do sends an HTTP request and returns an HTTP response. 33 // 34 // Unlike http.RoundTripper, Client can attempt to interpret the response 35 // and handle higher-level protocol details such as redirects and 36 // authentication. 37 // 38 // Like http.RoundTripper, Client should not modify the request, and must 39 // always close the request body. 40 Do(*http.Request) (*http.Response, error) 41 } 42 43 // Repository is an HTTP client to a remote repository. 44 type Repository struct { 45 // Client is the underlying HTTP client used to access the remote registry. 46 // If nil, auth.DefaultClient is used. 47 Client Client 48 49 // Reference references the remote repository. 50 Reference registry.Reference 51 52 // PlainHTTP signals the transport to access the remote repository via HTTP 53 // instead of HTTPS. 54 PlainHTTP bool 55 56 // ManifestMediaTypes is used in `Accept` header for resolving manifests from 57 // references. It is also used in identifying manifests and blobs from 58 // descriptors. 59 // If an empty list is present, default manifest media types are used. 60 ManifestMediaTypes []string 61 62 // TagListPageSize specifies the page size when invoking the tag list API. 63 // If zero, the page size is determined by the remote registry. 64 // Reference: https://docs.docker.com/registry/spec/api/#tags 65 TagListPageSize int 66 67 // ReferrerListPageSize specifies the page size when invoking the Referrers 68 // API. 69 // If zero, the page size is determined by the remote registry. 70 // Reference: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md 71 ReferrerListPageSize int 72 73 // MaxMetadataBytes specifies a limit on how many response bytes are allowed 74 // in the server's response to the metadata APIs, such as catalog list, tag 75 // list, and referrers list. 76 // If zero, a default (currently 4MiB) is used. 77 MaxMetadataBytes int64 78 } 79 80 // NewRepository creates a client to the remote repository identified by a 81 // reference. 82 // Example: localhost:5000/hello-world 83 func NewRepository(reference string) (*Repository, error) { 84 ref, err := registry.ParseReference(reference) 85 if err != nil { 86 return nil, err 87 } 88 return &Repository{ 89 Reference: ref, 90 }, nil 91 } 92 93 // client returns an HTTP client used to access the remote repository. 94 // A default HTTP client is return if the client is not configured. 95 func (r *Repository) client() Client { 96 if r.Client == nil { 97 return auth.DefaultClient 98 } 99 return r.Client 100 } 101 102 // parseReference validates the reference. 103 // Both simplified or fully qualified references are accepted as input. 104 // A fully qualified reference is returned on success. 105 func (r *Repository) parseReference(reference string) (registry.Reference, error) { 106 ref, err := registry.ParseReference(reference) 107 if err != nil { 108 ref = registry.Reference{ 109 Registry: r.Reference.Registry, 110 Repository: r.Reference.Repository, 111 Reference: reference, 112 } 113 if err = ref.ValidateReference(); err != nil { 114 return registry.Reference{}, err 115 } 116 return ref, nil 117 } 118 if ref.Registry == r.Reference.Registry && ref.Repository == r.Reference.Repository { 119 return ref, nil 120 } 121 return registry.Reference{}, fmt.Errorf("%w %q: expect %q", errdef.ErrInvalidReference, ref, r.Reference) 122 } 123 124 // Tags lists the tags available in the repository. 125 func (r *Repository) Tags(ctx context.Context, fn func(tags []string) error) error { 126 ctx = withScopeHint(ctx, r.Reference, auth.ActionPull) 127 url := buildRepositoryTagListURL(r.PlainHTTP, r.Reference) 128 var err error 129 for err == nil { 130 url, err = r.tags(ctx, fn, url) 131 } 132 if err != errNoLink { 133 return err 134 } 135 return nil 136 } 137 138 // tags returns a single page of tag list with the next link. 139 func (r *Repository) tags(ctx context.Context, fn func(tags []string) error, url string) (string, error) { 140 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 141 if err != nil { 142 return "", err 143 } 144 if r.TagListPageSize > 0 { 145 q := req.URL.Query() 146 q.Set("n", strconv.Itoa(r.TagListPageSize)) 147 req.URL.RawQuery = q.Encode() 148 } 149 150 resp, err := r.client().Do(req) 151 if err != nil { 152 return "", err 153 } 154 defer resp.Body.Close() 155 156 if resp.StatusCode != http.StatusOK { 157 return "", errutil.ParseErrorResponse(resp) 158 } 159 var page struct { 160 Tags []string `json:"tags"` 161 } 162 lr := limitReader(resp.Body, r.MaxMetadataBytes) 163 if err := json.NewDecoder(lr).Decode(&page); err != nil { 164 return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) 165 } 166 if err := fn(page.Tags); err != nil { 167 return "", err 168 } 169 170 return parseLink(resp) 171 }