Merge pull request #1428 from tonistiigi/imagemeta-async

llb: update image meta resolver to async callback
v0.8
Tõnis Tiigi 2020-04-06 10:00:42 -07:00 committed by GitHub
commit c44cb42a69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 134 additions and 18 deletions

View File

@ -14,6 +14,15 @@ func WithMetaResolver(mr ImageMetaResolver) ImageOption {
})
}
// ResolveDigest uses the meta resolver to update the ref of image with full digest before marshaling.
// This makes image ref immutable and is recommended if you want to make sure meta resolver data
// matches the image used during the build.
func ResolveDigest(v bool) ImageOption {
return imageOptionFunc(func(ii *ImageInfo) {
ii.resolveDigest = v
})
}
// ImageMetaResolver can resolve image config metadata from a reference
type ImageMetaResolver interface {
ResolveImageConfig(ctx context.Context, ref string, opt ResolveImageConfigOpt) (digest.Digest, []byte, error)

View File

@ -0,0 +1,91 @@
package llb
import (
"context"
"encoding/json"
"testing"
"github.com/moby/buildkit/solver/pb"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
func TestImageMetaResolver(t *testing.T) {
t.Parallel()
tr := &testResolver{
digest: digest.FromBytes([]byte("foo")),
dir: "/bar",
}
st := Image("alpine", WithMetaResolver(tr))
require.Equal(t, false, tr.called)
def, err := st.Marshal(context.TODO())
require.NoError(t, err)
require.Equal(t, true, tr.called)
m, arr := parseDef(t, def.Def)
require.Equal(t, 2, len(arr))
dgst, idx := last(t, arr)
require.Equal(t, 0, idx)
require.Equal(t, m[dgst], arr[0])
require.Equal(t, "docker-image://docker.io/library/alpine:latest", arr[0].Op.(*pb.Op_Source).Source.GetIdentifier())
d, err := st.GetDir(context.TODO())
require.NoError(t, err)
require.Equal(t, "/bar", d)
}
func TestImageResolveDigest(t *testing.T) {
t.Parallel()
st := Image("alpine", WithMetaResolver(&testResolver{
digest: digest.FromBytes([]byte("bar")),
dir: "/foo",
}), ResolveDigest(true))
def, err := st.Marshal(context.TODO())
require.NoError(t, err)
m, arr := parseDef(t, def.Def)
require.Equal(t, 2, len(arr))
dgst, idx := last(t, arr)
require.Equal(t, 0, idx)
require.Equal(t, m[dgst], arr[0])
require.Equal(t, "docker-image://docker.io/library/alpine:latest@"+string(digest.FromBytes([]byte("bar"))), arr[0].Op.(*pb.Op_Source).Source.GetIdentifier())
d, err := st.GetDir(context.TODO())
require.NoError(t, err)
require.Equal(t, "/foo", d)
}
type testResolver struct {
digest digest.Digest
dir string
called bool
}
func (r *testResolver) ResolveImageConfig(ctx context.Context, ref string, opt ResolveImageConfigOpt) (digest.Digest, []byte, error) {
var img struct {
Config struct {
Env []string `json:"Env,omitempty"`
WorkingDir string `json:"WorkingDir,omitempty"`
User string `json:"User,omitempty"`
} `json:"config,omitempty"`
}
r.called = true
img.Config.WorkingDir = r.dir
dt, err := json.Marshal(img)
if err != nil {
return "", nil, errors.WithStack(err)
}
return r.digest, dt, nil
}

View File

@ -92,7 +92,8 @@ func (s *SourceOp) Inputs() []Output {
func Image(ref string, opts ...ImageOption) State {
r, err := reference.ParseNormalizedNamed(ref)
if err == nil {
ref = reference.TagNameOnly(r).String()
r = reference.TagNameOnly(r)
ref = r.String()
}
var info ImageInfo
for _, opt := range opts {
@ -116,21 +117,35 @@ func Image(ref string, opts ...ImageOption) State {
src := NewSource("docker-image://"+ref, attrs, info.Constraints) // controversial
if err != nil {
src.err = err
}
if info.metaResolver != nil {
_, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, ResolveImageConfigOpt{
Platform: info.Constraints.Platform,
ResolveMode: info.resolveMode.String(),
})
if err != nil {
src.err = err
} else {
st, err := NewState(src.Output()).WithImageConfig(dt)
if err == nil {
return st
}
src.err = err
} else if info.metaResolver != nil {
if _, ok := r.(reference.Digested); ok || !info.resolveDigest {
return NewState(src.Output()).Async(func(ctx context.Context, st State) (State, error) {
_, dt, err := info.metaResolver.ResolveImageConfig(ctx, ref, ResolveImageConfigOpt{
Platform: info.Constraints.Platform,
ResolveMode: info.resolveMode.String(),
})
if err != nil {
return State{}, err
}
return st.WithImageConfig(dt)
})
}
return Scratch().Async(func(ctx context.Context, _ State) (State, error) {
dgst, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, ResolveImageConfigOpt{
Platform: info.Constraints.Platform,
ResolveMode: info.resolveMode.String(),
})
if err != nil {
return State{}, err
}
if dgst != "" {
r, err = reference.WithDigest(r, dgst)
if err != nil {
return State{}, err
}
}
return NewState(NewSource("docker-image://"+r.String(), attrs, info.Constraints).Output()).WithImageConfig(dt)
})
}
return NewState(src.Output())
}
@ -176,9 +191,10 @@ func (r ResolveMode) String() string {
type ImageInfo struct {
constraintsWrapper
metaResolver ImageMetaResolver
resolveMode ResolveMode
RecordType string
metaResolver ImageMetaResolver
resolveDigest bool
resolveMode ResolveMode
RecordType string
}
func Git(remote, ref string, opts ...GitOption) State {