Skip to content

Commit

Permalink
Merge pull request moby#4190 from tonistiigi/duplicate-sourcemap
Browse files Browse the repository at this point in the history
llb: avoid duplicate instances of sourcemaps in provenance
  • Loading branch information
crazy-max authored Sep 5, 2023
2 parents e2dfabd + 1bbf73e commit 980ea2d
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 2 deletions.
42 changes: 40 additions & 2 deletions client/llb/sourcemap.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package llb

import (
"bytes"
"context"

"github.com/moby/buildkit/solver/pb"
Expand Down Expand Up @@ -47,6 +48,33 @@ func (s *SourceMap) Location(r []*pb.Range) ConstraintsOpt {
})
}

func equalSourceMap(sm1, sm2 *SourceMap) (out bool) {
if sm1 == nil || sm2 == nil {
return false
}
if sm1.Filename != sm2.Filename {
return false
}
if sm1.Language != sm2.Language {
return false
}
if len(sm1.Data) != len(sm2.Data) {
return false
}
if !bytes.Equal(sm1.Data, sm2.Data) {
return false
}
if sm1.Definition != nil && sm2.Definition != nil {
if len(sm1.Definition.Def) != len(sm2.Definition.Def) && len(sm1.Definition.Def) != 0 {
return false
}
if !bytes.Equal(sm1.Definition.Def[len(sm1.Definition.Def)-1], sm2.Definition.Def[len(sm2.Definition.Def)-1]) {
return false
}
}
return true
}

type SourceLocation struct {
SourceMap *SourceMap
Ranges []*pb.Range
Expand All @@ -69,8 +97,18 @@ func (smc *sourceMapCollector) Add(dgst digest.Digest, ls []*SourceLocation) {
for _, l := range ls {
idx, ok := smc.index[l.SourceMap]
if !ok {
idx = len(smc.maps)
smc.maps = append(smc.maps, l.SourceMap)
idx = -1
// slow equality check
for i, m := range smc.maps {
if equalSourceMap(m, l.SourceMap) {
idx = i
break
}
}
if idx == -1 {
idx = len(smc.maps)
smc.maps = append(smc.maps, l.SourceMap)
}
}
smc.index[l.SourceMap] = idx
}
Expand Down
155 changes: 155 additions & 0 deletions frontend/dockerfile/dockerfile_provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
Expand All @@ -16,16 +17,20 @@ import (

"github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local"
"github.com/containerd/containerd/content/proxy"
"github.com/containerd/containerd/platforms"
"github.com/containerd/continuity/fs/fstest"
intoto "github.com/in-toto/in-toto-golang/in_toto"
provenanceCommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend/dockerui"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/solver/llbsolver/provenance"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/contentutil"
"github.com/moby/buildkit/util/testutil"
"github.com/moby/buildkit/util/testutil/integration"
Expand Down Expand Up @@ -1113,3 +1118,153 @@ func testDockerIgnoreMissingProvenance(t *testing.T, sb integration.Sandbox) {
}, "", frontend, nil)
require.NoError(t, err)
}

func testFrontendDeduplicateSources(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 scratch as base
COPY foo foo2
FROM linked
COPY bar bar2
`)

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

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{
"target": "base",
},
})
if err != nil {
return nil, err
}
ref, err := res.SingleRef()
if err != nil {
return nil, err
}
st, err := ref.ToState()
if err != nil {
return nil, err
}

def, err := st.Marshal(ctx)
if err != nil {
return nil, err
}

dt, ok := res.Metadata["containerimage.config"]
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
}

res, err = f.SolveGateway(ctx, c, gateway.SolveRequest{
FrontendOpt: map[string]string{
"context:linked": "input:baseinput",
"input-metadata:linked": string(dt),
},
FrontendInputs: map[string]*pb.Definition{
"baseinput": def.ToPB(),
},
})
if err != nil {
return nil, err
}
return res, nil
}

product := "buildkit_test"

destDir := t.TempDir()

ref := identity.NewID()

_, err = c.Build(ctx, client.SolveOpt{
LocalDirs: map[string]string{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir,
},
},
Ref: ref,
}, product, b, nil)
require.NoError(t, err)

dt, err := os.ReadFile(filepath.Join(destDir, "foo2"))
require.NoError(t, err)
require.Equal(t, "data", string(dt))

dt, err = os.ReadFile(filepath.Join(destDir, "bar2"))
require.NoError(t, err)
require.Equal(t, "data2", string(dt))

history, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
Ref: ref,
EarlyExit: true,
})
require.NoError(t, err)

store := proxy.NewContentStore(c.ContentClient())

var provDt []byte
for {
ev, err := history.Recv()
if err != nil {
require.Equal(t, io.EOF, err)
break
}
require.Equal(t, ref, ev.Record.Ref)

for _, prov := range ev.Record.Result.Attestations {
if len(prov.Annotations) == 0 || prov.Annotations["in-toto.io/predicate-type"] != "https://slsa.dev/provenance/v0.2" {
t.Logf("skipping non-slsa provenance: %s", prov.MediaType)
continue
}

provDt, err = content.ReadBlob(ctx, store, ocispecs.Descriptor{
MediaType: prov.MediaType,
Digest: prov.Digest,
Size: prov.Size_,
})
require.NoError(t, err)
}
}

require.NotEqual(t, len(provDt), 0)

var pred provenance.ProvenancePredicate
require.NoError(t, json.Unmarshal(provDt, &pred))

sources := pred.Metadata.BuildKitMetadata.Source.Infos

require.Equal(t, 1, len(sources))
require.Equal(t, "Dockerfile", sources[0].Filename)
require.Equal(t, "Dockerfile", sources[0].Language)

require.Equal(t, dockerfile, sources[0].Data)
require.NotEqual(t, 0, len(sources[0].Definition))
}
1 change: 1 addition & 0 deletions frontend/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ var allTests = integration.TestFuncs(
testMultiPlatformWarnings,
testNilContextInSolveGateway,
testCopyUnicodePath,
testFrontendDeduplicateSources,
)

// Tests that depend on the `security.*` entitlements
Expand Down

0 comments on commit 980ea2d

Please sign in to comment.