Skip to content

Commit cc82be7

Browse files
authored
Merge pull request moby#5688 from tonistiigi/dockerfile-eager-ncontext
dockerfile: avoid too eager loading of unreachable named contextes
2 parents f3aafa7 + d394e48 commit cc82be7

File tree

5 files changed

+199
-101
lines changed

5 files changed

+199
-101
lines changed

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 73 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -206,17 +206,17 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
206206
return nil, errors.Errorf("Client and MainContext cannot both be provided")
207207
}
208208

209-
namedContext := func(ctx context.Context, name string, copt dockerui.ContextOpt) (*llb.State, *dockerspec.DockerOCIImage, error) {
209+
namedContext := func(name string, copt dockerui.ContextOpt) (*dockerui.NamedContext, error) {
210210
if opt.Client == nil {
211-
return nil, nil, nil
211+
return nil, nil
212212
}
213213
if !strings.EqualFold(name, "scratch") && !strings.EqualFold(name, "context") {
214214
if copt.Platform == nil {
215215
copt.Platform = opt.TargetPlatform
216216
}
217-
return opt.Client.NamedContext(ctx, name, copt)
217+
return opt.Client.NamedContext(name, copt)
218218
}
219-
return nil, nil, nil
219+
return nil, nil
220220
}
221221

222222
lint, err := newRuleLinter(dt, &opt)
@@ -349,33 +349,16 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
349349
}
350350

