Skip to content

Commit c574c58

Browse files
authored
Merge pull request moby#4908 from tonistiigi/platforms-verify
verifier: verify platforms of the build result
2 parents 843be86 + acb1bed commit c574c58

File tree

5 files changed

+364
-35
lines changed

5 files changed

+364
-35
lines changed

client/build_test.go

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"github.com/moby/buildkit/util/testutil/echoserver"
2929
"github.com/moby/buildkit/util/testutil/integration"
3030
"github.com/moby/buildkit/util/testutil/workers"
31-
digest "github.com/opencontainers/go-digest"
3231
"github.com/pkg/errors"
3332
"github.com/stretchr/testify/require"
3433
"github.com/tonistiigi/fsutil"
@@ -210,43 +209,14 @@ func testWarnings(t *testing.T, sb integration.Sandbox) {
210209
return r, nil
211210
}
212211

213-
status := make(chan *SolveStatus)
214-
statusDone := make(chan struct{})
215-
done := make(chan struct{})
212+
wc := newWarningsCapture()
216213

217-
var warnings []*VertexWarning
218-
vertexes := map[digest.Digest]struct{}{}
219-
220-
go func() {
221-
defer close(statusDone)
222-
for {
223-
select {
224-
case st, ok := <-status:
225-
if !ok {
226-
return
227-
}
228-
for _, s := range st.Vertexes {
229-
vertexes[s.Digest] = struct{}{}
230-
}
231-
warnings = append(warnings, st.Warnings...)
232-
case <-done:
233-
return
234-
}
235-
}
236-
}()
237-
238-
_, err = c.Build(ctx, SolveOpt{}, product, b, status)
214+
_, err = c.Build(ctx, SolveOpt{}, product, b, wc.status)
239215
require.NoError(t, err)
240216

241-
select {
242-
case <-statusDone:
243-
case <-time.After(10 * time.Second):
244-
close(done)
245-
}
246-
247-
<-statusDone
217+
warnings := wc.wait()
248218

249-
require.Equal(t, 1, len(vertexes))
219+
require.Equal(t, 1, len(wc.vertexes))
250220
require.Equal(t, 1, len(warnings))
251221

252222
w := warnings[0]
@@ -257,7 +227,7 @@ func testWarnings(t *testing.T, sb integration.Sandbox) {
257227
require.Equal(t, "and more detail", string(w.Detail[1]))
258228
require.Equal(t, "https://example.com", w.URL)
259229
require.Equal(t, 3, w.Level)
260-
_, ok := vertexes[w.Vertex]
230+
_, ok := wc.vertexes[w.Vertex]
261231
require.True(t, ok)
262232

263233
require.Equal(t, "mydockerfile", w.SourceInfo.Filename)

client/client_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
219219
testSolverOptLocalDirsStillWorks,
220220
testOCIIndexMediatype,
221221
testLayerLimitOnMounts,
222+
testFrontendVerifyPlatforms,
222223
}
223224

224225
func TestIntegration(t *testing.T) {
@@ -9999,6 +10000,166 @@ func testMountStubsTimestamp(t *testing.T, sb integration.Sandbox) {
999910000
}
1000010001
}
1000110002

