docker: init fetchdocker nix code for docker2nix

This change adds granular, non-docker daemon docker image fetchers and
a docker image layer compositor to be used in conjunction with the
`docker2nix` utility provided by the `haskellPackages.hocker` package.

This change includes a hackage package version bump and updated sha256
for recent fixes released to `hocker` resulting from formulating this
patch.

+258 -2
+38
pkgs/build-support/fetchdocker/credentials.nix
··· 1 + # We provide three paths to get the credentials into the builder's 2 + # environment: 3 + # 4 + # 1. Via impureEnvVars. This method is difficult for multi-user Nix 5 + # installations (but works very well for single-user Nix 6 + # installations!) because it requires setting the environment 7 + # variables on the nix-daemon which is either complicated or unsafe 8 + # (i.e: configuring via Nix means the secrets will be persisted 9 + # into the store) 10 + # 11 + # 2. If the DOCKER_CREDENTIALS key with a path to a credentials file 12 + # is added to the NIX_PATH (usually via the '-I ' argument to most 13 + # Nix tools) then an attempt will be made to read credentials from 14 + # it. The semantics are simple, the file should contain two lines 15 + # for the username and password based authentication: 16 + # 17 + # $ cat ./credentials-file.txt 18 + # DOCKER_USER=myusername 19 + # DOCKER_PASS=mypassword 20 + # 21 + # ... and a single line for the token based authentication: 22 + # 23 + # $ cat ./credentials-file.txt 24 + # DOCKER_TOKEN=mytoken 25 + # 26 + # 3. A credential file at /etc/nix-docker-credentials.txt with the 27 + # same format as the file described in #2 can also be used to 28 + # communicate credentials to the builder. This is necessary for 29 + # situations (like Hydra) where you cannot customize the NIX_PATH 30 + # given to the nix-build invocation to provide it with the 31 + # DOCKER_CREDENTIALS path 32 + let 33 + pathParts = 34 + (builtins.filter 35 + ({path, prefix}: "DOCKER_CREDENTIALS" == prefix) 36 + builtins.nixPath); 37 + in 38 + if (pathParts != []) then (builtins.head pathParts).path else ""
+61
pkgs/build-support/fetchdocker/default.nix
··· 1 + { stdenv, lib, coreutils, bash, gnutar, jq, writeText }: 2 + let 3 + stripScheme = 4 + builtins.replaceStrings [ "https://" "http://" ] [ "" "" ]; 5 + stripNixStore = 6 + s: lib.removePrefix "/nix/store/" s; 7 + in 8 + { name 9 + , registry ? "https://registry-1.docker.io/v2/" 10 + , repository ? "library" 11 + , imageName 12 + , tag 13 + , imageLayers 14 + , imageConfig 15 + , image ? "${stripScheme registry}/${repository}/${imageName}:${tag}" 16 + }: 17 + 18 + # Make sure there are *no* slashes in the repository or container 19 + # names since we use these to make the output derivation name for the 20 + # nix-store path. 21 + assert null == lib.findFirst (c: "/"==c) null (lib.stringToCharacters repository); 22 + assert null == lib.findFirst (c: "/"==c) null (lib.stringToCharacters imageName); 23 + 24 + let 25 + # Abuse `builtins.toPath` to collapse possible double slashes 26 + repoTag0 = builtins.toString (builtins.toPath "/${stripScheme registry}/${repository}/${imageName}"); 27 + repoTag1 = lib.removePrefix "/" repoTag0; 28 + 29 + layers = builtins.map stripNixStore imageLayers; 30 + 31 + manifest = 32 + writeText "manifest.json" (builtins.toJSON [ 33 + { Config = stripNixStore imageConfig; 34 + Layers = layers; 35 + RepoTags = [ "${repoTag1}:${tag}" ]; 36 + }]); 37 + 38 + repositories = 39 + writeText "repositories" (builtins.toJSON { 40 + "${repoTag1}" = { 41 + "${tag}" = lib.last layers; 42 + }; 43 + }); 44 + 45 + imageFileStorePaths = 46 + writeText "imageFileStorePaths.txt" 47 + (lib.concatStringsSep "\n" ((lib.unique imageLayers) ++ [imageConfig])); 48 + in 49 + stdenv.mkDerivation { 50 + builder = ./fetchdocker-builder.sh; 51 + buildInputs = [ coreutils ]; 52 + preferLocalBuild = true; 53 + 54 + inherit name imageName repository tag; 55 + inherit bash gnutar manifest repositories; 56 + inherit imageFileStorePaths; 57 + 58 + passthru = { 59 + inherit image; 60 + }; 61 + }
+13
pkgs/build-support/fetchdocker/fetchDockerConfig.nix
··· 1 + pkgargs@{ stdenv, lib, haskellPackages, writeText, gawk }: 2 + let 3 + generic-fetcher = 4 + import ./generic-fetcher.nix pkgargs; 5 + in 6 + 7 + args@{ repository ? "library", imageName, tag, ... }: 8 + 9 + generic-fetcher ({ 10 + fetcher = "hocker-config"; 11 + name = "${repository}_${imageName}_${tag}-config.json"; 12 + tag = "unused"; 13 + } // args)
+13
pkgs/build-support/fetchdocker/fetchDockerLayer.nix
··· 1 + pkgargs@{ stdenv, lib, haskellPackages, writeText, gawk }: 2 + let 3 + generic-fetcher = 4 + import ./generic-fetcher.nix pkgargs; 5 + in 6 + 7 + args@{ layerDigest, ... }: 8 + 9 + generic-fetcher ({ 10 + fetcher = "hocker-layer"; 11 + name = "docker-layer-${layerDigest}.tar.gz"; 12 + tag = "unused"; 13 + } // args)
+28
pkgs/build-support/fetchdocker/fetchdocker-builder.sh
··· 1 + source "${stdenv}/setup" 2 + header "exporting ${repository}/${imageName} (tag: ${tag}) into ${out}" 3 + mkdir -p "${out}" 4 + 5 + cat <<EOF > "${out}/compositeImage.sh" 6 + #! ${bash}/bin/bash 7 + # 8 + # Create a tar archive of a docker image's layers, docker image config 9 + # json, manifest.json, and repositories json; this streams directly to 10 + # stdout and is intended to be used in concert with docker load, i.e: 11 + # 12 + # ${out}/compositeImage.sh | docker load 13 + 14 + # The first character follow the 's' command for sed becomes the 15 + # delimiter sed will use; this makes the transformation regex easy to 16 + # read. We feed tar a file listing the files we want in the archive, 17 + # because the paths are absolute and docker load wants them flattened in 18 + # the archive, we need to transform all of the paths going in by 19 + # stripping everything *including* the last solidus so that we end up 20 + # with the basename of the path. 21 + ${gnutar}/bin/tar \ 22 + --transform='s=.*/==' \ 23 + --transform="s=.*-manifest.json=manifest.json=" \ 24 + --transform="s=.*-repositories=repositories=" \ 25 + -c "${manifest}" "${repositories}" -T "${imageFileStorePaths}" 26 + EOF 27 + chmod +x "${out}/compositeImage.sh" 28 + stopNest
+97
pkgs/build-support/fetchdocker/generic-fetcher.nix
··· 1 + { stdenv, lib, haskellPackages, writeText, gawk }: 2 + let 3 + awk = "${gawk}/bin/awk"; 4 + dockerCredentialsFile = import ./credentials.nix; 5 + stripScheme = 6 + builtins.replaceStrings [ "https://" "http://" ] [ "" "" ]; 7 + in 8 + { fetcher 9 + , name 10 + , registry ? "https://registry-1.docker.io/v2/" 11 + , repository ? "library" 12 + , imageName 13 + , sha256 14 + , tag ? "" 15 + , layerDigest ? "" 16 + }: 17 + 18 + # There must be no slashes in the repository or container names since 19 + # we use these to make the output derivation name for the nix store 20 + # path 21 + assert null == lib.findFirst (c: "/"==c) null (lib.stringToCharacters repository); 22 + assert null == lib.findFirst (c: "/"==c) null (lib.stringToCharacters imageName); 23 + 24 + # Only allow hocker-config and hocker-layer as fetchers for now 25 + assert (builtins.elem fetcher ["hocker-config" "hocker-layer"]); 26 + 27 + # If layerDigest is non-empty then it must not have a 'sha256:' prefix! 28 + assert 29 + (if layerDigest != "" 30 + then !lib.hasPrefix "sha256:" layerDigest 31 + else true); 32 + 33 + let 34 + layerDigestFlag = 35 + lib.optionalString (layerDigest != "") "--layer ${layerDigest}"; 36 + in 37 + stdenv.mkDerivation { 38 + inherit name; 39 + builder = writeText "${fetcher}-builder.sh" '' 40 + source "$stdenv/setup" 41 + header "${fetcher} exporting to $out" 42 + 43 + declare -A creds 44 + 45 + # This is a hack for Hydra since we have no way of adding values 46 + # to the NIX_PATH for Hydra jobsets!! 47 + staticCredentialsFile="/etc/nix-docker-credentials.txt" 48 + if [ ! -f "$dockerCredentialsFile" -a -f "$staticCredentialsFile" ]; then 49 + echo "credentials file not set, falling back on static credentials file at: $staticCredentialsFile" 50 + dockerCredentialsFile=$staticCredentialsFile 51 + fi 52 + 53 + if [ -f "$dockerCredentialsFile" ]; then 54 + header "using credentials from $dockerCredentialsFile" 55 + 56 + CREDSFILE=$(cat "$dockerCredentialsFile") 57 + creds[token]=$(${awk} -F'=' '/DOCKER_TOKEN/ {print $2}' <<< "$CREDSFILE" | head -n1) 58 + 59 + # Prefer DOCKER_TOKEN over the username and password 60 + # authentication method 61 + if [ -z "''${creds[token]}" ]; then 62 + creds[user]=$(${awk} -F'=' '/DOCKER_USER/ {print $2}' <<< "$CREDSFILE" | head -n1) 63 + creds[pass]=$(${awk} -F'=' '/DOCKER_PASS/ {print $2}' <<< "$CREDSFILE" | head -n1) 64 + fi 65 + fi 66 + 67 + # These variables will be filled in first by the impureEnvVars, if 68 + # those variables are empty then they will default to the 69 + # credentials that may have been read in from the 'DOCKER_CREDENTIALS' 70 + DOCKER_USER="''${DOCKER_USER:-''${creds[user]}}" 71 + DOCKER_PASS="''${DOCKER_PASS:-''${creds[pass]}}" 72 + DOCKER_TOKEN="''${DOCKER_TOKEN:-''${creds[token]}}" 73 + 74 + ${fetcher} --out="$out" \ 75 + ''${registry:+--registry "$registry"} \ 76 + ''${DOCKER_USER:+--username "$DOCKER_USER"} \ 77 + ''${DOCKER_PASS:+--password "$DOCKER_PASS"} \ 78 + ''${DOCKER_TOKEN:+--token "$DOCKER_TOKEN"} \ 79 + ${layerDigestFlag} \ 80 + "${repository}/${imageName}" \ 81 + "${tag}" 82 + 83 + stopNest 84 + ''; 85 + 86 + buildInputs = [ haskellPackages.hocker ]; 87 + 88 + outputHashAlgo = "sha256"; 89 + outputHashMode = "flat"; 90 + outputHash = sha256; 91 + 92 + preferLocalBuild = true; 93 + 94 + impureEnvVars = [ "DOCKER_USER" "DOCKER_PASS" "DOCKER_TOKEN" ]; 95 + 96 + inherit registry dockerCredentialsFile; 97 + }
+2 -2
pkgs/development/haskell-modules/hackage-packages.nix
··· 103952 103952 }: 103953 103953 mkDerivation { 103954 103954 pname = "hocker"; 103955 - version = "1.0.0"; 103956 - sha256 = "16indvxpf2zzdkb7hp09zfnn1zkjwc1pcg2560x2vj7x4akh25mv"; 103955 + version = "1.0.2"; 103956 + sha256 = "1bdzbggvin83m778qq6367mpv2cwgwpbahhlzf290iwikmhmhgr2"; 103957 103957 isLibrary = true; 103958 103958 isExecutable = true; 103959 103959 libraryHaskellDepends = [
+6
pkgs/top-level/all-packages.nix
··· 146 146 147 147 fetchdarcs = callPackage ../build-support/fetchdarcs { }; 148 148 149 + fetchdocker = callPackage ../build-support/fetchdocker { }; 150 + 151 + fetchDockerConfig = callPackage ../build-support/fetchdocker/fetchDockerConfig.nix { }; 152 + 153 + fetchDockerLayer = callPackage ../build-support/fetchdocker/fetchDockerLayer.nix { }; 154 + 149 155 fetchfossil = callPackage ../build-support/fetchfossil { }; 150 156 151 157 fetchgit = callPackage ../build-support/fetchgit {