4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+ "io"
7
8
"net/http"
8
9
"net/http/httptest"
9
10
"net/url"
@@ -16,16 +17,20 @@ import (
16
17
17
18
"github.com/containerd/containerd/content"
18
19
"github.com/containerd/containerd/content/local"
20
+ "github.com/containerd/containerd/content/proxy"
19
21
"github.com/containerd/containerd/platforms"
20
22
"github.com/containerd/continuity/fs/fstest"
21
23
intoto "github.com/in-toto/in-toto-golang/in_toto"
22
24
provenanceCommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
25
+ controlapi "github.com/moby/buildkit/api/services/control"
23
26
"github.com/moby/buildkit/client"
24
27
"github.com/moby/buildkit/client/llb"
25
28
"github.com/moby/buildkit/exporter/containerimage/exptypes"
26
29
"github.com/moby/buildkit/frontend/dockerui"
27
30
gateway "github.com/moby/buildkit/frontend/gateway/client"
31
+ "github.com/moby/buildkit/identity"
28
32
"github.com/moby/buildkit/solver/llbsolver/provenance"
33
+ "github.com/moby/buildkit/solver/pb"
29
34
"github.com/moby/buildkit/util/contentutil"
30
35
"github.com/moby/buildkit/util/testutil"
31
36
"github.com/moby/buildkit/util/testutil/integration"
@@ -1113,3 +1118,153 @@ func testDockerIgnoreMissingProvenance(t *testing.T, sb integration.Sandbox) {
1113
1118
}, "" , frontend , nil )
1114
1119
require .NoError (t , err )
1115
1120
}
1121
+
1122
+ func testFrontendDeduplicateSources (t * testing.T , sb integration.Sandbox ) {
1123
+ ctx := sb .Context ()
1124
+
1125
+ c , err := client .New (ctx , sb .Address ())
1126
+ require .NoError (t , err )
1127
+ defer c .Close ()
1128
+
1129
+ dockerfile := []byte (`
1130
+ FROM scratch as base
1131
+ COPY foo foo2
1132
+
1133
+ FROM linked
1134
+ COPY bar bar2
1135
+ ` )
1136
+
1137
+ dir := integration .Tmpdir (
1138
+ t ,
1139
+ fstest .CreateFile ("Dockerfile" , dockerfile , 0600 ),
1140
+ fstest .CreateFile ("foo" , []byte ("data" ), 0600 ),
1141
+ fstest .CreateFile ("bar" , []byte ("data2" ), 0600 ),
1142
+ )
1143
+
1144
+ f := getFrontend (t , sb )
1145
+
1146
+ b := func (ctx context.Context , c gateway.Client ) (* gateway.Result , error ) {
1147
+ res , err := f .SolveGateway (ctx , c , gateway.SolveRequest {
1148
+ FrontendOpt : map [string ]string {
1149
+ "target" : "base" ,
1150
+ },
1151
+ })
1152
+ if err != nil {
1153
+ return nil , err
1154
+ }
1155
+ ref , err := res .SingleRef ()
1156
+ if err != nil {
1157
+ return nil , err
1158
+ }
1159
+ st , err := ref .ToState ()
1160
+ if err != nil {
1161
+ return nil , err
1162
+ }
1163
+
1164
+ def , err := st .Marshal (ctx )
1165
+ if err != nil {
1166
+ return nil , err
1167
+ }
1168
+
1169
+ dt , ok := res .Metadata ["containerimage.config" ]
1170
+ if ! ok {
1171
+ return nil , errors .Errorf ("no containerimage.config in metadata" )
1172
+ }
1173
+
1174
+ dt , err = json .Marshal (map [string ][]byte {
1175
+ "containerimage.config" : dt ,
1176
+ })
1177
+ if err != nil {
1178
+ return nil , err
1179
+ }
1180
+
1181
+ res , err = f .SolveGateway (ctx , c , gateway.SolveRequest {
1182
+ FrontendOpt : map [string ]string {
1183
+ "context:linked" : "input:baseinput" ,
1184
+ "input-metadata:linked" : string (dt ),
1185
+ },
1186
+ FrontendInputs : map [string ]* pb.Definition {
1187
+ "baseinput" : def .ToPB (),
1188
+ },
1189
+ })
1190
+ if err != nil {
1191
+ return nil , err
1192
+ }
1193
+ return res , nil
1194
+ }
1195
+
1196
+ product := "buildkit_test"
1197
+
1198
+ destDir := t .TempDir ()
1199
+
1200
+ ref := identity .NewID ()
1201
+
1202
+ _ , err = c .Build (ctx , client.SolveOpt {
1203
+ LocalDirs : map [string ]string {
1204
+ dockerui .DefaultLocalNameDockerfile : dir ,
1205
+ dockerui .DefaultLocalNameContext : dir ,
1206
+ },
1207
+ Exports : []client.ExportEntry {
1208
+ {
1209
+ Type : client .ExporterLocal ,
1210
+ OutputDir : destDir ,
1211
+ },
1212
+ },
1213
+ Ref : ref ,
1214
+ }, product , b , nil )
1215
+ require .NoError (t , err )
1216
+
1217
+ dt , err := os .ReadFile (filepath .Join (destDir , "foo2" ))
1218
+ require .NoError (t , err )
1219
+ require .Equal (t , "data" , string (dt ))
1220
+
1221
+ dt , err = os .ReadFile (filepath .Join (destDir , "bar2" ))
1222
+ require .NoError (t , err )
1223
+ require .Equal (t , "data2" , string (dt ))
1224
+
1225
+ history , err := c .ControlClient ().ListenBuildHistory (ctx , & controlapi.BuildHistoryRequest {
1226
+ Ref : ref ,
1227
+ EarlyExit : true ,
1228
+ })
1229
+ require .NoError (t , err )
1230
+
1231
+ store := proxy .NewContentStore (c .ContentClient ())
1232
+
1233
+ var provDt []byte
1234
+ for {
1235
+ ev , err := history .Recv ()
1236
+ if err != nil {
1237
+ require .Equal (t , io .EOF , err )
1238
+ break
1239
+ }
1240
+ require .Equal (t , ref , ev .Record .Ref )
1241
+
1242
+ for _ , prov := range ev .Record .Result .Attestations {
1243
+ if len (prov .Annotations ) == 0 || prov .Annotations ["in-toto.io/predicate-type" ] != "https://slsa.dev/provenance/v0.2" {
1244
+ t .Logf ("skipping non-slsa provenance: %s" , prov .MediaType )
1245
+ continue
1246
+ }
1247
+
1248
+ provDt , err = content .ReadBlob (ctx , store , ocispecs.Descriptor {
1249
+ MediaType : prov .MediaType ,
1250
+ Digest : prov .Digest ,
1251
+ Size : prov .Size_ ,
1252
+ })
1253
+ require .NoError (t , err )
1254
+ }
1255
+ }
1256
+
1257
+ require .NotEqual (t , len (provDt ), 0 )
1258
+
1259
+ var pred provenance.ProvenancePredicate
1260
+ require .NoError (t , json .Unmarshal (provDt , & pred ))
1261
+
1262
+ sources := pred .Metadata .BuildKitMetadata .Source .Infos
1263
+
1264
+ require .Equal (t , 1 , len (sources ))
1265
+ require .Equal (t , "Dockerfile" , sources [0 ].Filename )
1266
+ require .Equal (t , "Dockerfile" , sources [0 ].Language )
1267
+
1268
+ require .Equal (t , dockerfile , sources [0 ].Data )
1269
+ require .NotEqual (t , 0 , len (sources [0 ].Definition ))
1270
+ }
0 commit comments