Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1{
2 bashInteractive,
3 buildPackages,
4 cacert,
5 callPackage,
6 closureInfo,
7 coreutils,
8 devShellTools,
9 e2fsprogs,
10 proot,
11 fakeNss,
12 fakeroot,
13 file,
14 go,
15 jq,
16 jshon,
17 lib,
18 makeWrapper,
19 moreutils,
20 nix,
21 nixosTests,
22 pigz,
23 rsync,
24 runCommand,
25 runtimeShell,
26 shadow,
27 skopeo,
28 stdenv,
29 storeDir ? builtins.storeDir,
30 symlinkJoin,
31 tarsum,
32 util-linux,
33 vmTools,
34 writeClosure,
35 writeScript,
36 writeShellScriptBin,
37 writeText,
38 writeTextDir,
39 writePython3,
40 zstd,
41}:
42
43let
44 inherit (lib)
45 optionals
46 optionalString
47 ;
48
49 inherit (lib)
50 escapeShellArgs
51 toList
52 ;
53
54 inherit (devShellTools)
55 valueToString
56 ;
57
58 mkDbExtraCommand =
59 contents:
60 let
61 contentsList = if builtins.isList contents then contents else [ contents ];
62 in
63 ''
64 echo "Generating the nix database..."
65 echo "Warning: only the database of the deepest Nix layer is loaded."
66 echo " If you want to use nix commands in the container, it would"
67 echo " be better to only have one layer that contains a nix store."
68
69 export NIX_REMOTE=local?root=$PWD
70 # A user is required by nix
71 # https://github.com/NixOS/nix/blob/9348f9291e5d9e4ba3c4347ea1b235640f54fd79/src/libutil/util.cc#L478
72 export USER=nobody
73 ${buildPackages.nix}/bin/nix-store --load-db < ${
74 closureInfo { rootPaths = contentsList; }
75 }/registration
76 # Reset registration times to make the image reproducible
77 ${buildPackages.sqlite}/bin/sqlite3 nix/var/nix/db/db.sqlite "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
78
79 mkdir -p nix/var/nix/gcroots/docker/
80 for i in ${lib.concatStringsSep " " contentsList}; do
81 ln -s $i nix/var/nix/gcroots/docker/$(basename $i)
82 done;
83 '';
84
85 # The OCI Image specification recommends that configurations use values listed
86 # in the Go Language document for GOARCH.
87 # Reference: https://github.com/opencontainers/image-spec/blob/master/config.md#properties
88 # For the mapping from Nixpkgs system parameters to GOARCH, we can reuse the
89 # mapping from the go package.
90 defaultArchitecture = go.GOARCH;
91
92 compressors = {
93 none = {
94 ext = "";
95 nativeInputs = [ ];
96 compress = "cat";
97 decompress = "cat";
98 };
99 gz = {
100 ext = ".gz";
101 nativeInputs = [ pigz ];
102 compress = "pigz -p$NIX_BUILD_CORES -nTR";
103 decompress = "pigz -d -p$NIX_BUILD_CORES";
104 };
105 zstd = {
106 ext = ".zst";
107 nativeInputs = [ zstd ];
108 compress = "zstd -T$NIX_BUILD_CORES";
109 decompress = "zstd -d -T$NIX_BUILD_CORES";
110 };
111 };
112
113 compressorForImage =
114 compressor: imageName:
115 compressors.${compressor}
116 or (throw "in docker image ${imageName}: compressor must be one of: [${toString builtins.attrNames compressors}]");
117
118in
119rec {
120 examples = callPackage ./examples.nix {
121 inherit
122 buildImage
123 buildLayeredImage
124 fakeNss
125 pullImage
126 shadowSetup
127 buildImageWithNixDb
128 streamNixShellImage
129 ;
130 };
131
132 tests = {
133 inherit (nixosTests)
134 docker-tools
135 docker-tools-overlay
136 # requires remote builder
137 # docker-tools-cross
138 ;
139 };
140
141 pullImage =
142 let
143 fixName = name: builtins.replaceStrings [ "/" ":" ] [ "-" "-" ] name;
144 in
145 lib.fetchers.withNormalizedHash { } (
146 {
147 imageName,
148 # To find the digest of an image, you can use skopeo:
149 # see doc/functions.xml
150 imageDigest,
151 outputHash,
152 outputHashAlgo,
153 os ? "linux",
154 # Image architecture, defaults to the architecture of the `hostPlatform` when unset
155 arch ? defaultArchitecture,
156 # This is used to set name to the pulled image
157 finalImageName ? imageName,
158 # This used to set a tag to the pulled image
159 finalImageTag ? "latest",
160 # This is used to disable TLS certificate verification, allowing access to http registries on (hopefully) trusted networks
161 tlsVerify ? true,
162
163 name ? fixName "docker-image-${finalImageName}-${finalImageTag}.tar",
164 }:
165
166 runCommand name
167 {
168 inherit imageDigest;
169 imageName = finalImageName;
170 imageTag = finalImageTag;
171 impureEnvVars = lib.fetchers.proxyImpureEnvVars;
172
173 inherit outputHash outputHashAlgo;
174 outputHashMode = "flat";
175
176 nativeBuildInputs = [ skopeo ];
177 SSL_CERT_FILE = "${cacert.out}/etc/ssl/certs/ca-bundle.crt";
178
179 sourceURL = "docker://${imageName}@${imageDigest}";
180 destNameTag = "${finalImageName}:${finalImageTag}";
181 }
182 ''
183 skopeo \
184 --insecure-policy \
185 --tmpdir=$TMPDIR \
186 --override-os ${os} \
187 --override-arch ${arch} \
188 copy \
189 --src-tls-verify=${lib.boolToString tlsVerify} \
190 "$sourceURL" "docker-archive://$out:$destNameTag" \
191 | cat # pipe through cat to force-disable progress bar
192 ''
193 );
194
195 # We need to sum layer.tar, not a directory, hence tarsum instead of nix-hash.
196 # And we cannot untar it, because then we cannot preserve permissions etc.
197 inherit tarsum; # pkgs.dockerTools.tarsum
198
199 # buildEnv creates symlinks to dirs, which is hard to edit inside the overlay VM
200 mergeDrvs =
201 {
202 derivations,
203 onlyDeps ? false,
204 }:
205 runCommand "merge-drvs"
206 {
207 inherit derivations onlyDeps;
208 }
209 ''
210 if [[ -n "$onlyDeps" ]]; then
211 echo $derivations > $out
212 exit 0
213 fi
214
215 mkdir $out
216 for derivation in $derivations; do
217 echo "Merging $derivation..."
218 if [[ -d "$derivation" ]]; then
219 # If it's a directory, copy all of its contents into $out.
220 cp -drf --preserve=mode -f $derivation/* $out/
221 else
222 # Otherwise treat the derivation as a tarball and extract it
223 # into $out.
224 tar -C $out -xpf $drv || true
225 fi
226 done
227 '';
228
229 # Helper for setting up the base files for managing users and
230 # groups, only if such files don't exist already. It is suitable for
231 # being used in a runAsRoot script.
232 shadowSetup = ''
233 export PATH=${shadow}/bin:$PATH
234 mkdir -p /etc/pam.d
235 if [[ ! -f /etc/passwd ]]; then
236 echo "root:x:0:0::/root:${runtimeShell}" > /etc/passwd
237 echo "root:!x:::::::" > /etc/shadow
238 fi
239 if [[ ! -f /etc/group ]]; then
240 echo "root:x:0:" > /etc/group
241 echo "root:x::" > /etc/gshadow
242 fi
243 if [[ ! -f /etc/pam.d/other ]]; then
244 cat > /etc/pam.d/other <<EOF
245 account sufficient pam_unix.so
246 auth sufficient pam_rootok.so
247 password requisite pam_unix.so nullok yescrypt
248 session required pam_unix.so
249 EOF
250 fi
251 if [[ ! -f /etc/login.defs ]]; then
252 touch /etc/login.defs
253 fi
254 '';
255
256 # Run commands in a virtual machine.
257 runWithOverlay =
258 {
259 name,
260 fromImage ? null,
261 fromImageName ? null,
262 fromImageTag ? null,
263 diskSize ? 1024,
264 buildVMMemorySize ? 512,
265 preMount ? "",
266 postMount ? "",
267 postUmount ? "",
268 }:
269 vmTools.runInLinuxVM (
270 runCommand name
271 {
272 preVM = vmTools.createEmptyImage {
273 size = diskSize;
274 fullName = "docker-run-disk";
275 destination = "./image";
276 };
277 inherit fromImage fromImageName fromImageTag;
278 memSize = buildVMMemorySize;
279
280 nativeBuildInputs = [
281 util-linux
282 e2fsprogs
283 jshon
284 rsync
285 jq
286 ];
287 }
288 ''
289 mkdir disk
290 mkfs /dev/${vmTools.hd}
291 mount /dev/${vmTools.hd} disk
292 cd disk
293
294 function dedup() {
295 declare -A seen
296 while read ln; do
297 if [[ -z "''${seen["$ln"]:-}" ]]; then
298 echo "$ln"; seen["$ln"]=1
299 fi
300 done
301 }
302
303 if [[ -n "$fromImage" ]]; then
304 echo "Unpacking base image..."
305 mkdir image
306 tar -C image -xpf "$fromImage"
307
308 if [[ -n "$fromImageName" ]] && [[ -n "$fromImageTag" ]]; then
309 parentID="$(
310 cat "image/manifest.json" |
311 jq -r '.[] | select(.RepoTags | contains([$desiredTag])) | rtrimstr(".json")' \
312 --arg desiredTag "$fromImageName:$fromImageTag"
313 )"
314 else
315 echo "From-image name or tag wasn't set. Reading the first ID."
316 parentID="$(cat "image/manifest.json" | jq -r '.[0].Config | rtrimstr(".json")')"
317 fi
318
319 # In case of repeated layers, unpack only the last occurrence of each
320 cat ./image/manifest.json | jq -r '.[0].Layers | .[]' | tac | dedup | tac > layer-list
321 else
322 touch layer-list
323 fi
324
325 # Unpack all of the parent layers into the image.
326 lowerdir=""
327 extractionID=0
328 for layerTar in $(cat layer-list); do
329 echo "Unpacking layer $layerTar"
330 extractionID=$((extractionID + 1))
331
332 mkdir -p image/$extractionID/layer
333 tar -C image/$extractionID/layer -xpf image/$layerTar
334 rm image/$layerTar
335
336 find image/$extractionID/layer -name ".wh.*" -exec bash -c 'name="$(basename {}|sed "s/^.wh.//")"; mknod "$(dirname {})/$name" c 0 0; rm {}' \;
337
338 # Get the next lower directory and continue the loop.
339 lowerdir=image/$extractionID/layer''${lowerdir:+:}$lowerdir
340 done
341
342 mkdir work
343 mkdir layer
344 mkdir mnt
345
346 ${lib.optionalString (preMount != "") ''
347 # Execute pre-mount steps
348 echo "Executing pre-mount steps..."
349 ${preMount}
350 ''}
351
352 if [ -n "$lowerdir" ]; then
353 mount -t overlay overlay -olowerdir=$lowerdir,workdir=work,upperdir=layer mnt
354 else
355 mount --bind layer mnt
356 fi
357
358 ${lib.optionalString (postMount != "") ''
359 # Execute post-mount steps
360 echo "Executing post-mount steps..."
361 ${postMount}
362 ''}
363
364 umount mnt
365
366 (
367 cd layer
368 cmd='name="$(basename {})"; touch "$(dirname {})/.wh.$name"; rm "{}"'
369 find . -type c -exec bash -c "$cmd" \;
370 )
371
372 ${postUmount}
373 ''
374 );
375
376 exportImage =
377 {
378 name ? fromImage.name,
379 fromImage,
380 fromImageName ? null,
381 fromImageTag ? null,
382 diskSize ? 1024,
383 }:
384 runWithOverlay {
385 inherit
386 name
387 fromImage
388 fromImageName
389 fromImageTag
390 diskSize
391 ;
392
393 postMount = ''
394 echo "Packing raw image..."
395 mkdir -p $out
396 tar -C mnt --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" -cf $out/layer.tar .
397 '';
398
399 postUmount = ''
400 mv $out/layer.tar .
401 rm -rf $out
402 mv layer.tar $out
403 '';
404 };
405
406 # Create an executable shell script which has the coreutils in its
407 # PATH. Since root scripts are executed in a blank environment, even
408 # things like `ls` or `echo` will be missing.
409 shellScript =
410 name: text:
411 writeScript name ''
412 #!${runtimeShell}
413 set -e
414 export PATH=${coreutils}/bin:/bin
415 ${text}
416 '';
417
418 # Create a "layer" (set of files).
419 mkPureLayer =
420 {
421 # Name of the layer
422 name,
423 # JSON containing configuration and metadata for this layer.
424 baseJson,
425 # Files to add to the layer.
426 copyToRoot ? null,
427 # When copying the contents into the image, preserve symlinks to
428 # directories (see `rsync -K`). Otherwise, transform those symlinks
429 # into directories.
430 keepContentsDirlinks ? false,
431 # Additional commands to run on the layer before it is tar'd up.
432 extraCommands ? "",
433 uid ? 0,
434 gid ? 0,
435 }:
436 runCommand "docker-layer-${name}"
437 {
438 inherit baseJson extraCommands;
439 contents = copyToRoot;
440 nativeBuildInputs = [
441 jshon
442 rsync
443 tarsum
444 ];
445 }
446 ''
447 mkdir layer
448 if [[ -n "$contents" ]]; then
449 echo "Adding contents..."
450 for item in $contents; do
451 echo "Adding $item"
452 rsync -a${if keepContentsDirlinks then "K" else "k"} --chown=0:0 $item/ layer/
453 done
454 else
455 echo "No contents to add to layer."
456 fi
457
458 chmod ug+w layer
459
460 if [[ -n "$extraCommands" ]]; then
461 (cd layer; eval "$extraCommands")
462 fi
463
464 # Tar up the layer and throw it into 'layer.tar'.
465 echo "Packing layer..."
466 mkdir $out
467 tarhash=$(tar -C layer --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=${toString uid} --group=${toString gid} -cf - . | tee -p $out/layer.tar | tarsum)
468
469 # Add a 'checksum' field to the JSON, with the value set to the
470 # checksum of the tarball.
471 cat ${baseJson} | jshon -s "$tarhash" -i checksum > $out/json
472
473 # Indicate to docker that we're using schema version 1.0.
474 echo -n "1.0" > $out/VERSION
475
476 echo "Finished building layer '${name}'"
477 '';
478
479 # Make a "root" layer; required if we need to execute commands as a
480 # privileged user on the image. The commands themselves will be
481 # performed in a virtual machine sandbox.
482 mkRootLayer =
483 {
484 # Name of the image.
485 name,
486 # Script to run as root. Bash.
487 runAsRoot,
488 # Files to add to the layer. If null, an empty layer will be created.
489 # To add packages to /bin, use `buildEnv` or similar.
490 copyToRoot ? null,
491 # When copying the contents into the image, preserve symlinks to
492 # directories (see `rsync -K`). Otherwise, transform those symlinks
493 # into directories.
494 keepContentsDirlinks ? false,
495 # JSON containing configuration and metadata for this layer.
496 baseJson,
497 # Existing image onto which to append the new layer.
498 fromImage ? null,
499 # Name of the image we're appending onto.
500 fromImageName ? null,
501 # Tag of the image we're appending onto.
502 fromImageTag ? null,
503 # How much disk to allocate for the temporary virtual machine.
504 diskSize ? 1024,
505 # How much memory to allocate for the temporary virtual machine.
506 buildVMMemorySize ? 512,
507 # Commands (bash) to run on the layer; these do not require sudo.
508 extraCommands ? "",
509 }:
510 # Generate an executable script from the `runAsRoot` text.
511 let
512 runAsRootScript = shellScript "run-as-root.sh" runAsRoot;
513 extraCommandsScript = shellScript "extra-commands.sh" extraCommands;
514 in
515 runWithOverlay {
516 name = "docker-layer-${name}";
517
518 inherit
519 fromImage
520 fromImageName
521 fromImageTag
522 diskSize
523 buildVMMemorySize
524 ;
525
526 preMount = lib.optionalString (copyToRoot != null && copyToRoot != [ ]) ''
527 echo "Adding contents..."
528 for item in ${escapeShellArgs (map (c: "${c}") (toList copyToRoot))}; do
529 echo "Adding $item..."
530 rsync -a${if keepContentsDirlinks then "K" else "k"} --chown=0:0 $item/ layer/
531 done
532
533 chmod ug+w layer
534 '';
535
536 postMount = ''
537 mkdir -p mnt/{dev,proc,sys,tmp} mnt${storeDir}
538
539 # Mount /dev, /sys and the nix store as shared folders.
540 mount --rbind /dev mnt/dev
541 mount --rbind /sys mnt/sys
542 mount --rbind ${storeDir} mnt${storeDir}
543
544 # Execute the run as root script. See 'man unshare' for
545 # details on what's going on here; basically this command
546 # means that the runAsRootScript will be executed in a nearly
547 # completely isolated environment.
548 #
549 # Ideally we would use --mount-proc=mnt/proc or similar, but this
550 # doesn't work. The workaround is to setup proc after unshare.
551 # See: https://github.com/karelzak/util-linux/issues/648
552 unshare -imnpuf --mount-proc sh -c 'mount --rbind /proc mnt/proc && chroot mnt ${runAsRootScript}'
553
554 # Unmount directories and remove them.
555 umount -R mnt/dev mnt/sys mnt${storeDir}
556 rmdir --ignore-fail-on-non-empty \
557 mnt/dev mnt/proc mnt/sys mnt${storeDir} \
558 mnt$(dirname ${storeDir})
559 '';
560
561 postUmount = ''
562 (cd layer; ${extraCommandsScript})
563
564 echo "Packing layer..."
565 mkdir -p $out
566 tarhash=$(tar -C layer --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" -cf - . |
567 tee -p $out/layer.tar |
568 ${tarsum}/bin/tarsum)
569
570 cat ${baseJson} | jshon -s "$tarhash" -i checksum > $out/json
571 # Indicate to docker that we're using schema version 1.0.
572 echo -n "1.0" > $out/VERSION
573
574 echo "Finished building layer '${name}'"
575 '';
576 };
577
578 buildLayeredImage = lib.makeOverridable (
579 {
580 name,
581 compressor ? "gz",
582 ...
583 }@args:
584 let
585 stream = streamLayeredImage (builtins.removeAttrs args [ "compressor" ]);
586 compress = compressorForImage compressor name;
587 in
588 runCommand "${baseNameOf name}.tar${compress.ext}" {
589 inherit (stream) imageName;
590 passthru = {
591 inherit (stream) imageTag;
592 inherit stream;
593 };
594 nativeBuildInputs = compress.nativeInputs;
595 } "${stream} | ${compress.compress} > $out"
596 );
597
598 # 1. extract the base image
599 # 2. create the layer
600 # 3. add layer deps to the layer itself, diffing with the base image
601 # 4. compute the layer id
602 # 5. put the layer in the image
603 # 6. repack the image
604 buildImage = lib.makeOverridable (
605 args@{
606 # Image name.
607 name,
608 # Image tag, when null then the nix output hash will be used.
609 tag ? null,
610 # Parent image, to append to.
611 fromImage ? null,
612 # Name of the parent image; will be read from the image otherwise.
613 fromImageName ? null,
614 # Tag of the parent image; will be read from the image otherwise.
615 fromImageTag ? null,
616 # Files to put on the image (a nix store path or list of paths).
617 copyToRoot ? null,
618 # When copying the contents into the image, preserve symlinks to
619 # directories (see `rsync -K`). Otherwise, transform those symlinks
620 # into directories.
621 keepContentsDirlinks ? false,
622 # Docker config; e.g. what command to run on the container.
623 config ? null,
624 # Image architecture, defaults to the architecture of the `hostPlatform` when unset
625 architecture ? defaultArchitecture,
626 # Optional bash script to run on the files prior to fixturizing the layer.
627 extraCommands ? "",
628 uid ? 0,
629 gid ? 0,
630 # Optional bash script to run as root on the image when provisioning.
631 runAsRoot ? null,
632 # Size of the virtual machine disk to provision when building the image.
633 diskSize ? 1024,
634 # Size of the virtual machine memory to provision when building the image.
635 buildVMMemorySize ? 512,
636 # Time of creation of the image.
637 created ? "1970-01-01T00:00:01Z",
638 # Compressor to use. One of: none, gz, zstd.
639 compressor ? "gz",
640 # Populate the nix database in the image with the dependencies of `copyToRoot`.
641 includeNixDB ? false,
642 # Deprecated.
643 contents ? null,
644 }:
645
646 let
647 checked =
648 lib.warnIf (contents != null)
649 "in docker image ${name}: The contents parameter is deprecated. Change to copyToRoot if the contents are designed to be copied to the root filesystem, such as when you use `buildEnv` or similar between contents and your packages. Use copyToRoot = buildEnv { ... }; or similar if you intend to add packages to /bin."
650 lib.throwIf
651 (contents != null && copyToRoot != null)
652 "in docker image ${name}: You can not specify both contents and copyToRoot.";
653
654 rootContents = if copyToRoot == null then contents else copyToRoot;
655
656 baseName = baseNameOf name;
657
658 # Create a JSON blob of the configuration. Set the date to unix zero.
659 baseJson =
660 let
661 pure = writeText "${baseName}-config.json" (
662 builtins.toJSON {
663 inherit created config architecture;
664 preferLocalBuild = true;
665 os = "linux";
666 }
667 );
668 impure =
669 runCommand "${baseName}-config.json"
670 {
671 nativeBuildInputs = [ jq ];
672 preferLocalBuild = true;
673 }
674 ''
675 jq ".created = \"$(TZ=utc date --iso-8601="seconds")\"" ${pure} > $out
676 '';
677 in
678 if created == "now" then impure else pure;
679
680 compress = compressorForImage compressor name;
681
682 # TODO: add the dependencies of the config json.
683 extraCommandsWithDB =
684 if includeNixDB then (mkDbExtraCommand rootContents) + extraCommands else extraCommands;
685
686 layer =
687 if runAsRoot == null then
688 mkPureLayer {
689 name = baseName;
690 inherit
691 baseJson
692 keepContentsDirlinks
693 uid
694 gid
695 ;
696 extraCommands = extraCommandsWithDB;
697 copyToRoot = rootContents;
698 }
699 else
700 mkRootLayer {
701 name = baseName;
702 inherit
703 baseJson
704 fromImage
705 fromImageName
706 fromImageTag
707 keepContentsDirlinks
708 runAsRoot
709 diskSize
710 buildVMMemorySize
711 ;
712 extraCommands = extraCommandsWithDB;
713 copyToRoot = rootContents;
714 };
715 result =
716 runCommand "docker-image-${baseName}.tar${compress.ext}"
717 {
718 nativeBuildInputs = [
719 jshon
720 jq
721 moreutils
722 ]
723 ++ compress.nativeInputs;
724 # Image name must be lowercase
725 imageName = lib.toLower name;
726 imageTag = lib.optionalString (tag != null) tag;
727 inherit fromImage baseJson;
728 layerClosure = writeClosure [ layer ];
729 passthru.buildArgs = args;
730 passthru.layer = layer;
731 passthru.imageTag =
732 if tag != null then
733 tag
734 else
735 lib.head (
736 lib.strings.splitString "-" (baseNameOf (builtins.unsafeDiscardStringContext result.outPath))
737 );
738 }
739 ''
740 ${lib.optionalString (tag == null) ''
741 outName="$(basename "$out")"
742 outHash=$(echo "$outName" | cut -d - -f 1)
743
744 imageTag=$outHash
745 ''}
746
747 # Print tar contents:
748 # 1: Interpreted as relative to the root directory
749 # 2: With no trailing slashes on directories
750 # This is useful for ensuring that the output matches the
751 # values generated by the "find" command
752 ls_tar() {
753 for f in $(tar -tf $1 | xargs realpath -ms --relative-to=.); do
754 if [[ "$f" != "." ]]; then
755 echo "/$f"
756 fi
757 done
758 }
759
760 mkdir image
761 touch baseFiles
762 baseEnvs='[]'
763 if [[ -n "$fromImage" ]]; then
764 echo "Unpacking base image..."
765 tar -C image -xpf "$fromImage"
766
767 # Store the layers and the environment variables from the base image
768 cat ./image/manifest.json | jq -r '.[0].Layers | .[]' > layer-list
769 configName="$(cat ./image/manifest.json | jq -r '.[0].Config')"
770 baseEnvs="$(cat "./image/$configName" | jq '.config.Env // []')"
771
772 # Extract the parentID from the manifest
773 if [[ -n "$fromImageName" ]] && [[ -n "$fromImageTag" ]]; then
774 parentID="$(
775 cat "image/manifest.json" |
776 jq -r '.[] | select(.RepoTags | contains([$desiredTag])) | rtrimstr(".json")' \
777 --arg desiredTag "$fromImageName:$fromImageTag"
778 )"
779 else
780 echo "From-image name or tag wasn't set. Reading the first ID."
781 parentID="$(cat "image/manifest.json" | jq -r '.[0].Config | rtrimstr(".json")')"
782 fi
783
784 # Otherwise do not import the base image configuration and manifest
785 chmod a+w image image/*.json
786 rm -f image/*.json
787
788 for l in image/*/layer.tar; do
789 ls_tar $l >> baseFiles
790 done
791 else
792 touch layer-list
793 fi
794
795 chmod -R ug+rw image
796
797 mkdir temp
798 cp ${layer}/* temp/
799 chmod ug+w temp/*
800
801 for dep in $(cat $layerClosure); do
802 find $dep >> layerFiles
803 done
804
805 echo "Adding layer..."
806 # Record the contents of the tarball with ls_tar.
807 ls_tar temp/layer.tar >> baseFiles
808
809 # Append nix/store directory to the layer so that when the layer is loaded in the
810 # image /nix/store has read permissions for non-root users.
811 # nix/store is added only if the layer has /nix/store paths in it.
812 if [ $(wc -l < $layerClosure) -gt 1 ] && [ $(grep -c -e "^/nix/store$" baseFiles) -eq 0 ]; then
813 mkdir -p nix/store
814 chmod -R 555 nix
815 echo "./nix" >> layerFiles
816 echo "./nix/store" >> layerFiles
817 fi
818
819 # Get the files in the new layer which were *not* present in
820 # the old layer, and record them as newFiles.
821 comm <(sort -n baseFiles|uniq) \
822 <(sort -n layerFiles|uniq|grep -v ${layer}) -1 -3 > newFiles
823 # Append the new files to the layer.
824 tar -rpf temp/layer.tar --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" \
825 --owner=0 --group=0 --no-recursion --verbatim-files-from --files-from newFiles
826
827 echo "Adding meta..."
828
829 # If we have a parentID, add it to the json metadata.
830 if [[ -n "$parentID" ]]; then
831 cat temp/json | jshon -s "$parentID" -i parent > tmpjson
832 mv tmpjson temp/json
833 fi
834
835 # Take the sha256 sum of the generated json and use it as the layer ID.
836 # Compute the size and add it to the json under the 'Size' field.
837 layerID=$(sha256sum temp/json|cut -d ' ' -f 1)
838 size=$(stat --printf="%s" temp/layer.tar)
839 cat temp/json | jshon -s "$layerID" -i id -n $size -i Size > tmpjson
840 mv tmpjson temp/json
841
842 # Use the temp folder we've been working on to create a new image.
843 mv temp image/$layerID
844
845 # Add the new layer ID to the end of the layer list
846 (
847 cat layer-list
848 # originally this used `sed -i "1i$layerID" layer-list`, but
849 # would fail if layer-list was completely empty.
850 echo "$layerID/layer.tar"
851 ) | sponge layer-list
852
853 # Create image json and image manifest
854 imageJson=$(cat ${baseJson} | jq '.config.Env = $baseenv + .config.Env' --argjson baseenv "$baseEnvs")
855 imageJson=$(echo "$imageJson" | jq ". + {\"rootfs\": {\"diff_ids\": [], \"type\": \"layers\"}}")
856 manifestJson=$(jq -n "[{\"RepoTags\":[\"$imageName:$imageTag\"]}]")
857
858 for layerTar in $(cat ./layer-list); do
859 layerChecksum=$(sha256sum image/$layerTar | cut -d ' ' -f1)
860 imageJson=$(echo "$imageJson" | jq ".history |= . + [{\"created\": \"$(jq -r .created ${baseJson})\"}]")
861 # diff_ids order is from the bottom-most to top-most layer
862 imageJson=$(echo "$imageJson" | jq ".rootfs.diff_ids |= . + [\"sha256:$layerChecksum\"]")
863 manifestJson=$(echo "$manifestJson" | jq ".[0].Layers |= . + [\"$layerTar\"]")
864 done
865
866 imageJsonChecksum=$(echo "$imageJson" | sha256sum | cut -d ' ' -f1)
867 echo "$imageJson" > "image/$imageJsonChecksum.json"
868 manifestJson=$(echo "$manifestJson" | jq ".[0].Config = \"$imageJsonChecksum.json\"")
869 echo "$manifestJson" > image/manifest.json
870
871 # Store the json under the name image/repositories.
872 jshon -n object \
873 -n object -s "$layerID" -i "$imageTag" \
874 -i "$imageName" > image/repositories
875
876 # Make the image read-only.
877 chmod -R a-w image
878
879 echo "Cooking the image..."
880 tar -C image --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --xform s:'^./':: -c . | ${compress.compress} > $out
881
882 echo "Finished."
883 '';
884
885 in
886 checked result
887 );
888
889 # Merge the tarballs of images built with buildImage into a single
890 # tarball that contains all images. Running `docker load` on the resulting
891 # tarball will load the images into the docker daemon.
892 mergeImages =
893 images:
894 runCommand "merge-docker-images"
895 {
896 inherit images;
897 nativeBuildInputs = [
898 file
899 jq
900 ]
901 ++ compressors.none.nativeInputs
902 ++ compressors.gz.nativeInputs
903 ++ compressors.zstd.nativeInputs;
904 }
905 ''
906 mkdir image inputs
907 # Extract images
908 repos=()
909 manifests=()
910 last_image_mime="application/gzip"
911 for item in $images; do
912 name=$(basename $item)
913 mkdir inputs/$name
914
915 last_image_mime=$(file --mime-type -b $item)
916 case $last_image_mime in
917 "application/x-tar") ${compressors.none.decompress};;
918 "application/zstd") ${compressors.zstd.decompress};;
919 "application/gzip") ${compressors.gz.decompress};;
920 *) echo "error: unexpected layer type $last_image_mime" >&2; exit 1;;
921 esac < $item | tar -xC inputs/$name
922
923 if [ -f inputs/$name/repositories ]; then
924 repos+=(inputs/$name/repositories)
925 fi
926 if [ -f inputs/$name/manifest.json ]; then
927 manifests+=(inputs/$name/manifest.json)
928 fi
929 done
930 # Copy all layers from input images to output image directory
931 cp -R --update=none inputs/*/* image/
932 # Merge repositories objects and manifests
933 jq -s add "''${repos[@]}" > repositories
934 jq -s add "''${manifests[@]}" > manifest.json
935 # Replace output image repositories and manifest with merged versions
936 mv repositories image/repositories
937 mv manifest.json image/manifest.json
938 # Create tarball and gzip
939 tar -C image --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --xform s:'^./':: -c . | (
940 case $last_image_mime in
941 "application/x-tar") ${compressors.none.compress};;
942 "application/zstd") ${compressors.zstd.compress};;
943 "application/gzip") ${compressors.gz.compress};;
944 # `*)` not needed; already checked.
945 esac
946 ) > $out
947 '';
948
949 # Provide a /etc/passwd and /etc/group that contain root and nobody.
950 # Useful when packaging binaries that insist on using nss to look up
951 # username/groups (like nginx).
952 # /bin/sh is fine to not exist, and provided by another shim.
953 inherit fakeNss; # alias
954
955 # This provides a /usr/bin/env, for shell scripts using the
956 # "#!/usr/bin/env executable" shebang.
957 usrBinEnv = runCommand "usr-bin-env" { } ''
958 mkdir -p $out/usr/bin
959 ln -s ${coreutils}/bin/env $out/usr/bin
960 '';
961
962 # This provides /bin/sh, pointing to bashInteractive.
963 # The use of bashInteractive here is intentional to support cases like `docker run -it <image_name>`, so keep these use cases in mind if making any changes to how this works.
964 binSh = runCommand "bin-sh" { } ''
965 mkdir -p $out/bin
966 ln -s ${bashInteractive}/bin/bash $out/bin/sh
967 '';
968
969 # This provides the ca bundle in common locations
970 caCertificates = runCommand "ca-certificates" { } ''
971 mkdir -p $out/etc/ssl/certs $out/etc/pki/tls/certs
972 # Old NixOS compatibility.
973 ln -s ${cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-bundle.crt
974 # NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility.
975 ln -s ${cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-certificates.crt
976 # CentOS/Fedora compatibility.
977 ln -s ${cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/pki/tls/certs/ca-bundle.crt
978 '';
979
980 # Build an image and populate its nix database with the provided
981 # contents. The main purpose is to be able to use nix commands in
982 # the container.
983 # Be careful since this doesn't work well with multilayer.
984 # TODO: add the dependencies of the config json.
985 buildImageWithNixDb = args: buildImage (args // { includeNixDB = true; });
986
987 buildLayeredImageWithNixDb = args: buildLayeredImage (args // { includeNixDB = true; });
988
989 # Arguments are documented in ../../../doc/build-helpers/images/dockertools.section.md
990 streamLayeredImage = lib.makeOverridable (
991 {
992 name,
993 tag ? null,
994 fromImage ? null,
995 contents ? [ ],
996 config ? { },
997 architecture ? defaultArchitecture,
998 created ? "1970-01-01T00:00:01Z",
999 mtime ? "1970-01-01T00:00:01Z",
1000 uid ? 0,
1001 gid ? 0,
1002 uname ? "root",
1003 gname ? "root",
1004 maxLayers ? 100,
1005 extraCommands ? "",
1006 fakeRootCommands ? "",
1007 enableFakechroot ? false,
1008 includeStorePaths ? true,
1009 includeNixDB ? false,
1010 passthru ? { },
1011 # Pipeline used to produce docker layers. If not set, popularity contest
1012 # algorithm is used. If set, maxLayers is ignored as the author of the
1013 # pipeline can use one of the available functions (like "limit_layers")
1014 # to control the amount of layers.
1015 # See: pkgs/build-support/flatten-references-graph/src/flatten_references_graph/pipe.py
1016 # for available functions, and it's test for how to use them.
1017 # WARNING!! this interface is highly experimental and subject to change.
1018 layeringPipeline ? null,
1019 # Enables debug logging for the layering pipeline.
1020 debug ? false,
1021 }:
1022 assert (
1023 lib.assertMsg (layeringPipeline == null -> maxLayers > 1)
1024 "the maxLayers argument of dockerTools.buildLayeredImage function must be greather than 1 (current value: ${toString maxLayers})"
1025 );
1026 assert (
1027 lib.assertMsg (enableFakechroot -> !stdenv.hostPlatform.isDarwin) ''
1028 cannot use `enableFakechroot` because `proot` is not portable to Darwin. Workarounds:
1029 - use `fakeRootCommands` with the restricted `fakeroot` environment
1030 - cross-compile your packages
1031 - run your packages in a virtual machine
1032 Discussion: https://github.com/NixOS/nixpkgs/issues/327311''
1033 );
1034 let
1035 baseName = baseNameOf name;
1036
1037 streamScript = writePython3 "stream" { } ./stream_layered_image.py;
1038 baseJson = writeText "${baseName}-base.json" (
1039 builtins.toJSON {
1040 inherit config architecture;
1041 os = "linux";
1042 }
1043 );
1044
1045 contentsList = if builtins.isList contents then contents else [ contents ];
1046 bind-paths = builtins.toString (
1047 builtins.map (path: "--bind=${path}:${path}!") [
1048 "/dev/"
1049 "/proc/"
1050 "/sys/"
1051 "${builtins.storeDir}/"
1052 "$out/layer.tar"
1053 ]
1054 );
1055
1056 # We store the customisation layer as a tarball, to make sure that
1057 # things like permissions set on 'extraCommands' are not overridden
1058 # by Nix. Then we precompute the sha256 for performance.
1059 customisationLayer = symlinkJoin {
1060 name = "${baseName}-customisation-layer";
1061 paths = contentsList;
1062 extraCommands = (lib.optionalString includeNixDB (mkDbExtraCommand contents)) + extraCommands;
1063 inherit fakeRootCommands;
1064 nativeBuildInputs = [
1065 fakeroot
1066 ]
1067 ++ optionals enableFakechroot [
1068 proot
1069 ];
1070 postBuild = ''
1071 mv $out old_out
1072 (cd old_out; eval "$extraCommands" )
1073
1074 mkdir $out
1075 ${
1076 if enableFakechroot then
1077 ''
1078 proot -r $PWD/old_out ${bind-paths} --pwd=/ fakeroot bash -e -c '
1079 if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; fi
1080 source $stdenv/setup
1081 eval "$fakeRootCommands"
1082 tar \
1083 --sort name \
1084 --exclude=./dev \
1085 --exclude=./proc \
1086 --exclude=./sys \
1087 --exclude=.${builtins.storeDir} \
1088 --numeric-owner --mtime "@$SOURCE_DATE_EPOCH" \
1089 --hard-dereference \
1090 -cf $out/layer.tar .
1091 '
1092 ''
1093 else
1094 ''
1095 fakeroot bash -e -c '
1096 if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; fi
1097 source $stdenv/setup
1098 cd old_out
1099 eval "$fakeRootCommands"
1100 tar \
1101 --sort name \
1102 --numeric-owner --mtime "@$SOURCE_DATE_EPOCH" \
1103 --hard-dereference \
1104 -cf $out/layer.tar .
1105 '
1106 ''
1107 }
1108 sha256sum $out/layer.tar \
1109 | cut -f 1 -d ' ' \
1110 > $out/checksum
1111 '';
1112 };
1113
1114 layersJsonFile = buildPackages.dockerMakeLayers {
1115 inherit debug;
1116 closureRoots = optionals includeStorePaths [
1117 baseJson
1118 customisationLayer
1119 ];
1120 excludePaths = [
1121 baseJson
1122 customisationLayer
1123 ];
1124 pipeline =
1125 if layeringPipeline != null then
1126 layeringPipeline
1127 else
1128 import ./popularity-contest-layering-pipeline.nix { inherit lib jq runCommand; } {
1129 inherit fromImage maxLayers;
1130 };
1131 };
1132
1133 conf =
1134 runCommand "${baseName}-conf.json"
1135 {
1136 inherit
1137 fromImage
1138 created
1139 mtime
1140 uid
1141 gid
1142 uname
1143 gname
1144 layersJsonFile
1145 ;
1146 imageName = lib.toLower name;
1147 preferLocalBuild = true;
1148 passthru.imageTag =
1149 if tag != null then
1150 tag
1151 else
1152 lib.head (
1153 lib.strings.splitString "-" (baseNameOf (builtins.unsafeDiscardStringContext conf.outPath))
1154 );
1155 nativeBuildInputs = [ jq ];
1156 }
1157 ''
1158 ${
1159 if (tag == null) then
1160 ''
1161 outName="$(basename "$out")"
1162 outHash=$(echo "$outName" | cut -d - -f 1)
1163
1164 imageTag=$outHash
1165 ''
1166 else
1167 ''
1168 imageTag="${tag}"
1169 ''
1170 }
1171
1172 # convert "created" and "mtime" to iso format
1173 if [[ "$created" != "now" ]]; then
1174 created="$(date -Iseconds -d "$created")"
1175 fi
1176 if [[ "$mtime" != "now" ]]; then
1177 mtime="$(date -Iseconds -d "$mtime")"
1178 fi
1179
1180 jq '
1181 . + {
1182 "store_dir": $store_dir,
1183 "from_image": $from_image,
1184 "store_layers": $store_layers[0],
1185 "customisation_layer", $customisation_layer,
1186 "repo_tag": $repo_tag,
1187 "created": $created,
1188 "mtime": $mtime,
1189 "uid": $uid,
1190 "gid": $gid,
1191 "uname": $uname,
1192 "gname": $gname
1193 }
1194 ' --arg store_dir "${storeDir}" \
1195 --argjson from_image ${if fromImage == null then "null" else "'\"${fromImage}\"'"} \
1196 --slurpfile store_layers "$layersJsonFile" \
1197 --arg customisation_layer ${customisationLayer} \
1198 --arg repo_tag "$imageName:$imageTag" \
1199 --arg created "$created" \
1200 --arg mtime "$mtime" \
1201 --arg uid "$uid" \
1202 --arg gid "$gid" \
1203 --arg uname "$uname" \
1204 --arg gname "$gname" \
1205 ${baseJson} \
1206 | tee $out
1207 '';
1208
1209 result =
1210 runCommand "stream-${baseName}"
1211 {
1212 inherit conf;
1213 inherit (conf) imageName;
1214 inherit streamScript;
1215 preferLocalBuild = true;
1216 passthru = passthru // {
1217 inherit (conf) imageTag;
1218 inherit conf;
1219 inherit streamScript;
1220
1221 # Distinguish tarballs and exes at the Nix level so functions that
1222 # take images can know in advance how the image is supposed to be used.
1223 isExe = true;
1224 };
1225 nativeBuildInputs = [ makeWrapper ];
1226 }
1227 ''
1228 makeWrapper $streamScript $out --add-flags $conf
1229 '';
1230 in
1231 result
1232 );
1233
1234 # This function streams a docker image that behaves like a nix-shell for a derivation
1235 # Docs: doc/build-helpers/images/dockertools.section.md
1236 # Tests: nixos/tests/docker-tools-nix-shell.nix
1237 streamNixShellImage =
1238 {
1239 drv,
1240 name ? drv.name + "-env",
1241 tag ? null,
1242 uid ? 1000,
1243 gid ? 1000,
1244 homeDirectory ? "/build",
1245 shell ? bashInteractive + "/bin/bash",
1246 command ? null,
1247 run ? null,
1248 }:
1249 assert lib.assertMsg (!(drv.drvAttrs.__structuredAttrs or false))
1250 "streamNixShellImage: Does not work with the derivation ${drv.name} because it uses __structuredAttrs";
1251 assert lib.assertMsg (
1252 command == null || run == null
1253 ) "streamNixShellImage: Can't specify both command and run";
1254 let
1255
1256 # A binary that calls the command to build the derivation
1257 builder = writeShellScriptBin "buildDerivation" ''
1258 exec ${lib.escapeShellArg (valueToString drv.drvAttrs.builder)} ${lib.escapeShellArgs (map valueToString drv.drvAttrs.args)}
1259 '';
1260
1261 staticPath = "${dirOf shell}:${lib.makeBinPath [ builder ]}";
1262
1263 # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L493-L526
1264 rcfile = writeText "nix-shell-rc" ''
1265 unset PATH
1266 dontAddDisableDepTrack=1
1267 # TODO: https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L506
1268 [ -e $stdenv/setup ] && source $stdenv/setup
1269 PATH=${staticPath}:"$PATH"
1270 SHELL=${lib.escapeShellArg shell}
1271 BASH=${lib.escapeShellArg shell}
1272 set +e
1273 [ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '
1274 if [ "$(type -t runHook)" = function ]; then
1275 runHook shellHook
1276 fi
1277 unset NIX_ENFORCE_PURITY
1278 shopt -u nullglob
1279 shopt -s execfail
1280 ${optionalString (command != null || run != null) ''
1281 ${optionalString (command != null) command}
1282 ${optionalString (run != null) run}
1283 exit
1284 ''}
1285 '';
1286
1287 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/globals.hh#L464-L465
1288 sandboxBuildDir = "/build";
1289
1290 drvEnv =
1291 devShellTools.unstructuredDerivationInputEnv { inherit (drv) drvAttrs; }
1292 // devShellTools.derivationOutputEnv {
1293 outputList = drv.outputs;
1294 outputMap = drv;
1295 };
1296
1297 # Environment variables set in the image
1298 envVars = {
1299
1300 # Root certificates for internet access
1301 SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
1302 NIX_SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
1303
1304 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1027-L1030
1305 # PATH = "/path-not-set";
1306 # Allows calling bash and `buildDerivation` as the Cmd
1307 PATH = staticPath;
1308
1309 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1032-L1038
1310 HOME = homeDirectory;
1311
1312 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1040-L1044
1313 NIX_STORE = storeDir;
1314
1315 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1046-L1047
1316 # TODO: Make configurable?
1317 NIX_BUILD_CORES = "1";
1318
1319 }
1320 // drvEnv
1321 // {
1322
1323 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1008-L1010
1324 NIX_BUILD_TOP = sandboxBuildDir;
1325
1326 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1012-L1013
1327 TMPDIR = sandboxBuildDir;
1328 TEMPDIR = sandboxBuildDir;
1329 TMP = sandboxBuildDir;
1330 TEMP = sandboxBuildDir;
1331
1332 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1015-L1019
1333 PWD = sandboxBuildDir;
1334
1335 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1071-L1074
1336 # We don't set it here because the output here isn't handled in any special way
1337 # NIX_LOG_FD = "2";
1338
1339 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1076-L1077
1340 TERM = "xterm-256color";
1341 };
1342
1343 in
1344 streamLayeredImage {
1345 inherit name tag;
1346 contents = [
1347 binSh
1348 usrBinEnv
1349 (fakeNss.override {
1350 # Allows programs to look up the build user's home directory
1351 # https://github.com/NixOS/nix/blob/ffe155abd36366a870482625543f9bf924a58281/src/libstore/build/local-derivation-goal.cc#L906-L910
1352 # Slightly differs however: We use the passed-in homeDirectory instead of sandboxBuildDir.
1353 # We're doing this because it's arguably a bug in Nix that sandboxBuildDir is used here: https://github.com/NixOS/nix/issues/6379
1354 extraPasswdLines = [
1355 "nixbld:x:${toString uid}:${toString gid}:Build user:${homeDirectory}:/noshell"
1356 ];
1357 extraGroupLines = [
1358 "nixbld:!:${toString gid}:"
1359 ];
1360 })
1361 ];
1362
1363 fakeRootCommands = ''
1364 # Effectively a single-user installation of Nix, giving the user full
1365 # control over the Nix store. Needed for building the derivation this
1366 # shell is for, but also in case one wants to use Nix inside the
1367 # image
1368 mkdir -p ./nix/{store,var/nix} ./etc/nix
1369 chown -R ${toString uid}:${toString gid} ./nix ./etc/nix
1370
1371 # Gives the user control over the build directory
1372 mkdir -p .${sandboxBuildDir}
1373 chown -R ${toString uid}:${toString gid} .${sandboxBuildDir}
1374 '';
1375
1376 # Run this image as the given uid/gid
1377 config.User = "${toString uid}:${toString gid}";
1378 config.Cmd =
1379 # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L185-L186
1380 # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L534-L536
1381 if run == null then
1382 [
1383 shell
1384 "--rcfile"
1385 rcfile
1386 ]
1387 else
1388 [
1389 shell
1390 rcfile
1391 ];
1392 config.WorkingDir = sandboxBuildDir;
1393 config.Env = lib.mapAttrsToList (name: value: "${name}=${value}") envVars;
1394 };
1395
1396 # Wrapper around streamNixShellImage to build an image from the result
1397 # Docs: doc/build-helpers/images/dockertools.section.md
1398 # Tests: nixos/tests/docker-tools-nix-shell.nix
1399 buildNixShellImage =
1400 {
1401 drv,
1402 compressor ? "gz",
1403 ...
1404 }@args:
1405 let
1406 stream = streamNixShellImage (builtins.removeAttrs args [ "compressor" ]);
1407 compress = compressorForImage compressor drv.name;
1408 in
1409 runCommand "${drv.name}-env.tar${compress.ext}" {
1410 inherit (stream) imageName;
1411 passthru = { inherit (stream) imageTag; };
1412 nativeBuildInputs = compress.nativeInputs;
1413 } "${stream} | ${compress.compress} > $out";
1414}