1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package ociserver
21
22 import (
23 "context"
24 "errors"
25 "fmt"
26 "log"
27 "net/http"
28 "sync/atomic"
29
30 "cuelabs.dev/go/oci/ociregistry"
31 "cuelabs.dev/go/oci/ociregistry/internal/ocirequest"
32 ocispecroot "github.com/opencontainers/image-spec/specs-go"
33 )
34
35
36 const debug = false
37
38 var v2 = ocispecroot.Versioned{
39 SchemaVersion: 2,
40 }
41
42
43 type Options struct {
44
45
46 DisableReferrersAPI bool
47
48
49
50
51
52
53 DisableSinglePostUpload bool
54
55
56
57
58
59 MaxListPageSize int
60
61
62
63
64
65 OmitDigestFromTagGetResponse bool
66
67
68
69 OmitLinkHeaderFromResponses bool
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 LocationForUploadID func(string) (string, error)
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 LocationsForDescriptor func(isManifest bool, desc ociregistry.Descriptor) ([]string, error)
102
103 DebugID string
104 }
105
106 var debugID int32
107
108
109
110
111
112
113
114 func New(backend ociregistry.Interface, opts *Options) http.Handler {
115 if opts == nil {
116 opts = new(Options)
117 }
118 r := ®istry{
119 opts: *opts,
120 backend: backend,
121 }
122 if r.opts.DebugID == "" {
123 r.opts.DebugID = fmt.Sprintf("ociserver%d", atomic.AddInt32(&debugID, 1))
124 }
125 return r
126 }
127
128 func (r *registry) logf(f string, a ...any) {
129 log.Printf("ociserver %s: %s", r.opts.DebugID, fmt.Sprintf(f, a...))
130 }
131
132 type registry struct {
133 opts Options
134 backend ociregistry.Interface
135 }
136
137 var handlers = []func(r *registry, ctx context.Context, w http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) error{
138 ocirequest.ReqPing: (*registry).handlePing,
139 ocirequest.ReqBlobGet: (*registry).handleBlobGet,
140 ocirequest.ReqBlobHead: (*registry).handleBlobHead,
141 ocirequest.ReqBlobDelete: (*registry).handleBlobDelete,
142 ocirequest.ReqBlobStartUpload: (*registry).handleBlobStartUpload,
143 ocirequest.ReqBlobUploadBlob: (*registry).handleBlobUploadBlob,
144 ocirequest.ReqBlobMount: (*registry).handleBlobMount,
145 ocirequest.ReqBlobUploadInfo: (*registry).handleBlobUploadInfo,
146 ocirequest.ReqBlobUploadChunk: (*registry).handleBlobUploadChunk,
147 ocirequest.ReqBlobCompleteUpload: (*registry).handleBlobCompleteUpload,
148 ocirequest.ReqManifestGet: (*registry).handleManifestGet,
149 ocirequest.ReqManifestHead: (*registry).handleManifestHead,
150 ocirequest.ReqManifestPut: (*registry).handleManifestPut,
151 ocirequest.ReqManifestDelete: (*registry).handleManifestDelete,
152 ocirequest.ReqTagsList: (*registry).handleTagsList,
153 ocirequest.ReqReferrersList: (*registry).handleReferrersList,
154 ocirequest.ReqCatalogList: (*registry).handleCatalogList,
155 }
156
157 func (r *registry) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
158 if rerr := r.v2(resp, req); rerr != nil {
159 writeError(resp, rerr)
160 return
161 }
162 }
163
164
165
166 func (r *registry) v2(resp http.ResponseWriter, req *http.Request) (_err error) {
167 if debug {
168 r.logf("registry.v2 %v %s {", req.Method, req.URL)
169 defer func() {
170 if _err != nil {
171 r.logf("} -> %v", _err)
172 } else {
173 r.logf("}")
174 }
175 }()
176 }
177
178 rreq, err := ocirequest.Parse(req.Method, req.URL)
179 if err != nil {
180 resp.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
181 return handlerErrorForRequestParseError(err)
182 }
183 handle := handlers[rreq.Kind]
184 return handle(r, req.Context(), resp, req, rreq)
185 }
186
187 func (r *registry) handlePing(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) error {
188 resp.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
189 return nil
190 }
191
192 func (r *registry) setLocationHeader(resp http.ResponseWriter, isManifest bool, desc ociregistry.Descriptor, defaultLocation string) error {
193 loc := defaultLocation
194 if r.opts.LocationsForDescriptor != nil {
195 locs, err := r.opts.LocationsForDescriptor(isManifest, desc)
196 if err != nil {
197 what := "blob"
198 if isManifest {
199 what = "manifest"
200 }
201 return fmt.Errorf("cannot determine location for %s: %v", what, err)
202 }
203 if len(locs) > 0 {
204 loc = locs[0]
205 }
206 }
207 resp.Header().Set("Location", loc)
208 resp.Header().Set("Docker-Content-Digest", string(desc.Digest))
209 return nil
210 }
211
212
213
214 type ParseError struct {
215 error
216 }
217
218 func handlerErrorForRequestParseError(err error) error {
219 if err == nil {
220 return nil
221 }
222 var perr *ocirequest.ParseError
223 if !errors.As(err, &perr) {
224 return err
225 }
226 switch perr.Err {
227 case ocirequest.ErrNotFound:
228 return withHTTPCode(http.StatusNotFound, err)
229 case ocirequest.ErrBadlyFormedDigest:
230 return withHTTPCode(http.StatusBadRequest, err)
231 case ocirequest.ErrMethodNotAllowed:
232 return withHTTPCode(http.StatusMethodNotAllowed, err)
233 case ocirequest.ErrBadRequest:
234 return withHTTPCode(http.StatusBadRequest, err)
235 }
236 return err
237 }
238
View as plain text