Skip to content

Commit

Permalink
dockerfile: fix missing source mapping for COPY --link command
Browse files Browse the repository at this point in the history
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit eb22207)
  • Loading branch information
tonistiigi committed Apr 24, 2024
1 parent 4317777 commit 6d689a3
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 3 deletions.
5 changes: 2 additions & 3 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -1340,11 +1340,10 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
copyOpts := []llb.ConstraintsOpt{
llb.Platform(*d.platform),
}
copy(copyOpts, fileOpt)
copyOpts = append(copyOpts, fileOpt...)
copyOpts = append(copyOpts, llb.ProgressGroup(pgID, pgName, true))

var mergeOpts []llb.ConstraintsOpt
copy(mergeOpts, fileOpt)
mergeOpts := append([]llb.ConstraintsOpt{}, fileOpt...)
d.cmdIndex--
mergeOpts = append(mergeOpts, llb.ProgressGroup(pgID, pgName, false), llb.WithCustomName(prefixCommand(d, "LINK "+name, d.prefixPlatform, &platform, env)))

Expand Down
118 changes: 118 additions & 0 deletions frontend/dockerfile/dockerfile_provenance_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dockerfile

import (
"bytes"
"context"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -1130,6 +1131,123 @@ func testDockerIgnoreMissingProvenance(t *testing.T, sb integration.Sandbox) {
require.NoError(t, err)
}

func testCommandSourceMapping(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
ctx := sb.Context()

c, err := client.New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()

dockerfile := []byte(`FROM alpine
RUN echo "hello" > foo
WORKDIR /tmp
COPY foo foo2
COPY --link foo foo3
ADD bar bar`)

dir := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo", []byte("data"), 0600),
fstest.CreateFile("bar", []byte("data2"), 0600),
)

registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)

target := registry + "/buildkit/testsourcemappingprov:latest"
f := getFrontend(t, sb)

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
FrontendAttrs: map[string]string{
"attest:provenance": "mode=max",
},
Exports: []client.ExportEntry{
{
Type: client.ExporterImage,
Attrs: map[string]string{
"name": target,
"push": "true",
},
},
},
}, nil)
require.NoError(t, err)

desc, provider, err := contentutil.ProviderFromRef(target)
require.NoError(t, err)
imgs, err := testutil.ReadImages(sb.Context(), provider, desc)
require.NoError(t, err)
require.Equal(t, 2, len(imgs.Images))

expPlatform := platforms.Format(platforms.Normalize(platforms.DefaultSpec()))

img := imgs.Find(expPlatform)
require.NotNil(t, img)

att := imgs.FindAttestation(expPlatform)
type stmtT struct {
Predicate provenancetypes.ProvenancePredicate `json:"predicate"`
}
var stmt stmtT
require.NoError(t, json.Unmarshal(att.LayersRaw[0], &stmt))
pred := stmt.Predicate

def := pred.BuildConfig.Definition

steps := map[string]provenancetypes.BuildStep{}
for _, step := range def {
steps[step.ID] = step
}
// ensure all IDs are unique
require.Equal(t, len(steps), len(def))

src := pred.Metadata.BuildKitMetadata.Source

lines := make([]bool, bytes.Count(dockerfile, []byte("\n"))+1)

for id, loc := range src.Locations {
// - only context upload can be without source mapping
// - every step must only be in one line
// - perform bounds check for location
step, ok := steps[id]
require.True(t, ok, "definition for step %s not found", id)

if len(loc.Locations) == 0 {
s := step.Op.GetSource()
require.NotNil(t, s, "unmapped step %s is not source", id)
require.Equal(t, "local://context", s.Identifier)
} else if len(loc.Locations) >= 1 {
require.Equal(t, 1, len(loc.Locations), "step %s has more than one location", id)
}

for _, loc := range loc.Locations {
for _, r := range loc.Ranges {
require.Equal(t, r.Start.Line, r.End.Line, "step %s has range with multiple lines", id)

idx := r.Start.Line - 1
if idx < 0 || int(idx) >= len(lines) {
t.Fatalf("step %s has invalid range on line %d", id, idx)
}
lines[idx] = true
}
}
}

// ensure all lines are covered
for i, covered := range lines {
require.True(t, covered, "line %d is not covered", i+1)
}
}

func testFrontendDeduplicateSources(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
ctx := sb.Context()
Expand Down
1 change: 1 addition & 0 deletions frontend/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ var allTests = integration.TestFuncs(
testNilProvenance,
testDuplicatePlatformProvenance,
testDockerIgnoreMissingProvenance,
testCommandSourceMapping,
testSBOMScannerArgs,
testMultiPlatformWarnings,
testNilContextInSolveGateway,
Expand Down

0 comments on commit 6d689a3

Please sign in to comment.