Merge pull request #113 from tonistiigi/share-resolver

imagesource: share config resolver with frontend
docker-18.09
Akihiro Suda 2017-09-06 18:28:33 +09:00 committed by GitHub
commit 2f3ecb50dd
10 changed files with 222 additions and 114 deletions

View File

@ -3,9 +3,6 @@ package llb
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"io"
"io/ioutil"
"net/http" "net/http"
"sync" "sync"
"time" "time"
@ -13,9 +10,9 @@ import (
"github.com/BurntSushi/locker" "github.com/BurntSushi/locker"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker" "github.com/containerd/containerd/remotes/docker"
"github.com/moby/buildkit/util/imageutil"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -35,7 +32,7 @@ func WithDefaultMetaResolver(ii *ImageInfo) {
} }
type ImageMetaResolver interface { type ImageMetaResolver interface {
Resolve(ctx context.Context, ref string) (*ocispec.Image, error) ResolveImageConfig(ctx context.Context, ref string) (*ocispec.Image, error)
} }
func NewImageMetaResolver() ImageMetaResolver { func NewImageMetaResolver() ImageMetaResolver {
@ -63,7 +60,7 @@ type imageMetaResolver struct {
cache map[string]*ocispec.Image cache map[string]*ocispec.Image
} }
func (imr *imageMetaResolver) Resolve(ctx context.Context, ref string) (*ocispec.Image, error) { func (imr *imageMetaResolver) ResolveImageConfig(ctx context.Context, ref string) (*ocispec.Image, error) {
imr.locker.Lock(ref) imr.locker.Lock(ref)
defer imr.locker.Unlock(ref) defer imr.locker.Unlock(ref)
@ -71,87 +68,13 @@ func (imr *imageMetaResolver) Resolve(ctx context.Context, ref string) (*ocispec
return img, nil return img, nil
} }
ref, desc, err := imr.resolver.Resolve(ctx, ref) img, err := imageutil.Config(ctx, ref, imr.resolver, imr.ingester)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fetcher, err := imr.resolver.Fetcher(ctx, ref) imr.cache[ref] = img
if err != nil { return img, nil
return nil, err
}
handlers := []images.Handler{
remotes.FetchHandler(imr.ingester, fetcher),
childrenConfigHandler(imr.ingester),
}
if err := images.Dispatch(ctx, images.Handlers(handlers...), desc); err != nil {
return nil, err
}
config, err := images.Config(ctx, imr.ingester, desc)
if err != nil {
return nil, err
}
var ociimage ocispec.Image
r, err := imr.ingester.Reader(ctx, config.Digest)
if err != nil {
return nil, err
}
defer r.Close()
dec := json.NewDecoder(r)
if err := dec.Decode(&ociimage); err != nil {
return nil, err
}
if dec.More() {
return nil, errors.New("invalid image config")
}
imr.cache[ref] = &ociimage
return &ociimage, nil
}
func childrenConfigHandler(provider content.Provider) images.HandlerFunc {
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
var descs []ocispec.Descriptor
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
p, err := content.ReadBlob(ctx, provider, desc.Digest)
if err != nil {
return nil, err
}
// TODO(stevvooe): We just assume oci manifest, for now. There may be
// subtle differences from the docker version.
var manifest ocispec.Manifest
if err := json.Unmarshal(p, &manifest); err != nil {
return nil, err
}
descs = append(descs, manifest.Config)
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
p, err := content.ReadBlob(ctx, provider, desc.Digest)
if err != nil {
return nil, err
}
var index ocispec.Index
if err := json.Unmarshal(p, &index); err != nil {
return nil, err
}
descs = append(descs, index.Manifests...)
case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
// childless data types.
return nil, nil
default:
return nil, errors.Errorf("encountered unknown type %v; children may not be fetched", desc.MediaType)
}
return descs, nil
}
} }
type inMemoryIngester struct { type inMemoryIngester struct {
@ -184,20 +107,6 @@ func (i *inMemoryIngester) Writer(ctx context.Context, ref string, size int64, e
return w, nil return w, nil
} }
func (i *inMemoryIngester) Reader(ctx context.Context, dgst digest.Digest) (io.ReadCloser, error) {
rdr, err := i.reader(ctx, dgst)
if err != nil {
return nil, err
}
return ioutil.NopCloser(rdr), nil
}
type ReaderAt interface {
io.ReaderAt
io.Closer
Size() int64
}
func (i *inMemoryIngester) ReaderAt(ctx context.Context, dgst digest.Digest) (content.ReaderAt, error) { func (i *inMemoryIngester) ReaderAt(ctx context.Context, dgst digest.Digest) (content.ReaderAt, error) {
r, err := i.reader(ctx, dgst) r, err := i.reader(ctx, dgst)
if err != nil { if err != nil {

View File

@ -85,7 +85,7 @@ func Image(ref string, opts ...ImageOption) State {
opt(&info) opt(&info)
} }
if info.metaResolver != nil { if info.metaResolver != nil {
img, err := info.metaResolver.Resolve(context.TODO(), ref) img, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref)
if err != nil { if err != nil {
src.err = err src.err = err
} else { } else {

View File

@ -28,6 +28,7 @@ type Opt struct {
Exporters map[string]exporter.Exporter Exporters map[string]exporter.Exporter
SessionManager *session.Manager SessionManager *session.Manager
Frontends map[string]frontend.Frontend Frontends map[string]frontend.Frontend
ImageSource source.Source
} }
type Controller struct { // TODO: ControlService type Controller struct { // TODO: ControlService
@ -43,6 +44,7 @@ func NewController(opt Opt) (*Controller, error) {
CacheManager: opt.CacheManager, CacheManager: opt.CacheManager,
Worker: opt.Worker, Worker: opt.Worker,
InstructionCache: opt.InstructionCache, InstructionCache: opt.InstructionCache,
ImageSource: opt.ImageSource,
}), }),
} }
return c, nil return c, nil

View File

@ -137,5 +137,6 @@ func defaultControllerOpts(root string, pd pullDeps) (*Opt, error) {
Exporters: exporters, Exporters: exporters,
SessionManager: sessm, SessionManager: sessm,
Frontends: frontends, Frontends: frontends,
ImageSource: is,
}, nil }, nil
} }

