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>
(cherry picked from commit bcd8298c35f53f79b42c6e089e8da114ddb5c57e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Upstream-commit: 21cce932b05a4b979359624668f7f09e83dc90cd
Component: engine
This commit is contained in:
Tonis Tiigi
2018-11-21 12:04:58 -08:00
committed by Sebastiaan van Stijn
parent 89a08e8974
commit c9f96feac5

View File

@ -8,6 +8,7 @@ import (
"io/ioutil"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/containerd/containerd/content"
@ -57,13 +58,15 @@ type SourceOpt struct {
type imageSource struct {
SourceOpt
g flightcontrol.Group
g flightcontrol.Group
resolverCache *resolverCache
}
// NewSource creates a new image source
func NewSource(opt SourceOpt) (source.Source, error) {
is := &imageSource{
SourceOpt: opt,
SourceOpt: opt,
resolverCache: newResolverCache(),
}
return is, nil
@ -74,6 +77,9 @@ func (is *imageSource) ID() string {
}
func (is *imageSource) getResolver(ctx context.Context, rfn resolver.ResolveOptionsFunc, ref string) remotes.Resolver {
if res := is.resolverCache.Get(ctx, ref); res != nil {
return res
}
opt := docker.ResolverOptions{
Client: tracing.DefaultClient,
}
@ -82,6 +88,7 @@ func (is *imageSource) getResolver(ctx context.Context, rfn resolver.ResolveOpti
}
opt.Credentials = is.getCredentialsFromSession(ctx)
r := docker.NewResolver(opt)
r = is.resolverCache.Add(ctx, ref, r)
return r
}
@ -380,6 +387,11 @@ func (p *puller) Snapshot(ctx context.Context) (cache.ImmutableRef, error) {
return nil, err
}
// workaround for GCR bug that requires a request to manifest endpoint for authentication to work.
// if current resolver has not used manifests do a dummy request.
// in most cases resolver should be cached and extra request is not needed.
ensureManifestRequested(ctx, p.resolver, p.ref)
var (
schema1Converter *schema1.Converter
handlers []images.Handler
@ -791,3 +803,90 @@ func resolveModeToString(rm source.ResolveMode) string {
}
return ""
}
type resolverCache struct {
mu sync.Mutex
m map[string]cachedResolver
}
type cachedResolver struct {
timeout time.Time
remotes.Resolver
counter int64
}
func (cr *cachedResolver) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
atomic.AddInt64(&cr.counter, 1)
return cr.Resolver.Resolve(ctx, ref)
}
func (r *resolverCache) Add(ctx context.Context, ref string, resolver remotes.Resolver) remotes.Resolver {
r.mu.Lock()
defer r.mu.Unlock()
ref = r.domain(ref) + "-" + session.FromContext(ctx)
cr, ok := r.m[ref]
cr.timeout = time.Now().Add(time.Minute)
if ok {
return &cr
}
cr.Resolver = resolver
r.m[ref] = cr
return &cr
}
func (r *resolverCache) domain(refStr string) string {
ref, err := distreference.ParseNormalizedNamed(refStr)
if err != nil {
return refStr
}
return distreference.Domain(ref)
}
func (r *resolverCache) Get(ctx context.Context, ref string) remotes.Resolver {
r.mu.Lock()
defer r.mu.Unlock()
ref = r.domain(ref) + "-" + session.FromContext(ctx)
cr, ok := r.m[ref]
if !ok {
return nil
}
return &cr
}
func (r *resolverCache) clean(now time.Time) {
r.mu.Lock()
for k, cr := range r.m {
if now.After(cr.timeout) {
delete(r.m, k)
}
}
r.mu.Unlock()
}
func newResolverCache() *resolverCache {
rc := &resolverCache{
m: map[string]cachedResolver{},
}
t := time.NewTicker(time.Minute)
go func() {
for {
rc.clean(<-t.C)
}
}()
return rc
}
func ensureManifestRequested(ctx context.Context, res remotes.Resolver, ref string) {
cr, ok := res.(*cachedResolver)
if !ok {
return
}
if atomic.LoadInt64(&cr.counter) == 0 {
res.Resolve(ctx, ref)
}
}