Skip to content

Commit db9a88b

Browse files
authored
Properly Version Build Dependencies for Azlinux (#337)
* Add InstallWithReqs() to worker interface * Finish initial implementation for azlinux, unit test currently broken * Add working build dependency constraints test using local package repo * Fix cases where build dep keys would be unsorted, minor rename * Remove InstallWithReqs from worker interface and move to rpm-specific helper function * Move test helper function for pinned build tests into test itself * Remove/update some comments, remove unused common.go * add --refresh flag to tdnf install * prefix build-dependencies package with underlying package name
1 parent 2260399 commit db9a88b

File tree

6 files changed

+208
-16
lines changed

6 files changed

+208
-16
lines changed

frontend/azlinux/handle_rpm.go

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,80 @@ func handleRPM(w worker) gwclient.BuildFunc {
5252
}
5353
}
5454

55-
func installBuildDeps(w worker, spec *dalec.Spec, targetKey string, opts ...llb.ConstraintsOpt) llb.StateOption {
56-
return func(in llb.State) llb.State {
57-
deps := spec.GetBuildDeps(targetKey)
58-
if len(deps) == 0 {
59-
return in
55+
// Creates and installs an rpm meta-package that requires the passed in deps as runtime-dependencies
56+
func installBuildDepsPackage(target string, packageName string, w worker, deps map[string]dalec.PackageConstraints, installOpts ...installOpt) installFunc {
57+
// depsOnly is a simple dalec spec that only includes build dependencies and their constraints
58+
depsOnly := dalec.Spec{
59+
Name: fmt.Sprintf("%s-build-dependencies", packageName),
60+
Description: "Provides build dependencies for mariner2 and azlinux3",
61+
Version: "1.0",
62+
License: "Apache 2.0",
63+
Revision: "1",
64+
Dependencies: &dalec.PackageDependencies{
65+
Runtime: deps,
66+
},
67+
}
68+
69+
return func(ctx context.Context, client gwclient.Client, sOpt dalec.SourceOpts) (llb.RunOption, error) {
70+
pg := dalec.ProgressGroup("Building container for build dependencies")
71+
72+
// create an RPM with just the build dependencies, using our same base worker
73+
rpmDir, err := specToRpmLLB(ctx, w, client, &depsOnly, sOpt, target, pg)
74+
if err != nil {
75+
return nil, err
6076
}
77+
78+
var opts []llb.ConstraintsOpt
6179
opts = append(opts, dalec.ProgressGroup("Install build deps"))
6280

63-
return in.Run(w.Install(deps, installWithConstraints(opts)), dalec.WithConstraints(opts...)).Root()
81+
rpmMountDir := "/tmp/rpms"
82+
83+
installOpts = append([]installOpt{
84+
noGPGCheck,
85+
withMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))),
86+
installWithConstraints(opts),
87+
}, installOpts...)
88+
89+
// install the built RPMs into the worker itself
90+
return w.Install([]string{"/tmp/rpms/*/*.rpm"}, installOpts...), nil
6491
}
6592
}
6693

