Skip to content

Commit a05e530

Browse files
committed
azlinux/mariner: Use provided platform for outputs
Before this change, the mariner/azl targets were ignoring the client provided platform for the produced aritifacts. With this change the platform is set on images so it will rely on binfmt_misc to execute those images correctly. There's probably some optimizations that can be made here to run certain tasks on the native platform, some thoughts: - Use native (host-arch) golang to download go modules - Use native (host-arch) tdnf to download/install non-native packages onto the target build environment. This does *not* add support for cross compilation (run x86 code to generate, e.g., arm64 code). Signed-off-by: Brian Goff <[email protected]>
1 parent cb95d4d commit a05e530

File tree

11 files changed

+268
-18
lines changed

11 files changed

+268
-18
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ on:
3131
- go.mod
3232
- go.sum
3333

34+
env:
35+
# Used in tests to determine if certain tests should be skipped.
36+
# Setting this ensures that they are *not* skipped and instead make sure CI
37+
# is setup to be able to properly run all tests.
38+
DALEC_CI: "1"
39+
3440
permissions:
3541
contents: read
3642

@@ -93,6 +99,8 @@ jobs:
9399
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
94100
- name: download deps
95101
run: go mod download
102+
- name: Setup QEMU
103+
run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
96104
- name: Run integaration tests
97105
run: go test -v -json ./test | go run ./cmd/test2json2gha
98106
- name: dump logs

frontend/azlinux/handle_container.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func handleContainer(w worker) gwclient.BuildFunc {
2525

2626
pg := dalec.ProgressGroup("Building " + targetKey + " container: " + spec.Name)
2727

28-
rpmDir, err := specToRpmLLB(ctx, w, client, spec, sOpt, targetKey, pg)
28+
rpmDir, err := specToRpmLLB(ctx, w, client, spec, sOpt, targetKey, pg, dalec.WithPlatform(platform))
2929
if err != nil {
3030
return nil, nil, fmt.Errorf("error creating rpm: %w", err)
3131
}
@@ -35,7 +35,7 @@ func handleContainer(w worker) gwclient.BuildFunc {
3535
return nil, nil, err
3636
}
3737

38-
st, err := specToContainerLLB(w, spec, targetKey, rpmDir, rpms, sOpt, pg)
38+
st, err := specToContainerLLB(w, spec, targetKey, rpmDir, rpms, sOpt, pg, dalec.WithPlatform(platform))
3939
if err != nil {
4040
return nil, nil, err
4141
}

frontend/azlinux/handle_depsonly.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func handleDepsOnly(w worker) gwclient.BuildFunc {
2121
return nil, nil, err
2222
}
2323

24-
baseImg, err := w.Base(sOpt, pg)
24+
baseImg, err := w.Base(sOpt, pg, dalec.WithPlatform(platform))
2525
if err != nil {
2626
return nil, nil, err
2727
}

frontend/azlinux/handle_rpm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func handleRPM(w worker) gwclient.BuildFunc {
2626
return nil, nil, err
2727
}
2828

29-
st, err := specToRpmLLB(ctx, w, client, spec, sOpt, targetKey, pg)
29+
st, err := specToRpmLLB(ctx, w, client, spec, sOpt, targetKey, pg, dalec.WithPlatform(platform))
3030
if err != nil {
3131
return nil, nil, err
3232
}

frontend/debug/handler.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ func Handle(ctx context.Context, client gwclient.Client) (*gwclient.Result, erro
2525
Name: "gomods",
2626
Description: "Outputs all the gomodule dependencies for the spec",
2727
})
28-
2928
return r.Handle(ctx, client)
3029
}

frontend/gateway.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import (
1717
)
1818

1919
const (
20-
requestIDKey = "requestid"
21-
dalecSubrequstForwardBuild = "dalec.forward.build"
20+
// KeyRequestID is a key used in buildkit to performa subrequest
21+
// This is exposed for convenience only.
22+
KeyRequestID = "requestid"
2223

23-
gatewayFrontend = "gateway.v0"
24+
dalecSubrequstForwardBuild = "dalec.forward.build"
25+
gatewayFrontend = "gateway.v0"
2426
)
2527