351351
if st.Name != "" {
352-
s, img, err := namedContext(ctx, st.Name, dockerui.ContextOpt{
352+
nc, err := namedContext(st.Name, dockerui.ContextOpt{
353353
Platform: ds.platform,
354354
ResolveMode: opt.ImageResolveMode.String(),
355355
AsyncLocalOpts: ds.asyncLocalOpts,
356356
})
357357
if err != nil {
358358
return nil, err
359359
}
360-
if s != nil {
361-
ds.dispatched = true
362-
ds.state = *s
363-
if img != nil {
364-
// timestamps are inherited as-is, regardless to SOURCE_DATE_EPOCH
365-
// https://github.com/moby/buildkit/issues/4614
366-
ds.image = *img
367-
if img.Architecture != "" && img.OS != "" {
368-
ds.platform = &ocispecs.Platform{
369-
OS: img.OS,
370-
Architecture: img.Architecture,
371-
Variant: img.Variant,
372-
OSVersion: img.OSVersion,
373-
}
374-
if img.OSFeatures != nil {
375-
ds.platform.OSFeatures = append([]string{}, img.OSFeatures...)
376-
}
377-
}
378-
}
360+
if nc != nil {
361+
ds.namedContext = nc
379362
allDispatchStates.addState(ds)
380363
ds.base = nil // reset base set by addState
381364
continue
@@ -467,7 +450,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
467450
// resolve image config for every stage
468451
if d.base == nil && !d.dispatched && !d.resolved {
469452
d.resolved = reachable // avoid re-resolving if called again after onbuild
470-
if d.stage.BaseName == emptyImageName {
453+
if d.stage.BaseName == emptyImageName && d.namedContext == nil {
471454
d.state = llb.Scratch()
472455
d.image = emptyImage(platformOpt.targetPlatform)
473456
d.platform = &platformOpt.targetPlatform
@@ -499,25 +482,58 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
499482
d.stage.BaseName = reference.TagNameOnly(ref).String()
500483

501484
var isScratch bool
502-
st, img, err := namedContext(ctx, d.stage.BaseName, dockerui.ContextOpt{
503-
ResolveMode: opt.ImageResolveMode.String(),
504-
Platform: platform,
505-
AsyncLocalOpts: d.asyncLocalOpts,
506-
})
507-
if err != nil {
508-
return err
509-
}
510-
if st != nil {
511-
if img != nil {
512-
d.image = *img
513-
} else {
514-
d.image = emptyImage(platformOpt.targetPlatform)
515-
}
516-
d.state = st.Platform(*platform)
517-
d.platform = platform
518-
return nil
519-
}
520485
if reachable {
486+
// stage was named context
487+
if d.namedContext != nil {
488+
st, img, err := d.namedContext.Load(ctx)
489+
if err != nil {
490+
return err
491+
}
492+
d.dispatched = true
493+
d.state = *st
494+
if img != nil {
495+
// timestamps are inherited as-is, regardless to SOURCE_DATE_EPOCH
496+
// https://github.com/moby/buildkit/issues/4614
497+
d.image = *img
498+
if img.Architecture != "" && img.OS != "" {
499+
d.platform = &ocispecs.Platform{
500+
OS: img.OS,
501+
Architecture: img.Architecture,
502+
Variant: img.Variant,
503+
OSVersion: img.OSVersion,
504+
}
505+
if img.OSFeatures != nil {
506+
d.platform.OSFeatures = append([]string{}, img.OSFeatures...)
507+
}
508+
}
509+
}
510+
return nil
511+
}
512+
513+
// check if base is named context
514+
nc, err := namedContext(d.stage.BaseName, dockerui.ContextOpt{
515+
ResolveMode: opt.ImageResolveMode.String(),
516+
Platform: platform,
517+
AsyncLocalOpts: d.asyncLocalOpts,
518+
})
519+
if err != nil {
520+
return err
521+
}
522+
if nc != nil {
523+
st, img, err := nc.Load(ctx)
524+
if err != nil {
525+
return err
526+
}
527+
if img != nil {
528+
d.image = *img
529+
} else {
530+
d.image = emptyImage(platformOpt.targetPlatform)
531+
}
532+
d.state = st.Platform(*platform)
533+
d.platform = platform
534+
return nil
535+
}
536+
521537
prefix := "["
522538
if opt.MultiPlatformRequested && platform != nil {
523539
prefix += platforms.FormatAll(*platform) + " "
@@ -1015,19 +1031,20 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
10151031
}
10161032

10171033
type dispatchState struct {
1018-
opt dispatchOpt
1019-
state llb.State
1020-
image dockerspec.DockerOCIImage
1021-
platform *ocispecs.Platform
1022-
stage instructions.Stage
1023-
base *dispatchState
1024-
baseImg *dockerspec.DockerOCIImage // immutable, unlike image
1025-
dispatched bool
1026-
resolved bool // resolved is set to true if base image has been resolved
1027-
onBuildInit bool
1028-
deps map[*dispatchState]instructions.Command
1029-
buildArgs []instructions.KeyValuePairOptional
1030-
commands []command
1034+
opt dispatchOpt
1035+
state llb.State
1036+
image dockerspec.DockerOCIImage
1037+
namedContext *dockerui.NamedContext
1038+
platform *ocispecs.Platform
1039+
stage instructions.Stage
1040+
base *dispatchState
1041+
baseImg *dockerspec.DockerOCIImage // immutable, unlike image
1042+
dispatched bool
1043+
resolved bool // resolved is set to true if base image has been resolved
1044+
onBuildInit bool
1045+
deps map[*dispatchState]instructions.Command
1046+
buildArgs []instructions.KeyValuePairOptional
1047+
commands []command
10311048
// ctxPaths marks the paths this dispatchState uses from the build context.
10321049
ctxPaths map[string]struct{}
10331050
// paths marks the paths that are used by this dispatchState.

frontend/dockerfile/dockerfile_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ var allTests = integration.TestFuncs(
205205
testFrontendDeduplicateSources,
206206
testDuplicateLayersProvenance,
207207
testSourcePolicyWithNamedContext,
208+
testEagerNamedContextLookup,
208209
testEmptyStringArgInEnv,
209210
testInvalidJSONCommands,
210211
testHistoryError,
@@ -9167,6 +9168,51 @@ func testSourcePolicyWithNamedContext(t *testing.T, sb integration.Sandbox) {
91679168
require.Equal(t, "foo", string(dt))
91689169
}
91699170

9171+
// testEagerNamedContextLookup tests that named context are not loaded if
9172+
// they are not used by current build.
9173+
func testEagerNamedContextLookup(t *testing.T, sb integration.Sandbox) {
9174+
integration.SkipOnPlatform(t, "windows")
9175+
9176+
f := getFrontend(t, sb)
9177+
c, err := client.New(sb.Context(), sb.Address())
9178+
require.NoError(t, err)
9179+
defer c.Close()
9180+
9181+
dockerfile := []byte(`
9182+
FROM broken AS notused
9183+
9184+
FROM alpine AS base
9185+
RUN echo "base" > /foo
9186+
9187+
FROM busybox AS otherstage
9188+
9189+
FROM scratch
9190+
COPY --from=base /foo /foo
9191+
`)
9192+
9193+
dir := integration.Tmpdir(t, fstest.CreateFile("Dockerfile", dockerfile, 0600))
9194+
9195+
out := t.TempDir()
9196+
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
9197+
Exports: []client.ExportEntry{
9198+
{Type: client.ExporterLocal, OutputDir: out},
9199+
},
9200+
FrontendAttrs: map[string]string{
9201+
"context:notused": "docker-image://docker.io/library/nosuchimage:latest",
9202+
"context:busybox": "docker-image://docker.io/library/dontexist:latest",
9203+
},
9204+
LocalMounts: map[string]fsutil.FS{
9205+
dockerui.DefaultLocalNameDockerfile: dir,
9206+
dockerui.DefaultLocalNameContext: dir,
9207+
},
9208+
}, nil)
9209+
require.NoError(t, err)
9210+
9211+
dt, err := os.ReadFile(filepath.Join(out, "foo"))
9212+
require.NoError(t, err)
9213+
require.Equal(t, "base\n", string(dt))
9214+
}
9215+
91709216
func testBaseImagePlatformMismatch(t *testing.T, sb integration.Sandbox) {
91719217
integration.SkipOnPlatform(t, "windows")
91729218
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush)

frontend/dockerui/config.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/moby/buildkit/frontend/gateway/client"
1919
"github.com/moby/buildkit/solver/pb"
2020
"github.com/moby/buildkit/util/flightcontrol"
21-
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
2221
"github.com/moby/patternmatcher/ignorefile"
2322
digest "github.com/opencontainers/go-digest"
2423
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
@@ -452,10 +451,10 @@ func (bc *Client) MainContext(ctx context.Context, opts ...llb.LocalOption) (*ll
452451
return &st, nil
453452
}
454453

455-
func (bc *Client) NamedContext(ctx context.Context, name string, opt ContextOpt) (*llb.State, *dockerspec.DockerOCIImage, error) {
454+
func (bc *Client) NamedContext(name string, opt ContextOpt) (*NamedContext, error) {
456455
named, err := reference.ParseNormalizedNamed(name)
457456
if err != nil {
458-
return nil, nil, errors.Wrapf(err, "invalid context name %s", name)
457+
return nil, errors.Wrapf(err, "invalid context name %s", name)
459458
}
460459
name = strings.TrimSuffix(reference.FamiliarString(named), ":latest")
461460

@@ -464,11 +463,11 @@ func (bc *Client) NamedContext(ctx context.Context, name string, opt ContextOpt)
464463
pp = *opt.Platform
465464
}
466465
pname := name + "::" + platforms.FormatAll(platforms.Normalize(pp))
467-
st, img, err := bc.namedContext(ctx, name, pname, opt)
468-
if err != nil || st != nil {
469-
return st, img, err
466+
nc, err := bc.namedContext(name, pname, opt)
467+
if err != nil || nc != nil {
468+
return nc, err
470469
}
471-
return bc.namedContext(ctx, name, name, opt)
470+
return bc.namedContext(name, name, opt)
472471
}
473472

474473
func (bc *Client) IsNoCache(name string) bool {

0 commit comments

Comments
 (0)