diff --git a/frontend/dockerfile/builder/build.go b/frontend/dockerfile/builder/build.go index fe63cc914c17..17fcac871f55 100644 --- a/frontend/dockerfile/builder/build.go +++ b/frontend/dockerfile/builder/build.go @@ -787,8 +787,8 @@ func warnOpts(sm *llb.SourceMap, r *parser.Range, detail [][]byte, url string) c return opts } -func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Context, string) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { - return func(ctx context.Context, name string) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { +func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Context, string, string) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { + return func(ctx context.Context, name, resolveMode string) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { named, err := reference.ParseNormalizedNamed(name) if err != nil { return nil, nil, nil, errors.Wrapf(err, "invalid context name %s", name) @@ -801,7 +801,7 @@ func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Conte } if p != nil { name := name + "::" + platforms.Format(platforms.Normalize(*p)) - st, img, bi, err := contextByName(ctx, c, name, p) + st, img, bi, err := contextByName(ctx, c, name, p, resolveMode) if err != nil { return nil, nil, nil, err } @@ -809,11 +809,11 @@ func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Conte return st, img, bi, nil } } - return contextByName(ctx, c, name, p) + return contextByName(ctx, c, name, p, resolveMode) } } -func contextByName(ctx context.Context, c client.Client, name string, platform *ocispecs.Platform) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { +func contextByName(ctx context.Context, c client.Client, name string, platform *ocispecs.Platform, resolveMode string) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { opts := c.BuildOpts().Opts v, ok := opts["context:"+name] if !ok { @@ -829,13 +829,38 @@ func contextByName(ctx context.Context, c client.Client, name string, platform * ref := strings.TrimPrefix(vv[1], "//") imgOpt := []llb.ImageOption{ llb.WithCustomName("[context " + name + "] " + ref), - llb.WithMetaResolver(c), } if platform != nil { imgOpt = append(imgOpt, llb.Platform(*platform)) } + + named, err := reference.ParseNormalizedNamed(ref) + if err != nil { + return nil, nil, nil, err + } + + named = reference.TagNameOnly(named) + + _, data, err := c.ResolveImageConfig(ctx, named.String(), llb.ResolveImageConfigOpt{ + Platform: platform, + ResolveMode: resolveMode, + LogName: fmt.Sprintf("[context %s] load metadata for %s", name, ref), + }) + if err != nil { + return nil, nil, nil, err + } + + var img dockerfile2llb.Image + if err := json.Unmarshal(data, &img); err != nil { + return nil, nil, nil, err + } + st := llb.Image(ref, imgOpt...) - return &st, nil, nil, nil + st, err = st.WithImageConfig(data) + if err != nil { + return nil, nil, nil, err + } + return &st, &img, nil, nil case "git": st, ok := detectGitContext(v, "1") if !ok { diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 3f934e28d6cc..2dddd4ea441d 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -67,16 +67,16 @@ type ConvertOpt struct { SourceMap *llb.SourceMap Hostname string Warn func(short, url string, detail [][]byte, location *parser.Range) - ContextByName func(context.Context, string) (*llb.State, *Image, *binfotypes.BuildInfo, error) + ContextByName func(ctx context.Context, name, resolveMode string) (*llb.State, *Image, *binfotypes.BuildInfo, error) } func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, *binfotypes.BuildInfo, error) { buildInfo := &binfotypes.BuildInfo{} contextByName := opt.ContextByName - opt.ContextByName = func(ctx context.Context, name string) (*llb.State, *Image, *binfotypes.BuildInfo, error) { + opt.ContextByName = func(ctx context.Context, name, resolveMode string) (*llb.State, *Image, *binfotypes.BuildInfo, error) { if !strings.EqualFold(name, "scratch") && !strings.EqualFold(name, "context") { if contextByName != nil { - st, img, bi, err := contextByName(ctx, name) + st, img, bi, err := contextByName(ctx, name, resolveMode) if err != nil { return nil, nil, nil, err } @@ -163,7 +163,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, } if st.Name != "" { - s, img, bi, err := opt.ContextByName(ctx, st.Name) + s, img, bi, err := opt.ContextByName(ctx, st.Name, opt.ImageResolveMode.String()) if err != nil { return nil, nil, nil, err } @@ -308,7 +308,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, d.stage.BaseName = reference.TagNameOnly(ref).String() var isScratch bool - st, img, bi, err := opt.ContextByName(ctx, d.stage.BaseName) + st, img, bi, err := opt.ContextByName(ctx, d.stage.BaseName, opt.ImageResolveMode.String()) if err != nil { return err } diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 9e6ae4704a4f..d11ed45e3771 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -5223,6 +5223,7 @@ COPY --from=base /out / _, err = f.Solve(sb.Context(), c, client.SolveOpt{ FrontendAttrs: map[string]string{ + // Make sure image resolution works as expected, do not add a tag or locator. "context:busybox": "docker-image://alpine", }, LocalDirs: map[string]string{ @@ -5241,6 +5242,95 @@ COPY --from=base /out / dt, err := os.ReadFile(filepath.Join(destDir, "out")) require.NoError(t, err) require.True(t, len(dt) > 0) + + // Now test with an image with custom envs + dockerfile = []byte(` +FROM alpine:latest +ENV PATH=/foobar:$PATH +ENV FOOBAR=foobar +`) + + dir, err = tmpdir( + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + registry, err := sb.NewRegistry() + if errors.Is(err, integration.ErrRequirements) { + t.Skip(err.Error()) + } + require.NoError(t, err) + target := registry + "/buildkit/testnamedimagecontext:latest" + + _, err = f.Solve(sb.Context(), c, client.SolveOpt{ + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterImage, + Attrs: map[string]string{ + "name": target, + "push": "true", + }, + }, + }, + }, nil) + require.NoError(t, err) + + dockerfile = []byte(` +FROM busybox AS base +RUN cat /etc/alpine-release > /out +RUN env | grep PATH > /env_path +RUN env | grep FOOBAR > /env_foobar +FROM scratch +COPY --from=base /out / +COPY --from=base /env_path / +COPY --from=base /env_foobar / + `) + + dir, err = tmpdir( + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + f = getFrontend(t, sb) + + destDir, err = os.MkdirTemp("", "buildkit") + require.NoError(t, err) + defer os.RemoveAll(destDir) + + _, err = f.Solve(sb.Context(), c, client.SolveOpt{ + FrontendAttrs: map[string]string{ + "context:busybox": "docker-image://" + target, + }, + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterLocal, + OutputDir: destDir, + }, + }, + }, nil) + require.NoError(t, err) + + dt, err = os.ReadFile(filepath.Join(destDir, "out")) + require.NoError(t, err) + require.True(t, len(dt) > 0) + + dt, err = os.ReadFile(filepath.Join(destDir, "env_foobar")) + require.NoError(t, err) + require.Equal(t, "FOOBAR=foobar", strings.TrimSpace(string(dt))) + + dt, err = os.ReadFile(filepath.Join(destDir, "env_path")) + require.NoError(t, err) + require.Contains(t, string(dt), "/foobar:") } func testNamedLocalContext(t *testing.T, sb integration.Sandbox) {