2628
func getDockerfile(ctx context.Context, client gwclient.Client, build *dalec.SourceBuild, defPb *pb.Definition) ([]byte, error) {

frontend/mux.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (m *BuildMux) describe() (*gwclient.Result, error) {
117117
}
118118

119119
func (m *BuildMux) handleSubrequest(ctx context.Context, client gwclient.Client, opts map[string]string) (*gwclient.Result, bool, error) {
120-
switch opts[requestIDKey] {
120+
switch opts[KeyRequestID] {
121121
case "":
122122
return nil, false, nil
123123
case subrequests.RequestSubrequestsDescribe:
@@ -135,7 +135,7 @@ func (m *BuildMux) handleSubrequest(ctx context.Context, client gwclient.Client,
135135
res, err := handleDefaultPlatform()
136136
return res, true, err
137137
default:
138-
return nil, false, errors.Errorf("unsupported subrequest %q", opts[requestIDKey])
138+
return nil, false, errors.Errorf("unsupported subrequest %q", opts[KeyRequestID])
139139
}
140140
}
141141

@@ -369,7 +369,7 @@ func (m *BuildMux) Handle(ctx context.Context, client gwclient.Client) (_ *gwcli
369369
WithFields(logrus.Fields{
370370
"handlers": maps.Keys(m.handlers),
371371
"target": opts[keyTarget],
372-
"requestid": opts[requestIDKey],
372+
"requestid": opts[KeyRequestID],
373373
"targetKey": GetTargetKey(client),
374374
}))
375375

@@ -403,7 +403,7 @@ func (m *BuildMux) Handle(ctx context.Context, client gwclient.Client) (_ *gwcli
403403

404404
// If this request was a request to list targets, we need to modify the response a bit
405405
// Otherwise we can just return the result as is.
406-
if opts[requestIDKey] == bktargets.SubrequestsTargetsDefinition.Name {
406+
if opts[KeyRequestID] == bktargets.SubrequestsTargetsDefinition.Name {
407407
return m.fixupListResult(matched, res)
408408
}
409409
return res, nil

helpers.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/moby/buildkit/client/llb"
1414
"github.com/moby/buildkit/identity"
15+
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
1516
)
1617

1718
var disableDiffMerge atomic.Bool
@@ -399,3 +400,12 @@ func SortedMapValues[T any](m map[string]T) []T {
399400

400401
return out
401402
}
403+
404+
// WithPlatform sets the platform in the constraints opts
405+
// This is similar to [llb.Platform] except this takes a pointer so you don't
406+
// need to worry about dereferencing a potentially nil pointer.
407+
func WithPlatform(p *ocispecs.Platform) llb.ConstraintsOpt {
408+
return constraintsOptFunc(func(c *llb.Constraints) {
409+
c.Platform = p
410+
})
411+
}

test/azlinux_test.go

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package test
22

33
import (
4+
"bytes"
45
"context"
6+
"debug/elf"
57
"errors"
68
"fmt"
79
"os"
@@ -10,9 +12,16 @@ import (
1012

1113
"github.com/Azure/dalec"
1214
"github.com/Azure/dalec/frontend/azlinux"
15+
"github.com/containerd/platforms"
1316
"github.com/moby/buildkit/client/llb"
1417
gwclient "github.com/moby/buildkit/frontend/gateway/client"
1518
moby_buildkit_v1_frontend "github.com/moby/buildkit/frontend/gateway/pb"
19+
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
20+
)
21+
22+
var (
23+
linuxAmd64 = ocispecs.Platform{OS: "linux", Architecture: "amd64"}
24+
linuxArm64 = ocispecs.Platform{OS: "linux", Architecture: "arm64"}
1625
)
1726

1827
func TestMariner2(t *testing.T) {
@@ -41,6 +50,7 @@ func TestMariner2(t *testing.T) {
4150
ID: "mariner",
4251
VersionID: "2.0",
4352
},
53+
SupportedPlatforms: platforms.Any(linuxAmd64, linuxArm64),
4454
})
4555
}
4656

@@ -70,6 +80,7 @@ func TestAzlinux3(t *testing.T) {
7080
ID: "azurelinux",
7181
VersionID: "3.0",
7282
},
83+
SupportedPlatforms: platforms.Any(linuxAmd64, linuxArm64),
7384
})
7485
}
7586

@@ -124,8 +135,9 @@ type testLinuxConfig struct {
124135
Units string
125136
Targets string
126137
}
127-
Worker workerConfig
128-
Release OSRelease
138+
Worker workerConfig
139+
Release OSRelease
140+
SupportedPlatforms platforms.Matcher
129141
}
130142