10003+
func testFrontendVerifyPlatforms(t *testing.T, sb integration.Sandbox) {
10004+
requiresLinux(t)
10005+
c, err := New(sb.Context(), sb.Address())
10006+
require.NoError(t, err)
10007+
defer c.Close()
10008+
10009+
frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
10010+
st := llb.Scratch().File(
10011+
llb.Mkfile("foo", 0600, []byte("data")),
10012+
)
10013+
10014+
def, err := st.Marshal(sb.Context())
10015+
if err != nil {
10016+
return nil, err
10017+
}
10018+
10019+
return c.Solve(ctx, gateway.SolveRequest{
10020+
Definition: def.ToPB(),
10021+
})
10022+
}
10023+
10024+
wc := newWarningsCapture()
10025+
_, err = c.Build(sb.Context(), SolveOpt{
10026+
FrontendAttrs: map[string]string{
10027+
"platform": "linux/amd64,linux/arm64",
10028+
},
10029+
}, "", frontend, wc.status)
10030+
require.NoError(t, err)
10031+
warnings := wc.wait()
10032+
10033+
require.Len(t, warnings, 1)
10034+
require.Contains(t, string(warnings[0].Short), "Multiple platforms requested but result is not multi-platform")
10035+
10036+
wc = newWarningsCapture()
10037+
_, err = c.Build(sb.Context(), SolveOpt{
10038+
FrontendAttrs: map[string]string{},
10039+
}, "", frontend, wc.status)
10040+
require.NoError(t, err)
10041+
10042+
warnings = wc.wait()
10043+
require.Len(t, warnings, 0)
10044+
10045+
frontend = func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
10046+
res := gateway.NewResult()
10047+
platformsToTest := []string{"linux/amd64", "linux/arm64"}
10048+
expPlatforms := &exptypes.Platforms{
10049+
Platforms: make([]exptypes.Platform, len(platformsToTest)),
10050+
}
10051+
for i, platform := range platformsToTest {
10052+
st := llb.Scratch().File(
10053+
llb.Mkfile("platform", 0600, []byte(platform)),
10054+
)
10055+
10056+
def, err := st.Marshal(ctx)
10057+
if err != nil {
10058+
return nil, err
10059+
}
10060+
10061+
r, err := c.Solve(ctx, gateway.SolveRequest{
10062+
Definition: def.ToPB(),
10063+
})
10064+
if err != nil {
10065+
return nil, err
10066+
}
10067+
10068+
ref, err := r.SingleRef()
10069+
if err != nil {
10070+
return nil, err
10071+
}
10072+
10073+
_, err = ref.ToState()
10074+
if err != nil {
10075+
return nil, err
10076+
}
10077+
res.AddRef(platform, ref)
10078+
10079+
expPlatforms.Platforms[i] = exptypes.Platform{
10080+
ID: platform,
10081+
Platform: platforms.MustParse(platform),
10082+
}
10083+
}
10084+
dt, err := json.Marshal(expPlatforms)
10085+
if err != nil {
10086+
return nil, err
10087+
}
10088+
res.AddMeta(exptypes.ExporterPlatformsKey, dt)
10089+
10090+
return res, nil
10091+
}
10092+
10093+
wc = newWarningsCapture()
10094+
_, err = c.Build(sb.Context(), SolveOpt{
10095+
FrontendAttrs: map[string]string{
10096+
"platform": "linux/amd64,linux/arm64",
10097+
},
10098+
}, "", frontend, wc.status)
10099+
require.NoError(t, err)
10100+
warnings = wc.wait()
10101+
10102+
require.Len(t, warnings, 0)
10103+
10104+
wc = newWarningsCapture()
10105+
_, err = c.Build(sb.Context(), SolveOpt{
10106+
FrontendAttrs: map[string]string{},
10107+
}, "", frontend, wc.status)
10108+
require.NoError(t, err)
10109+
10110+
warnings = wc.wait()
10111+
require.Len(t, warnings, 1)
10112+
require.Contains(t, string(warnings[0].Short), "do not match result platforms linux/amd64,linux/arm64")
10113+
}
10114+
10115+
type warningsCapture struct {
10116+
status chan *SolveStatus
10117+
statusDone chan struct{}
10118+
done chan struct{}
10119+
warnings []*VertexWarning
10120+
vertexes map[digest.Digest]struct{}
10121+
}
10122+
10123+
func newWarningsCapture() *warningsCapture {
10124+
w := &warningsCapture{
10125+
status: make(chan *SolveStatus),
10126+
statusDone: make(chan struct{}),
10127+
done: make(chan struct{}),
10128+
vertexes: map[digest.Digest]struct{}{},
10129+
}
10130+
10131+
go func() {
10132+
defer close(w.statusDone)
10133+
for {
10134+
select {
10135+
case st, ok := <-w.status:
10136+
if !ok {
10137+
return
10138+
}
10139+
for _, s := range st.Vertexes {
10140+
w.vertexes[s.Digest] = struct{}{}
10141+
}
10142+
w.warnings = append(w.warnings, st.Warnings...)
10143+
case <-w.done:
10144+
return
10145+
}
10146+
}
10147+
}()
10148+
10149+
return w
10150+
}
10151+
10152+
func (w *warningsCapture) wait() []*VertexWarning {
10153+
select {
10154+
case <-w.statusDone:
10155+
case <-time.After(10 * time.Second):
10156+
close(w.done)
10157+
}
10158+
10159+
<-w.statusDone
10160+
return w.warnings
10161+
}
10162+
1000210163
func ensureFile(t *testing.T, path string) {
1000310164
st, err := os.Stat(path)
1000410165
require.NoError(t, err, "expected file at %s", path)

exporter/verifier/opts.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package verifier
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
7+
"github.com/containerd/containerd/platforms"
8+
"github.com/moby/buildkit/solver/result"
9+
)
10+
11+
const requestOptsKeys = "verifier.requestopts"
12+
13+
const (
14+
platformsKey = "platform"
15+
labelsPrefix = "label:"
16+
keyRequestID = "requestid"
17+
)
18+
19+
type RequestOpts struct {
20+
Platforms []string
21+
Labels map[string]string
22+
Request string
23+
}
24+
25+
func CaptureFrontendOpts[T comparable](m map[string]string, res *result.Result[T]) error {
26+
req := &RequestOpts{}
27+
if v, ok := m[platformsKey]; ok {
28+
req.Platforms = strings.Split(v, ",")
29+
} else {
30+
req.Platforms = []string{platforms.Format(platforms.Normalize(platforms.DefaultSpec()))}
31+
}
32+
33+
req.Labels = map[string]string{}
34+
for k, v := range m {
35+
if strings.HasPrefix(k, labelsPrefix) {
36+
req.Labels[strings.TrimPrefix(k, labelsPrefix)] = v
37+
}
38+
}
39+
req.Request = m[keyRequestID]
40+
41+
dt, err := json.Marshal(req)
42+
if err != nil {
43+
return err
44+
}
45+
res.AddMeta(requestOptsKeys, dt)
46+
return nil
47+
}
48+
49+
func getRequestOpts[T comparable](res *result.Result[T]) (*RequestOpts, error) {
50+
dt, ok := res.Metadata[requestOptsKeys]
51+
if !ok {
52+
return nil, nil
53+
}
54+
req := &RequestOpts{}
55+
if err := json.Unmarshal(dt, req); err != nil {
56+
return nil, err
57+
}
58+
return req, nil
59+
}

0 commit comments

Comments
 (0)