219 lines
5.4 KiB
Go
219 lines
5.4 KiB
Go
package resolver
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/images"
|
|
"github.com/containerd/containerd/remotes"
|
|
"github.com/containerd/containerd/remotes/docker"
|
|
distreference "github.com/docker/distribution/reference"
|
|
"github.com/moby/buildkit/session"
|
|
"github.com/moby/buildkit/source"
|
|
"github.com/moby/buildkit/version"
|
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
|
)
|
|
|
|
// DefaultPool is the default shared resolver pool instance
|
|
var DefaultPool = NewPool()
|
|
|
|
// Pool is a cache of recently used resolvers
|
|
type Pool struct {
|
|
mu sync.Mutex
|
|
m map[string]*authHandlerNS
|
|
}
|
|
|
|
// NewPool creates a new pool for caching resolvers
|
|
func NewPool() *Pool {
|
|
p := &Pool{
|
|
m: map[string]*authHandlerNS{},
|
|
}
|
|
time.AfterFunc(5*time.Minute, p.gc)
|
|
return p
|
|
}
|
|
|
|
func (p *Pool) gc() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
for k, ns := range p.m {
|
|
ns.muHandlers.Lock()
|
|
for key, h := range ns.handlers {
|
|
if time.Since(h.lastUsed) < 10*time.Minute {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(key, "/", 2)
|
|
if len(parts) != 2 {
|
|
delete(ns.handlers, key)
|
|
continue
|
|
}
|
|
c, err := ns.sm.Get(context.TODO(), parts[1], true)
|
|
if c == nil || err != nil {
|
|
delete(ns.handlers, key)
|
|
}
|
|
}
|
|
if len(ns.handlers) == 0 {
|
|
delete(p.m, k)
|
|
}
|
|
ns.muHandlers.Unlock()
|
|
}
|
|
|
|
time.AfterFunc(5*time.Minute, p.gc)
|
|
}
|
|
|
|
// Clear deletes currently cached items. This may be called on config changes for example.
|
|
func (p *Pool) Clear() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.m = map[string]*authHandlerNS{}
|
|
}
|
|
|
|
// GetResolver gets a resolver for a specified scope from the pool
|
|
func (p *Pool) GetResolver(hosts docker.RegistryHosts, ref, scope string, sm *session.Manager, g session.Group) *Resolver {
|
|
name := ref
|
|
named, err := distreference.ParseNormalizedNamed(ref)
|
|
if err == nil {
|
|
name = named.Name()
|
|
}
|
|
|
|
key := fmt.Sprintf("%s::%s", name, scope)
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
h, ok := p.m[key]
|
|
if !ok {
|
|
h = newAuthHandlerNS(sm)
|
|
p.m[key] = h
|
|
}
|
|
return newResolver(hosts, h, sm, g)
|
|
}
|
|
|
|
func newResolver(hosts docker.RegistryHosts, handler *authHandlerNS, sm *session.Manager, g session.Group) *Resolver {
|
|
if hosts == nil {
|
|
hosts = docker.ConfigureDefaultRegistries(
|
|
docker.WithClient(newDefaultClient()),
|
|
docker.WithPlainHTTP(docker.MatchLocalhost),
|
|
)
|
|
}
|
|
r := &Resolver{
|
|
hosts: hosts,
|
|
sm: sm,
|
|
g: g,
|
|
handler: handler,
|
|
}
|
|
headers := http.Header{}
|
|
headers.Set("User-Agent", version.UserAgent())
|
|
r.Resolver = docker.NewResolver(docker.ResolverOptions{
|
|
Hosts: r.HostsFunc,
|
|
Headers: headers,
|
|
})
|
|
return r
|
|
}
|
|
|
|
// Resolver is a wrapper around remotes.Resolver
|
|
type Resolver struct {
|
|
remotes.Resolver
|
|
hosts docker.RegistryHosts
|
|
sm *session.Manager
|
|
g session.Group
|
|
handler *authHandlerNS
|
|
auth *dockerAuthorizer
|
|
|
|
is images.Store
|
|
mode source.ResolveMode
|
|
}
|
|
|
|
// HostsFunc implements registry configuration of this Resolver
|
|
func (r *Resolver) HostsFunc(host string) ([]docker.RegistryHost, error) {
|
|
return func(domain string) ([]docker.RegistryHost, error) {
|
|
v, err := r.handler.g.Do(context.TODO(), domain, func(ctx context.Context) (interface{}, error) {
|
|
// long lock not needed because flightcontrol.Do
|
|
r.handler.muHosts.Lock()
|
|
v, ok := r.handler.hosts[domain]
|
|
r.handler.muHosts.Unlock()
|
|
if ok {
|
|
return v, nil
|
|
}
|
|
res, err := r.hosts(domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.handler.muHosts.Lock()
|
|
r.handler.hosts[domain] = res
|
|
r.handler.muHosts.Unlock()
|
|
return res, nil
|
|
})
|
|
if err != nil || v == nil {
|
|
return nil, err
|
|
}
|
|
vv := v.([]docker.RegistryHost)
|
|
if len(vv) == 0 {
|
|
return nil, nil
|
|
}
|
|
// make a copy so authorizer is set on unique instance
|
|
res := make([]docker.RegistryHost, len(vv))
|
|
copy(res, vv)
|
|
auth := newDockerAuthorizer(res[0].Client, r.handler, r.sm, r.g)
|
|
for i := range res {
|
|
res[i].Authorizer = auth
|
|
}
|
|
return res, nil
|
|
}(host)
|
|
}
|
|
|
|
// WithSession returns a new resolver that works with new session group
|
|
func (r *Resolver) WithSession(s session.Group) *Resolver {
|
|
r2 := *r
|
|
r2.auth = nil
|
|
r2.g = s
|
|
r2.Resolver = docker.NewResolver(docker.ResolverOptions{
|
|
Hosts: r2.HostsFunc, // this refers to the newly-configured session so we need to recreate the resolver.
|
|
})
|
|
return &r2
|
|
}
|
|
|
|
// WithImageStore returns new resolver that can also resolve from local images store
|
|
func (r *Resolver) WithImageStore(is images.Store, mode source.ResolveMode) *Resolver {
|
|
r2 := *r
|
|
r2.Resolver = r.Resolver
|
|
r2.is = is
|
|
r2.mode = mode
|
|
return &r2
|
|
}
|
|
|
|
// Fetcher returns a new fetcher for the provided reference.
|
|
func (r *Resolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
|
|
if atomic.LoadInt64(&r.handler.counter) == 0 {
|
|
r.Resolve(ctx, ref)
|
|
}
|
|
return r.Resolver.Fetcher(ctx, ref)
|
|
}
|
|
|
|
// Resolve attempts to resolve the reference into a name and descriptor.
|
|
func (r *Resolver) Resolve(ctx context.Context, ref string) (string, ocispecs.Descriptor, error) {
|
|
if r.mode == source.ResolveModePreferLocal && r.is != nil {
|
|
if img, err := r.is.Get(ctx, ref); err == nil {
|
|
return ref, img.Target, nil
|
|
}
|
|
}
|
|
|
|
n, desc, err := r.Resolver.Resolve(ctx, ref)
|
|
if err == nil {
|
|
atomic.AddInt64(&r.handler.counter, 1)
|
|
return n, desc, nil
|
|
}
|
|
|
|
if r.mode == source.ResolveModeDefault && r.is != nil {
|
|
if img, err := r.is.Get(ctx, ref); err == nil {
|
|
return ref, img.Target, nil
|
|
}
|
|
}
|
|
|
|
return "", ocispecs.Descriptor{}, err
|
|
}
|