View File

@ -79,7 +79,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
} }
d.stage.BaseName = reference.TagNameOnly(ref).String() d.stage.BaseName = reference.TagNameOnly(ref).String()
if opt.MetaResolver != nil { if opt.MetaResolver != nil {
img, err := metaResolver.Resolve(ctx, d.stage.BaseName) img, err := metaResolver.ResolveImageConfig(ctx, d.stage.BaseName)
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,6 +2,7 @@ package frontend
import ( import (
"github.com/moby/buildkit/cache" "github.com/moby/buildkit/cache"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -12,5 +13,5 @@ type Frontend interface {
type FrontendLLBBridge interface { type FrontendLLBBridge interface {
Solve(ctx context.Context, vtx [][]byte) (cache.ImmutableRef, error) Solve(ctx context.Context, vtx [][]byte) (cache.ImmutableRef, error)
// ImageConfigResolver ResolveImageConfig(ctx context.Context, ref string) (*ocispec.Image, error)
} }

View File

@ -14,6 +14,7 @@ import (
"github.com/moby/buildkit/util/progress" "github.com/moby/buildkit/util/progress"
"github.com/moby/buildkit/worker" "github.com/moby/buildkit/worker"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -25,6 +26,7 @@ type LLBOpt struct {
CacheManager cache.Manager // TODO: this shouldn't be needed before instruction cache CacheManager cache.Manager // TODO: this shouldn't be needed before instruction cache
Worker worker.Worker Worker worker.Worker
InstructionCache InstructionCache InstructionCache InstructionCache
ImageSource source.Source
} }
func NewLLBSolver(opt LLBOpt) *Solver { func NewLLBSolver(opt LLBOpt) *Solver {
@ -40,7 +42,7 @@ func NewLLBSolver(opt LLBOpt) *Solver {
default: default:
return nil, errors.Errorf("invalid op type %T", op) return nil, errors.Errorf("invalid op type %T", op)
} }
}, opt.InstructionCache) }, opt.InstructionCache, opt.ImageSource)
return s return s
} }
@ -71,13 +73,19 @@ type Solver struct {
jobs *jobList jobs *jobList
activeState activeState activeState activeState
cache InstructionCache cache InstructionCache
imageSource source.Source
} }
func New(resolve ResolveOpFunc, cache InstructionCache) *Solver { func New(resolve ResolveOpFunc, cache InstructionCache, imageSource source.Source) *Solver {
return &Solver{resolve: resolve, jobs: newJobList(), cache: cache} return &Solver{resolve: resolve, jobs: newJobList(), cache: cache, imageSource: imageSource}
}
type resolveImageConfig interface {
ResolveImageConfig(ctx context.Context, ref string) (*ocispec.Image, error)
} }
type llbBridge struct { type llbBridge struct {
resolveImageConfig
solver func(ctx context.Context, v *vertex, i Index) (Reference, error) solver func(ctx context.Context, v *vertex, i Index) (Reference, error)
} }
@ -148,6 +156,7 @@ func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, v Ve
} else { } else {
ref, err = f.Solve(ctx, &llbBridge{ ref, err = f.Solve(ctx, &llbBridge{
solver: s.getRef, solver: s.getRef,
resolveImageConfig: s.imageSource.(resolveImageConfig),
}, frontendOpt) }, frontendOpt)
} }
s.activeState.cancel(j) s.activeState.cancel(j)