131143
type OSRelease struct {
@@ -603,6 +615,7 @@ WantedBy=multi-user.target
603615
Dir: &dalec.SourceInlineDir{
604616

605617
Files: map[string]*dalec.SourceInlineFile{
618+
606619
"foo.service": {
607620
Contents: `
608621
# simple-socket.service
@@ -1144,6 +1157,11 @@ Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/boot
11441157
})
11451158
})
11461159

1160+
t.Run("platform", func(t *testing.T) {
1161+
ctx := startTestSpan(ctx, t)
1162+
testPlatforms(ctx, t, testConfig)
1163+
})
1164+
11471165
t.Run("custom worker", func(t *testing.T) {
11481166
t.Parallel()
11491167
ctx := startTestSpan(baseCtx, t)
@@ -1246,6 +1264,128 @@ func testCustomLinuxWorker(ctx context.Context, t *testing.T, targetCfg targetCo
12461264
// Unfortunately it seems like there is an issue with the gateway client passing
12471265
// in source policies.
12481266
})
1267+
1268+
}
1269+
1270+
func testPlatforms(ctx context.Context, t *testing.T, testConfig testLinuxConfig) {
1271+
t.Run("build against different platform", func(t *testing.T) {
1272+
t.Parallel()
1273+
1274+
ls, err := testEnv.Platforms(ctx)
1275+
if err != nil {
1276+
t.Fatal(err)
1277+
}
1278+
if len(ls) <= 1 {
1279+
t.Skipf("builder does not support multiple platforms: %s", platformsAsStringer(ls))
1280+
}
1281+
1282+
testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) {
1283+
p := readDefaultPlatform(ctx, t, client)
1284+
1285+
matcher := platforms.OnlyStrict(p)
1286+
var testPlatform *ocispecs.Platform
1287+
for _, p2 := range ls {
1288+
// Get the first platform that is not the host platform that matches a supported distro platform
1289+
if !matcher.Match(p2) && testConfig.SupportedPlatforms.Match(p2) {
1290+
testPlatform = &p2
1291+
break
1292+
}
1293+
}
1294+
1295+
if testPlatform == nil {
1296+
msg := "could not find a platform suitable for testing, host platform: %s, available: %s"
1297+
ps := platformStringer(p)
1298+
workerPlatforms := platformsAsStringer(ls)
1299+
if os.Getenv("DALEC_CI") != "" {
1300+
t.Fatalf(msg, ps, workerPlatforms)
1301+
}
1302+
t.Skipf(msg, ps, workerPlatforms)
1303+
}
1304+
1305+
spec := &dalec.Spec{
1306+
Name: "test-platforms",
1307+
Version: "0.0.1",
1308+
Revision: "1",
1309+
Description: "Testing building on platform different from host platform",
1310+
License: "MIT",
1311+
Dependencies: &dalec.PackageDependencies{
1312+
Build: map[string]dalec.PackageConstraints{
1313+
"golang": {},
1314+
},
1315+
},
1316+
Sources: map[string]dalec.Source{
1317+
"src": {
1318+
Inline: &dalec.SourceInline{
1319+
Dir: &dalec.SourceInlineDir{
1320+
Files: map[string]*dalec.SourceInlineFile{
1321+
"go.mod": {
1322+
Contents: "module test\n\ngo 1.21.6",
1323+
},
1324+
"main.go": {
1325+
Contents: "package main\n\nfunc main() {}\n",
1326+
},
1327+
},
1328+
},
1329+
},
1330+
},
1331+
},
1332+
Build: dalec.ArtifactBuild{
1333+
Steps: []dalec.BuildStep{
1334+
{Command: "cd src; go build -o /tmp/test"},
1335+
},
1336+
},
1337+
Artifacts: dalec.Artifacts{
1338+
Binaries: map[string]dalec.ArtifactConfig{
1339+
"/tmp/test": {},
1340+
},
1341+
},
1342+
}
1343+
1344+
tp := *testPlatform
1345+
req := newSolveRequest(withPlatform(tp), withSpec(ctx, t, spec), withBuildTarget(testConfig.Target.Container))
1346+
res := solveT(ctx, t, client, req)
1347+
1348+
imgPlatforms := readResultPlatforms(t, res)
1349+
if len(imgPlatforms) != 1 {
1350+
t.Fatal("expected image output to contain 1 platform")
1351+
}
1352+
1353+
if !platforms.OnlyStrict(tp).Match(imgPlatforms[0]) {
1354+
t.Errorf("Expected image platform %q, got: %q", platformStringer(tp), platformStringer(imgPlatforms[0]))
1355+
}
1356+
1357+
ref, err := res.SingleRef()
1358+
if err != nil {
1359+
t.Fatal(err)
1360+
}
1361+
if ref == nil {
1362+
t.Fatal("got empty reference -- most likely an empty (scratch) state was returned")
1363+
}
1364+
1365+
// Read the ELF header so we can determine what the target architecture is.
1366+
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
1367+
Filename: "/usr/bin/test",
1368+
})
1369+
if err != nil {
1370+
t.Fatal(err)
1371+
}
1372+
1373+
f, err := elf.NewFile(bytes.NewReader(dt))
1374+
if err != nil {
1375+
t.Fatal(err)
1376+
}
1377+
1378+
check := ocispecs.Platform{
1379+
OS: "linux",
1380+
}
1381+
elfToPlatform(f, &check)
1382+
1383+
if !platforms.OnlyStrict(*testPlatform).Match(check) {
1384+
t.Fatalf("output binary has unexpected platform, expected: %s, got: %s", platformStringer(*testPlatform), platformStringer(check))
1385+
}
1386+
})
1387+
})
1388+
12491389
}
12501390

12511391
func testPinnedBuildDeps(ctx context.Context, t *testing.T, targetCfg targetConfig, workerCfg workerConfig) {

0 commit comments

Comments
 (0)