Merge pull request #1428 from tonistiigi/imagemeta-async
llb: update image meta resolver to async callbackv0.8
commit
c44cb42a69
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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{
|
||||
} 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 {
|
||||
src.err = err
|
||||
} else {
|
||||
st, err := NewState(src.Output()).WithImageConfig(dt)
|
||||
if err == nil {
|
||||
return st
|
||||
return State{}, err
|
||||
}
|
||||
src.err = 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())
|
||||
}
|
||||
|
@ -177,6 +192,7 @@ func (r ResolveMode) String() string {
|
|||
type ImageInfo struct {
|
||||
constraintsWrapper
|
||||
metaResolver ImageMetaResolver
|
||||
resolveDigest bool
|
||||
resolveMode ResolveMode
|
||||
RecordType string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue