Loading doc/build-helpers/images/dockertools.section.md +10 −0 Original line number Diff line number Diff line Loading @@ -507,6 +507,16 @@ This allows the function to produce reproducible images. _Default value:_ `"1970-01-01T00:00:01Z"`. `uid` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-uid} `gid` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-gid} `uname` (String; _optional_) []{#dockerTools-buildLayeredImage-arg-uname} `gname` (String; _optional_) []{#dockerTools-buildLayeredImage-arg-gname} : Credentials for Nix store ownership. Can be overridden to e.g. `1000` / `1000` / `"user"` / `"user"` to enable building a container where Nix can be used as an unprivileged user in single-user mode. _Default value:_ `0` / `0` / `"root"` / `"root"` `maxLayers` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-maxLayers} : The maximum number of layers that will be used by the generated image. Loading nixos/tests/docker-tools.nix +21 −1 Original line number Diff line number Diff line Loading @@ -58,6 +58,20 @@ let ''; config.Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "/testfile" ]; }; nonRootTestImage = pkgs.dockerTools.streamLayeredImage rec { name = "non-root-test"; tag = "latest"; uid = 1000; gid = 1000; uname = "user"; gname = "user"; config = { User = "user"; Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "${pkgs.coreutils}/bin/stat" ]; }; }; in { name = "docker-tools"; meta = with pkgs.lib.maintainers; { Loading Loading @@ -181,7 +195,7 @@ in { ): docker.succeed( "docker load --input='${examples.bashLayeredWithUser}'", "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'", "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)'", "docker rmi ${examples.bashLayeredWithUser.imageName}", ) Loading Loading @@ -604,5 +618,11 @@ in { "${chownTestImage} | docker load", "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)" ) with subtest("streamLayeredImage: with non-root user"): docker.succeed( "${nonRootTestImage} | docker load", "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)" ) ''; }) pkgs/build-support/docker/default.nix +28 −35 Original line number Diff line number Diff line Loading @@ -890,41 +890,26 @@ rec { }) ); # Arguments are documented in ../../../doc/build-helpers/images/dockertools.section.md streamLayeredImage = lib.makeOverridable ( { # Image Name name , # Image tag, the Nix's output hash will be used if null tag ? null , # Parent image, to append to. fromImage ? null , # Files to put on the image (a nix store path or list of paths). contents ? [ ] , # Docker config; e.g. what command to run on the container. config ? { } , # Image architecture, defaults to the architecture of the `hostPlatform` when unset architecture ? defaultArchitecture , # Time of creation of the image. Passing "now" will make the # created date be the time of building. created ? "1970-01-01T00:00:01Z" , # Optional bash script to run on the files prior to fixturizing the layer. extraCommands ? "" , # Optional bash script to run inside fakeroot environment. # Could be used for changing ownership of files in customisation layer. fakeRootCommands ? "" , # Whether to run fakeRootCommands in fakechroot as well, so that they # appear to run inside the image, but have access to the normal Nix store. # Perhaps this could be enabled on by default on pkgs.stdenv.buildPlatform.isLinux enableFakechroot ? false , # We pick 100 to ensure there is plenty of room for extension. I # believe the actual maximum is 128. maxLayers ? 100 , # Whether to include store paths in the image. You generally want to leave # this on, but tooling may disable this to insert the store paths more # efficiently via other means, such as bind mounting the host store. includeStorePaths ? true , # Passthru arguments for the underlying derivation. passthru ? {} , tag ? null , fromImage ? null , contents ? [ ] , config ? { } , architecture ? defaultArchitecture , created ? "1970-01-01T00:00:01Z" , uid ? 0 , gid ? 0 , uname ? "root" , gname ? "root" , maxLayers ? 100 , extraCommands ? "" , fakeRootCommands ? "" , enableFakechroot ? false , includeStorePaths ? true , passthru ? {} , }: assert Loading Loading @@ -1007,7 +992,7 @@ rec { conf = runCommand "${baseName}-conf.json" { inherit fromImage maxLayers created; inherit fromImage maxLayers created uid gid uname gname; imageName = lib.toLower name; preferLocalBuild = true; passthru.imageTag = Loading Loading @@ -1086,14 +1071,22 @@ rec { "store_layers": $store_layers[0], "customisation_layer", $customisation_layer, "repo_tag": $repo_tag, "created": $created "created": $created, "uid": $uid, "gid": $gid, "uname": $uname, "gname": $gname } ' --arg store_dir "${storeDir}" \ --argjson from_image ${if fromImage == null then "null" else "'\"${fromImage}\"'"} \ --slurpfile store_layers store_layers.json \ --arg customisation_layer ${customisationLayer} \ --arg repo_tag "$imageName:$imageTag" \ --arg created "$created" | --arg created "$created" \ --arg uid "$uid" \ --arg gid "$gid" \ --arg uname "$uname" \ --arg gname "$gname" | tee $out ''; Loading pkgs/build-support/docker/stream_layered_image.py +17 −10 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ image as an uncompressed tarball to stdout: the fields with the same name on the image spec [2]. * "created" can be "now". * "created" is also used as mtime for files added to the image. * "uid", "gid", "uname", "gname" is the file ownership, for example, 0, 0, "root", "root". * "store_layers" is a list of layers in ascending order, where each layer is the list of store paths to include in that layer. Loading Loading @@ -45,7 +47,7 @@ from datetime import datetime, timezone from collections import namedtuple def archive_paths_to(obj, paths, mtime): def archive_paths_to(obj, paths, mtime, uid, gid, uname, gname): """ Writes the given store paths as a tar file to the given stream. Loading @@ -61,14 +63,14 @@ def archive_paths_to(obj, paths, mtime): def apply_filters(ti): ti.mtime = mtime ti.uid = 0 ti.gid = 0 ti.uname = "root" ti.gname = "root" ti.uid = uid ti.gid = gid ti.uname = uname ti.gname = gname return ti def nix_root(ti): ti.mode = 0o0555 # r-xr-xr-x ti.mode = 0o0755 # rwxr-xr-x return ti def dir(path): Loading Loading @@ -208,7 +210,7 @@ def overlay_base_config(from_image, final_config): return final_config def add_layer_dir(tar, paths, store_dir, mtime): def add_layer_dir(tar, paths, store_dir, mtime, uid, gid, uname, gname): """ Appends given store paths to a TarFile object as a new layer. Loading @@ -231,7 +233,7 @@ def add_layer_dir(tar, paths, store_dir, mtime): archive_paths_to( extract_checksum, paths, mtime=mtime, mtime, uid, gid, uname, gname ) (checksum, size) = extract_checksum.extract() Loading @@ -247,7 +249,7 @@ def add_layer_dir(tar, paths, store_dir, mtime): archive_paths_to( write, paths, mtime=mtime, mtime, uid, gid, uname, gname ) write.close() Loading Loading @@ -324,6 +326,10 @@ def main(): else datetime.fromisoformat(conf["created"]) ) mtime = int(created.timestamp()) uid = int(conf["uid"]) gid = int(conf["gid"]) uname = conf["uname"] gname = conf["gname"] store_dir = conf["store_dir"] from_image = load_from_image(conf["from_image"]) Loading @@ -336,7 +342,8 @@ def main(): for num, store_layer in enumerate(conf["store_layers"], start=start): print("Creating layer", num, "from paths:", store_layer, file=sys.stderr) info = add_layer_dir(tar, store_layer, store_dir, mtime=mtime) info = add_layer_dir(tar, store_layer, store_dir, mtime, uid, gid, uname, gname) layers.append(info) print("Creating layer", len(layers) + 1, "with customisation...", Loading Loading
doc/build-helpers/images/dockertools.section.md +10 −0 Original line number Diff line number Diff line Loading @@ -507,6 +507,16 @@ This allows the function to produce reproducible images. _Default value:_ `"1970-01-01T00:00:01Z"`. `uid` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-uid} `gid` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-gid} `uname` (String; _optional_) []{#dockerTools-buildLayeredImage-arg-uname} `gname` (String; _optional_) []{#dockerTools-buildLayeredImage-arg-gname} : Credentials for Nix store ownership. Can be overridden to e.g. `1000` / `1000` / `"user"` / `"user"` to enable building a container where Nix can be used as an unprivileged user in single-user mode. _Default value:_ `0` / `0` / `"root"` / `"root"` `maxLayers` (Number; _optional_) []{#dockerTools-buildLayeredImage-arg-maxLayers} : The maximum number of layers that will be used by the generated image. Loading
nixos/tests/docker-tools.nix +21 −1 Original line number Diff line number Diff line Loading @@ -58,6 +58,20 @@ let ''; config.Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "/testfile" ]; }; nonRootTestImage = pkgs.dockerTools.streamLayeredImage rec { name = "non-root-test"; tag = "latest"; uid = 1000; gid = 1000; uname = "user"; gname = "user"; config = { User = "user"; Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "${pkgs.coreutils}/bin/stat" ]; }; }; in { name = "docker-tools"; meta = with pkgs.lib.maintainers; { Loading Loading @@ -181,7 +195,7 @@ in { ): docker.succeed( "docker load --input='${examples.bashLayeredWithUser}'", "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'", "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)'", "docker rmi ${examples.bashLayeredWithUser.imageName}", ) Loading Loading @@ -604,5 +618,11 @@ in { "${chownTestImage} | docker load", "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)" ) with subtest("streamLayeredImage: with non-root user"): docker.succeed( "${nonRootTestImage} | docker load", "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)" ) ''; })
pkgs/build-support/docker/default.nix +28 −35 Original line number Diff line number Diff line Loading @@ -890,41 +890,26 @@ rec { }) ); # Arguments are documented in ../../../doc/build-helpers/images/dockertools.section.md streamLayeredImage = lib.makeOverridable ( { # Image Name name , # Image tag, the Nix's output hash will be used if null tag ? null , # Parent image, to append to. fromImage ? null , # Files to put on the image (a nix store path or list of paths). contents ? [ ] , # Docker config; e.g. what command to run on the container. config ? { } , # Image architecture, defaults to the architecture of the `hostPlatform` when unset architecture ? defaultArchitecture , # Time of creation of the image. Passing "now" will make the # created date be the time of building. created ? "1970-01-01T00:00:01Z" , # Optional bash script to run on the files prior to fixturizing the layer. extraCommands ? "" , # Optional bash script to run inside fakeroot environment. # Could be used for changing ownership of files in customisation layer. fakeRootCommands ? "" , # Whether to run fakeRootCommands in fakechroot as well, so that they # appear to run inside the image, but have access to the normal Nix store. # Perhaps this could be enabled on by default on pkgs.stdenv.buildPlatform.isLinux enableFakechroot ? false , # We pick 100 to ensure there is plenty of room for extension. I # believe the actual maximum is 128. maxLayers ? 100 , # Whether to include store paths in the image. You generally want to leave # this on, but tooling may disable this to insert the store paths more # efficiently via other means, such as bind mounting the host store. includeStorePaths ? true , # Passthru arguments for the underlying derivation. passthru ? {} , tag ? null , fromImage ? null , contents ? [ ] , config ? { } , architecture ? defaultArchitecture , created ? "1970-01-01T00:00:01Z" , uid ? 0 , gid ? 0 , uname ? "root" , gname ? "root" , maxLayers ? 100 , extraCommands ? "" , fakeRootCommands ? "" , enableFakechroot ? false , includeStorePaths ? true , passthru ? {} , }: assert Loading Loading @@ -1007,7 +992,7 @@ rec { conf = runCommand "${baseName}-conf.json" { inherit fromImage maxLayers created; inherit fromImage maxLayers created uid gid uname gname; imageName = lib.toLower name; preferLocalBuild = true; passthru.imageTag = Loading Loading @@ -1086,14 +1071,22 @@ rec { "store_layers": $store_layers[0], "customisation_layer", $customisation_layer, "repo_tag": $repo_tag, "created": $created "created": $created, "uid": $uid, "gid": $gid, "uname": $uname, "gname": $gname } ' --arg store_dir "${storeDir}" \ --argjson from_image ${if fromImage == null then "null" else "'\"${fromImage}\"'"} \ --slurpfile store_layers store_layers.json \ --arg customisation_layer ${customisationLayer} \ --arg repo_tag "$imageName:$imageTag" \ --arg created "$created" | --arg created "$created" \ --arg uid "$uid" \ --arg gid "$gid" \ --arg uname "$uname" \ --arg gname "$gname" | tee $out ''; Loading
pkgs/build-support/docker/stream_layered_image.py +17 −10 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ image as an uncompressed tarball to stdout: the fields with the same name on the image spec [2]. * "created" can be "now". * "created" is also used as mtime for files added to the image. * "uid", "gid", "uname", "gname" is the file ownership, for example, 0, 0, "root", "root". * "store_layers" is a list of layers in ascending order, where each layer is the list of store paths to include in that layer. Loading Loading @@ -45,7 +47,7 @@ from datetime import datetime, timezone from collections import namedtuple def archive_paths_to(obj, paths, mtime): def archive_paths_to(obj, paths, mtime, uid, gid, uname, gname): """ Writes the given store paths as a tar file to the given stream. Loading @@ -61,14 +63,14 @@ def archive_paths_to(obj, paths, mtime): def apply_filters(ti): ti.mtime = mtime ti.uid = 0 ti.gid = 0 ti.uname = "root" ti.gname = "root" ti.uid = uid ti.gid = gid ti.uname = uname ti.gname = gname return ti def nix_root(ti): ti.mode = 0o0555 # r-xr-xr-x ti.mode = 0o0755 # rwxr-xr-x return ti def dir(path): Loading Loading @@ -208,7 +210,7 @@ def overlay_base_config(from_image, final_config): return final_config def add_layer_dir(tar, paths, store_dir, mtime): def add_layer_dir(tar, paths, store_dir, mtime, uid, gid, uname, gname): """ Appends given store paths to a TarFile object as a new layer. Loading @@ -231,7 +233,7 @@ def add_layer_dir(tar, paths, store_dir, mtime): archive_paths_to( extract_checksum, paths, mtime=mtime, mtime, uid, gid, uname, gname ) (checksum, size) = extract_checksum.extract() Loading @@ -247,7 +249,7 @@ def add_layer_dir(tar, paths, store_dir, mtime): archive_paths_to( write, paths, mtime=mtime, mtime, uid, gid, uname, gname ) write.close() Loading Loading @@ -324,6 +326,10 @@ def main(): else datetime.fromisoformat(conf["created"]) ) mtime = int(created.timestamp()) uid = int(conf["uid"]) gid = int(conf["gid"]) uname = conf["uname"] gname = conf["gname"] store_dir = conf["store_dir"] from_image = load_from_image(conf["from_image"]) Loading @@ -336,7 +342,8 @@ def main(): for num, store_layer in enumerate(conf["store_layers"], start=start): print("Creating layer", num, "from paths:", store_layer, file=sys.stderr) info = add_layer_dir(tar, store_layer, store_dir, mtime=mtime) info = add_layer_dir(tar, store_layer, store_dir, mtime, uid, gid, uname, gname) layers.append(info) print("Creating layer", len(layers) + 1, "with customisation...", Loading