View File

@ -8,6 +8,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/BurntSushi/locker"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
@ -17,6 +18,7 @@ import (
"github.com/containerd/containerd/snapshot" "github.com/containerd/containerd/snapshot"
"github.com/moby/buildkit/cache" "github.com/moby/buildkit/cache"
"github.com/moby/buildkit/source" "github.com/moby/buildkit/source"
"github.com/moby/buildkit/util/imageutil"
"github.com/moby/buildkit/util/progress" "github.com/moby/buildkit/util/progress"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/identity"
@ -40,17 +42,24 @@ type blobmapper interface {
SetBlob(ctx gocontext.Context, key string, blob digest.Digest) error SetBlob(ctx gocontext.Context, key string, blob digest.Digest) error
} }
type resolveRecord struct {
desc ocispec.Descriptor
ts time.Time
}
type imageSource struct { type imageSource struct {
SourceOpt SourceOpt
resolver remotes.Resolver resolver remotes.Resolver
lru map[string]resolveRecord
} }
func NewSource(opt SourceOpt) (source.Source, error) { func NewSource(opt SourceOpt) (source.Source, error) {
is := &imageSource{ is := &imageSource{
SourceOpt: opt, SourceOpt: opt,
resolver: docker.NewResolver(docker.ResolverOptions{ lru: map[string]resolveRecord{},
resolver: newCachedResolver(docker.NewResolver(docker.ResolverOptions{
Client: http.DefaultClient, Client: http.DefaultClient,
}), }), 5*time.Second),
} }
if _, ok := opt.Snapshotter.(blobmapper); !ok { if _, ok := opt.Snapshotter.(blobmapper); !ok {
@ -64,13 +73,8 @@ func (is *imageSource) ID() string {
return source.DockerImageScheme return source.DockerImageScheme
} }
type puller struct { func (is *imageSource) ResolveImageConfig(ctx context.Context, ref string) (*ocispec.Image, error) {
is *imageSource return imageutil.Config(ctx, ref, is.resolver, is.ContentStore)
resolveOnce sync.Once
src *source.ImageIdentifier
desc ocispec.Descriptor
ref string
resolveErr error
} }
func (is *imageSource) Resolve(ctx context.Context, id source.Identifier) (source.SourceInstance, error) { func (is *imageSource) Resolve(ctx context.Context, id source.Identifier) (source.SourceInstance, error) {
@ -86,9 +90,34 @@ func (is *imageSource) Resolve(ctx context.Context, id source.Identifier) (sourc
return p, nil return p, nil
} }
type puller struct {
is *imageSource
resolveOnce sync.Once
src *source.ImageIdentifier
desc ocispec.Descriptor
ref string
resolveErr error
}
func (p *puller) resolve(ctx context.Context) error { func (p *puller) resolve(ctx context.Context) error {
p.resolveOnce.Do(func() { p.resolveOnce.Do(func() {
resolveProgressDone := oneOffProgress(ctx, "resolve "+p.src.Reference.String()) resolveProgressDone := oneOffProgress(ctx, "resolve "+p.src.Reference.String())
dgst := p.src.Reference.Digest()
if dgst != "" {
info, err := p.is.ContentStore.Info(ctx, dgst)
if err == nil {
p.ref = p.src.Reference.String()
p.desc = ocispec.Descriptor{
Size: info.Size,
Digest: dgst,
MediaType: ocispec.MediaTypeImageManifest, // TODO: detect schema1/manifest-list
}
resolveProgressDone(nil)
return
}
}
ref, desc, err := p.is.resolver.Resolve(ctx, p.src.Reference.String()) ref, desc, err := p.is.resolver.Resolve(ctx, p.src.Reference.String())
if err != nil { if err != nil {
p.resolveErr = err p.resolveErr = err
@ -383,3 +412,45 @@ func oneOffProgress(ctx context.Context, id string) func(err error) error {
return err return err
} }
} }
func newCachedResolver(r remotes.Resolver, timeout time.Duration) remotes.Resolver {
return &cachedResolver{
Resolver: r,
cache: map[string]cachedResult{},
timeout: timeout,
locker: locker.NewLocker(),
}
}
type cachedResolver struct {
remotes.Resolver
cache map[string]cachedResult
timeout time.Duration
locker *locker.Locker
}
func (cr *cachedResolver) Resolve(ctx gocontext.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
cr.locker.Lock(ref)
defer cr.locker.Unlock(ref)
r, ok := cr.cache[ref]
if ok && time.Since(r.ts) < cr.timeout {
return r.name, r.desc, nil
}
delete(cr.cache, ref)
n, d, err := cr.Resolver.Resolve(ctx, ref)
if err != nil {
return "", d, err
}
cr.cache[ref] = cachedResult{
name: n,
desc: d,
ts: time.Now(),
}
return n, d, nil
}
type cachedResult struct {
name string
desc ocispec.Descriptor
ts time.Time
}

115
util/imageutil/config.go Normal file
View File

@ -0,0 +1,115 @@
package imageutil
import (
"context"
"encoding/json"
"io"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/remotes"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type IngesterProvider interface {
content.Ingester
content.Provider
}
func Config(ctx context.Context, ref string, resolver remotes.Resolver, ingester IngesterProvider) (*ocispec.Image, error) {
ref, desc, err := resolver.Resolve(ctx, ref)
if err != nil {
return nil, err
}
fetcher, err := resolver.Fetcher(ctx, ref)
if err != nil {
return nil, err
}
handlers := []images.Handler{
remotes.FetchHandler(ingester, fetcher),
childrenConfigHandler(ingester),
}
if err := images.Dispatch(ctx, images.Handlers(handlers...), desc); err != nil {
return nil, err
}
config, err := images.Config(ctx, ingester, desc)
if err != nil {
return nil, err
}
var ociimage ocispec.Image
r, err := ingester.ReaderAt(ctx, config.Digest)
if err != nil {
return nil, err
}
defer r.Close()
dec := json.NewDecoder(readerAtToReader(r))
if err := dec.Decode(&ociimage); err != nil {
return nil, err
}
if dec.More() {
return nil, errors.New("invalid image config")
}
return &ociimage, nil
}
func childrenConfigHandler(provider content.Provider) images.HandlerFunc {
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
var descs []ocispec.Descriptor
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
p, err := content.ReadBlob(ctx, provider, desc.Digest)
if err != nil {
return nil, err
}
// TODO(stevvooe): We just assume oci manifest, for now. There may be
// subtle differences from the docker version.
var manifest ocispec.Manifest
if err := json.Unmarshal(p, &manifest); err != nil {
return nil, err
}
descs = append(descs, manifest.Config)
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
p, err := content.ReadBlob(ctx, provider, desc.Digest)
if err != nil {
return nil, err
}
var index ocispec.Index
if err := json.Unmarshal(p, &index); err != nil {
return nil, err
}
descs = append(descs, index.Manifests...)
case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
// childless data types.
return nil, nil
default:
return nil, errors.Errorf("encountered unknown type %v; children may not be fetched", desc.MediaType)
}
return descs, nil
}
}
func readerAtToReader(r io.ReaderAt) io.Reader {
return &reader{ra: r}
}
type reader struct {
ra io.ReaderAt
offset int64
}
func (r *reader) Read(b []byte) (int, error) {
n, err := r.ra.ReadAt(b, r.offset)
r.offset += int64(n)
return n, err
}

View File

@ -33,7 +33,7 @@ github.com/google/shlex 6f45313302b9c56850fc17f99e40caebce98c716
golang.org/x/time 8be79e1e0910c292df4e79c241bb7e8f7e725959 golang.org/x/time 8be79e1e0910c292df4e79c241bb7e8f7e725959
github.com/BurntSushi/locker 392720b78f44e9d0249fcac6c43b111b47a370b8 github.com/BurntSushi/locker 392720b78f44e9d0249fcac6c43b111b47a370b8
github.com/docker/docker 6f723db8c6f0c7f0b252674a9673a25b5978db04 https://github.com/simonferquel/docker.git github.com/docker/docker 6f723db8c6f0c7f0b252674a9673a25b5978db04 https://github.com/tonistiigi/docker.git
github.com/pkg/profile 5b67d428864e92711fcbd2f8629456121a56d91f github.com/pkg/profile 5b67d428864e92711fcbd2f8629456121a56d91f
github.com/tonistiigi/fsutil d49833a9a6fa5b41f63e7e338038633d10276b57 github.com/tonistiigi/fsutil d49833a9a6fa5b41f63e7e338038633d10276b57