Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/registry-facade/pkg/registry/layersource.go
2499 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package registry
6
7
import (
8
"bytes"
9
"compress/gzip"
10
"context"
11
"io"
12
"io/fs"
13
"os"
14
"strconv"
15
"strings"
16
"sync"
17
18
"github.com/containerd/containerd/errdefs"
19
"github.com/containerd/containerd/remotes"
20
lru "github.com/hashicorp/golang-lru"
21
"github.com/opencontainers/go-digest"
22
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
23
"github.com/pkg/errors"
24
"golang.org/x/xerrors"
25
26
"github.com/gitpod-io/gitpod/common-go/log"
27
"github.com/gitpod-io/gitpod/registry-facade/api"
28
)
29
30
const (
31
// labelSkipNLayer is a label on images that tells registry-facade to discard the first N layer.
32
// If the value of this label
33
// - is not a number (cannot be parsed by strconv.ParseUint), registry-facade fails to use the image,
34
// - is larger than the number of layers in the image, the image is considered empty (i.e. to have no layer).
35
labelSkipNLayer = "skip-n.registry-facade.gitpod.io"
36
)
37
38
// LayerSource provides layers for a workspace image
39
type LayerSource interface {
40
BlobSource
41
GetLayer(ctx context.Context, spec *api.ImageSpec) ([]AddonLayer, error)
42
Envs(ctx context.Context, spec *api.ImageSpec) ([]EnvModifier, error)
43
}
44
45
// AddonLayer is an OCI descriptor for a layer + the layers diffID
46
type AddonLayer struct {
47
Descriptor ociv1.Descriptor
48
DiffID digest.Digest
49
}
50
51
type filebackedLayer struct {
52
AddonLayer
53
Filename string
54
}
55
56
// FileLayerSource provides the same layers independent of the workspace spec
57
type FileLayerSource []filebackedLayer
58
59
func (s FileLayerSource) Name() string {
60
return "filelayer"
61
}
62
63
// Envs returns the list of env modifiers
64
func (s FileLayerSource) Envs(ctx context.Context, spec *api.ImageSpec) ([]EnvModifier, error) {
65
return nil, nil
66
}
67
68
// GetLayer return all layers of this source
69
func (s FileLayerSource) GetLayer(ctx context.Context, spec *api.ImageSpec) ([]AddonLayer, error) {
70
res := make([]AddonLayer, len(s))
71
for i := range s {
72
res[i] = s[i].AddonLayer
73
}
74
return res, nil
75
}
76
77
// HasBlob checks if a digest can be served by this blob source
78
func (s FileLayerSource) HasBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) bool {
79
for _, l := range s {
80
if l.Descriptor.Digest == dgst {
81
return true
82
}
83
}
84
return false
85
}
86
87
// GetBlob provides access to a blob. If a ReadCloser is returned the receiver is expected to
88
// call close on it eventually.
89
func (s FileLayerSource) GetBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) (dontCache bool, mediaType string, url string, data io.ReadCloser, err error) {
90
var src filebackedLayer
91
for _, l := range s {
92
if l.Descriptor.Digest == dgst {
93
src = l
94
break
95
}
96
}
97
if src.Filename == "" {
98
err = errdefs.ErrNotFound
99
return
100
}
101
102
f, err := os.OpenFile(src.Filename, os.O_RDONLY, 0)
103
if errors.Is(err, fs.ErrNotExist) {
104
err = errdefs.ErrNotFound
105
return
106
}
107
if err != nil {
108
return
109
}
110
111
return false, src.Descriptor.MediaType, "", f, nil
112
}
113
114
// NewFileLayerSource produces a static layer source where each file is expected to be a gzipped layer
115
func NewFileLayerSource(ctx context.Context, file ...string) (FileLayerSource, error) {
116
var res FileLayerSource
117
for _, fn := range file {
118
fr, err := os.OpenFile(fn, os.O_RDONLY, 0)
119
if err != nil {
120
return nil, err
121
}
122
defer fr.Close()
123
124
stat, err := fr.Stat()
125
if err != nil {
126
return nil, err
127
}
128
129
dgst, err := digest.FromReader(fr)
130
if err != nil {
131
return nil, err
132
}
133
134
// start again to read the diffID
135
_, err = fr.Seek(0, 0)
136
if err != nil {
137
return nil, err
138
}
139
diffr, err := gzip.NewReader(fr)
140
if err != nil {
141
return nil, err
142
}
143
defer diffr.Close()
144
diffID, err := digest.FromReader(diffr)
145
if err != nil {
146
return nil, err
147
}
148
149
desc := ociv1.Descriptor{
150
MediaType: ociv1.MediaTypeImageLayer,
151
Digest: dgst,
152
Size: stat.Size(),
153
}
154
res = append(res, filebackedLayer{
155
AddonLayer: AddonLayer{
156
Descriptor: desc,
157
DiffID: diffID,
158
},
159
Filename: fn,
160
})
161
162
log.WithField("diffID", diffID).WithField("fn", fn).Debug("loaded static layer")
163
}
164
165
return res, nil
166
}
167
168
type imagebackedLayer struct {
169
AddonLayer
170
NewFetcher func() (remotes.Fetcher, error)
171
}
172
173
// ImageLayerSource provides additional layers from another image
174
type ImageLayerSource struct {
175
envs []EnvModifier
176
layers []imagebackedLayer
177
}
178
179
func (s ImageLayerSource) Name() string {
180
return "imagelayer"
181
}
182
183
// Envs returns the list of env modifiers
184
func (s ImageLayerSource) Envs(ctx context.Context, spec *api.ImageSpec) ([]EnvModifier, error) {
185
return s.envs, nil
186
}
187
188
// GetLayer return all layers of this source
189
func (s ImageLayerSource) GetLayer(ctx context.Context, spec *api.ImageSpec) ([]AddonLayer, error) {
190
res := make([]AddonLayer, len(s.layers))
191
for i := range s.layers {
192
res[i] = s.layers[i].AddonLayer
193
}
194
return res, nil
195
}
196
197
// HasBlob checks if a digest can be served by this blob source
198
func (s ImageLayerSource) HasBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) bool {
199
for _, l := range s.layers {
200
if l.Descriptor.Digest == dgst {
201
return true
202
}
203
}
204
return false
205
}
206
207
// GetBlob provides access to a blob. If a ReadCloser is returned the receiver is expected to
208
// call close on it eventually.
209
func (s ImageLayerSource) GetBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) (dontCache bool, mediaType string, url string, data io.ReadCloser, err error) {
210
var src imagebackedLayer
211
for _, l := range s.layers {
212
if l.Descriptor.Digest == dgst {
213
src = l
214
}
215
}
216
if src.NewFetcher == nil {
217
err = errdefs.ErrNotFound
218
return
219
}
220
221
fetcher, err := src.NewFetcher()
222
if err != nil {
223
return
224
}
225
rc, err := fetcher.Fetch(ctx, src.Descriptor)
226
if err != nil {
227
return
228
}
229
230
return false, src.Descriptor.MediaType, "", rc, nil
231
}
232
233
const (
234
envPrefixSet = "GITPOD_ENV_SET_"
235
envPrefixAppend = "GITPOD_ENV_APPEND_"
236
envPrefixPrepend = "GITPOD_ENV_PREPEND_"
237
)
238
239
// NewStaticSourceFromImage downloads image layers into the store and uses them as static layer
240
func NewStaticSourceFromImage(ctx context.Context, newResolver ResolverProvider, ref string) (*ImageLayerSource, error) {
241
resolver := newResolver()
242
_, desc, err := resolver.Resolve(ctx, ref)
243
if err != nil {
244
return nil, err
245
}
246
fetcher, err := resolver.Fetcher(ctx, ref)
247
if err != nil {
248
return nil, err
249
}
250
251
manifest, _, err := DownloadManifest(ctx, AsFetcherFunc(fetcher), desc)
252
if err != nil {
253
return nil, err
254
}
255
256
cfg, err := DownloadConfig(ctx, AsFetcherFunc(fetcher), ref, manifest.Config)
257
if err != nil {
258
return nil, err
259
}
260
261
// images can mark the first N layers as irrelevant.
262
// We use labels for that to ship that information with the image.
263
skipN, err := getSkipNLabelValue(&cfg.Config)
264
if err != nil {
265
return nil, err
266
}
267
268
res := make([]imagebackedLayer, 0, len(manifest.Layers))
269
for i, ml := range manifest.Layers {
270
if i < skipN {
271
continue
272
}
273
274
l := imagebackedLayer{
275
AddonLayer: AddonLayer{
276
Descriptor: ml,
277
DiffID: cfg.RootFS.DiffIDs[i],
278
},
279
NewFetcher: func() (remotes.Fetcher, error) {
280
// Must create a new resolver for each fetcher, otherwise this will keep using the originally
281
// provided pull secret which eventually expires.
282
resolver := newResolver()
283
return resolver.Fetcher(ctx, ref)
284
},
285
}
286
res = append(res, l)
287
}
288
289
var envs []EnvModifier
290
parsedEnvs := parseEnvs(cfg.Config.Env)
291
for _, name := range parsedEnvs.keys {
292
value := parsedEnvs.values[name]
293
if strings.HasPrefix(name, envPrefixAppend) {
294
name = strings.TrimPrefix(name, envPrefixAppend)
295
if name == "" || value == "" {
296
continue
297
}
298
envs = append(envs, newAppendEnvModifier(name, value))
299
} else if strings.HasPrefix(name, envPrefixPrepend) {
300
name = strings.TrimPrefix(name, envPrefixPrepend)
301
if name == "" || value == "" {
302
continue
303
}
304
envs = append(envs, newPrependEnvModifier(name, value))
305
} else if strings.HasPrefix(name, envPrefixSet) {
306
name = strings.TrimPrefix(name, envPrefixSet)
307
if name == "" {
308
continue
309
}
310
envs = append(envs, newSetEnvModifier(name, value))
311
}
312
}
313
314
return &ImageLayerSource{
315
layers: res,
316
envs: envs,
317
}, nil
318
}
319
320
// getSkipNLabelValue returns the parsed label value of the LabelSkipNLayer label.
321
func getSkipNLabelValue(cfg *ociv1.ImageConfig) (skipN int, err error) {
322
v, ok := cfg.Labels[labelSkipNLayer]
323
if !ok {
324
return 0, nil
325
}
326
327
vi, err := strconv.ParseUint(v, 10, 16)
328
if err != nil {
329
return 0, xerrors.Errorf("skipN layer label: %w", err)
330
}
331
return int(vi), nil
332
}
333
334
// CompositeLayerSource appends layers from different sources
335
type CompositeLayerSource []LayerSource
336
337
func (cs CompositeLayerSource) Name() string {
338
return "composite"
339
}
340
341
// Envs returns the list of env modifiers
342
func (cs CompositeLayerSource) Envs(ctx context.Context, spec *api.ImageSpec) ([]EnvModifier, error) {
343
var res []EnvModifier
344
for _, s := range cs {
345
envs, err := s.Envs(ctx, spec)
346
if err != nil {
347
return nil, err
348
}
349
res = append(res, envs...)
350
}
351
return res, nil
352
}
353
354
// GetLayer returns the list of all layers from all sources
355
func (cs CompositeLayerSource) GetLayer(ctx context.Context, spec *api.ImageSpec) ([]AddonLayer, error) {
356
var res []AddonLayer
357
for _, s := range cs {
358
ls, err := s.GetLayer(ctx, spec)
359
if err != nil {
360
return nil, err
361
}
362
res = append(res, ls...)
363
}
364
return res, nil
365
}
366
367
// HasBlob checks if a digest can be served by this blob source
368
func (cs CompositeLayerSource) HasBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) bool {
369
for _, s := range cs {
370
if s.HasBlob(ctx, spec, dgst) {
371
return true
372
}
373
}
374
return false
375
}
376
377
// GetBlob provides access to a blob. If a ReadCloser is returned the receiver is expected to
378
// call close on it eventually.
379
func (cs CompositeLayerSource) GetBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) (dontCache bool, mediaType string, url string, data io.ReadCloser, err error) {
380
for _, s := range cs {
381
if s.HasBlob(ctx, spec, dgst) {
382
return s.GetBlob(ctx, spec, dgst)
383
}
384
}
385
386
err = errdefs.ErrNotFound
387
return
388
}
389
390
// RefSource extracts an image reference from an image spec
391
type RefSource func(*api.ImageSpec) (ref []string, err error)
392
393
// NewSpecMappedImageSource creates a new spec mapped image source
394
func NewSpecMappedImageSource(resolver ResolverProvider, refSource RefSource) (*SpecMappedImagedSource, error) {
395
cache, err := lru.New(128)
396
if err != nil {
397
return nil, err
398
}
399
return &SpecMappedImagedSource{
400
RefSource: refSource,
401
Resolver: resolver,
402
cache: cache,
403
}, nil
404
}
405
406
// SpecMappedImagedSource provides layers from other images based on the image spec
407
type SpecMappedImagedSource struct {
408
RefSource RefSource
409
Resolver ResolverProvider
410
411
// TODO: add ttl
412
cache *lru.Cache
413
}
414
415
func (src *SpecMappedImagedSource) Name() string {
416
return "specmapped"
417
}
418
419
// Envs returns the list of env modifiers
420
func (src *SpecMappedImagedSource) Envs(ctx context.Context, spec *api.ImageSpec) ([]EnvModifier, error) {
421
lsrcs, err := src.getDelegate(ctx, spec)
422
if err != nil {
423
return nil, err
424
}
425
var res []EnvModifier
426
for _, lsrc := range lsrcs {
427
if lsrc == nil {
428
continue
429
}
430
envs, err := lsrc.Envs(ctx, spec)
431
if err != nil {
432
return nil, err
433
}
434
res = append(res, envs...)
435
}
436
return res, nil
437
}
438
439
// GetLayer returns the list of all layers from this source
440
func (src *SpecMappedImagedSource) GetLayer(ctx context.Context, spec *api.ImageSpec) ([]AddonLayer, error) {
441
lsrcs, err := src.getDelegate(ctx, spec)
442
if err != nil {
443
return nil, err
444
}
445
var res []AddonLayer
446
for _, lsrc := range lsrcs {
447
if lsrc == nil {
448
continue
449
}
450
ls, err := lsrc.GetLayer(ctx, spec)
451
if err != nil {
452
return nil, err
453
}
454
res = append(res, ls...)
455
}
456
return res, nil
457
}
458
459
// HasBlob checks if a digest can be served by this blob source
460
func (src *SpecMappedImagedSource) HasBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) bool {
461
lsrcs, err := src.getDelegate(ctx, spec)
462
if err != nil {
463
return false
464
}
465
for _, lsrc := range lsrcs {
466
if lsrc == nil {
467
continue
468
}
469
if lsrc.HasBlob(ctx, spec, dgst) {
470
return true
471
}
472
}
473
return false
474
}
475
476
// GetBlob provides access to a blob. If a ReadCloser is returned the receiver is expected to
477
// call close on it eventually.
478
func (src *SpecMappedImagedSource) GetBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) (dontCache bool, mediaType string, url string, data io.ReadCloser, err error) {
479
lsrcs, err := src.getDelegate(ctx, spec)
480
if err != nil {
481
return
482
}
483
for _, lsrc := range lsrcs {
484
if lsrc == nil {
485
continue
486
}
487
if lsrc.HasBlob(ctx, spec, dgst) {
488
return lsrc.GetBlob(ctx, spec, dgst)
489
}
490
}
491
err = errdefs.ErrNotFound
492
return
493
}
494
495
// getDelegate returns the cached layer source delegate computed from the image spec
496
func (src *SpecMappedImagedSource) getDelegate(ctx context.Context, spec *api.ImageSpec) ([]LayerSource, error) {
497
refs, err := src.RefSource(spec)
498
if err != nil {
499
return nil, err
500
}
501
layers := make([]LayerSource, len(refs))
502
503
for i, ref := range refs {
504
if ref == "" {
505
continue
506
}
507
if s, ok := src.cache.Get(ref); ok {
508
layers[i] = s.(LayerSource)
509
continue
510
}
511
lsrc, err := NewStaticSourceFromImage(ctx, src.Resolver, ref)
512
if err != nil {
513
return nil, err
514
}
515
src.cache.Add(ref, lsrc)
516
layers[i] = lsrc
517
}
518
return layers, nil
519
}
520
521
// NewContentLayerSource creates a new layer source providing the content layer of an image spec
522
func NewContentLayerSource() (*ContentLayerSource, error) {
523
blobCache, err := lru.New(128)
524
if err != nil {
525
return nil, err
526
}
527
return &ContentLayerSource{
528
blobCache: blobCache,
529
}, nil
530
}
531
532
// ContentLayerSource provides layers from other images based on the image spec
533
type ContentLayerSource struct {
534
blobCache *lru.Cache
535
}
536
537
func (src *ContentLayerSource) Name() string {
538
return "contentlayer"
539
}
540
541
// Envs returns the list of env modifiers
542
func (src *ContentLayerSource) Envs(ctx context.Context, spec *api.ImageSpec) ([]EnvModifier, error) {
543
return nil, nil
544
}
545
546
// GetLayer returns the list of all layers from this source
547
func (src *ContentLayerSource) GetLayer(ctx context.Context, spec *api.ImageSpec) ([]AddonLayer, error) {
548
res := make([]AddonLayer, len(spec.ContentLayer))
549
for i, layer := range spec.ContentLayer {
550
if dl := layer.GetDirect(); dl != nil {
551
dgst := digest.FromBytes(dl.Content)
552
res[i] = AddonLayer{
553
Descriptor: ociv1.Descriptor{
554
MediaType: ociv1.MediaTypeImageLayer,
555
Digest: dgst,
556
Size: int64(len(dl.Content)),
557
},
558
DiffID: dgst,
559
}
560
continue
561
}
562
563
if rl := layer.GetRemote(); rl != nil {
564
dgst, err := digest.Parse(rl.Digest)
565
if err != nil {
566
return nil, xerrors.Errorf("cannot parse layer digest %s: %w", rl.Digest, err)
567
}
568
diffID, err := digest.Parse(rl.DiffId)
569
if err != nil {
570
return nil, xerrors.Errorf("cannot parse layer diffID %s: %w", rl.DiffId, err)
571
}
572
var urls []string
573
if rl.Url != "" {
574
urls = []string{rl.Url}
575
}
576
577
res[i] = AddonLayer{
578
Descriptor: ociv1.Descriptor{
579
MediaType: rl.MediaType,
580
Digest: dgst,
581
URLs: urls,
582
Size: rl.Size,
583
},
584
DiffID: diffID,
585
}
586
continue
587
}
588
589
return nil, xerrors.Errorf("unknown layer type")
590
}
591
return res, nil
592
}
593
594
// HasBlob checks if a digest can be served by this blob source
595
func (src *ContentLayerSource) HasBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) bool {
596
if src.blobCache.Contains(dgst) {
597
return true
598
}
599
for _, layer := range spec.ContentLayer {
600
if dl := layer.GetDirect(); dl != nil {
601
if digest.FromBytes(dl.Content) == dgst {
602
return true
603
}
604
}
605
606
if rl := layer.GetRemote(); rl != nil {
607
if rl.Digest == dgst.String() {
608
return true
609
}
610
}
611
}
612
return false
613
}
614
615
// GetBlob provides access to a blob. If a ReadCloser is returned the receiver is expected to
616
// call close on it eventually.
617
func (src *ContentLayerSource) GetBlob(ctx context.Context, spec *api.ImageSpec, dgst digest.Digest) (dontCache bool, mediaType string, url string, data io.ReadCloser, err error) {
618
if blob, ok := src.blobCache.Get(dgst); ok {
619
return false, ociv1.MediaTypeImageLayer, "", io.NopCloser(bytes.NewReader(blob.([]byte))), nil
620
}
621
622
for _, layer := range spec.ContentLayer {
623
if dl := layer.GetDirect(); dl != nil {
624
if digest.FromBytes(dl.Content) == dgst {
625
return false, ociv1.MediaTypeImageLayer, "", io.NopCloser(bytes.NewReader(dl.Content)), nil
626
}
627
}
628
629
if rl := layer.GetRemote(); rl != nil {
630
if rl.Digest == dgst.String() {
631
mt := ociv1.MediaTypeImageLayerGzip
632
if rl.DiffId == rl.Digest || rl.DiffId == "" {
633
mt = ociv1.MediaTypeImageLayer
634
}
635
636
return false, mt, rl.Url, nil, nil
637
}
638
}
639
}
640
641
err = errdefs.ErrNotFound
642
return
643
}
644
645
// ParsedEnvs is parsed image envs configuration
646
type ParsedEnvs struct {
647
keys []string
648
values map[string]string
649
}
650
651
// ParseEnv parses environment variables
652
func parseEnvs(envs []string) *ParsedEnvs {
653
result := ParsedEnvs{
654
values: make(map[string]string),
655
}
656
for _, e := range envs {
657
parts := strings.SplitN(e, "=", 2)
658
if len(parts) == 0 {
659
continue
660
}
661
key := parts[0]
662
var value string
663
if len(parts) > 1 {
664
value = parts[1]
665
}
666
result.keys = append(result.keys, key)
667
result.values[key] = value
668
}
669
return &result
670
}
671
672
// Set the give value as a variable's value of the given name
673
func (envs *ParsedEnvs) Set(name, value string) {
674
_, exists := envs.values[name]
675
if !exists {
676
envs.keys = append(envs.keys, name)
677
}
678
envs.values[name] = value
679
}
680
681
// Append the given value to a variable's value of the given name
682
func (envs *ParsedEnvs) Append(name, value string) {
683
current, exists := envs.values[name]
684
if exists {
685
envs.values[name] = value + current
686
} else {
687
envs.keys = append(envs.keys, name)
688
envs.values[name] = value
689
}
690
}
691
692
// Prepend the given value to a variable's value of the given name
693
func (envs *ParsedEnvs) Prepend(name, value string) {
694
current, exists := envs.values[name]
695
if exists {
696
envs.values[name] = current + value
697
} else {
698
envs.keys = append(envs.keys, name)
699
envs.values[name] = value
700
}
701
}
702
703
func (envs *ParsedEnvs) serialize() (result []string) {
704
for _, key := range envs.keys {
705
result = append(result, key+"="+envs.values[key])
706
}
707
return
708
}
709
710
// EnvModifier modifies an image envs configuration
711
type EnvModifier func(*ParsedEnvs)
712
713
func newSetEnvModifier(name, value string) EnvModifier {
714
return func(pe *ParsedEnvs) {
715
pe.Set(name, value)
716
}
717
}
718
719
func newAppendEnvModifier(name, value string) EnvModifier {
720
return func(pe *ParsedEnvs) {
721
pe.Append(name, value)
722
}
723
}
724
725
func newPrependEnvModifier(name, value string) EnvModifier {
726
return func(pe *ParsedEnvs) {
727
pe.Prepend(name, value)
728
}
729
}
730
731
// NewRevisioningLayerSource produces a new revisioning layer source
732
func NewRevisioningLayerSource(active LayerSource) *RevisioningLayerSource {
733
return &RevisioningLayerSource{
734
active: active,
735
}
736
}
737
738
type RevisioningLayerSource struct {
739
mu sync.RWMutex
740
active LayerSource
741
past []LayerSource
742
}
743
744
func (src *RevisioningLayerSource) Name() string {
745
src.mu.RLock()
746
defer src.mu.RUnlock()
747
748
return src.active.Name()
749
}
750
751
func (src *RevisioningLayerSource) Update(s LayerSource) {
752
src.mu.Lock()
753
defer src.mu.Unlock()
754
755
src.past = append(src.past, src.active)
756
src.active = s
757
}
758
759
func (src *RevisioningLayerSource) GetLayer(ctx context.Context, spec *api.ImageSpec) ([]AddonLayer, error) {
760
src.mu.RLock()
761
defer src.mu.RUnlock()
762
763
return src.active.GetLayer(ctx, spec)
764
}
765
766
func (src *RevisioningLayerSource) Envs(ctx context.Context, spec *api.ImageSpec) ([]EnvModifier, error) {
767
src.mu.RLock()
768
defer src.mu.RUnlock()
769
770
return src.active.Envs(ctx, spec)
771
}
772
773
// HasBlob checks if a digest can be served by this blob source
774
func (src *RevisioningLayerSource) HasBlob(ctx context.Context, details *api.ImageSpec, dgst digest.Digest) bool {
775
src.mu.RLock()
776
defer src.mu.RUnlock()
777
778
if src.active.HasBlob(ctx, details, dgst) {
779
return true
780
}
781
for _, p := range src.past {
782
if p.HasBlob(ctx, details, dgst) {
783
return true
784
}
785
}
786
return false
787
}
788
789
// GetBlob provides access to a blob. If a ReadCloser is returned the receiver is expected to
790
// call close on it eventually.
791
func (src *RevisioningLayerSource) GetBlob(ctx context.Context, details *api.ImageSpec, dgst digest.Digest) (dontCache bool, mediaType string, url string, data io.ReadCloser, err error) {
792
src.mu.RLock()
793
defer src.mu.RUnlock()
794
795
if src.active.HasBlob(ctx, details, dgst) {
796
return src.active.GetBlob(ctx, details, dgst)
797
}
798
for _, p := range src.past {
799
if p.HasBlob(ctx, details, dgst) {
800
return p.GetBlob(ctx, details, dgst)
801
}
802
}
803
804
err = errdefs.ErrNotFound
805
return
806
}
807
808