1 package cache
2
3 import (
4 "errors"
5 "fmt"
6 "io"
7
8 "github.com/google/go-containerregistry/pkg/name"
9 v1 "github.com/google/go-containerregistry/pkg/v1"
10 "github.com/google/go-containerregistry/pkg/v1/remote"
11
12 "edge-infra.dev/pkg/f8n/warehouse/oci"
13 "edge-infra.dev/pkg/f8n/warehouse/oci/cache/providers/memory"
14 "edge-infra.dev/pkg/f8n/warehouse/oci/layer"
15 whremote "edge-infra.dev/pkg/f8n/warehouse/oci/remote"
16 )
17
18
19
20 type Cache interface {
21 Get(name.Reference, ...GetOption) (oci.Artifact, error)
22 Exists(v1.Hash) bool
23 Add(oci.Artifact) (oci.Artifact, error)
24 Len() int
25 }
26
27
28
29 type LazyCache struct {
30 tags map[string]v1.Hash
31 puller *remote.Puller
32 memory *memory.ArcCache
33 recorder Recorder
34 }
35
36
37
38
39
40 type Recorder interface {
41 RecordGet(hit bool, provider string, objType string)
42
43 }
44
45 var (
46
47 ErrNotFound = errors.New("content not found")
48
49
50 ErrInterfaceConversion = errors.New("failed to convert cached object to requested type")
51 )
52
53
54 func New(opts ...Option) (*LazyCache, error) {
55 var (
56 mcache *memory.ArcCache
57 options = makeOptions(opts...)
58 )
59
60 if options.memoryCacheSize > 0 {
61 memorycache, err := memory.New(options.memoryCacheSize)
62 if err != nil {
63 return nil, err
64 }
65 mcache = memorycache
66 }
67
68 return &LazyCache{
69 tags: map[string]v1.Hash{},
70 memory: mcache,
71 recorder: options.recorder,
72 puller: options.puller,
73 }, nil
74 }
75
76
77
78 func (w *LazyCache) Get(ref name.Reference, opts ...GetOption) (oci.Artifact, error) {
79 options := makeGetOptions(opts...)
80 if w.puller != nil {
81 options.remoteOpts = append(options.remoteOpts, remote.Reuse(w.puller))
82 }
83
84 h, err := w.resolveRef(ref, options.resolveTag, options.remoteOpts...)
85 if err != nil {
86 return nil, err
87 }
88
89 if a, err := w.getArtifact(h); err == nil {
90 return a, nil
91 } else if !errors.Is(err, ErrNotFound) {
92 return nil, fmt.Errorf("error getting cached artifact: %v", err)
93 }
94
95 return w.getRemote(ref, options.remoteOpts...)
96 }
97
98
99 func (w *LazyCache) Exists(h v1.Hash) bool {
100 return w.memory.Exists(h)
101 }
102
103
104
105
106 func (w *LazyCache) Add(a oci.Artifact) (oci.Artifact, error) {
107 h, err := a.Digest()
108 if err != nil {
109 return nil, err
110 }
111
112 var cached oci.Artifact
113 switch a := a.(type) {
114 case oci.Unwrapper:
115 return w.Add(a.Unwrap())
116 case v1.ImageIndex:
117 cached = ImageIndex(a, w)
118 case v1.Image:
119 cached = Image(a, w)
120 default:
121 return nil, oci.ErrInvalidArtifact
122 }
123 w.memory.Add(h, cached)
124
125 return cached, nil
126 }
127
128
129 func (w *LazyCache) Len() int {
130 if w.memory != nil {
131 return w.memory.Len()
132 }
133 return 0
134 }
135
136
137
138
139 func (w *LazyCache) resolveRef(ref name.Reference, fetchTag bool, remoteOpts ...remote.Option) (v1.Hash, error) {
140 var h v1.Hash
141 switch r := ref.(type) {
142 case name.Digest:
143 hash, err := v1.NewHash(r.DigestStr())
144 if err != nil {
145 return v1.Hash{}, err
146 }
147 h = hash
148 case name.Tag:
149 resolved, ok := w.tags[r.String()]
150 if fetchTag || !ok {
151 desc, err := remote.Head(r, remoteOpts...)
152 if err != nil {
153 return v1.Hash{}, err
154 }
155 h = desc.Digest
156 } else {
157 h = resolved
158 }
159 w.tags[r.String()] = h
160 default:
161 return v1.Hash{}, fmt.Errorf("invalid ref. ref must either be a Digest or Tag, but was %T", ref)
162 }
163 return h, nil
164 }
165
166
167 func (w *LazyCache) getRemote(ref name.Reference, opts ...remote.Option) (oci.Artifact, error) {
168 a, err := whremote.Get(ref, opts...)
169 if err != nil {
170 return nil, err
171 }
172 return w.Add(a)
173 }
174
175
176
177 func (w *LazyCache) recordGet(hit bool, provider string, objType string) {
178 if w.recorder != nil {
179 w.recorder.RecordGet(hit, provider, objType)
180 }
181 }
182
183 func (w *LazyCache) getArtifact(h v1.Hash) (oci.Artifact, error) {
184 var obj any
185 var hit bool
186 if w.memory != nil {
187 obj, hit = w.memory.Get(h)
188 if !hit {
189 w.recordGet(hit, "memory", "artifact")
190 return nil, ErrNotFound
191 }
192 }
193 var artifact oci.Artifact
194 switch a := obj.(type) {
195 case oci.Unwrapper:
196 artifact = a.Unwrap()
197 case v1.ImageIndex:
198 artifact = a
199 case v1.Image:
200 artifact = a
201 default:
202 return nil, ErrInterfaceConversion
203 }
204 w.recordGet(hit, "memory", "artifact")
205 return artifact, nil
206 }
207
208 func (w *LazyCache) putBlob(h v1.Hash, blob []byte) {
209 w.memory.Add(h, blob)
210 }
211
212 func (w *LazyCache) getLayer(h v1.Hash) (layer.Layer, error) {
213 var obj any
214 var hit bool
215 if w.memory != nil {
216 obj, hit = w.memory.Get(h)
217 if !hit {
218 w.recordGet(hit, "memory", "layer")
219 return nil, ErrNotFound
220 }
221 }
222
223 l, ok := obj.(layer.Layer)
224 if !ok {
225 return nil, ErrInterfaceConversion
226 }
227 w.recordGet(hit, "memory", "layer")
228
229 return l, nil
230 }
231
232 func (w *LazyCache) putLayer(l layer.Layer) (layer.Layer, error) {
233 h, err := l.Digest()
234 if err != nil {
235 return nil, err
236 }
237 rc, err := l.Uncompressed()
238 if err != nil {
239 return nil, err
240 }
241 data, err := io.ReadAll(rc)
242 if err != nil {
243 return nil, err
244 }
245 cached := Layer(l, w, data)
246 w.memory.Add(h, cached)
247
248 return cached, nil
249 }
250
251 func (w *LazyCache) getImage(h v1.Hash) (v1.Image, error) {
252 var obj any
253 var hit bool
254 if w.memory != nil {
255 obj, hit = w.memory.Get(h)
256 if !hit {
257 w.recordGet(hit, "memory", "image")
258 return nil, ErrNotFound
259 }
260 }
261 img, ok := obj.(v1.Image)
262 if !ok {
263 return nil, ErrInterfaceConversion
264 }
265 w.recordGet(hit, "memory", "image")
266
267 return img, nil
268 }
269
270 func (w *LazyCache) putImage(img v1.Image) (v1.Image, error) {
271 h, err := img.Digest()
272 if err != nil {
273 return nil, err
274 }
275 cached := Image(img, w)
276 w.memory.Add(h, cached)
277
278 return cached, nil
279 }
280
281 func (w *LazyCache) getIndex(h v1.Hash) (v1.ImageIndex, error) {
282 var obj any
283 var hit bool
284 if w.memory != nil {
285 obj, hit = w.memory.Get(h)
286 if !hit {
287 w.recordGet(hit, "memory", "index")
288 return nil, ErrNotFound
289 }
290 }
291 ii, ok := obj.(v1.ImageIndex)
292 if !ok {
293 return nil, ErrInterfaceConversion
294 }
295 w.recordGet(hit, "memory", "index")
296
297 return ii, nil
298 }
299
300 func (w *LazyCache) putIndex(ii v1.ImageIndex) (v1.ImageIndex, error) {
301 h, err := ii.Digest()
302 if err != nil {
303 return nil, err
304 }
305 cached := ImageIndex(ii, w)
306 w.memory.Add(h, cached)
307
308 return cached, nil
309 }
310
View as plain text