Merge pull request #95409 from utdemir/stream_layered_image_fix
dockerTools.streamLayeredImage: Store the customisation layer as a tarball
This commit is contained in:
commit
a5931fa6e3
@ -219,18 +219,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
|
|||||||
)
|
)
|
||||||
|
|
||||||
with subtest("Ensure correct behavior when no store is needed"):
|
with subtest("Ensure correct behavior when no store is needed"):
|
||||||
# This check tests two requirements simultaneously
|
# This check tests that buildLayeredImage can build images that don't need a store.
|
||||||
# 1. buildLayeredImage can build images that don't need a store.
|
|
||||||
# 2. Layers of symlinks are eliminated by the customization layer.
|
|
||||||
#
|
|
||||||
docker.succeed(
|
docker.succeed(
|
||||||
"docker load --input='${pkgs.dockerTools.examples.no-store-paths}'"
|
"docker load --input='${pkgs.dockerTools.examples.no-store-paths}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Busybox will not recognize argv[0] and print an error message with argv[0],
|
|
||||||
# but it confirms that the custom-true symlink is present.
|
|
||||||
docker.succeed("docker run --rm no-store-paths custom-true |& grep custom-true")
|
|
||||||
|
|
||||||
# This check may be loosened to allow an *empty* store rather than *no* store.
|
# This check may be loosened to allow an *empty* store rather than *no* store.
|
||||||
docker.succeed("docker run --rm no-store-paths ls /")
|
docker.succeed("docker run --rm no-store-paths ls /")
|
||||||
docker.fail("docker run --rm no-store-paths ls /nix/store")
|
docker.fail("docker run --rm no-store-paths ls /nix/store")
|
||||||
|
@ -718,28 +718,41 @@ rec {
|
|||||||
architecture = buildPackages.go.GOARCH;
|
architecture = buildPackages.go.GOARCH;
|
||||||
os = "linux";
|
os = "linux";
|
||||||
});
|
});
|
||||||
customisationLayer = runCommand "${name}-customisation-layer" { inherit extraCommands; } ''
|
|
||||||
cp -r ${contentsEnv}/ $out
|
|
||||||
|
|
||||||
if [[ -n $extraCommands ]]; then
|
contentsList = if builtins.isList contents then contents else [ contents ];
|
||||||
chmod u+w $out
|
|
||||||
(cd $out; eval "$extraCommands")
|
# We store the customisation layer as a tarball, to make sure that
|
||||||
fi
|
# things like permissions set on 'extraCommands' are not overriden
|
||||||
'';
|
# by Nix. Then we precompute the sha256 for performance.
|
||||||
contentsEnv = symlinkJoin {
|
customisationLayer = symlinkJoin {
|
||||||
name = "${name}-bulk-layers";
|
name = "${name}-customisation-layer";
|
||||||
paths = if builtins.isList contents
|
paths = contentsList;
|
||||||
then contents
|
inherit extraCommands;
|
||||||
else [ contents ];
|
postBuild = ''
|
||||||
|
mv $out old_out
|
||||||
|
(cd old_out; eval "$extraCommands" )
|
||||||
|
|
||||||
|
mkdir $out
|
||||||
|
|
||||||
|
tar \
|
||||||
|
--owner 0 --group 0 --mtime "@$SOURCE_DATE_EPOCH" \
|
||||||
|
--hard-dereference \
|
||||||
|
-C old_out \
|
||||||
|
-cf $out/layer.tar .
|
||||||
|
|
||||||
|
sha256sum $out/layer.tar \
|
||||||
|
| cut -f 1 -d ' ' \
|
||||||
|
> $out/checksum
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# NOTE: the `closures` parameter is a list of closures to include.
|
closureRoots = [ baseJson ] ++ contentsList;
|
||||||
# The TOP LEVEL store paths themselves will never be present in the
|
overallClosure = writeText "closure" (lib.concatStringsSep " " closureRoots);
|
||||||
# resulting image. At this time (2020-06-18) none of these layers
|
|
||||||
# are appropriate to include, as they are all created as
|
# These derivations are only created as implementation details of docker-tools,
|
||||||
# implementation details of dockerTools.
|
# so they'll be excluded from the created images.
|
||||||
closures = [ baseJson contentsEnv ];
|
unnecessaryDrvs = [ baseJson overallClosure ];
|
||||||
overallClosure = writeText "closure" (lib.concatStringsSep " " closures);
|
|
||||||
conf = runCommand "${name}-conf.json" {
|
conf = runCommand "${name}-conf.json" {
|
||||||
inherit maxLayers created;
|
inherit maxLayers created;
|
||||||
imageName = lib.toLower name;
|
imageName = lib.toLower name;
|
||||||
@ -751,9 +764,6 @@ rec {
|
|||||||
paths = referencesByPopularity overallClosure;
|
paths = referencesByPopularity overallClosure;
|
||||||
buildInputs = [ jq ];
|
buildInputs = [ jq ];
|
||||||
} ''
|
} ''
|
||||||
paths() {
|
|
||||||
cat $paths ${lib.concatMapStringsSep " " (path: "| (grep -v ${path} || true)") (closures ++ [ overallClosure ])}
|
|
||||||
}
|
|
||||||
${if (tag == null) then ''
|
${if (tag == null) then ''
|
||||||
outName="$(basename "$out")"
|
outName="$(basename "$out")"
|
||||||
outHash=$(echo "$outName" | cut -d - -f 1)
|
outHash=$(echo "$outName" | cut -d - -f 1)
|
||||||
@ -768,6 +778,12 @@ rec {
|
|||||||
created="$(date -Iseconds -d "$created")"
|
created="$(date -Iseconds -d "$created")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
paths() {
|
||||||
|
cat $paths ${lib.concatMapStringsSep " "
|
||||||
|
(path: "| (grep -v ${path} || true)")
|
||||||
|
unnecessaryDrvs}
|
||||||
|
}
|
||||||
|
|
||||||
# Create $maxLayers worth of Docker Layers, one layer per store path
|
# Create $maxLayers worth of Docker Layers, one layer per store path
|
||||||
# unless there are more paths than $maxLayers. In that case, create
|
# unless there are more paths than $maxLayers. In that case, create
|
||||||
# $maxLayers-1 for the most popular layers, and smush the remainaing
|
# $maxLayers-1 for the most popular layers, and smush the remainaing
|
||||||
|
@ -298,21 +298,10 @@ rec {
|
|||||||
name = "no-store-paths";
|
name = "no-store-paths";
|
||||||
tag = "latest";
|
tag = "latest";
|
||||||
extraCommands = ''
|
extraCommands = ''
|
||||||
chmod a+w bin
|
|
||||||
|
|
||||||
# This removes sharing of busybox and is not recommended. We do this
|
# This removes sharing of busybox and is not recommended. We do this
|
||||||
# to make the example suitable as a test case with working binaries.
|
# to make the example suitable as a test case with working binaries.
|
||||||
cp -r ${pkgs.pkgsStatic.busybox}/* .
|
cp -r ${pkgs.pkgsStatic.busybox}/* .
|
||||||
'';
|
'';
|
||||||
contents = [
|
|
||||||
# This layer has no dependencies and its symlinks will be dereferenced
|
|
||||||
# when creating the customization layer.
|
|
||||||
(pkgs.runCommand "layer-to-flatten" {} ''
|
|
||||||
mkdir -p $out/bin
|
|
||||||
ln -s /bin/true $out/bin/custom-true
|
|
||||||
''
|
|
||||||
)
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
nixLayered = pkgs.dockerTools.buildLayeredImageWithNixDb {
|
nixLayered = pkgs.dockerTools.buildLayeredImageWithNixDb {
|
||||||
@ -415,7 +404,7 @@ rec {
|
|||||||
pkgs.dockerTools.buildLayeredImage {
|
pkgs.dockerTools.buildLayeredImage {
|
||||||
name = "bash-layered-with-user";
|
name = "bash-layered-with-user";
|
||||||
tag = "latest";
|
tag = "latest";
|
||||||
contents = [ pkgs.bash pkgs.coreutils (nonRootShadowSetup { uid = 999; user = "somebody"; }) ];
|
contents = [ pkgs.bash pkgs.coreutils ] ++ nonRootShadowSetup { uid = 999; user = "somebody"; };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@ function does all this.
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
@ -45,21 +44,14 @@ from datetime import datetime, timezone
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
def archive_paths_to(obj, paths, mtime, add_nix, filter=None):
|
def archive_paths_to(obj, paths, mtime):
|
||||||
"""
|
"""
|
||||||
Writes the given store paths as a tar file to the given stream.
|
Writes the given store paths as a tar file to the given stream.
|
||||||
|
|
||||||
obj: Stream to write to. Should have a 'write' method.
|
obj: Stream to write to. Should have a 'write' method.
|
||||||
paths: List of store paths.
|
paths: List of store paths.
|
||||||
add_nix: Whether /nix and /nix/store directories should be
|
|
||||||
prepended to the archive.
|
|
||||||
filter: An optional transformation to be applied to TarInfo
|
|
||||||
objects. Should take a single TarInfo object and return
|
|
||||||
another one. Defaults to identity.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
filter = filter if filter else lambda i: i
|
|
||||||
|
|
||||||
# gettarinfo makes the paths relative, this makes them
|
# gettarinfo makes the paths relative, this makes them
|
||||||
# absolute again
|
# absolute again
|
||||||
def append_root(ti):
|
def append_root(ti):
|
||||||
@ -72,7 +64,7 @@ def archive_paths_to(obj, paths, mtime, add_nix, filter=None):
|
|||||||
ti.gid = 0
|
ti.gid = 0
|
||||||
ti.uname = "root"
|
ti.uname = "root"
|
||||||
ti.gname = "root"
|
ti.gname = "root"
|
||||||
return filter(ti)
|
return ti
|
||||||
|
|
||||||
def nix_root(ti):
|
def nix_root(ti):
|
||||||
ti.mode = 0o0555 # r-xr-xr-x
|
ti.mode = 0o0555 # r-xr-xr-x
|
||||||
@ -85,11 +77,9 @@ def archive_paths_to(obj, paths, mtime, add_nix, filter=None):
|
|||||||
|
|
||||||
with tarfile.open(fileobj=obj, mode="w|") as tar:
|
with tarfile.open(fileobj=obj, mode="w|") as tar:
|
||||||
# To be consistent with the docker utilities, we need to have
|
# To be consistent with the docker utilities, we need to have
|
||||||
# these directories first when building layer tarballs. But
|
# these directories first when building layer tarballs.
|
||||||
# we don't need them on the customisation layer.
|
tar.addfile(apply_filters(nix_root(dir("/nix"))))
|
||||||
if add_nix:
|
tar.addfile(apply_filters(nix_root(dir("/nix/store"))))
|
||||||
tar.addfile(apply_filters(nix_root(dir("/nix"))))
|
|
||||||
tar.addfile(apply_filters(nix_root(dir("/nix/store"))))
|
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
path = pathlib.Path(path)
|
path = pathlib.Path(path)
|
||||||
@ -136,7 +126,7 @@ class ExtractChecksum:
|
|||||||
LayerInfo = namedtuple("LayerInfo", ["size", "checksum", "path", "paths"])
|
LayerInfo = namedtuple("LayerInfo", ["size", "checksum", "path", "paths"])
|
||||||
|
|
||||||
|
|
||||||
def add_layer_dir(tar, paths, mtime, add_nix=True, filter=None):
|
def add_layer_dir(tar, paths, mtime):
|
||||||
"""
|
"""
|
||||||
Appends given store paths to a TarFile object as a new layer.
|
Appends given store paths to a TarFile object as a new layer.
|
||||||
|
|
||||||
@ -144,11 +134,6 @@ def add_layer_dir(tar, paths, mtime, add_nix=True, filter=None):
|
|||||||
paths: List of store paths.
|
paths: List of store paths.
|
||||||
mtime: 'mtime' of the added files and the layer tarball.
|
mtime: 'mtime' of the added files and the layer tarball.
|
||||||
Should be an integer representing a POSIX time.
|
Should be an integer representing a POSIX time.
|
||||||
add_nix: Whether /nix and /nix/store directories should be
|
|
||||||
added to a layer.
|
|
||||||
filter: An optional transformation to be applied to TarInfo
|
|
||||||
objects inside the layer. Should take a single TarInfo
|
|
||||||
object and return another one. Defaults to identity.
|
|
||||||
|
|
||||||
Returns: A 'LayerInfo' object containing some metadata of
|
Returns: A 'LayerInfo' object containing some metadata of
|
||||||
the layer added.
|
the layer added.
|
||||||
@ -164,8 +149,6 @@ def add_layer_dir(tar, paths, mtime, add_nix=True, filter=None):
|
|||||||
extract_checksum,
|
extract_checksum,
|
||||||
paths,
|
paths,
|
||||||
mtime=mtime,
|
mtime=mtime,
|
||||||
add_nix=add_nix,
|
|
||||||
filter=filter
|
|
||||||
)
|
)
|
||||||
(checksum, size) = extract_checksum.extract()
|
(checksum, size) = extract_checksum.extract()
|
||||||
|
|
||||||
@ -182,8 +165,6 @@ def add_layer_dir(tar, paths, mtime, add_nix=True, filter=None):
|
|||||||
write,
|
write,
|
||||||
paths,
|
paths,
|
||||||
mtime=mtime,
|
mtime=mtime,
|
||||||
add_nix=add_nix,
|
|
||||||
filter=filter
|
|
||||||
)
|
)
|
||||||
write.close()
|
write.close()
|
||||||
|
|
||||||
@ -199,29 +180,38 @@ def add_layer_dir(tar, paths, mtime, add_nix=True, filter=None):
|
|||||||
return LayerInfo(size=size, checksum=checksum, path=path, paths=paths)
|
return LayerInfo(size=size, checksum=checksum, path=path, paths=paths)
|
||||||
|
|
||||||
|
|
||||||
def add_customisation_layer(tar, path, mtime):
|
def add_customisation_layer(target_tar, customisation_layer, mtime):
|
||||||
"""
|
"""
|
||||||
Adds the contents of the store path as a new layer. This is different
|
Adds the customisation layer as a new layer. This is layer is structured
|
||||||
than the 'add_layer_dir' function defaults in the sense that the contents
|
differently; given store path has the 'layer.tar' and corresponding
|
||||||
of a single store path will be added to the root of the layer. eg (without
|
sha256sum ready.
|
||||||
the /nix/store prefix).
|
|
||||||
|
|
||||||
tar: 'tarfile.TarFile' object for the new layer to be added to.
|
tar: 'tarfile.TarFile' object for the new layer to be added to.
|
||||||
path: A store path.
|
customisation_layer: Path containing the layer archive.
|
||||||
mtime: 'mtime' of the added files and the layer tarball. Should be an
|
mtime: 'mtime' of the added layer tarball.
|
||||||
integer representing a POSIX time.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filter(ti):
|
checksum_path = os.path.join(customisation_layer, "checksum")
|
||||||
ti.name = re.sub("^/nix/store/[^/]*", "", ti.name)
|
with open(checksum_path) as f:
|
||||||
return ti
|
checksum = f.read().strip()
|
||||||
return add_layer_dir(
|
assert len(checksum) == 64, f"Invalid sha256 at ${checksum_path}."
|
||||||
tar,
|
|
||||||
[path],
|
layer_path = os.path.join(customisation_layer, "layer.tar")
|
||||||
mtime=mtime,
|
|
||||||
add_nix=False,
|
path = f"{checksum}/layer.tar"
|
||||||
filter=filter
|
tarinfo = target_tar.gettarinfo(layer_path)
|
||||||
)
|
tarinfo.name = path
|
||||||
|
tarinfo.mtime = mtime
|
||||||
|
|
||||||
|
with open(layer_path, "rb") as f:
|
||||||
|
target_tar.addfile(tarinfo, f)
|
||||||
|
|
||||||
|
return LayerInfo(
|
||||||
|
size=None,
|
||||||
|
checksum=checksum,
|
||||||
|
path=path,
|
||||||
|
paths=[customisation_layer]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_bytes(tar, path, content, mtime):
|
def add_bytes(tar, path, content, mtime):
|
||||||
|
Loading…
Reference in New Issue
Block a user