94+
func installBuildDeps(ctx context.Context, w worker, client gwclient.Client, spec *dalec.Spec, targetKey string, opts ...llb.ConstraintsOpt) (llb.StateOption, error) {
95+
deps := spec.GetBuildDeps(targetKey)
96+
if len(deps) == 0 {
97+
return func(in llb.State) llb.State { return in }, nil
98+
}
99+
100+
sOpt, err := frontend.SourceOptFromClient(ctx, client)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
opts = append(opts, dalec.ProgressGroup("Install build deps"))
106+
107+
installOpt, err := installBuildDepsPackage(targetKey, spec.Name, w, deps, installWithConstraints(opts))(ctx, client, sOpt)
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
return func(in llb.State) llb.State {
113+
return in.Run(installOpt, dalec.WithConstraints(opts...)).Root()
114+
}, nil
115+
}
116+
67117
func specToRpmLLB(ctx context.Context, w worker, client gwclient.Client, spec *dalec.Spec, sOpt dalec.SourceOpts, targetKey string, opts ...llb.ConstraintsOpt) (llb.State, error) {
68118
base, err := w.Base(sOpt, opts...)
69-
base = base.With(installBuildDeps(w, spec, targetKey, opts...))
70119
if err != nil {
71120
return llb.Scratch(), err
72121
}
73122

123+
installOpt, err := installBuildDeps(ctx, w, client, spec, targetKey, opts...)
124+
if err != nil {
125+
return llb.Scratch(), err
126+
}
127+
base = base.With(installOpt)
128+
74129
br, err := rpm.SpecToBuildrootLLB(base, spec, sOpt, targetKey, opts...)
75130
if err != nil {
76131
return llb.Scratch(), err

frontend/azlinux/handler.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const (
1818
tdnfCacheDir = "/var/cache/tdnf"
1919
)
2020

21+
type installFunc func(context.Context, gwclient.Client, dalec.SourceOpts) (llb.RunOption, error)
22+
2123
type worker interface {
2224
Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.State, error)
2325
Install(pkgs []string, opts ...installOpt) llb.RunOption
@@ -60,26 +62,33 @@ func handleDebug(w worker) gwclient.BuildFunc {
6062
if err != nil {
6163
return nil, err
6264
}
63-
return rpm.HandleDebug(getSpecWorker(w, sOpt))(ctx, client)
65+
return rpm.HandleDebug(getSpecWorker(ctx, w, client, sOpt))(ctx, client)
6466
}
6567
}
6668

67-
func getSpecWorker(w worker, sOpt dalec.SourceOpts) rpm.WorkerFunc {
69+
func getSpecWorker(ctx context.Context, w worker, client gwclient.Client, sOpt dalec.SourceOpts) rpm.WorkerFunc {
6870
return func(resolver llb.ImageMetaResolver, spec *dalec.Spec, targetKey string, opts ...llb.ConstraintsOpt) (llb.State, error) {
6971
st, err := w.Base(sOpt, opts...)
7072
if err != nil {
7173
return llb.Scratch(), err
7274
}
7375
if spec.HasGomods() {
74-
deps := spec.GetBuildDeps(targetKey)
76+
deps := dalec.SortMapKeys(spec.GetBuildDeps(targetKey))
77+
7578
hasGolang := func(s string) bool {
7679
return s == "golang" || s == "msft-golang"
7780
}
7881

7982
if !slices.ContainsFunc(deps, hasGolang) {
8083
return llb.Scratch(), errors.New("spec contains go modules but does not have golang in build deps")
8184
}
82-
st = st.With(installBuildDeps(w, spec, targetKey, opts...))
85+
86+
installOpt, err := installBuildDeps(ctx, w, client, spec, targetKey, opts...)
87+
if err != nil {
88+
return llb.Scratch(), err
89+
}
90+
91+
st = st.With(installOpt)
8392
}
8493
return st, nil
8594
}

frontend/azlinux/install.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ type installConfig struct {
2020
// this acts like installing to a chroot.
2121
root string
2222

23+
// Additional mounts to add to the tdnf install command (useful if installing RPMS which are mounted to a local directory)
24+
mounts []llb.RunOption
25+
2326
constraints []llb.ConstraintsOpt
2427
}
2528

@@ -29,6 +32,12 @@ func noGPGCheck(cfg *installConfig) {
2932
cfg.noGPGCheck = true
3033
}
3134

35+
func withMounts(opts ...llb.RunOption) installOpt {
36+
return func(cfg *installConfig) {
37+
cfg.mounts = append(cfg.mounts, opts...)
38+
}
39+
}
40+
3241
func withManifests(cfg *installConfig) {
3342
cfg.manifest = true
3443
}
@@ -104,7 +113,7 @@ const manifestSh = "manifest.sh"
104113

105114
func tdnfInstall(cfg *installConfig, relVer string, pkgs []string) llb.RunOption {
106115
cmdFlags := tdnfInstallFlags(cfg)
107-
cmdArgs := fmt.Sprintf("set -ex; tdnf install -y --releasever=%s %s %s", relVer, cmdFlags, strings.Join(pkgs, " "))
116+
cmdArgs := fmt.Sprintf("set -ex; tdnf install -y --refresh --releasever=%s %s %s", relVer, cmdFlags, strings.Join(pkgs, " "))
108117

109118
var runOpts []llb.RunOption
110119

@@ -118,5 +127,7 @@ func tdnfInstall(cfg *installConfig, relVer string, pkgs []string) llb.RunOption
118127
}
119128

120129
runOpts = append(runOpts, dalec.ShArgs(cmdArgs))
130+
runOpts = append(runOpts, cfg.mounts...)
131+
121132
return dalec.WithRunOptions(runOpts...)
122133
}

frontend/windows/handle_zip.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ func withSourcesMounted(dst string, states map[string]llb.State, sources map[str
128128
}
129129

130130
func buildBinaries(ctx context.Context, spec *dalec.Spec, worker llb.State, client gwclient.Client, sOpt dalec.SourceOpts, targetKey string) (llb.State, error) {
131-
worker = worker.With(installBuildDeps(spec.GetBuildDeps(targetKey)))
131+
deps := dalec.SortMapKeys(spec.GetBuildDeps(targetKey))
132+
133+
// note: we do not yet support pinning build dependencies for windows workers
134+
worker = worker.With(installBuildDeps(deps))
132135

133136
sources, err := specToSourcesLLB(worker, spec, sOpt)
134137
if err != nil {

helpers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ func (s *Spec) GetRuntimeDeps(targetKey string) []string {
285285

286286
}
287287

288-
func (s *Spec) GetBuildDeps(targetKey string) []string {
288+
func (s *Spec) GetBuildDeps(targetKey string) map[string]PackageConstraints {
289289
var deps *PackageDependencies
290290
if t, ok := s.Targets[targetKey]; ok {
291291
deps = t.Dependencies
@@ -298,7 +298,7 @@ func (s *Spec) GetBuildDeps(targetKey string) []string {
298298
}
299299
}
300300

301-
return SortMapKeys(deps.Build)
301+
return deps.Build
302302
}
303303

304304
func (s *Spec) GetTestDeps(targetKey string) []string {

test/azlinux_test.go

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ type workerConfig struct {
111111
type targetConfig struct {
112112
// Package is the target for creating a package.
113113
Package string
114-
// Container is the target for creating a container.
114+
// Container is the target for creating a container
115115
Container string
116116
// Target is the build target for creating the worker image.
117117
Worker string
@@ -1149,6 +1149,12 @@ Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/boot
11491149
ctx := startTestSpan(baseCtx, t)
11501150
testCustomLinuxWorker(ctx, t, testConfig.Target, testConfig.Worker)
11511151
})
1152+
1153+
t.Run("pinned build dependencies", func(t *testing.T) {
1154+
t.Parallel()
1155+
ctx := startTestSpan(baseCtx, t)
1156+
testPinnedBuildDeps(ctx, t, testConfig.Target, testConfig.Worker)
1157+
})
11521158
}
11531159

11541160
func testCustomLinuxWorker(ctx context.Context, t *testing.T, targetCfg targetConfig, workerCfg workerConfig) {
@@ -1242,6 +1248,114 @@ func testCustomLinuxWorker(ctx context.Context, t *testing.T, targetCfg targetCo
12421248
})
12431249
}
12441250

1251+
func testPinnedBuildDeps(ctx context.Context, t *testing.T, targetCfg targetConfig, workerCfg workerConfig) {
1252+
var pkgName = "dalec-test-package"
1253+
1254+
getTestPackageSpec := func(version string) *dalec.Spec {
1255+
depSpec := &dalec.Spec{
1256+
Name: pkgName,
1257+
Version: version,
1258+
Revision: "1",
1259+
Description: "A basic package for various testing uses",
1260+
License: "MIT",
1261+
Sources: map[string]dalec.Source{
1262+
"version.txt": {
1263+
Inline: &dalec.SourceInline{
1264+
File: &dalec.SourceInlineFile{
1265+
Contents: "version: " + version,
1266+
},
1267+
},
1268+
},
1269+
},
1270+
Artifacts: dalec.Artifacts{
1271+
Docs: map[string]dalec.ArtifactConfig{
1272+
"version.txt": {},
1273+
},
1274+
},
1275+
}
1276+
1277+
return depSpec
1278+
}
1279+
1280+
depSpecs := []*dalec.Spec{
1281+
getTestPackageSpec("1.1.1"),
1282+
getTestPackageSpec("1.2.0"),
1283+
getTestPackageSpec("1.3.0"),
1284+
}
1285+
1286+
spec := &dalec.Spec{
1287+
Name: "dalec-test-pinned-build-deps",
1288+
Version: "0.0.1",
1289+
Revision: "1",
1290+
Description: "Testing allowing custom worker images to be provided",
1291+
License: "MIT",
1292+
}
1293+
1294+
tests := []struct {
1295+
name string
1296+
constraints string
1297+
want string
1298+
}{
1299+
{
1300+
name: "exact dep available",
1301+
constraints: "== 1.1.1",
1302+
want: "1.1.1",
1303+
},
1304+
1305+
{
1306+
name: "lt dep available",
1307+
constraints: "< 1.3.0",
1308+
want: "1.2.0",
1309+
},
1310+
1311+
{
1312+
name: "gt dep available",
1313+
constraints: "> 1.2.0",
1314+
want: "1.3.0",
1315+
},
1316+
}
1317+
1318+
testEnv.RunTest(ctx, t, func(ctx context.Context, gwc gwclient.Client) {
1319+
// Build the worker target, this will give us the worker image as an output.
1320+
// Note: Currently we need to provide a dalec spec just due to how the router is setup.
1321+
// The spec can be nil, though, it just needs to be parsable by yaml unmarshaller.
1322+
sr := newSolveRequest(withBuildTarget(targetCfg.Worker), withSpec(ctx, t, nil))
1323+
worker := reqToState(ctx, gwc, sr, t)
1324+
1325+
var pkgs []llb.State
1326+
for _, depSpec := range depSpecs {
1327+
sr := newSolveRequest(withSpec(ctx, t, depSpec), withBuildTarget(targetCfg.Package))
1328+
pkg := reqToState(ctx, gwc, sr, t)
1329+
pkgs = append(pkgs, pkg)
1330+
}
1331+
worker = worker.With(workerCfg.CreateRepo(llb.Merge(pkgs)))
1332+
1333+
for _, tt := range tests {
1334+
spec.Dependencies = &dalec.PackageDependencies{
1335+
Build: map[string]dalec.PackageConstraints{
1336+
pkgName: {
1337+
Version: []string{tt.constraints},
1338+
},
1339+
},
1340+
}
1341+
1342+
spec.Build.Steps = []dalec.BuildStep{
1343+
{
1344+
Command: fmt.Sprintf(`[[ $(cat /usr/share/doc/%s/version.txt) == "version: %s" ]]`, pkgName, tt.want),
1345+
},
1346+
}
1347+
1348+
sr = newSolveRequest(withSpec(ctx, t, spec), withBuildContext(ctx, t, workerCfg.ContextName, worker), withBuildTarget(targetCfg.Container))
1349+
res := solveT(ctx, t, gwc, sr)
1350+
_, err := res.SingleRef()
1351+
1352+
if err != nil {
1353+
t.Fatal(err)
1354+
}
1355+
}
1356+
})
1357+
}
1358+
12451359
func validatePathAndPermissions(ctx context.Context, ref gwclient.Reference, path string, expected os.FileMode) error {
12461360
stat, err := ref.StatFile(ctx, gwclient.StatRequest{Path: path})
12471361
if err != nil {

0 commit comments

Comments
 (0)