From 6f63865cf470ce99b36bceacbefbb6886d05be51 Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Fri, 3 Feb 2023 17:49:39 -0600 Subject: [PATCH 1/6] dockerTools: Add minimal test case for #214434 --- nixos/tests/docker-tools.nix | 51 +++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index 98704ecb2fb65..58d1d45f2fad9 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -1,6 +1,49 @@ # this test creates a simple GNU image with docker tools and sees if it executes -import ./make-test-python.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: +let + # nixpkgs#214434: dockerTools.buildImage fails to unpack base images + # containing duplicate rootfs diffs when those duplicate tarballs + # appear under the manifest's 'Layers'. Docker can generate images + # like this even though dockerTools does not. + repeatedLayerTestImage = + let + # Rootfs diffs for layers 1 and 2 are identical (and empty) + layer1 = pkgs.dockerTools.buildImage { name = "empty"; }; + layer2 = layer1.overrideAttrs (_: { fromImage = layer1; }); + repeatedRootfsDiffs = pkgs.runCommandNoCC "image-with-links.tar" { + nativeBuildInputs = [pkgs.jq]; + } '' + mkdir contents + tar -xf "${layer2}" -C contents + cd contents + first_rootfs=$(jq -r '.[0].Layers[0]' manifest.json) + second_rootfs=$(jq -r '.[0].Layers[1]' manifest.json) + target_rootfs=$(sha256sum "$first_rootfs" | cut -d' ' -f 1).tar + + # Replace duplicated rootfs diffs with symlinks to one tarball + chmod -R ug+w . + mv "$first_rootfs" "$target_rootfs" + rm "$second_rootfs" + ln -s "../$target_rootfs" "$first_rootfs" + ln -s "../$target_rootfs" "$second_rootfs" + + # Update manifest's layers to use the symlinks' target + cat manifest.json | \ + jq ".[0].Layers[0] = \"$target_rootfs\"" | + jq ".[0].Layers[1] = \"$target_rootfs\"" > manifest.json.new + mv manifest.json.new manifest.json + + tar --sort=name --hard-dereference -cf $out . + ''; + in pkgs.dockerTools.buildImage { + fromImage = repeatedRootfsDiffs; + name = "repeated-layer-test"; + copyToRoot = pkgs.bash; + # A runAsRoot script is required to force previous layers to be unpacked + runAsRoot = ""; + }; +in { name = "docker-tools"; meta = with pkgs.lib.maintainers; { maintainers = [ lnl7 roberth ]; @@ -221,6 +264,12 @@ import ./make-test-python.nix ({ pkgs, ... }: { "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order" ) + with subtest("Ensure repeated base layers handled by buildImage"): + docker.succeed( + "docker load --input='${repeatedLayerTestImage}'", + "docker run --rm ${repeatedLayerTestImage.imageName} /bin/bash -c 'exit 0'" + ) + with subtest("Ensure environment variables are correctly inherited"): docker.succeed( "docker load --input='${examples.environmentVariables}'" From f4e4cac0c86f0e16aac773083aebbfc0e7daea43 Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Fri, 3 Feb 2023 17:50:36 -0600 Subject: [PATCH 2/6] dockerTools: Correctly unpack duplicate rootfs diffs This PR addresses issue #214434 by preventing dockerTools.buildImage from deleting rootfs diffs until after they've been unpacked. --- pkgs/build-support/docker/default.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix index bdc93f3643f10..0e4011a532b39 100644 --- a/pkgs/build-support/docker/default.nix +++ b/pkgs/build-support/docker/default.nix @@ -259,13 +259,14 @@ rec { mkdir -p image/$extractionID/layer tar -C image/$extractionID/layer -xpf image/$layerTar - rm image/$layerTar find image/$extractionID/layer -name ".wh.*" -exec bash -c 'name="$(basename {}|sed "s/^.wh.//")"; mknod "$(dirname {})/$name" c 0 0; rm {}' \; # Get the next lower directory and continue the loop. lowerdir=image/$extractionID/layer''${lowerdir:+:}$lowerdir done + # Don't remove tarballs until all unpacked in case some are used more than once + awk '{print "image/"$0}' layer-list | xargs rm -f mkdir work mkdir layer From eb38ad04efae0ebcd7217c4ccace3da0102d85af Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Mon, 6 Feb 2023 11:05:13 -0600 Subject: [PATCH 3/6] dockerTools: ensure runAsRoot script not optimized away in test Co-authored-by: Robert Hensing --- nixos/tests/docker-tools.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index 58d1d45f2fad9..09741858c82ea 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -41,7 +41,9 @@ let name = "repeated-layer-test"; copyToRoot = pkgs.bash; # A runAsRoot script is required to force previous layers to be unpacked - runAsRoot = ""; + runAsRoot = '' + echo 'runAsRoot has run.' + ''; }; in { name = "docker-tools"; From c66cabe33ea1f969ef9562feeef0a015b83a60b1 Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Mon, 6 Feb 2023 11:15:33 -0600 Subject: [PATCH 4/6] dockerTools: use more familiar terminology to describe test image Co-authored-by: Robert Hensing --- nixos/tests/docker-tools.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index 09741858c82ea..06100f9d78426 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, ... }: let # nixpkgs#214434: dockerTools.buildImage fails to unpack base images - # containing duplicate rootfs diffs when those duplicate tarballs + # containing duplicate layers when those duplicate tarballs # appear under the manifest's 'Layers'. Docker can generate images # like this even though dockerTools does not. repeatedLayerTestImage = From 298c543e55284a3be8e8785302883fb873e005af Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Mon, 6 Feb 2023 12:18:23 -0600 Subject: [PATCH 5/6] dockerTools: Specify 'latest' tag for repeated layer test image --- nixos/tests/docker-tools.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index 06100f9d78426..44b583ebcea55 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -39,6 +39,7 @@ let in pkgs.dockerTools.buildImage { fromImage = repeatedRootfsDiffs; name = "repeated-layer-test"; + tag = "latest"; copyToRoot = pkgs.bash; # A runAsRoot script is required to force previous layers to be unpacked runAsRoot = '' From 84e04ccf8570e9f8072486f7d750d326225c7117 Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Mon, 6 Feb 2023 12:19:29 -0600 Subject: [PATCH 6/6] dockerTools: Preprocess layers list before unpack to handle repeated layers --- pkgs/build-support/docker/default.nix | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix index 0e4011a532b39..7fa5aeafc8e3d 100644 --- a/pkgs/build-support/docker/default.nix +++ b/pkgs/build-support/docker/default.nix @@ -229,6 +229,15 @@ rec { mount /dev/${vmTools.hd} disk cd disk + function dedup() { + declare -A seen + while read ln; do + if [[ -z "''${seen["$ln"]:-}" ]]; then + echo "$ln"; seen["$ln"]=1 + fi + done + } + if [[ -n "$fromImage" ]]; then echo "Unpacking base image..." mkdir image @@ -245,7 +254,8 @@ rec { parentID="$(cat "image/manifest.json" | jq -r '.[0].Config | rtrimstr(".json")')" fi - cat ./image/manifest.json | jq -r '.[0].Layers | .[]' > layer-list + # In case of repeated layers, unpack only the last occurrence of each + cat ./image/manifest.json | jq -r '.[0].Layers | .[]' | tac | dedup | tac > layer-list else touch layer-list fi @@ -259,14 +269,13 @@ rec { mkdir -p image/$extractionID/layer tar -C image/$extractionID/layer -xpf image/$layerTar + rm image/$layerTar find image/$extractionID/layer -name ".wh.*" -exec bash -c 'name="$(basename {}|sed "s/^.wh.//")"; mknod "$(dirname {})/$name" c 0 0; rm {}' \; # Get the next lower directory and continue the loop. lowerdir=image/$extractionID/layer''${lowerdir:+:}$lowerdir done - # Don't remove tarballs until all unpacked in case some are used more than once - awk '{print "image/"$0}' layer-list | xargs rm -f mkdir work mkdir layer