Repository navigation
Expand file tree
/
Copy pathBUCK
More file actions
717 lines (657 loc) · 28.2 KB
/
Copy pathBUCK
File metadata and controls
717 lines (657 loc) · 28.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
# SPDX-FileCopyrightText: Amutable GmbH <https://amutable.com/>
# SPDX-License-Identifier: MPL-2.0
"""Image construction smoke targets.
Every image here is declared once and built for each release in `catalog//:releases.bzl`. What
varies is the package manager, chosen by a `select()` on the distribution constraint, and the
package sets that manager resolves; the operations themselves are the same either way. No
distribution is the default, so what a build names is one of the per-distribution aliases each
image rule declares for itself.
"""
load("@tine//box:defs.bzl", "box")
load("@tine//distribution:defs.bzl", "distribution")
load("@tine//image:defs.bzl", "image")
load("@tine//platform:architecture.bzl", "architecture")
load("@tine//python:defs.bzl", "python")
load("@tine//tests:defs.bzl", "tests")
load(":expected.json", _expected = "value")
# The catalog lists the releases in catalog//:releases.bzl. A release added there also gets images
# here, and no target below names a release.
DISTRIBUTIONS = distribution.for_package()
# A test that checks only the Fedora images builds for Fedora, so that it runs in a box of the Fedora release.
_FEDORA = DISTRIBUTIONS["fedora"]["distribution"]
# A build artifact a command names through $(location), chrooted or not.
export_file(
name = "marker.txt",
src = "marker.txt",
)
export_file(
name = "configure.sh",
src = "configure.sh",
)
export_file(
name = "configure.py",
src = "configure.py",
)
export_file(
name = "manifest_check.py",
src = "manifest_check.py",
)
export_file(
name = "reproducibility_check.py",
src = "reproducibility_check.py",
)
# Minimal install + ordered-ops demo. `:demo` is both the tar result and a logical image.
image.archive(
name = "demo",
packages = ["bash", "coreutils"]
# Some extra coverage for distribution.select().
+ distribution.select({
"DEFAULT": [],
"arch": ["archlinux-keyring"],
"fedora": ["fedora-gpg-keys"],
}),
ops = [
image.ops.run(["/usr/bin/mkdir", "-p", "/buildroot/etc/tine"]),
image.ops.run(["/usr/bin/cp", "$(location :marker.txt)", "/buildroot/etc/tine/from-location"]),
# image.ops.copy() takes a source directly, so it needs no $(location) around the label.
image.ops.copy(":marker.txt", "/etc/tine/from-copy"),
# A chrooted command runs with the image's own binaries. Buck's macro parser claims
# `$(...)`, so the shell substitution is escaped.
image.ops.run(["/usr/bin/bash", "-c", "echo built-by-\\$(echo tine) > /etc/tine/marker"], chroot = True),
# A repository-owned script, run inside the image. The project is mounted there, so
# `$(location)` names the script and its data exactly as it would outside.
image.ops.run(
[
"/usr/bin/bash",
"$(location :configure.sh)",
"$(location :marker.txt)",
"from-script-argument",
],
chroot = True,
),
# The same, in python, which this image does not install: the interpreter is Buck's own.
image.ops.python(["$(location :configure.py)", "$(location :marker.txt)", "from-python-argument"], chroot = True),
# Unchrooted, the same interpreter sees the image at /buildroot, like any box command.
image.ops.python(["-c", "from pathlib import Path; Path('/buildroot/etc/tine/from-python-box').write_text('x\\n')"]),
# Ensure we don't leak the /tine sandbox mount into image builds
image.ops.run(["/usr/bin/test", "!", "-d", "/tine"], chroot = True),
],
format = "tar",
)
# Base-only image exercising a single-component delta stack.
image.archive(
name = "base",
packages = ["bash"],
format = "tar",
)
# Chained images: the child adds one file and whiteouts another.
image.layer(
name = "chained-base",
packages = ["bash", "coreutils"],
ops = [
image.ops.write_file("/etc/tine/from-base-kept", "base"),
image.ops.write_file("/etc/tine/from-base-gone", "base"),
],
)
image.layer(
name = "chained-child.layer",
parent = ":chained-base",
ops = [
image.ops.write_file("/etc/tine/from-child", "child"),
image.ops.remove("/etc/tine/from-base-gone"),
],
)
image.archive(
name = "chained",
parent = ":chained-child.layer",
format = "tar",
)
# Package installation and mutations sharing one physical delta.
image.layer(
name = "layered-install-base",
packages = ["bash"],
ops = [
image.ops.run(["/usr/bin/bash", "-c", "echo base > /etc/from-base-install"], chroot = True),
],
)
image.layer(
name = "layered-install.layer",
parent = ":layered-install-base",
packages = ["coreutils"],
ops = [
image.ops.run(["/usr/bin/bash", "-c", "ls /usr/bin/ls > /etc/from-child-install"], chroot = True),
],
)
image.archive(
name = "layered-install",
parent = ":layered-install.layer",
format = "tar",
)
# Round-trip an escaped name and an escaped whiteout through stored deltas.
image.layer(
name = "escaped-base",
packages = ["bash", "coreutils"],
ops = [
image.ops.mkdir("/etc/tine"),
image.ops.run(
[
"/usr/bin/bash",
"-c",
"echo kept > '/etc/tine/esc\\x2dkept'; echo gone > '/etc/tine/esc\\x2dgone'",
],
chroot = True,
),
],
)
image.layer(
name = "escaped-child.layer",
parent = ":escaped-base",
ops = [image.ops.remove("/etc/tine/esc\\x2dgone")],
)
image.archive(
name = "escaped",
parent = ":escaped-child.layer",
format = "tar",
)
# Bootable GPT image; `buck run :boot-demo-vm` starts it with the box's vmspawn stack.
# Supply-chain and format subtargets such as `:boot-demo[sbom]` and `:boot-demo[qcow2]` build on demand.
# The version stamps os-release, partition labels, and the UKI and disk file names. A product names
# one here, or asks for a git-derived one with `version = "auto:1.4.2"`, which needs a build through
# `bin/tine` (see "Image versioning" in images.md); this demo has nothing to version.
image.disk(
name = "boot-demo",
definitions = image.DEFAULT_USR_VERITY_PARTITIONS,
version = "unversioned",
# The release names what a bootable system of its own family needs, so this stays symbolic.
# The composition generates the databases those packages only describe, hwdb's among them.
package_sets = ["bootable"],
packages = ["grep", "less"]
# Microcode is a package like any other; the UKI driver packs whatever vendor blobs the image
# ends up shipping. Only x86 has any, and only x86 mirrors carry the packages.
+ architecture.select({
"arm64": [],
"x86_64": distribution.select({
"DEFAULT": [],
"arch": ["amd-ucode", "intel-ucode"],
"fedora": ["amd-ucode-firmware", "microcode_ctl"],
}),
})
# Only Arch's systemd package contains portablectl.
+ distribution.select({"DEFAULT": ["systemd-container"], "arch": []}),
ops = [
# systemd-confext mounts /etc read-only by default. This image writes /etc at boot, and
# login fails with "PAM failure, aborting" once a confext is merged.
image.ops.write_file("/usr/lib/systemd/confext.conf.d/10-mutable.conf", "[ConfExt]\nMutable=ephemeral\n"),
# The ESP partition of the layout copies /boot of the image. The disk therefore contains
# this file on its ESP.
image.ops.copy(":demo[sbom][spdx]", "/boot/metadata/demo.spdx.json"),
],
profiles = [
image.uki_profile(
id = "debug",
title = "Boot with debug logs enabled",
cmdline = ["debug", "systemd.log_level=debug", "systemd.journald.forward_to_console=1"],
),
],
# An absolute path names a BMP the image itself ships, here the one Arch's systemd package
# installs; the other distributions ship no BMP and embed none.
splash = distribution.select({"DEFAULT": None, "arch": "/usr/share/systemd/bootctl/splash-arch.bmp"}),
# A shipped appliance has no terminal to read a man page on.
install_docs = False,
# Ship the qcow2 with free space past its partitions, so a VM booting it can claim it without
# resizing the disk first. This image boots from tmpfs, so nothing here consumes it; a real
# appliance creates a root partition in it on first boot.
virtual_size = "2G",
)
image.vm(
name = "boot-demo-vm",
autologin = "root",
# A run-only kernel command line argument: the UKI carries a fixed one, so this can only arrive
# through the channel vmspawn picks for its trailing arguments. The smoke asserts it landed.
cmdline_extra = ["tine.smoke=1"],
credentials = {
"firstboot.locale": "C.UTF-8",
"firstboot.timezone": "UTC",
},
image = ":boot-demo",
extensions = [":demo-confext", ":demo-ext"],
portables = [":demo-portable", ":demo-portable-ext"],
)
# Boot smoke test: drives :boot-demo-vm's console over a pty (via the shared tine driver),
# asserts the system settles, then powers off. `buck test` runs it, `buck run` boots it by hand.
tests.vm_smoke(
name = "boot-demo-vm-smoke",
vm = ":boot-demo-vm",
checks = [
# The distribution that booted is the one that was asked for. A select reaches here through
# the alias the macro declares, which configures this target too. This reads the file the
# build produced rather than the `/etc` symlink to it: a `/usr`-only disk boots with a
# volatile root, and whether anything recreates that symlink is a property of the
# distribution's own systemd rather than of the image.
# Each family's os-release ID is the family's name.
distribution.select({name: "grep -q '^ID={}$' /usr/lib/os-release".format(name) for name in distribution.FAMILIES}),
"bootctl | grep -F 'Secure Boot: disabled (setup)'",
# The build compiled the hardware database into /usr, which is read-only here, so a guest
# that has it got it from the build rather than from a boot-time regeneration.
"test -s /usr/lib/udev/hwdb.bin",
# The runner's cmdline_extra reached the kernel the UKI booted.
"grep -q tine.smoke=1 /proc/cmdline",
# The initrd carries networkd for IMDS, which is inert on a non-cloud VM.
'[ -z "$(journalctl -b -q _RUNTIME_SCOPE=initrd _SYSTEMD_UNIT=systemd-networkd.service)" ]',
# systemd-sysext and systemd-confext merged the extensions the runner binds in at boot.
"test -x /usr/bin/zsh",
"test -f /etc/tine/from-confext",
"touch /etc/tine/written-after-merge",
# portabled checks the extension-release files of demo-portable-ext against the os-release
# of demo-portable.
"portablectl attach --now --runtime --extension demo-portable-ext demo-portable",
"portablectl is-attached --extension demo-portable-ext demo-portable",
"systemctl is-active demo-portable.service",
# Without demo-portable-ext, demo-portable.service fails. This shows that the files came
# from the extension.
"portablectl detach --now --runtime --extension demo-portable-ext demo-portable",
"portablectl attach --runtime demo-portable",
"! systemctl start demo-portable.service",
],
)
# The qcow2 and vmdk describe a disk of the image's virtual_size with the raw's content, while the raw stays
# as small as its partitions.
box.sh_test(
name = "virtual-size-test",
labels = ["image"],
box = "tine//distribution:tools.box",
script = """
raw=$1
shift
test "$(stat -c %s "$raw")" -lt $((900 << 20))
for disk in "$@"; do
test "$(qemu-img info --output=json "$disk" | python3 -c 'import json, sys; print(json.load(sys.stdin)["virtual-size"])')" = 2147483648
qemu-img compare -q -f raw "$raw" "$disk"
done
""",
args = ["$(location :boot-demo.fedora)", "$(location :boot-demo.fedora[qcow2])", "$(location :boot-demo.fedora[vmdk])"],
distro = _FEDORA,
)
# The AMI parameters register the disk for the image's architecture with a root volume of its virtual_size.
box.sh_test(
name = "ami-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = """
python3 -c '
import json, sys
register = json.load(open(sys.argv[1]))
assert register["Architecture"] == sys.argv[2], register
assert register["BlockDeviceMappings"][0]["Ebs"]["VolumeSize"] == 2, register
' "$@"
""",
args = ["$(location :boot-demo.fedora[ami])"] + architecture.select({name: [spellings.ec2] for name, spellings in architecture.ARCHITECTURES.items()}),
distro = _FEDORA,
)
# data-disk has no ESP partition, so image.disk builds it without systemd-boot, UKIs or an initrd.
image.disk(
name = "data-disk",
packages = ["bash"],
definitions = [
image.partition(
type = "root",
filesystem = "ext4",
copy_files = ["/"],
minimize = "guess",
),
],
)
box.sh_test(
name = "data-disk-test",
labels = ["image"],
box = "@tine//tools:dev.box",
test = "data-disk-test.sh",
args = ["$(location :data-disk.fedora)"],
distro = _FEDORA,
)
# demo-ext extends boot-demo. The DDI contains only the files that demo-ext adds to boot-demo,
# and its extension-release file pins the exact build of boot-demo. `boot-demo-vm` binds
# it into /var/lib/extensions.
image.sysext(
name = "demo-ext",
parent = ":boot-demo",
packages = ["zsh"],
install_docs = False,
# install a file from source tree; covers reproducibility check under varying umasks
ops = [image.ops.copy("from-source.txt", "/usr/lib/tine/from-source")],
)
# demo-confext contains only /etc. Its extension-release file pins the exact build of boot-demo,
# as the one of demo-ext does.
image.confext(
name = "demo-confext",
parent = ":boot-demo",
ops = [image.ops.write_file("/etc/tine/from-confext", "confext\n")],
)
# portablectl attaches only the units whose names start with the image name, so the service is
# called demo-portable.service.
image.portable(
name = "demo-portable",
packages = ["bash", "coreutils"],
install_docs = False,
# demo-portable-ext must not contain this directory, because only demo-portable declares it.
tmpfiles = ["d /etc/tine/from-parent-tmpfiles 0755 - -"],
ops = [image.ops.copy("demo-portable.service", "/usr/lib/systemd/system/demo-portable.service")],
)
# demo-portable.service fails unless it finds the files that demo-portable-ext adds to /usr and
# /etc. systemd merges only /usr, /opt and /etc of an extension, so the extension DDI does not
# contain the file that demo-portable-ext writes to /var. A portable service that needs a directory
# under /var must find it in the portable image.
image.extension(
name = "demo-portable-ext",
parent = ":demo-portable",
classes = ["sysext", "confext"],
scope = ["portable"],
ops = [
image.ops.write_file("/usr/lib/tine/from-portable-ext", "usr\n"),
image.ops.write_file("/etc/tine/from-portable-ext", "etc\n"),
image.ops.write_file("/var/lib/tine/from-portable-ext", "var\n"),
],
)
_PRELOADED_EXTENSIONS = [":demo-confext", ":demo-ext"]
_PRELOADED_PORTABLES = [":demo-portable", ":demo-portable-ext"]
# boot-demo with the demo DDIs on its ESP, where systemd-sysupdate would update them. Nothing merges or
# attaches them at boot yet: whoever instantiates the disk does that.
image.ddi_preloaded_disk(
name = "boot-demo-preloaded",
base = ":boot-demo",
extensions = _PRELOADED_EXTENSIONS,
portables = _PRELOADED_PORTABLES,
)
# boot-demo's published raw.zst and AMI parameters, the way a project that builds on a release of it
# imports them, and the same DDIs on its ESP.
image.imported_disk(
name = "boot-demo-imported",
ami = ":boot-demo[ami]",
disk = ":boot-demo[raw.zst]",
# As boot-demo's own, which a release would publish beside the disk.
virtual_size = "2G",
)
image.ddi_preloaded_disk(
name = "boot-demo-imported-preloaded",
base = ":boot-demo-imported",
extensions = _PRELOADED_EXTENSIONS,
portables = _PRELOADED_PORTABLES,
)
# The preloaded disk defaults to the virtual_size of the imported one.
box.sh_test(
name = "imported-virtual-size-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = """test "$(qemu-img info --output=json "$1" | jq '."virtual-size"')" = 2147483648""",
args = ["$(location :boot-demo-imported-preloaded.fedora[qcow2])"],
distro = _FEDORA,
)
image.vm(
name = "boot-demo-preloaded-vm",
autologin = "root",
credentials = {
"firstboot.locale": "C.UTF-8",
"firstboot.timezone": "UTC",
},
image = ":boot-demo-preloaded",
)
# The checks activate the DDIs on the ESP the way an instance would, see "image.ddi_preloaded_disk" in images.md.
# The ESP carries one version of each, so the links can name the .raw.v directories.
tests.vm_smoke(
name = "boot-demo-preloaded-vm-smoke",
vm = ":boot-demo-preloaded-vm",
checks = [
# The disk has boot-demo's virtual_size
'test "$(lsblk --nodeps --noheadings --bytes --output SIZE /dev/vda)" = 2147483648',
# The ESP is where systemd-gpt-auto-generator mounts it, which is /boot on this image.
'test -d "$(bootctl --print-esp-path)/extensions"',
"mkdir -p /run/extensions /run/confexts",
'ln -s "$(bootctl --print-esp-path)/extensions/demo-ext.raw.v" /run/extensions/',
'ln -s "$(bootctl --print-esp-path)/extensions/demo-confext.raw.v" /run/confexts/',
"systemd-sysext refresh",
"systemd-confext refresh",
# systemd-vpick picked each newest version out of its directory, and the extension-release of
# each matches boot-demo.
"test -x /usr/bin/zsh",
"test -f /etc/tine/from-confext",
'portablectl attach --now --runtime --extension "$(bootctl --print-esp-path)/portables/demo-portable-ext.raw.v" "$(bootctl --print-esp-path)/portables/demo-portable.raw.v"',
"systemctl is-active demo-portable.service",
],
)
# The preloaded disk registers with the AMI parameters of boot-demo, under its own name.
box.sh_test(
name = "preloaded-ami-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = """
python3 -c '
import json, sys
base, preloaded = (json.load(open(path)) for path in sys.argv[1:])
assert preloaded["Name"] == preloaded["Description"] == "boot-demo-preloaded", preloaded
names = ("Name", "Description")
assert {k: v for k, v in preloaded.items() if k not in names} == {k: v for k, v in base.items() if k not in names}, (base, preloaded)
' "$@"
""",
args = ["$(location :boot-demo.fedora[ami])", "$(location :boot-demo-preloaded.fedora[ami])"],
distro = _FEDORA,
)
# Preloading the imported raw.zst writes the same disk as preloading boot-demo itself, and registers it
# with the same AMI parameters, under its own name.
box.sh_test(
name = "imported-disk-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = """
python3 -c '
import hashlib, json, sys
disk, imported_disk, ami, imported_ami = sys.argv[1:]
digests = [hashlib.file_digest(open(path, "rb"), "sha256").hexdigest() for path in (disk, imported_disk)]
assert digests[0] == digests[1], digests
ami, imported_ami = json.load(open(ami)), json.load(open(imported_ami))
assert imported_ami["Name"] == "boot-demo-imported-preloaded", imported_ami
names = ("Name", "Description")
assert {k: v for k, v in ami.items() if k not in names} == {k: v for k, v in imported_ami.items() if k not in names}
' "$@"
""",
args = [
"$(location :boot-demo-preloaded.fedora)",
"$(location :boot-demo-imported-preloaded.fedora)",
"$(location :boot-demo-preloaded.fedora[ami])",
"$(location :boot-demo-imported-preloaded.fedora[ami])",
],
distro = _FEDORA,
)
# A file tree that differs per distribution is one copy whose source is a select, rather than a
# target per distribution and an alias choosing between them. A distribution the select names no
# tree for gets `None`, which copies nothing; only arch ships one so every other exercises that.
image.layer(
name = "tree.layer",
parent = ":base",
ops = [
image.ops.copy("tree/os", "/"),
image.ops.copy(distribution.select({"DEFAULT": None, "arch": "tree/os.arch"}), "/"),
],
)
image.archive(
name = "tree",
parent = ":tree.layer",
format = "tar",
)
# The test runs once per release of the catalog. It checks that the select picks the arch tree for
# Arch and no tree for the other releases.
box.sh_test(
name = "tree-test",
labels = ["image"],
box = "@tine//tools:dev.box",
test = "tree-test.sh",
args = ["$(location :tree)"] + distribution.select({d["release"]: [name if name == "arch" else "none"] for name, d in DISTRIBUTIONS.items()}),
)
# Replacing a populated directory exercises the opaque-dir marker.
image.layer(
name = "opaque-base",
packages = ["bash", "coreutils"],
ops = [
image.ops.run(["/usr/bin/bash", "-c", "mkdir -p /etc/tine/sub; echo old > /etc/tine/sub/old"], chroot = True),
],
)
image.layer(
name = "opaque-child.layer",
parent = ":opaque-base",
ops = [
image.ops.remove("/etc/tine/sub"),
image.ops.run(["/usr/bin/bash", "-c", "mkdir -p /etc/tine/sub; echo new > /etc/tine/sub/fresh"], chroot = True),
],
)
image.archive(
name = "opaque",
parent = ":opaque-child.layer",
format = "tar",
)
# The UKI's module selection still carries what the demo image boots through. The smoke proves the
# same thing by booting; this pins it against the real kernel without a VM, so a change to the
# default list that drops one fails here with the module named. Only modules Fedora builds as
# modules: it links dm-mod, ext4, virtio_blk, virtio_pci, ahci and sd_mod into the kernel.
box.sh_test(
name = "uki-modules-test",
labels = ["image"],
box = "@tine//tools:dev.box",
test = "@tine//tests:contains.sh",
args = ["$(location :boot-demo.fedora[uki][modules])"]
+ [
'"path": ".*/{}\\.ko'.format(module)
for module in ["erofs", "dm-verity", "ena", "loop", "overlay", "nvme", "vfat", "virtio_scsi", "virtio_net", "virtiofs"]
],
distro = _FEDORA,
)
# The file manifest is what tells a consumer which paths an image ships, so its framing and its
# view of a real tree are part of the contract rather than an implementation detail. The checker
# is a python file of its own rather than a script the test inlines, so the linters see it.
box.sh_test(
name = "manifest-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = 'python3 "$@"',
args = [
"$(location :manifest_check.py)",
"$(location :demo.fedora[manifest])",
],
distro = _FEDORA,
)
box.sh_test(
name = "boot-artifacts-test",
labels = ["image"],
box = "@tine//tools:dev.box",
test = "boot-artifacts.sh",
args = [
"$(location :boot-demo.fedora[boot][kernel])",
"$(location :boot-demo.fedora[boot][uki])",
"$(location :boot-demo.fedora[boot][initrd])",
"$(location :boot-demo.fedora[initrd])",
]
+ architecture.select({name: [name] for name in architecture.ARCHITECTURES}),
distro = _FEDORA,
)
# The splash the image ships reached the UKI. The image tools' box carries ukify, which lists a
# UKI's sections one per `.name:` line.
box.sh_test(
name = "uki-splash-test",
labels = ["image"],
box = "tine//distribution:tools.box",
script = 'test "$(ukify inspect "$1" | grep -cx ".splash:")" = "$2"',
args = ["$(location :boot-demo[boot][uki])"] + distribution.select({"DEFAULT": ["0"], "arch": ["1"]}),
)
# The microcode the image installs reached the UKI, which only an x86 kernel takes that way. The
# image tools' box carries the ukify the checker reads sections with.
box.sh_test(
name = "uki-microcode-test",
labels = ["image"],
box = "tine//distribution:tools.box",
test = "uki-microcode.sh",
args = ["$(location :boot-demo.fedora[boot][uki])"] + architecture.select({name: [name] for name in architecture.ARCHITECTURES}),
distro = _FEDORA,
)
# Once per distribution, because each package system captures its database in its own format.
box.sh_test(
name = "pkgdb-format-test",
labels = ["image"],
box = "@tine//tools:dev.box",
test = "pkgdb-format.sh",
args = ["$(location :demo[pkgdb])"] + distribution.select({"arch": ["tar.zst", "alpm"], "debian": ["tar.zst", "dpkg"], "fedora": ["sqlite", "rpm"]}),
)
# A DDI's file name is what a systemd-sysupdate transfer matches, so it is part of the contract
# rather than an implementation detail. The example sets no version, so this pins the rule's
# default alongside the shape; the architecture is spelled the way systemd's `%a` does.
box.sh_test(
name = "sysext-name-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = 'test "$(basename "$1")" = "$2" && test "$(basename "$3")" = "$4"',
args = ["$(location :demo-ext.fedora)"]
+ architecture.select({name: ["demo-ext_0_{}.sysext.raw".format(spellings.systemd)] for name, spellings in architecture.ARCHITECTURES.items()})
+ ["$(location :demo-confext.fedora)"]
+ architecture.select({name: ["demo-confext_0_{}.confext.raw".format(spellings.systemd)] for name, spellings in architecture.ARCHITECTURES.items()}),
distro = _FEDORA,
)
# portablectl takes the image name from the DDI's file name, up to the first "_", and matches the
# units it attaches against that name.
box.sh_test(
name = "portable-name-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = 'test "$(basename "$1")" = "$2" && test "$(basename "$3")" = "$4"',
args = ["$(location :demo-portable.fedora)"]
+ architecture.select({name: ["demo-portable_0_{}.raw".format(spellings.systemd)] for name, spellings in architecture.ARCHITECTURES.items()})
+ ["$(location :demo-portable-ext.fedora)"]
+ architecture.select({name: ["demo-portable-ext_0_{}.raw".format(spellings.systemd)] for name, spellings in architecture.ARCHITECTURES.items()}),
distro = _FEDORA,
)
# The portable image contains its os-release and no extension-release files. The extension contains
# extension-release files, no os-release, and none of the files of demo-portable.
box.sh_test(
name = "portable-manifest-test",
labels = ["image"],
box = "@tine//tools:dev.box",
test = "portable-manifest.sh",
args = [
"$(location :demo-portable.fedora[disk-manifest])",
"$(location :demo-portable-ext.fedora[disk-manifest])",
],
distro = _FEDORA,
)
# [NAME, DIGEST, PATH] for each target expected.json tracks, per architecture. Sorted, so the command line
# of reproducibility-test stays put when an entry is added and Buck reuses its cached run.
_reproducibility_targets = {
arch: [(name, digests[name]["sha256"], "$(location :{})".format(name)) for name in sorted(digests)] for arch, digests in _expected.items()
}
# Compare the current builds to the checksums in ./expected.json to validate reproducibility: between
# local developer machine and GitHub runner, at different times. Neither image has an external input
# such as a git-derived version, so a digest only moves when the catalog pins a package the image
# carries or a build rule changes what it writes. When one of those moved it legitimately, take over
# the digests a failure writes with `buck run tine//examples/image:refresh-expected` and commit them
# with the change that moved them.
box.sh_test(
name = "reproducibility-test",
labels = ["image"],
box = "@tine//tools:dev.box",
script = 'python3 "$@"',
# the architecture, where a failure writes its actual digests, then the targets
args = ["$(location :reproducibility_check.py)"]
+ architecture.select({
arch: [arch, "{}/actual.{}.json".format(package_name(), arch)] + [item for target in targets for item in target]
for arch, targets in _reproducibility_targets.items()
}),
distro = _FEDORA,
# expected.json records the digests of images that the catalog's default boxes build. The boxes of
# another release build the same images with other tools, which write different bytes.
target_compatible_with = []
if read_config("tine", "box") == None
else select({
"DEFAULT": select_incompatible("expected.json records the digests that the catalog's default boxes build"),
}),
)
python.binary(
name = "refresh-expected",
box = "@tine//tools:dev.box",
main = "expected.py",
deps = ["@tine//:util"],
)