dockerfile: test named contexts with multi-platform

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
master
Tonis Tiigi 2021-12-13 23:26:10 -08:00
parent 4eadeaf0f2
commit 20285bb53e
2 changed files with 223 additions and 59 deletions

View File

@ -462,7 +462,7 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
}
c.Warn(ctx, defVtx, msg, warnOpts(sourceMap, location, detail, url))
},
ContextByName: contextByName(c, tp),
ContextByName: contextByNameFunc(c, tp),
})
if err != nil {
@ -804,7 +804,7 @@ func warnOpts(sm *llb.SourceMap, r *parser.Range, detail [][]byte, url string) c
return opts
}
func contextByName(c client.Client, p *ocispecs.Platform) func(context.Context, string) (*llb.State, *dockerfile2llb.Image, error) {
func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Context, string) (*llb.State, *dockerfile2llb.Image, error) {
return func(ctx context.Context, name string) (*llb.State, *dockerfile2llb.Image, error) {
named, err := reference.ParseNormalizedNamed(name)
if err != nil {
@ -812,68 +812,82 @@ func contextByName(c client.Client, p *ocispecs.Platform) func(context.Context,
}
name = strings.TrimSuffix(reference.FamiliarString(named), ":latest")
opts := c.BuildOpts().Opts
v, ok := opts["context:"+name]
if !ok {
return nil, nil, nil
}
vv := strings.SplitN(v, ":", 2)
if len(vv) != 2 {
return nil, nil, errors.Errorf("invalid context specifier %s for %s", v, name)
}
switch vv[0] {
case "docker-image":
st := llb.Image(strings.TrimPrefix(vv[1], "//"), llb.WithCustomName("[context "+name+"] "+vv[1]), llb.WithMetaResolver(c))
return &st, nil, nil
case "git":
st, ok := detectGitContext(v, "")
if !ok {
return nil, nil, errors.Errorf("invalid git context %s", v)
}
return st, nil, nil
case "http", "https":
st, ok := detectGitContext(v, "")
if !ok {
httpst := llb.HTTP(v, llb.WithCustomName("[context "+name+"] "+v))
st = &httpst
}
return st, nil, nil
case "local":
st := llb.Local(vv[1], llb.WithCustomName("[context "+name+"] load from client"), llb.SessionID(c.BuildOpts().SessionID), llb.SharedKeyHint("context:"+name))
return &st, nil, nil
case "input":
inputs, err := c.Inputs(ctx)
if p != nil {
name := name + "::" + platforms.Format(platforms.Normalize(*p))
st, img, err := contextByName(ctx, c, name)
if err != nil {
return nil, nil, err
}
st, ok := inputs[vv[1]]
if !ok {
return nil, nil, errors.Errorf("invalid input %s for %s", vv[1], name)
if st != nil {
return st, img, nil
}
md, ok := opts["input-metadata:"+vv[1]]
if ok {
m := make(map[string][]byte)
if err := json.Unmarshal([]byte(md), &m); err != nil {
return nil, nil, errors.Wrapf(err, "failed to parse input metadata %s", md)
}
dt, ok := m["containerimage.config"]
if ok {
st, err = st.WithImageConfig([]byte(dt))
if err != nil {
return nil, nil, err
}
var img dockerfile2llb.Image
if err := json.Unmarshal(dt, &img); err != nil {
return nil, nil, errors.Wrapf(err, "failed to parse image config for %s", name)
}
return &st, &img, nil
}
}
return &st, nil, nil
default:
return nil, nil, errors.Errorf("unsupported context source %s for %s", vv[0], name)
}
return contextByName(ctx, c, name)
}
}
func contextByName(ctx context.Context, c client.Client, name string) (*llb.State, *dockerfile2llb.Image, error) {
opts := c.BuildOpts().Opts
v, ok := opts["context:"+name]
if !ok {
return nil, nil, nil
}
vv := strings.SplitN(v, ":", 2)
if len(vv) != 2 {
return nil, nil, errors.Errorf("invalid context specifier %s for %s", v, name)
}
switch vv[0] {
case "docker-image":
st := llb.Image(strings.TrimPrefix(vv[1], "//"), llb.WithCustomName("[context "+name+"] "+vv[1]), llb.WithMetaResolver(c))
return &st, nil, nil
case "git":
st, ok := detectGitContext(v, "1")
if !ok {
return nil, nil, errors.Errorf("invalid git context %s", v)
}
return st, nil, nil
case "http", "https":
st, ok := detectGitContext(v, "1")
if !ok {
httpst := llb.HTTP(v, llb.WithCustomName("[context "+name+"] "+v))
st = &httpst
}
return st, nil, nil
case "local":
st := llb.Local(vv[1], llb.WithCustomName("[context "+name+"] load from client"), llb.SessionID(c.BuildOpts().SessionID), llb.SharedKeyHint("context:"+name))
return &st, nil, nil
case "input":
inputs, err := c.Inputs(ctx)
if err != nil {
return nil, nil, err
}
st, ok := inputs[vv[1]]
if !ok {
return nil, nil, errors.Errorf("invalid input %s for %s", vv[1], name)
}
md, ok := opts["input-metadata:"+vv[1]]
if ok {
m := make(map[string][]byte)
if err := json.Unmarshal([]byte(md), &m); err != nil {
return nil, nil, errors.Wrapf(err, "failed to parse input metadata %s", md)
}
dt, ok := m["containerimage.config"]
if ok {
st, err = st.WithImageConfig([]byte(dt))
if err != nil {
return nil, nil, err
}
var img dockerfile2llb.Image
if err := json.Unmarshal(dt, &img); err != nil {
return nil, nil, errors.Wrapf(err, "failed to parse image config for %s", name)
}
return &st, &img, nil
}
}
return &st, nil, nil
default:
return nil, nil, errors.Errorf("unsupported context source %s for %s", vv[0], name)
}
}

View File

@ -122,6 +122,7 @@ var allTests = integration.TestFuncs(
testNamedImageContext,
testNamedLocalContext,
testNamedInputContext,
testNamedMultiplatformInputContext,
)
var fileOpTests = integration.TestFuncs(
@ -5627,6 +5628,155 @@ COPY --from=build /foo /out /
require.Equal(t, "foo is bar\n", string(dt))
}
func testNamedMultiplatformInputContext(t *testing.T, sb integration.Sandbox) {
ctx := sb.Context()
c, err := client.New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()
dockerfile := []byte(`
FROM --platform=$BUILDPLATFORM alpine
ARG TARGETARCH
ENV FOO=bar-$TARGETARCH
RUN echo "foo $TARGETARCH" > /out
`)
dir, err := tmpdir(
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)
dockerfile2 := []byte(`
FROM base AS build
RUN echo "foo is $FOO" > /foo
FROM scratch
COPY --from=build /foo /out /
`)
dir2, err := tmpdir(
fstest.CreateFile("Dockerfile", dockerfile2, 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)
f := getFrontend(t, sb)
b := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
res, err := f.SolveGateway(ctx, c, gateway.SolveRequest{
FrontendOpt: map[string]string{
"platform": "linux/amd64,linux/arm64",
},
})
if err != nil {
return nil, err
}
if len(res.Refs) != 2 {
return nil, errors.Errorf("expected 2 refs, got %d", len(res.Refs))
}
inputs := map[string]*pb.Definition{}
st, err := res.Refs["linux/amd64"].ToState()
if err != nil {
return nil, err
}
def, err := st.Marshal(ctx)
if err != nil {
return nil, err
}
inputs["base::linux/amd64"] = def.ToPB()
st, err = res.Refs["linux/arm64"].ToState()
if err != nil {
return nil, err
}
def, err = st.Marshal(ctx)
if err != nil {
return nil, err
}
inputs["base::linux/arm64"] = def.ToPB()
frontendOpt := map[string]string{
"dockerfilekey": builder.DefaultLocalNameDockerfile + "2",
"context:base::linux/amd64": "input:base::linux/amd64",
"context:base::linux/arm64": "input:base::linux/arm64",
"platform": "linux/amd64,linux/arm64",
}
dt, ok := res.Metadata["containerimage.config/linux/amd64"]
if !ok {
return nil, errors.Errorf("no containerimage.config in metadata")
}
dt, err = json.Marshal(map[string][]byte{
"containerimage.config": dt,
})
if err != nil {
return nil, err
}
frontendOpt["input-metadata:base::linux/amd64"] = string(dt)
dt, ok = res.Metadata["containerimage.config/linux/arm64"]
if !ok {
return nil, errors.Errorf("no containerimage.config in metadata")
}
dt, err = json.Marshal(map[string][]byte{
"containerimage.config": dt,
})
if err != nil {
return nil, err
}
frontendOpt["input-metadata:base::linux/arm64"] = string(dt)
res, err = f.SolveGateway(ctx, c, gateway.SolveRequest{
FrontendOpt: frontendOpt,
FrontendInputs: inputs,
})
if err != nil {
return nil, err
}
return res, nil
}
product := "buildkit_test"
destDir, err := ioutil.TempDir("", "buildkit")
require.NoError(t, err)
defer os.RemoveAll(destDir)
_, err = c.Build(ctx, client.SolveOpt{
LocalDirs: map[string]string{
builder.DefaultLocalNameDockerfile: dir,
builder.DefaultLocalNameContext: dir,
builder.DefaultLocalNameDockerfile + "2": dir2,
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir,
},
},
}, product, b, nil)
require.NoError(t, err)
dt, err := ioutil.ReadFile(filepath.Join(destDir, "linux_amd64/out"))
require.NoError(t, err)
require.Equal(t, "foo amd64\n", string(dt))
dt, err = ioutil.ReadFile(filepath.Join(destDir, "linux_amd64/foo"))
require.NoError(t, err)
require.Equal(t, "foo is bar-amd64\n", string(dt))
dt, err = ioutil.ReadFile(filepath.Join(destDir, "linux_arm64/out"))
require.NoError(t, err)
require.Equal(t, "foo arm64\n", string(dt))
dt, err = ioutil.ReadFile(filepath.Join(destDir, "linux_arm64/foo"))
require.NoError(t, err)
require.Equal(t, "foo is bar-arm64\n", string(dt))
}
func tmpdir(appliers ...fstest.Applier) (string, error) {
tmpdir, err := ioutil.TempDir("", "buildkit-dockerfile")
if err != nil {