Browse code

builder: add workaround for gcr auth issue

GCR does not currently support 401 response from blob endpoints.
This detects the case where no manifest requests have been
performed for the current resolver and does a dummy request
to enable authorization.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2018/11/22 05:04:58
Showing 1 changed files
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"io/ioutil"
9 9
 	"runtime"
10 10
 	"sync"
11
+	"sync/atomic"
11 12
 	"time"
12 13
 
13 14
 	"github.com/containerd/containerd/content"
... ...
@@ -56,13 +57,15 @@ type SourceOpt struct {
56 56
 
57 57
 type imageSource struct {
58 58
 	SourceOpt
59
-	g flightcontrol.Group
59
+	g             flightcontrol.Group
60
+	resolverCache *resolverCache
60 61
 }
61 62
 
62 63
 // NewSource creates a new image source
63 64
 func NewSource(opt SourceOpt) (source.Source, error) {
64 65
 	is := &imageSource{
65
-		SourceOpt: opt,
66
+		SourceOpt:     opt,
67
+		resolverCache: newResolverCache(),
66 68
 	}
67 69
 
68 70
 	return is, nil
... ...
@@ -73,6 +76,10 @@ func (is *imageSource) ID() string {
73 73
 }
74 74
 
75 75
 func (is *imageSource) getResolver(ctx context.Context, rfn resolver.ResolveOptionsFunc, ref string, sm *session.Manager) remotes.Resolver {
76
+	if res := is.resolverCache.Get(ctx, ref); res != nil {
77
+		return res
78
+	}
79
+
76 80
 	opt := docker.ResolverOptions{
77 81
 		Client: tracing.DefaultClient,
78 82
 	}
... ...
@@ -81,6 +88,7 @@ func (is *imageSource) getResolver(ctx context.Context, rfn resolver.ResolveOpti
81 81
 	}
82 82
 	opt.Credentials = is.getCredentialsFromSession(ctx, sm)
83 83
 	r := docker.NewResolver(opt)
84
+	r = is.resolverCache.Add(ctx, ref, r)
84 85
 	return r
85 86
 }
86 87
 
... ...
@@ -382,6 +390,11 @@ func (p *puller) Snapshot(ctx context.Context) (cache.ImmutableRef, error) {
382 382
 	}
383 383
 
384 384
 	platform := platforms.Only(p.platform)
385
+	// workaround for GCR bug that requires a request to manifest endpoint for authentication to work.
386
+	// if current resolver has not used manifests do a dummy request.
387
+	// in most cases resolver should be cached and extra request is not needed.
388
+	ensureManifestRequested(ctx, p.resolver, p.ref)
389
+
385 390
 	var (
386 391
 		schema1Converter *schema1.Converter
387 392
 		handlers         []images.Handler
... ...
@@ -795,3 +808,90 @@ func resolveModeToString(rm source.ResolveMode) string {
795 795
 	}
796 796
 	return ""
797 797
 }
798
+
799
+type resolverCache struct {
800
+	mu sync.Mutex
801
+	m  map[string]cachedResolver
802
+}
803
+
804
+type cachedResolver struct {
805
+	timeout time.Time
806
+	remotes.Resolver
807
+	counter int64
808
+}
809
+
810
+func (cr *cachedResolver) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
811
+	atomic.AddInt64(&cr.counter, 1)
812
+	return cr.Resolver.Resolve(ctx, ref)
813
+}
814
+
815
+func (r *resolverCache) Add(ctx context.Context, ref string, resolver remotes.Resolver) remotes.Resolver {
816
+	r.mu.Lock()
817
+	defer r.mu.Unlock()
818
+
819
+	ref = r.domain(ref) + "-" + session.FromContext(ctx)
820
+
821
+	cr, ok := r.m[ref]
822
+	cr.timeout = time.Now().Add(time.Minute)
823
+	if ok {
824
+		return &cr
825
+	}
826
+
827
+	cr.Resolver = resolver
828
+	r.m[ref] = cr
829
+	return &cr
830
+}
831
+
832
+func (r *resolverCache) domain(refStr string) string {
833
+	ref, err := distreference.ParseNormalizedNamed(refStr)
834
+	if err != nil {
835
+		return refStr
836
+	}
837
+	return distreference.Domain(ref)
838
+}
839
+
840
+func (r *resolverCache) Get(ctx context.Context, ref string) remotes.Resolver {
841
+	r.mu.Lock()
842
+	defer r.mu.Unlock()
843
+
844
+	ref = r.domain(ref) + "-" + session.FromContext(ctx)
845
+
846
+	cr, ok := r.m[ref]
847
+	if !ok {
848
+		return nil
849
+	}
850
+	return &cr
851
+}
852
+
853
+func (r *resolverCache) clean(now time.Time) {
854
+	r.mu.Lock()
855
+	for k, cr := range r.m {
856
+		if now.After(cr.timeout) {
857
+			delete(r.m, k)
858
+		}
859
+	}
860
+	r.mu.Unlock()
861
+}
862
+
863
+func newResolverCache() *resolverCache {
864
+	rc := &resolverCache{
865
+		m: map[string]cachedResolver{},
866
+	}
867
+	t := time.NewTicker(time.Minute)
868
+	go func() {
869
+		for {
870
+			rc.clean(<-t.C)
871
+		}
872
+	}()
873
+	return rc
874
+}
875
+
876
+func ensureManifestRequested(ctx context.Context, res remotes.Resolver, ref string) {
877
+	cr, ok := res.(*cachedResolver)
878
+	if !ok {
879
+		return
880
+	}
881
+	if atomic.LoadInt64(&cr.counter) == 0 {
882
+		res.Resolve(ctx, ref)
883
+	}
884
+}