Skip to content

Commit 345294d

Browse files
committed
frontend: rewrite HandleDepsOnly to use pkg/container build infra
The way depsonly worked was always a bit janky and didn't actually support the full dependency constraint specification. Additionally I found the shift to dnf from tdnf broke due to `--alldeps` being missing (possibly just in mariner2, but still missing). This shifts depsonly to use BuildPkg and BuildContainer where we create a meta package with just the runtime deps. Because depsonly allows specifying a partial spec (ie missing things like name, license, other normally required fields) we have to fill in those details so rpmbuild can succeed. Add deps-only integration tests for all RPM distros with two sub-tests: - minimal spec: only runtime deps, verifies curl is installed - full spec: includes sources, build steps, and a shell script artifact; verifies runtime deps are installed and build artifacts are excluded - replaces the "e2e" test in docker-bake.hcl and tets all relevant distros Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent 9273860 commit 345294d

7 files changed

Lines changed: 125 additions & 70 deletions

File tree

docker-bake.hcl

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ group "default" {
33
}
44

55
group "test" {
6-
targets = ["runc-test", "test-deps-only"]
6+
targets = ["runc-test"]
77
}
88

99
variable "FRONTEND_REF" {
@@ -227,42 +227,6 @@ target "examples" {
227227
tags = ["local/dalec/examples/${f}:${distro}"]
228228
}
229229

230-
target "deps-only" {
231-
name = "deps-only-${distro}"
232-
matrix = {
233-
distro = ["mariner2"]
234-
}
235-
dockerfile-inline = <<EOT
236-
dependencies:
237-
runtime:
238-
patch: {}
239-
bash: {}
240-
EOT
241-
args = {
242-
"BUILDKIT_SYNTAX" = "dalec_frontend"
243-
}
244-
contexts = {
245-
"dalec_frontend" = "target:frontend"
246-
}
247-
target = "${distro}/container/depsonly"
248-
tags = ["local/dalec/deps-only:${distro}"]
249-
}
250-
251-
target "test-deps-only" {
252-
dockerfile-inline = <<EOT
253-
FROM deps-only-context
254-
# Make sure the deps-only target has the runtime dependencies we expect and not, for instance, "rpm"
255-
RUN command -v bash
256-
RUN command -v patch
257-
RUN if command -v rpm; then echo should be a distroless image but rpm binary is installed; exit 1; fi
258-
EOT
259-
260-
contexts = {
261-
"deps-only-context" = "target:deps-only-mariner2"
262-
}
263-
}
264-
265-
266230
variable "CI_FRONTEND_CACHE_SCOPE" {
267231
default = "dalec/frontend/ci"
268232
}

targets/linux/rpm/distro/container.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,36 @@ func (cfg *Config) HandleDepsOnly(ctx context.Context, client gwclient.Client) (
9595
}
9696

9797
pc := dalec.Platform(platform)
98-
worker := cfg.Worker(sOpt, pg, pc)
9998

100-
deps := dalec.SortMapKeys(rtDeps)
99+
// NOTE: Deps-only allows bare specs, ie specs with just the runtime deps included.
100+
// This means we may need to fill in some of the details that are required by the package manager.
101+
depsSpec := &dalec.Spec{
102+
Name: spec.Name + "-runtime-deps",
103+
License: spec.License,
104+
Version: spec.Version,
105+
Revision: spec.Revision,
106+
Description: "Runtime dependencies meta package",
107+
Dependencies: &dalec.PackageDependencies{
108+
Runtime: rtDeps,
109+
},
110+
}
101111

102-
withDownloads := worker.Run(dalec.ShArgs("set -ex; mkdir -p /tmp/rpms/RPMS/$(uname -m)")).
103-
Run(cfg.Install(deps,
104-
DnfDownloadAllDeps("/tmp/rpms/RPMS/$(uname -m)")), pg).Root()
105-
rpmDir := llb.Scratch().File(llb.Copy(withDownloads, "/tmp/rpms", "/", dalec.WithDirContentsOnly()), pg)
112+
if depsSpec.Name == "-runtime-deps" {
113+
// Name cannot start with "-"
114+
depsSpec.Name = "dalec-user" + depsSpec.Name
115+
}
116+
if depsSpec.Version == "" {
117+
depsSpec.Version = "0.0.1"
118+
}
119+
if depsSpec.Revision == "" {
120+
depsSpec.Revision = "1"
121+
}
122+
if depsSpec.License == "" {
123+
depsSpec.License = "MIT"
124+
}
106125

107-
ctr := cfg.BuildContainer(ctx, client, sOpt, spec, targetKey, rpmDir, pg, pc)
126+
pkg := cfg.BuildPkg(ctx, client, sOpt, depsSpec, targetKey, pg)
127+
ctr := cfg.BuildContainer(ctx, client, sOpt, spec, targetKey, pkg, pg)
108128

109129
def, err := ctr.Marshal(ctx, pc)
110130
if err != nil {

targets/linux/rpm/distro/dnf_install.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@ type dnfInstallConfig struct {
3434

3535
constraints []llb.ConstraintsOpt
3636

37-
downloadOnly bool
38-
39-
allDeps bool
40-
41-
downloadDir string
42-
4337
// When true, don't omit docs from the installed RPMs.
4438
includeDocs bool
4539

@@ -82,14 +76,6 @@ func DnfForceArch(arch string) DnfInstallOpt {
8276
}
8377
}
8478

85-
func DnfDownloadAllDeps(dest string) DnfInstallOpt {
86-
return func(cfg *dnfInstallConfig) {
87-
cfg.downloadOnly = true
88-
cfg.allDeps = true
89-
cfg.downloadDir = dest
90-
}
91-
}
92-
9379
func IncludeDocs(v bool) DnfInstallOpt {
9480
return func(cfg *dnfInstallConfig) {
9581
cfg.includeDocs = v
@@ -110,18 +96,6 @@ func dnfInstallFlags(cfg *dnfInstallConfig) string {
11096
cmdOpts += " --setopt=reposdir=/etc/yum.repos.d"
11197
}
11298

113-
if cfg.downloadOnly {
114-
cmdOpts += " --downloadonly"
115-
}
116-
117-
if cfg.allDeps {
118-
cmdOpts += " --alldeps"
119-
}
120-
121-
if cfg.downloadDir != "" {
122-
cmdOpts += " --downloaddir " + cfg.downloadDir
123-
}
124-
12599
if !cfg.includeDocs {
126100
cmdOpts += " --setopt=tsflags=nodocs"
127101
}

test/linux_target_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ type targetConfig struct {
6161
Package string
6262
// Container is the target for creating a container
6363
Container string
64+
// DepsOnly is the target for creating a deps-only container (no package built, only runtime deps installed).
65+
DepsOnly string
6466
// Worker is the target for creating the worker image.
6567
Worker string
6668
// Sysext is the target for creating a systemd system extension.
@@ -718,6 +720,16 @@ index 0000000..5260cb1
718720
t.Run("container", func(t *testing.T) {
719721
t.Parallel()
720722

723+
t.Run("depsonly", func(t *testing.T) {
724+
if testConfig.Target.DepsOnly == "" {
725+
t.Skip("depsonly target not defined")
726+
}
727+
728+
t.Parallel()
729+
ctx := startTestSpan(ctx, t)
730+
testDepsOnly(ctx, t, testConfig)
731+
})
732+
721733
t.Run("creates_post_install_symlinks", func(t *testing.T) {
722734
t.Parallel()
723735

@@ -5403,6 +5415,85 @@ echo "This is a third test binary"
54035415
})
54045416
}
54055417

5418+
func testDepsOnly(ctx context.Context, t *testing.T, testConfig testLinuxConfig) {
5419+
t.Run("minimal spec", func(t *testing.T) {
5420+
t.Parallel()
5421+
ctx := startTestSpan(ctx, t)
5422+
5423+
spec := &dalec.Spec{
5424+
Dependencies: &dalec.PackageDependencies{
5425+
Runtime: map[string]dalec.PackageConstraints{
5426+
"curl": {},
5427+
},
5428+
},
5429+
}
5430+
5431+
testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) {
5432+
req := newSolveRequest(withSpec(ctx, t, spec), withBuildTarget(testConfig.Target.DepsOnly))
5433+
res := solveT(ctx, t, client, req)
5434+
5435+
ref, err := res.SingleRef()
5436+
assert.NilError(t, err)
5437+
5438+
_, err = ref.StatFile(ctx, gwclient.StatRequest{Path: "/usr/bin/curl"})
5439+
assert.NilError(t, err)
5440+
})
5441+
})
5442+
5443+
t.Run("full spec", func(t *testing.T) {
5444+
t.Parallel()
5445+
ctx := startTestSpan(ctx, t)
5446+
5447+
// Full spec includes sources, build steps, and a shell script artifact.
5448+
// The deps-only target should install only runtime deps (curl) and NOT
5449+
// include the built artifact (/usr/bin/my-script) or its implicit dep.
5450+
spec := fillMetadata("test-deps-only-full", &dalec.Spec{
5451+
Sources: map[string]dalec.Source{
5452+
"my-script": {
5453+
Inline: &dalec.SourceInline{
5454+
File: &dalec.SourceInlineFile{
5455+
Contents: "#!/usr/bin/env bash\necho hello from deps-only test\n",
5456+
Permissions: 0o700,
5457+
},
5458+
},
5459+
},
5460+
},
5461+
Build: dalec.ArtifactBuild{
5462+
Steps: []dalec.BuildStep{
5463+
{Command: "/bin/true"},
5464+
},
5465+
},
5466+
Artifacts: dalec.Artifacts{
5467+
Binaries: map[string]dalec.ArtifactConfig{
5468+
"my-script": {},
5469+
},
5470+
},
5471+
Dependencies: &dalec.PackageDependencies{
5472+
Runtime: map[string]dalec.PackageConstraints{
5473+
"curl": {},
5474+
},
5475+
},
5476+
})
5477+
5478+
testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) {
5479+
req := newSolveRequest(withSpec(ctx, t, spec), withBuildTarget(testConfig.Target.DepsOnly))
5480+
res := solveT(ctx, t, client, req)
5481+
5482+
ref, err := res.SingleRef()
5483+
assert.NilError(t, err)
5484+
5485+
// Runtime dep should be installed.
5486+
_, err = ref.StatFile(ctx, gwclient.StatRequest{Path: "/usr/bin/curl"})
5487+
assert.NilError(t, err)
5488+
5489+
// The shell script artifact should NOT be present — deps-only
5490+
// never builds the package, so no artifacts are installed.
5491+
_, err = ref.StatFile(ctx, gwclient.StatRequest{Path: "/usr/bin/my-script"})
5492+
assert.ErrorContains(t, err, "no such file")
5493+
})
5494+
})
5495+
}
5496+
54065497
func testLinuxSpec(t *testing.T, userSpec dalec.Spec) dalec.Spec {
54075498
t.Helper()
54085499

test/target_almalinux_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func TestAlmalinux9(t *testing.T) {
1717
Key: "almalinux9",
1818
Package: "almalinux9/rpm",
1919
Container: "almalinux9/container",
20+
DepsOnly: "almalinux9/container/depsonly",
2021
Worker: "almalinux9/worker",
2122
FormatDepEqual: func(v, _ string) string {
2223
return v
@@ -65,6 +66,7 @@ func TestAlmalinux8(t *testing.T) {
6566
Target: targetConfig{
6667
Package: "almalinux8/rpm",
6768
Container: "almalinux8/container",
69+
DepsOnly: "almalinux8/container/depsonly",
6870
Worker: "almalinux8/worker",
6971
FormatDepEqual: func(v, _ string) string {
7072
return v

test/target_azlinux_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func TestMariner2(t *testing.T) {
4646
Key: azlinux.Mariner2TargetKey,
4747
Package: "mariner2/rpm",
4848
Container: "mariner2/container",
49+
DepsOnly: "mariner2/container/depsonly",
4950
Worker: "mariner2/worker",
5051
FormatDepEqual: func(v, _ string) string {
5152
return v
@@ -91,6 +92,7 @@ func TestAzlinux3(t *testing.T) {
9192
Key: "azlinux3",
9293
Package: "azlinux3/rpm",
9394
Container: "azlinux3/container",
95+
DepsOnly: "azlinux3/container/depsonly",
9496
Worker: "azlinux3/worker",
9597
Sysext: "azlinux3/testing/sysext",
9698
ListExpectedSignFiles: azlinuxListSignFiles("azl3"),

test/target_rockylinux_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func TestRockylinux9(t *testing.T) {
1717
Key: "rockylinux9",
1818
Package: "rockylinux9/rpm",
1919
Container: "rockylinux9/container",
20+
DepsOnly: "rockylinux9/container/depsonly",
2021
Worker: "rockylinux9/worker",
2122
FormatDepEqual: func(v, _ string) string {
2223
return v
@@ -65,6 +66,7 @@ func TestRockylinux8(t *testing.T) {
6566
Target: targetConfig{
6667
Package: "rockylinux8/rpm",
6768
Container: "rockylinux8/container",
69+
DepsOnly: "rockylinux8/container/depsonly",
6870
Worker: "rockylinux8/worker",
6971
FormatDepEqual: func(v, _ string) string {
7072
return v

0 commit comments

Comments
 (0)