1# this test creates a simple GNU image with docker tools and sees if it executes
2
3{ pkgs, ... }:
4let
5 # nixpkgs#214434: dockerTools.buildImage fails to unpack base images
6 # containing duplicate layers when those duplicate tarballs
7 # appear under the manifest's 'Layers'. Docker can generate images
8 # like this even though dockerTools does not.
9 repeatedLayerTestImage =
10 let
11 # Rootfs diffs for layers 1 and 2 are identical (and empty)
12 layer1 = pkgs.dockerTools.buildImage { name = "empty"; };
13 layer2 = layer1.overrideAttrs (_: {
14 fromImage = layer1;
15 });
16 repeatedRootfsDiffs =
17 pkgs.runCommand "image-with-links.tar"
18 {
19 nativeBuildInputs = [ pkgs.jq ];
20 }
21 ''
22 mkdir contents
23 tar -xf "${layer2}" -C contents
24 cd contents
25 first_rootfs=$(jq -r '.[0].Layers[0]' manifest.json)
26 second_rootfs=$(jq -r '.[0].Layers[1]' manifest.json)
27 target_rootfs=$(sha256sum "$first_rootfs" | cut -d' ' -f 1).tar
28
29 # Replace duplicated rootfs diffs with symlinks to one tarball
30 chmod -R ug+w .
31 mv "$first_rootfs" "$target_rootfs"
32 rm "$second_rootfs"
33 ln -s "../$target_rootfs" "$first_rootfs"
34 ln -s "../$target_rootfs" "$second_rootfs"
35
36 # Update manifest's layers to use the symlinks' target
37 cat manifest.json | \
38 jq ".[0].Layers[0] = \"$target_rootfs\"" |
39 jq ".[0].Layers[1] = \"$target_rootfs\"" > manifest.json.new
40 mv manifest.json.new manifest.json
41
42 tar --sort=name --hard-dereference -cf $out .
43 '';
44 in
45 pkgs.dockerTools.buildImage {
46 fromImage = repeatedRootfsDiffs;
47 name = "repeated-layer-test";
48 tag = "latest";
49 copyToRoot = pkgs.bash;
50 # A runAsRoot script is required to force previous layers to be unpacked
51 runAsRoot = ''
52 echo 'runAsRoot has run.'
53 '';
54 };
55
56 chownTestImage = pkgs.dockerTools.streamLayeredImage {
57 name = "chown-test";
58 tag = "latest";
59 enableFakechroot = true;
60 fakeRootCommands = ''
61 touch /testfile
62 chown 12345:12345 /testfile
63 '';
64 config.Cmd = [
65 "${pkgs.coreutils}/bin/stat"
66 "-c"
67 "%u:%g"
68 "/testfile"
69 ];
70 };
71
72 nonRootTestImage = pkgs.dockerTools.streamLayeredImage {
73 name = "non-root-test";
74 tag = "latest";
75 uid = 1000;
76 gid = 1000;
77 uname = "user";
78 gname = "user";
79 config = {
80 User = "user";
81 Cmd = [
82 "${pkgs.coreutils}/bin/stat"
83 "-c"
84 "%u:%g"
85 "${pkgs.coreutils}/bin/stat"
86 ];
87 };
88 };
89in
90{
91 name = "docker-tools";
92 meta = with pkgs.lib.maintainers; {
93 maintainers = [
94 lnl7
95 roberth
96 ];
97 };
98
99 nodes = {
100 docker =
101 { ... }:
102 {
103 virtualisation = {
104 diskSize = 3072;
105 docker.enable = true;
106 };
107 };
108 };
109
110 testScript = with pkgs.dockerTools; ''
111 unix_time_second1 = "1970-01-01T00:00:01Z"
112
113 docker.wait_for_unit("sockets.target")
114
115 with subtest("includeStorePath"):
116 with subtest("assumption"):
117 docker.succeed("${examples.helloOnRoot} | docker load")
118 docker.succeed("docker run --rm hello | grep -i hello")
119 docker.succeed("docker image rm hello:latest")
120
121 with subtest("includeStorePath = false; breaks example"):
122 docker.succeed("${examples.helloOnRootNoStore} | docker load")
123 docker.fail("docker run --rm hello | grep -i hello")
124 docker.succeed("docker image rm hello:latest")
125 with subtest("includeStorePath = false; breaks example (fakechroot)"):
126 docker.succeed("${examples.helloOnRootNoStoreFakechroot} | docker load")
127 docker.fail("docker run --rm hello | grep -i hello")
128 docker.succeed("docker image rm hello:latest")
129
130 with subtest("Ensure ZERO paths are added to the store"):
131 docker.fail("${examples.helloOnRootNoStore} | ${pkgs.crane}/bin/crane export - - | tar t | grep 'nix/store/'")
132 with subtest("Ensure ZERO paths are added to the store (fakechroot)"):
133 docker.fail("${examples.helloOnRootNoStoreFakechroot} | ${pkgs.crane}/bin/crane export - - | tar t | grep 'nix/store/'")
134
135 with subtest("includeStorePath = false; works with mounted store"):
136 docker.succeed("${examples.helloOnRootNoStore} | docker load")
137 docker.succeed("docker run --rm --volume ${builtins.storeDir}:${builtins.storeDir}:ro hello | grep -i hello")
138 docker.succeed("docker image rm hello:latest")
139 with subtest("includeStorePath = false; works with mounted store (fakechroot)"):
140 docker.succeed("${examples.helloOnRootNoStoreFakechroot} | docker load")
141 docker.succeed("docker run --rm --volume ${builtins.storeDir}:${builtins.storeDir}:ro hello | grep -i hello")
142 docker.succeed("docker image rm hello:latest")
143
144 with subtest("Ensure Docker images use a stable date by default"):
145 docker.succeed(
146 "docker load --input='${examples.bash}'"
147 )
148 assert unix_time_second1 in docker.succeed(
149 "docker inspect ${examples.bash.imageName} "
150 + "| ${pkgs.jq}/bin/jq -r .[].Created",
151 )
152
153 docker.succeed("docker run --rm ${examples.bash.imageName} bash --version")
154 # Check imageTag attribute matches image
155 docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.bash.imageTag}'")
156 docker.succeed("docker rmi ${examples.bash.imageName}")
157
158 # The remaining combinations
159 with subtest("Ensure imageTag attribute matches image"):
160 docker.succeed(
161 "docker load --input='${examples.bashNoTag}'"
162 )
163 docker.succeed(
164 "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTag.imageTag}'"
165 )
166 docker.succeed("docker rmi ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag}")
167
168 docker.succeed(
169 "docker load --input='${examples.bashNoTagLayered}'"
170 )
171 docker.succeed(
172 "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagLayered.imageTag}'"
173 )
174 docker.succeed("docker rmi ${examples.bashNoTagLayered.imageName}:${examples.bashNoTagLayered.imageTag}")
175
176 docker.succeed(
177 "${examples.bashNoTagStreamLayered} | docker load"
178 )
179 docker.succeed(
180 "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagStreamLayered.imageTag}'"
181 )
182 docker.succeed(
183 "docker rmi ${examples.bashNoTagStreamLayered.imageName}:${examples.bashNoTagStreamLayered.imageTag}"
184 )
185
186 docker.succeed(
187 "docker load --input='${examples.nixLayered}'"
188 )
189 docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.nixLayered.imageTag}'")
190 docker.succeed("docker rmi ${examples.nixLayered.imageName}")
191
192 with subtest("Check that images with alternative compression schemas load"):
193 docker.succeed(
194 "docker load --input='${examples.bashZstdCompressed}'",
195 "docker rmi ${examples.bashZstdCompressed.imageName}",
196 )
197 docker.succeed(
198 "docker load --input='${examples.bashUncompressed}'",
199 "docker rmi ${examples.bashUncompressed.imageName}",
200 )
201 docker.succeed(
202 "docker load --input='${examples.bashLayeredUncompressed}'",
203 "docker rmi ${examples.bashLayeredUncompressed.imageName}",
204 )
205 docker.succeed(
206 "docker load --input='${examples.bashLayeredZstdCompressed}'",
207 "docker rmi ${examples.bashLayeredZstdCompressed.imageName}",
208 )
209
210 with subtest(
211 "Check if the nix store is correctly initialized by listing "
212 "dependencies of the installed Nix binary"
213 ):
214 docker.succeed(
215 "docker load --input='${examples.nix}'",
216 "docker run --rm ${examples.nix.imageName} nix-store -qR ${pkgs.nix}",
217 "docker rmi ${examples.nix.imageName}",
218 )
219
220 with subtest(
221 "Ensure (layered) nix store has correct permissions "
222 "and that the container starts when its process does not have uid 0"
223 ):
224 docker.succeed(
225 "docker load --input='${examples.bashLayeredWithUser}'",
226 "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 755 == $(stat --format=%a /nix) && test 755 == $(stat --format=%a /nix/store)'",
227 "docker rmi ${examples.bashLayeredWithUser.imageName}",
228 )
229
230 with subtest("The nix binary symlinks are intact"):
231 docker.succeed(
232 "docker load --input='${examples.nix}'",
233 "docker run --rm ${examples.nix.imageName} ${pkgs.bash}/bin/bash -c 'test nix == $(readlink ${pkgs.nix}/bin/nix-daemon)'",
234 "docker rmi ${examples.nix.imageName}",
235 )
236
237 with subtest("The nix binary symlinks are intact when the image is layered"):
238 docker.succeed(
239 "docker load --input='${examples.nixLayered}'",
240 "docker run --rm ${examples.nixLayered.imageName} ${pkgs.bash}/bin/bash -c 'test nix == $(readlink ${pkgs.nix}/bin/nix-daemon)'",
241 "docker rmi ${examples.nixLayered.imageName}",
242 )
243
244 with subtest("The pullImage tool works"):
245 docker.succeed(
246 "docker load --input='${examples.testNixFromDockerHub}'",
247 "docker run --rm nix:2.2.1 nix-store --version",
248 "docker rmi nix:2.2.1",
249 )
250
251 with subtest("runAsRoot and entry point work"):
252 docker.succeed(
253 "docker load --input='${examples.nginx}'",
254 "docker run --name nginx -d -p 8000:80 ${examples.nginx.imageName}",
255 )
256 docker.wait_until_succeeds("curl -f http://localhost:8000/")
257 docker.succeed(
258 "docker rm --force nginx",
259 "docker rmi '${examples.nginx.imageName}'",
260 )
261
262 with subtest("A pulled image can be used as base image"):
263 docker.succeed(
264 "docker load --input='${examples.onTopOfPulledImage}'",
265 "docker run --rm ontopofpulledimage hello",
266 "docker rmi ontopofpulledimage",
267 )
268
269 with subtest("Regression test for issue #34779"):
270 docker.succeed(
271 "docker load --input='${examples.runAsRootExtraCommands}'",
272 "docker run --rm runasrootextracommands cat extraCommands",
273 "docker run --rm runasrootextracommands cat runAsRoot",
274 "docker rmi '${examples.runAsRootExtraCommands.imageName}'",
275 )
276
277 with subtest("Ensure Docker images can use an unstable date"):
278 docker.succeed(
279 "docker load --input='${examples.unstableDate}'"
280 )
281 assert unix_time_second1 not in docker.succeed(
282 "docker inspect ${examples.unstableDate.imageName} "
283 + "| ${pkgs.jq}/bin/jq -r .[].Created"
284 )
285
286 with subtest("Ensure Layered Docker images can use an unstable date"):
287 docker.succeed(
288 "docker load --input='${examples.unstableDateLayered}'"
289 )
290 assert unix_time_second1 not in docker.succeed(
291 "docker inspect ${examples.unstableDateLayered.imageName} "
292 + "| ${pkgs.jq}/bin/jq -r .[].Created"
293 )
294
295 with subtest("Ensure Layered Docker images work"):
296 docker.succeed(
297 "docker load --input='${examples.layered-image}'",
298 "docker run --rm ${examples.layered-image.imageName}",
299 "docker run --rm ${examples.layered-image.imageName} cat extraCommands",
300 )
301
302 with subtest("Ensure images built on top of layered Docker images work"):
303 docker.succeed(
304 "docker load --input='${examples.layered-on-top}'",
305 "docker run --rm ${examples.layered-on-top.imageName}",
306 )
307
308 with subtest("Ensure layered images built on top of layered Docker images work"):
309 docker.succeed(
310 "docker load --input='${examples.layered-on-top-layered}'",
311 "docker run --rm ${examples.layered-on-top-layered.imageName}",
312 )
313
314
315 def set_of_layers(image_name):
316 return set(
317 docker.succeed(
318 f"docker inspect {image_name} "
319 + "| ${pkgs.jq}/bin/jq -r '.[] | .RootFS.Layers | .[]'"
320 ).split()
321 )
322
323
324 with subtest("Ensure layers are shared between images"):
325 docker.succeed(
326 "docker load --input='${examples.another-layered-image}'"
327 )
328 layers1 = set_of_layers("${examples.layered-image.imageName}")
329 layers2 = set_of_layers("${examples.another-layered-image.imageName}")
330 assert bool(layers1 & layers2)
331
332 with subtest("Ensure order of layers is correct"):
333 docker.succeed(
334 "docker load --input='${examples.layersOrder}'"
335 )
336
337 for index in 1, 2, 3:
338 assert f"layer{index}" in docker.succeed(
339 f"docker run --rm ${examples.layersOrder.imageName} cat /tmp/layer{index}"
340 )
341
342 with subtest("Ensure layers unpacked in correct order before runAsRoot runs"):
343 assert "abc" in docker.succeed(
344 "docker load --input='${examples.layersUnpackOrder}'",
345 "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order"
346 )
347
348 with subtest("Ensure repeated base layers handled by buildImage"):
349 docker.succeed(
350 "docker load --input='${repeatedLayerTestImage}'",
351 "docker run --rm ${repeatedLayerTestImage.imageName} /bin/bash -c 'exit 0'"
352 )
353
354 with subtest("Ensure environment variables are correctly inherited"):
355 docker.succeed(
356 "docker load --input='${examples.environmentVariables}'"
357 )
358 out = docker.succeed("docker run --rm ${examples.environmentVariables.imageName} env")
359 env = out.splitlines()
360 assert "FROM_PARENT=true" in env, "envvars from the parent should be preserved"
361 assert "FROM_CHILD=true" in env, "envvars from the child should be preserved"
362 assert "LAST_LAYER=child" in env, "envvars from the child should take priority"
363
364 with subtest("Ensure environment variables of layered images are correctly inherited"):
365 docker.succeed(
366 "docker load --input='${examples.environmentVariablesLayered}'"
367 )
368 out = docker.succeed("docker run --rm ${examples.environmentVariablesLayered.imageName} env")
369 env = out.splitlines()
370 assert "FROM_PARENT=true" in env, "envvars from the parent should be preserved"
371 assert "FROM_CHILD=true" in env, "envvars from the child should be preserved"
372 assert "LAST_LAYER=child" in env, "envvars from the child should take priority"
373
374 with subtest(
375 "Ensure inherited environment variables of layered images are correctly resolved"
376 ):
377 # Read environment variables as stored in image config
378 config = docker.succeed(
379 "tar -xOf ${examples.environmentVariablesLayered} manifest.json | ${pkgs.jq}/bin/jq -r .[].Config"
380 ).strip()
381 out = docker.succeed(
382 f"tar -xOf ${examples.environmentVariablesLayered} {config} | ${pkgs.jq}/bin/jq -r '.config.Env | .[]'"
383 )
384 env = out.splitlines()
385 assert (
386 sum(entry.startswith("LAST_LAYER") for entry in env) == 1
387 ), "envvars overridden by child should be unique"
388
389 with subtest("Ensure image with only 2 layers can be loaded"):
390 docker.succeed(
391 "docker load --input='${examples.two-layered-image}'"
392 )
393
394 with subtest(
395 "Ensure the bulk layer doesn't miss store paths (regression test for #78744)"
396 ):
397 docker.succeed(
398 "docker load --input='${pkgs.dockerTools.examples.bulk-layer}'",
399 # Ensure the two output paths (ls and hello) are in the layer
400 "docker run bulk-layer ls /bin/hello",
401 )
402
403 with subtest(
404 "Ensure the bulk layer with a base image respects the number of maxLayers"
405 ):
406 docker.succeed(
407 "docker load --input='${pkgs.dockerTools.examples.layered-bulk-layer}'",
408 # Ensure the image runs correctly
409 "docker run layered-bulk-layer ls /bin/hello",
410 )
411
412 # Ensure the image has the correct number of layers
413 assert len(set_of_layers("layered-bulk-layer")) == 4
414
415 with subtest("Ensure only minimal paths are added to the store"):
416 # TODO: make an example that has no store paths, for example by making
417 # busybox non-self-referential.
418
419 # This check tests that buildLayeredImage can build images that don't need a store.
420 docker.succeed(
421 "docker load --input='${pkgs.dockerTools.examples.no-store-paths}'"
422 )
423
424 docker.succeed("docker run --rm no-store-paths ls / >/dev/console")
425
426 # If busybox isn't self-referential, we need this line
427 # docker.fail("docker run --rm no-store-paths ls /nix/store >/dev/console")
428 # However, it currently is self-referential, so we check that it is the
429 # only store path.
430 docker.succeed("diff <(docker run --rm no-store-paths ls /nix/store) <(basename ${pkgs.pkgsStatic.busybox}) >/dev/console")
431
432 with subtest("Ensure buildLayeredImage does not change store path contents."):
433 docker.succeed(
434 "docker load --input='${pkgs.dockerTools.examples.filesInStore}'",
435 "docker run --rm file-in-store nix-store --verify --check-contents",
436 "docker run --rm file-in-store |& grep 'some data'",
437 )
438
439 with subtest("Ensure cross compiled image can be loaded and has correct arch."):
440 docker.succeed(
441 "docker load --input='${pkgs.dockerTools.examples.cross}'",
442 )
443 assert (
444 docker.succeed(
445 "docker inspect ${pkgs.dockerTools.examples.cross.imageName} "
446 + "| ${pkgs.jq}/bin/jq -r .[].Architecture"
447 ).strip()
448 == "${if pkgs.stdenv.hostPlatform.system == "aarch64-linux" then "amd64" else "arm64"}"
449 )
450
451 with subtest("buildLayeredImage doesn't dereference /nix/store symlink layers"):
452 docker.succeed(
453 "docker load --input='${examples.layeredStoreSymlink}'",
454 "docker run --rm ${examples.layeredStoreSymlink.imageName} bash -c 'test -L ${examples.layeredStoreSymlink.passthru.symlink}'",
455 "docker rmi ${examples.layeredStoreSymlink.imageName}",
456 )
457
458 with subtest("buildImage supports registry/ prefix in image name"):
459 docker.succeed(
460 "docker load --input='${examples.prefixedImage}'"
461 )
462 docker.succeed(
463 "docker images --format '{{.Repository}}' | grep -F '${examples.prefixedImage.imageName}'"
464 )
465
466 with subtest("buildLayeredImage supports registry/ prefix in image name"):
467 docker.succeed(
468 "docker load --input='${examples.prefixedLayeredImage}'"
469 )
470 docker.succeed(
471 "docker images --format '{{.Repository}}' | grep -F '${examples.prefixedLayeredImage.imageName}'"
472 )
473
474 with subtest("buildLayeredImage supports running chown with fakeRootCommands"):
475 docker.succeed(
476 "docker load --input='${examples.layeredImageWithFakeRootCommands}'"
477 )
478 docker.succeed(
479 "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/alice | grep -E ^1000$'"
480 )
481
482 with subtest("Ensure docker load on merged images loads all of the constituent images"):
483 docker.succeed(
484 "docker load --input='${examples.mergedBashAndRedis}'"
485 )
486 docker.succeed(
487 "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.bash.imageName}-${examples.bash.imageTag}'"
488 )
489 docker.succeed(
490 "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.redis.imageName}-${examples.redis.imageTag}'"
491 )
492 docker.succeed("docker run --rm ${examples.bash.imageName} bash --version")
493 docker.succeed("docker run --rm ${examples.redis.imageName} redis-cli --version")
494 docker.succeed("docker rmi ${examples.bash.imageName}")
495 docker.succeed("docker rmi ${examples.redis.imageName}")
496
497 with subtest(
498 "Ensure docker load on merged images loads all of the constituent images (missing tags)"
499 ):
500 docker.succeed(
501 "docker load --input='${examples.mergedBashNoTagAndRedis}'"
502 )
503 docker.succeed(
504 "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.bashNoTag.imageName}-${examples.bashNoTag.imageTag}'"
505 )
506 docker.succeed(
507 "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.redis.imageName}-${examples.redis.imageTag}'"
508 )
509 # we need to explicitly specify the generated tag here
510 docker.succeed(
511 "docker run --rm ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag} bash --version"
512 )
513 docker.succeed("docker run --rm ${examples.redis.imageName} redis-cli --version")
514 docker.succeed("docker rmi ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag}")
515 docker.succeed("docker rmi ${examples.redis.imageName}")
516
517 with subtest("mergeImages preserves owners of the original images"):
518 docker.succeed(
519 "docker load --input='${examples.mergedBashFakeRoot}'"
520 )
521 docker.succeed(
522 "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/alice | grep -E ^1000$'"
523 )
524
525 with subtest("The image contains store paths referenced by the fakeRootCommands output"):
526 docker.succeed(
527 "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} /hello/bin/layeredImageWithFakeRootCommands-hello"
528 )
529
530 with subtest("mergeImage correctly deals with varying compression schemas in inputs"):
531 docker.succeed("docker load --input='${examples.mergeVaryingCompressor}'")
532
533 for sub_image, tag in [
534 ("${examples.redis.imageName}", "${examples.redis.imageTag}"),
535 ("${examples.bashUncompressed.imageName}", "${examples.bashUncompressed.imageTag}"),
536 ("${examples.bashZstdCompressed.imageName}", "${examples.bashZstdCompressed.imageTag}"),
537 ]:
538 docker.succeed(f"docker images --format '{{{{.Repository}}}}-{{{{.Tag}}}}' | grep -F '{sub_image}-{tag}'")
539 docker.succeed(f"docker rmi {sub_image}")
540
541
542 with subtest("exportImage produces a valid tarball"):
543 docker.succeed(
544 "tar -tf ${examples.exportBash} | grep '\./bin/bash' > /dev/null"
545 )
546
547 with subtest("layered image fakeRootCommands with fakechroot works"):
548 docker.succeed("${examples.imageViaFakeChroot} | docker load")
549 docker.succeed("docker run --rm image-via-fake-chroot | grep -i hello")
550 docker.succeed("docker image rm image-via-fake-chroot:latest")
551
552 with subtest("Ensure bare paths in contents are loaded correctly"):
553 docker.succeed(
554 "docker load --input='${examples.build-image-with-path}'",
555 "docker run --rm build-image-with-path bash -c '[[ -e /hello.txt ]]'",
556 "docker rmi build-image-with-path",
557 )
558 docker.succeed(
559 "${examples.layered-image-with-path} | docker load",
560 "docker run --rm layered-image-with-path bash -c '[[ -e /hello.txt ]]'",
561 "docker rmi layered-image-with-path",
562 )
563
564 with subtest("Ensure correct architecture is present in manifests."):
565 docker.succeed("""
566 docker load --input='${examples.build-image-with-architecture}'
567 docker inspect build-image-with-architecture \
568 | ${pkgs.jq}/bin/jq -er '.[] | select(.Architecture=="arm64").Architecture'
569 docker rmi build-image-with-architecture
570 """)
571 docker.succeed("""
572 ${examples.layered-image-with-architecture} | docker load
573 docker inspect layered-image-with-architecture \
574 | ${pkgs.jq}/bin/jq -er '.[] | select(.Architecture=="arm64").Architecture'
575 docker rmi layered-image-with-architecture
576 """)
577
578 with subtest("etc"):
579 docker.succeed("${examples.etc} | docker load")
580 docker.succeed("docker run --rm etc | grep localhost")
581 docker.succeed("docker image rm etc:latest")
582
583 with subtest("image-with-certs"):
584 docker.succeed("<${examples.image-with-certs} docker load")
585 docker.succeed("docker run --rm image-with-certs:latest test -r /etc/ssl/certs/ca-bundle.crt")
586 docker.succeed("docker run --rm image-with-certs:latest test -r /etc/ssl/certs/ca-certificates.crt")
587 docker.succeed("docker run --rm image-with-certs:latest test -r /etc/pki/tls/certs/ca-bundle.crt")
588 docker.succeed("docker image rm image-with-certs:latest")
589
590 with subtest("streamLayeredImage: chown is persistent in fakeRootCommands"):
591 docker.succeed(
592 "${chownTestImage} | docker load",
593 "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)"
594 )
595
596 with subtest("streamLayeredImage: with non-root user"):
597 docker.succeed(
598 "${nonRootTestImage} | docker load",
599 "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)"
600 )
601 '';
602}