dockerfile: test named contexts with multi-platform
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>master
parent
4eadeaf0f2
commit
20285bb53e
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue