From 4e93773cf7dd7e2fe9daa44af98ef65159582511 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 19:24:07 +0000 Subject: [PATCH 001/223] chore: Initial commit From 30424447574a0bc0ac8a7c9862b4000c70da846f Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 19:24:51 +0000 Subject: [PATCH 002/223] chore: Import Nixery from experimental Moves the existing Nixery code base to a git repository and switches to public equivalents of libraries used. --- tools/nixery/README.md | 99 +++++++++ tools/nixery/app.yaml | 14 ++ tools/nixery/build-registry-image.nix | 167 ++++++++++++++ tools/nixery/index.html | 90 ++++++++ tools/nixery/main.go | 309 ++++++++++++++++++++++++++ 5 files changed, 679 insertions(+) create mode 100644 tools/nixery/README.md create mode 100644 tools/nixery/app.yaml create mode 100644 tools/nixery/build-registry-image.nix create mode 100644 tools/nixery/index.html create mode 100644 tools/nixery/main.go diff --git a/tools/nixery/README.md b/tools/nixery/README.md new file mode 100644 index 000000000..6b1db4696 --- /dev/null +++ b/tools/nixery/README.md @@ -0,0 +1,99 @@ +# Nixery + +This package implements a Docker-compatible container registry that is capable +of transparently building and serving container images using [Nix][]. + +The project started out with the intention of becoming a Kubernetes controller +that can serve declarative image specifications specified in CRDs as container +images. The design for this is outlined in [a public gist][gist]. + +Currently it focuses on the ad-hoc creation of container images as outlined +below with an example instance available at +[nixery.appspot.com](https://nixery.appspot.com). + +This is not an officially supported Google project. + +## Ad-hoc container images + +Nixery supports building images on-demand based on the *image name*. Every +package that the user intends to include in the image is specified as a path +component of the image name. + +The path components refer to top-level keys in `nixpkgs` and are used to build a +container image using Nix's [buildLayeredImage][] functionality. + +The special meta-package `shell` provides an image base with many core +components (such as `bash` and `coreutils`) that users commonly expect in +interactive images. + +## Usage example + +Using the publicly available Nixery instance at `nixery.appspot.com`, one could +retrieve a container image containing `curl` and an interactive shell like this: + +```shell +tazjin@tazbox:~$ sudo docker run -ti nixery.appspot.com/shell/curl bash +Unable to find image 'nixery.appspot.com/shell/curl:latest' locally +latest: Pulling from shell/curl +7734b79e1ba1: Already exists +b0d2008d18cd: Pull complete +< ... some layers omitted ...> +Digest: sha256:178270bfe84f74548b6a43347d73524e5c2636875b673675db1547ec427cf302 +Status: Downloaded newer image for nixery.appspot.com/shell/curl:latest +bash-4.4# curl --version +curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.0.2q zlib/1.2.11 libssh2/1.8.0 nghttp2/1.35.1 +``` + +## Known issues + +* Initial build times for an image can be somewhat slow while Nixery retrieves + the required derivations from the Nix cache under-the-hood. + + Due to how the Docker Registry API works, there is no way to provide + feedback to the user during this period - hence the UX (in interactive mode) + is currently that "nothing is happening" for a while after the `Unable to + find image` message is printed. + +* For some reason these images do not currently work in GKE clusters. + Launching a Kubernetes pod that uses a Nixery image results in an error + stating `unable to convert a nil pointer to a runtime API image: + ImageInspectError`. + + This error comes from + [here](https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/dockershim/convert.go#L35) + and it occurs *after* the Kubernetes node has retrieved the image from + Nixery (as per the Nixery logs). + +## Kubernetes integration (in the future) + +**Note**: The Kubernetes integration is not yet implemented. + +The basic idea of the Kubernetes integration is to provide a way for users to +specify the contents of a container image as an API object in Kubernetes which +will be transparently built by Nix when the container is started up. + +For example, given a resource that looks like this: + +```yaml +--- +apiVersion: k8s.nixos.org/v1alpha +kind: NixImage +metadata: + name: curl-and-jq +data: + tag: v1 + contents: + - curl + - jq + - bash +``` + +One could create a container that references the `curl-and-jq` image, which will +then be created by Nix when the container image is pulled. + +The controller itself runs as a daemonset on every node in the cluster, +providing a host-mounted `/nix/store`-folder for caching purposes. + +[Nix]: https://nixos.org/ +[gist]: https://gist.github.com/tazjin/08f3d37073b3590aacac424303e6f745 +[buildLayeredImage]: https://grahamc.com/blog/nix-and-layered-docker-images diff --git a/tools/nixery/app.yaml b/tools/nixery/app.yaml new file mode 100644 index 000000000..223fa7582 --- /dev/null +++ b/tools/nixery/app.yaml @@ -0,0 +1,14 @@ +env: flex +runtime: custom + +resources: + cpu: 2 + memory_gb: 4 + disk_size_gb: 50 + +automatic_scaling: + max_num_instances: 3 + cool_down_period_sec: 60 + +env_variables: + BUCKET: "nixery-layers" diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix new file mode 100644 index 000000000..11030d38a --- /dev/null +++ b/tools/nixery/build-registry-image.nix @@ -0,0 +1,167 @@ +# This file contains a modified version of dockerTools.buildImage that, instead +# of outputting a single tarball which can be imported into a running Docker +# daemon, builds a manifest file that can be used for serving the image over a +# registry API. + +{ + # Image Name + name, + # Image tag, the Nix's output hash will be used if null + tag ? null, + # Files to put on the image (a nix store path or list of paths). + contents ? [], + # Packages to install by name (which must refer to top-level attributes of + # nixpkgs). This is passed in as a JSON-array in string form. + packages ? "[]", + # Optional bash script to run on the files prior to fixturizing the layer. + extraCommands ? "", uid ? 0, gid ? 0, + # Docker's lowest maximum layer limit is 42-layers for an old + # version of the AUFS graph driver. We pick 24 to ensure there is + # plenty of room for extension. I believe the actual maximum is + # 128. + maxLayers ? 24, + # Nix package set to use + pkgs ? (import {}) +}: + +# Since this is essentially a re-wrapping of some of the functionality that is +# implemented in the dockerTools, we need all of its components in our top-level +# namespace. +with pkgs; +with dockerTools; + +let + tarLayer = "application/vnd.docker.image.rootfs.diff.tar"; + baseName = baseNameOf name; + + # deepFetch traverses the top-level Nix package set to retrieve an item via a + # path specified in string form. + # + # For top-level items, the name of the key yields the result directly. Nested + # items are fetched by using dot-syntax, as in Nix itself. + # + # For example, `deepFetch pkgs "xorg.xev"` retrieves `pkgs.xorg.xev`. + deepFetch = s: n: + let path = lib.strings.splitString "." n; + err = builtins.throw "Could not find '${n}' in package set"; + in lib.attrsets.attrByPath path err s; + + # allContents is the combination of all derivations and store paths passed in + # directly, as well as packages referred to by name. + allContents = contents ++ (map (deepFetch pkgs) (builtins.fromJSON packages)); + + contentsEnv = symlinkJoin { + name = "bulk-layers"; + paths = allContents; + }; + + # The image build infrastructure expects to be outputting a slightly different + # format than the one we serve over the registry protocol. To work around its + # expectations we need to provide an empty JSON file that it can write some + # fun data into. + emptyJson = writeText "empty.json" "{}"; + + bulkLayers = mkManyPureLayers { + name = baseName; + configJson = emptyJson; + closure = writeText "closure" "${contentsEnv} ${emptyJson}"; + # One layer will be taken up by the customisationLayer, so + # take up one less. + maxLayers = maxLayers - 1; + }; + + customisationLayer = mkCustomisationLayer { + name = baseName; + contents = contentsEnv; + baseJson = emptyJson; + inherit uid gid extraCommands; + }; + + # Inspect the returned bulk layers to determine which layers belong to the + # image and how to serve them. + # + # This computes both an MD5 and a SHA256 hash of each layer, which are used + # for different purposes. See the registry server implementation for details. + # + # Some of this logic is copied straight from `buildLayeredImage`. + allLayersJson = runCommand "fs-layer-list.json" { + buildInputs = [ coreutils findutils jq openssl ]; + } '' + find ${bulkLayers} -mindepth 1 -maxdepth 1 | sort -t/ -k5 -n > layer-list + echo ${customisationLayer} >> layer-list + + for layer in $(cat layer-list); do + layerPath="$layer/layer.tar" + layerSha256=$(sha256sum $layerPath | cut -d ' ' -f1) + # The server application compares binary MD5 hashes and expects base64 + # encoding instead of hex. + layerMd5=$(openssl dgst -md5 -binary $layerPath | openssl enc -base64) + layerSize=$(wc -c $layerPath | cut -d ' ' -f1) + + jq -n -c --arg sha256 $layerSha256 --arg md5 $layerMd5 --arg size $layerSize --arg path $layerPath \ + '{ size: ($size | tonumber), sha256: $sha256, md5: $md5, path: $path }' >> fs-layers + done + + cat fs-layers | jq -s -c '.' > $out + ''; + allLayers = builtins.fromJSON (builtins.readFile allLayersJson); + + # Image configuration corresponding to the OCI specification for the file type + # 'application/vnd.oci.image.config.v1+json' + config = { + architecture = "amd64"; + os = "linux"; + rootfs.type = "layers"; + rootfs.diff_ids = map (layer: "sha256:${layer.sha256}") allLayers; + }; + configJson = writeText "${baseName}-config.json" (builtins.toJSON config); + configMetadata = with builtins; fromJSON (readFile (runCommand "config-meta" { + buildInputs = [ jq openssl ]; + } '' + size=$(wc -c ${configJson} | cut -d ' ' -f1) + sha256=$(sha256sum ${configJson} | cut -d ' ' -f1) + md5=$(openssl dgst -md5 -binary $layerPath | openssl enc -base64) + jq -n -c --arg size $size --arg sha256 $sha256 --arg md5 $md5 \ + '{ size: ($size | tonumber), sha256: $sha256, md5: $md5 }' \ + >> $out + '')); + + # Corresponds to the manifest JSON expected by the Registry API. + # + # This is Docker's "Image Manifest V2, Schema 2": + # https://docs.docker.com/registry/spec/manifest-v2-2/ + manifest = { + schemaVersion = 2; + mediaType = "application/vnd.docker.distribution.manifest.v2+json"; + + config = { + mediaType = "application/vnd.docker.container.image.v1+json"; + size = configMetadata.size; + digest = "sha256:${configMetadata.sha256}"; + }; + + layers = map (layer: { + mediaType = tarLayer; + digest = "sha256:${layer.sha256}"; + size = layer.size; + }) allLayers; + }; + + # This structure maps each layer digest to the actual tarball that will need + # to be served. It is used by the controller to cache the paths during a pull. + layerLocations = { + "${configMetadata.sha256}" = { + path = configJson; + md5 = configMetadata.md5; + }; + } // (builtins.listToAttrs (map (layer: { + name = "${layer.sha256}"; + value = { + path = layer.path; + md5 = layer.md5; + }; + }) allLayers)); + +in writeText "manifest-output.json" (builtins.toJSON { + inherit manifest layerLocations; +}) diff --git a/tools/nixery/index.html b/tools/nixery/index.html new file mode 100644 index 000000000..ebec9968c --- /dev/null +++ b/tools/nixery/index.html @@ -0,0 +1,90 @@ + + + + + + Nixery + + + +
+

Nixery

+ +
+

What is this?

+

+ Nixery provides the ability to pull ad-hoc container images from a Docker-compatible registry + server. The image names specify the contents the image should contain, which are then + retrieved and built by the Nix package manager. +

+

+ Nix is also responsible for the creation of the container images themselves. To do this it + uses an interesting layering strategy described in + this blog post. +

+

How does it work?

+

+ Simply point your local Docker installation (or other compatible registry client) at Nixery + and ask for an image with the contents you desire. Image contents are path separated in the + name, so for example if you needed an image that contains a shell and emacs you + could pull it as such: +

+

+ nixery.appspot.com/shell/emacs25-nox +

+

+ Image tags are currently ignored. Every package name needs to correspond to a key in the + nixpkgs package set. +

+

+ There are some special meta-packages which you must specify as the + first package in an image. These are: +

+
    +
  • shell: Provides default packages you would expect in an interactive environment
  • +
  • builder: Provides the above as well as Nix's standard build environment
  • +
+

+ Hence if you needed an interactive image with, for example, htop installed you + could run docker run -ti nixery.appspot.com/shell/htop bash. +

+

FAQ

+

+ Technically speaking none of these are frequently-asked questions (because no questions have + been asked so far), but I'm going to take a guess at a few anyways: +

+
    +
  • + Where is the source code for this? +
    + Not yet public, sorry. Check back later(tm). +
  • +
  • + Which revision of nixpkgs is used? +
    + Currently whatever was HEAD at the time I deployed this. One idea I've had is + to let users specify tags on images that correspond to commits in nixpkgs, however there is + some potential for abuse there (e.g. by triggering lots of builds on commits that have + broken Hydra builds) and I don't want to deal with that yet. +
  • +
  • + Who made this? +
    + @tazjin +
  • +
+ + diff --git a/tools/nixery/main.go b/tools/nixery/main.go new file mode 100644 index 000000000..29b22f301 --- /dev/null +++ b/tools/nixery/main.go @@ -0,0 +1,309 @@ +// Package main provides the implementation of a container registry that +// transparently builds container images based on Nix derivations. +// +// The Nix derivation used for image creation is responsible for creating +// objects that are compatible with the registry API. The targeted registry +// protocol is currently Docker's. +// +// When an image is requested, the required contents are parsed out of the +// request and a Nix-build is initiated that eventually responds with the +// manifest as well as information linking each layer digest to a local +// filesystem path. +// +// Nixery caches the filesystem paths and returns the manifest to the client. +// Subsequent requests for layer content per digest are then fulfilled by +// serving the files from disk. +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "regexp" + "strings" + + "cloud.google.com/go/storage" +) + +// ManifestMediaType stores the Content-Type used for the manifest itself. This +// corresponds to the "Image Manifest V2, Schema 2" described on this page: +// +// https://docs.docker.com/registry/spec/manifest-v2-2/ +const ManifestMediaType string = "application/vnd.docker.distribution.manifest.v2+json" + +// Image represents the information necessary for building a container image. This can +// be either a list of package names (corresponding to keys in the nixpkgs set) or a +// Nix expression that results in a *list* of derivations. +type image struct { + // Name of the container image. + name string + + // Names of packages to include in the image. These must correspond directly to + // top-level names of Nix packages in the nixpkgs tree. + packages []string +} + +// BuildResult represents the output of calling the Nix derivation responsible for building +// registry images. +// +// The `layerLocations` field contains the local filesystem paths to each individual image layer +// that will need to be served, while the `manifest` field contains the JSON-representation of +// the manifest that needs to be served to the client. +// +// The later field is simply treated as opaque JSON and passed through. +type BuildResult struct { + Manifest json.RawMessage `json:"manifest"` + LayerLocations map[string]struct { + Path string `json:"path"` + Md5 []byte `json:"md5"` + } `json:"layerLocations"` +} + +// imageFromName parses an image name into the corresponding structure which can +// be used to invoke Nix. +// +// It will expand convenience names under the hood (see the `convenienceNames` function below). +func imageFromName(name string) image { + packages := strings.Split(name, "/") + return image{ + name: name, + packages: convenienceNames(packages), + } +} + +// convenienceNames expands convenience package names defined by Nixery which let users +// include commonly required sets of tools in a container quickly. +// +// Convenience names must be specified as the first package in an image. +// +// Currently defined convenience names are: +// +// * `shell`: Includes bash, coreutils and other common command-line tools +// * `builder`: Includes the standard build environment, as well as everything from `shell` +func convenienceNames(packages []string) []string { + shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} + builderPackages := append(shellPackages, "stdenv") + + if packages[0] == "shell" { + return append(packages[1:], shellPackages...) + } else if packages[0] == "builder" { + return append(packages[1:], builderPackages...) + } else { + return packages + } +} + +// Call out to Nix and request that an image be built. Nix will, upon success, return +// a manifest for the container image. +func buildImage(image *image, ctx *context.Context, bucket *storage.BucketHandle) ([]byte, error) { + // This file is made available at runtime via Blaze. See the `data` declaration in `BUILD` + nixPath := "experimental/users/tazjin/nixery/build-registry-image.nix" + + packages, err := json.Marshal(image.packages) + if err != nil { + return nil, err + } + + cmd := exec.Command("nix-build", "--no-out-link", "--show-trace", "--argstr", "name", image.name, "--argstr", "packages", string(packages), nixPath) + + outpipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + + errpipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + + if err = cmd.Start(); err != nil { + log.Println("Error starting nix-build:", err) + return nil, err + } + log.Printf("Started Nix image build for ''%s'", image.name) + + stdout, _ := ioutil.ReadAll(outpipe) + stderr, _ := ioutil.ReadAll(errpipe) + + if err = cmd.Wait(); err != nil { + // TODO(tazjin): Propagate errors upwards in a usable format. + log.Printf("nix-build execution error: %s\nstdout: %s\nstderr: %s\n", err, stdout, stderr) + return nil, err + } + + log.Println("Finished Nix image build") + + buildOutput, err := ioutil.ReadFile(strings.TrimSpace(string(stdout))) + if err != nil { + return nil, err + } + + // The build output returned by Nix is deserialised to add all contained layers to the + // bucket. Only the manifest itself is re-serialised to JSON and returned. + var result BuildResult + err = json.Unmarshal(buildOutput, &result) + if err != nil { + return nil, err + } + + for layer, meta := range result.LayerLocations { + err = uploadLayer(ctx, bucket, layer, meta.Path, meta.Md5) + if err != nil { + return nil, err + } + } + + return json.Marshal(result.Manifest) +} + +// uploadLayer uploads a single layer to Cloud Storage bucket. Before writing any data +// the bucket is probed to see if the file already exists. +// +// If the file does exist, its MD5 hash is verified to ensure that the stored file is +// not - for example - a fragment of a previous, incomplete upload. +func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer string, path string, md5 []byte) error { + layerKey := fmt.Sprintf("layers/%s", layer) + obj := bucket.Object(layerKey) + + // Before uploading a layer to the bucket, probe whether it already exists. + // + // If it does and the MD5 checksum matches the expected one, the layer upload + // can be skipped. + attrs, err := obj.Attrs(*ctx) + + if err == nil && bytes.Equal(attrs.MD5, md5) { + log.Printf("Layer sha256:%s already exists in bucket, skipping upload", layer) + } else { + writer := obj.NewWriter(*ctx) + file, err := os.Open(path) + + if err != nil { + return fmt.Errorf("failed to open layer %s from path %s: %v", layer, path, err) + } + + size, err := io.Copy(writer, file) + if err != nil { + return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) + } + + if err = writer.Close(); err != nil { + return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) + } + + log.Printf("Uploaded layer sha256:%s (%v bytes written)\n", layer, size) + } + + return nil +} + +// layerRedirect constructs the public URL of the layer object in the Cloud Storage bucket +// and redirects the client there. +// +// The Docker client is known to follow redirects, but this might not be true for all other +// registry clients. +func layerRedirect(w http.ResponseWriter, bucket string, digest string) { + log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, bucket) + url := fmt.Sprintf("https://storage.googleapis.com/%s/layers/%s", bucket, digest) + w.Header().Set("Location", url) + w.WriteHeader(303) +} + +// prepareBucket configures the handle to a Cloud Storage bucket in which individual layers will be +// stored after Nix builds. Nixery does not directly serve layers to registry clients, instead it +// redirects them to the public URLs of the Cloud Storage bucket. +// +// The bucket is required for Nixery to function correctly, hence fatal errors are generated in case +// it fails to be set up correctly. +func prepareBucket(ctx *context.Context, bucket string) *storage.BucketHandle { + client, err := storage.NewClient(*ctx) + if err != nil { + log.Fatalln("Failed to set up Cloud Storage client:", err) + } + + bkt := client.Bucket(bucket) + + if _, err := bkt.Attrs(*ctx); err != nil { + log.Fatalln("Could not access configured bucket", err) + } + + return bkt +} + +var manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/(\w+)$`) +var layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) + +func main() { + bucketName := os.Getenv("BUCKET") + if bucketName == "" { + log.Fatalln("GCS bucket for layer storage must be specified") + } + + port := os.Getenv("PORT") + if port == "" { + port = "5726" + } + + ctx := context.Background() + bucket := prepareBucket(&ctx, bucketName) + + log.Printf("Starting Kubernetes Nix controller on port %s\n", port) + + log.Fatal(http.ListenAndServe(":"+port, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // When running on AppEngine, HTTP traffic should be redirected to HTTPS. + // + // This is achieved here by enforcing HSTS (with a one week duration) on responses. + if r.Header.Get("X-Forwarded-Proto") == "http" && strings.Contains(r.Host, "appspot.com") { + w.Header().Add("Strict-Transport-Security", "max-age=604800") + } + + // Serve an index page to anyone who visits the registry's base URL: + if r.RequestURI == "/" { + index, _ := ioutil.ReadFile("experimental/users/tazjin/nixery/index.html") + w.Header().Add("Content-Type", "text/html") + w.Write(index) + return + } + + // Acknowledge that we speak V2 + if r.RequestURI == "/v2/" { + fmt.Fprintln(w) + return + } + + // Serve the manifest (straight from Nix) + manifestMatches := manifestRegex.FindStringSubmatch(r.RequestURI) + if len(manifestMatches) == 3 { + imageName := manifestMatches[1] + log.Printf("Requesting manifest for image '%s'", imageName) + image := imageFromName(manifestMatches[1]) + manifest, err := buildImage(&image, &ctx, bucket) + + if err != nil { + log.Println("Failed to build image manifest", err) + return + } + + w.Header().Add("Content-Type", ManifestMediaType) + w.Write(manifest) + return + } + + // Serve an image layer. For this we need to first ask Nix for the + // manifest, then proceed to extract the correct layer from it. + layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) + if len(layerMatches) == 3 { + digest := layerMatches[2] + layerRedirect(w, bucketName, digest) + return + } + + w.WriteHeader(404) + }))) +} From 5d9b32977ddd332f47f89aa30202b80906d3e719 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 21:48:27 +0100 Subject: [PATCH 003/223] feat(build): Introduce build configuration using Nix Rather than migrating to Bazel, it seems more appropriate to use Nix for this project. The project is split into several different components (for data dependencies and binaries). A derivation for building an image for Nixery itself will be added. --- tools/nixery/default.nix | 43 +++++++++++ tools/nixery/go-deps.nix | 111 +++++++++++++++++++++++++++ tools/nixery/{ => static}/index.html | 0 3 files changed, 154 insertions(+) create mode 100644 tools/nixery/default.nix create mode 100644 tools/nixery/go-deps.nix rename tools/nixery/{ => static}/index.html (100%) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix new file mode 100644 index 000000000..19e7df963 --- /dev/null +++ b/tools/nixery/default.nix @@ -0,0 +1,43 @@ +{ pkgs ? import {} }: + +with pkgs; + +rec { + # Go implementation of the Nixery server which implements the + # container registry interface. + # + # Users will usually not want to use this directly, instead see the + # 'nixery' derivation below, which automatically includes runtime + # data dependencies. + nixery-server = buildGoPackage { + name = "nixery-server"; + + # Technically people should not be building Nixery through 'go get' + # or similar (as other required files will not be included), but + # buildGoPackage requires a package path. + goPackagePath = "github.com/google/nixery"; + + goDeps = ./go-deps.nix; + src = ./.; + + meta = { + description = "Container image build serving Nix-backed images"; + homepage = "https://github.com/google/nixery"; + license = lib.licenses.ascl20; + maintainers = [ lib.maintainers.tazjin ]; + }; + }; + + # Nix expression (unimported!) which is used by Nixery to build + # container images. + nixery-builder = runCommand "build-registry-image.nix" {} '' + cat ${./build-registry-image.nix} > $out + ''; + + # Static files to serve on the Nixery index. This is used primarily + # for the demo instance running at nixery.appspot.com and provides + # some background information for what Nixery is. + nixery-static = runCommand "nixery-static" {} '' + cp -r ${./static} $out + ''; +} diff --git a/tools/nixery/go-deps.nix b/tools/nixery/go-deps.nix new file mode 100644 index 000000000..57d9ecd64 --- /dev/null +++ b/tools/nixery/go-deps.nix @@ -0,0 +1,111 @@ +# This file was generated by https://github.com/kamilchm/go2nix v1.3.0 +[ + { + goPackagePath = "cloud.google.com/go"; + fetch = { + type = "git"; + url = "https://code.googlesource.com/gocloud"; + rev = "edd0968ab5054ee810843a77774d81069989494b"; + sha256 = "1mh8i72h6a1z9lp4cy9bwa2j87bm905zcsvmqwskdqi8z58cif4a"; + }; + } + { + goPackagePath = "github.com/golang/protobuf"; + fetch = { + type = "git"; + url = "https://github.com/golang/protobuf"; + rev = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"; + sha256 = "1k1wb4zr0qbwgpvz9q5ws9zhlal8hq7dmq62pwxxriksayl6hzym"; + }; + } + { + goPackagePath = "github.com/googleapis/gax-go"; + fetch = { + type = "git"; + url = "https://github.com/googleapis/gax-go"; + rev = "bd5b16380fd03dc758d11cef74ba2e3bc8b0e8c2"; + sha256 = "1lxawwngv6miaqd25s3ba0didfzylbwisd2nz7r4gmbmin6jsjrx"; + }; + } + { + goPackagePath = "github.com/hashicorp/golang-lru"; + fetch = { + type = "git"; + url = "https://github.com/hashicorp/golang-lru"; + rev = "59383c442f7d7b190497e9bb8fc17a48d06cd03f"; + sha256 = "0yzwl592aa32vfy73pl7wdc21855w17zssrp85ckw2nisky8rg9c"; + }; + } + { + goPackagePath = "go.opencensus.io"; + fetch = { + type = "git"; + url = "https://github.com/census-instrumentation/opencensus-go"; + rev = "b4a14686f0a98096416fe1b4cb848e384fb2b22b"; + sha256 = "1aidyp301v5ngwsnnc8v1s09vvbsnch1jc4vd615f7qv77r9s7dn"; + }; + } + { + goPackagePath = "golang.org/x/net"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/net"; + rev = "da137c7871d730100384dbcf36e6f8fa493aef5b"; + sha256 = "1qsiyr3irmb6ii06hivm9p2c7wqyxczms1a9v1ss5698yjr3fg47"; + }; + } + { + goPackagePath = "golang.org/x/oauth2"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/oauth2"; + rev = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33"; + sha256 = "06jwpvx0x2gjn2y959drbcir5kd7vg87k0r1216abk6rrdzzrzi2"; + }; + } + { + goPackagePath = "golang.org/x/sys"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/sys"; + rev = "fae7ac547cb717d141c433a2a173315e216b64c4"; + sha256 = "11pl0dycm5d8ar7g1l1w5q2cx0lms8i15n8mxhilhkdd2xpmh8f0"; + }; + } + { + goPackagePath = "golang.org/x/text"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/text"; + rev = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"; + sha256 = "0flv9idw0jm5nm8lx25xqanbkqgfiym6619w575p7nrdh0riqwqh"; + }; + } + { + goPackagePath = "google.golang.org/api"; + fetch = { + type = "git"; + url = "https://code.googlesource.com/google-api-go-client"; + rev = "069bea57b1be6ad0671a49ea7a1128025a22b73f"; + sha256 = "19q2b610lkf3z3y9hn6rf11dd78xr9q4340mdyri7kbijlj2r44q"; + }; + } + { + goPackagePath = "google.golang.org/genproto"; + fetch = { + type = "git"; + url = "https://github.com/google/go-genproto"; + rev = "c506a9f9061087022822e8da603a52fc387115a8"; + sha256 = "03hh80aqi58dqi5ykj4shk3chwkzrgq2f3k6qs5qhgvmcy79y2py"; + }; + } + { + goPackagePath = "google.golang.org/grpc"; + fetch = { + type = "git"; + url = "https://github.com/grpc/grpc-go"; + rev = "977142214c45640483838b8672a43c46f89f90cb"; + sha256 = "05wig23l2sil3bfdv19gq62sya7hsabqj9l8pzr1sm57qsvj218d"; + }; + } +] diff --git a/tools/nixery/index.html b/tools/nixery/static/index.html similarity index 100% rename from tools/nixery/index.html rename to tools/nixery/static/index.html From db1086a5bbb273d50c7a8645daa9c79801040e58 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 22:48:16 +0100 Subject: [PATCH 004/223] feat(main): Add additional envvars to configure Nixery Previously the code had hardcoded paths to runtime data (the Nix builder & web files), which have now been moved into configuration options. Additionally configuration for the application is now centralised in a single config struct, an instance of which is passed around the application. This makes it possible to implement a wrapper in Nix that will configure the runtime data locations automatically. --- tools/nixery/main.go | 81 +++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 29b22f301..5db200fd3 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -32,11 +32,20 @@ import ( "cloud.google.com/go/storage" ) -// ManifestMediaType stores the Content-Type used for the manifest itself. This -// corresponds to the "Image Manifest V2, Schema 2" described on this page: +// config holds the Nixery configuration options. +type config struct { + bucket string // GCS bucket to cache & serve layers + builder string // Nix derivation for building images + web string // Static files to serve over HTTP + port string // Port on which to launch HTTP server +} + +// ManifestMediaType is the Content-Type used for the manifest itself. +// This corresponds to the "Image Manifest V2, Schema 2" described on +// this page: // // https://docs.docker.com/registry/spec/manifest-v2-2/ -const ManifestMediaType string = "application/vnd.docker.distribution.manifest.v2+json" +const manifestMediaType string = "application/vnd.docker.distribution.manifest.v2+json" // Image represents the information necessary for building a container image. This can // be either a list of package names (corresponding to keys in the nixpkgs set) or a @@ -102,16 +111,19 @@ func convenienceNames(packages []string) []string { // Call out to Nix and request that an image be built. Nix will, upon success, return // a manifest for the container image. -func buildImage(image *image, ctx *context.Context, bucket *storage.BucketHandle) ([]byte, error) { - // This file is made available at runtime via Blaze. See the `data` declaration in `BUILD` - nixPath := "experimental/users/tazjin/nixery/build-registry-image.nix" - +func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage.BucketHandle) ([]byte, error) { packages, err := json.Marshal(image.packages) if err != nil { return nil, err } - cmd := exec.Command("nix-build", "--no-out-link", "--show-trace", "--argstr", "name", image.name, "--argstr", "packages", string(packages), nixPath) + cmd := exec.Command( + "nix-build", + "--no-out-link", + "--show-trace", + "--argstr", "name", image.name, + "--argstr", "packages", string(packages), cfg.builder, + ) outpipe, err := cmd.StdoutPipe() if err != nil { @@ -206,11 +218,11 @@ func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer strin // layerRedirect constructs the public URL of the layer object in the Cloud Storage bucket // and redirects the client there. // -// The Docker client is known to follow redirects, but this might not be true for all other -// registry clients. -func layerRedirect(w http.ResponseWriter, bucket string, digest string) { - log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, bucket) - url := fmt.Sprintf("https://storage.googleapis.com/%s/layers/%s", bucket, digest) +// The Docker client is known to follow redirects, but this might not +// be true for all other registry clients. +func layerRedirect(w http.ResponseWriter, cfg *config, digest string) { + log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, cfg.bucket) + url := fmt.Sprintf("https://storage.googleapis.com/%s/layers/%s", cfg.bucket, digest) w.Header().Set("Location", url) w.WriteHeader(303) } @@ -219,15 +231,15 @@ func layerRedirect(w http.ResponseWriter, bucket string, digest string) { // stored after Nix builds. Nixery does not directly serve layers to registry clients, instead it // redirects them to the public URLs of the Cloud Storage bucket. // -// The bucket is required for Nixery to function correctly, hence fatal errors are generated in case -// it fails to be set up correctly. -func prepareBucket(ctx *context.Context, bucket string) *storage.BucketHandle { +// The bucket is required for Nixery to function correctly, hence +// fatal errors are generated in case it fails to be set up correctly. +func prepareBucket(ctx *context.Context, cfg *config) *storage.BucketHandle { client, err := storage.NewClient(*ctx) if err != nil { log.Fatalln("Failed to set up Cloud Storage client:", err) } - bkt := client.Bucket(bucket) + bkt := client.Bucket(cfg.bucket) if _, err := bkt.Attrs(*ctx); err != nil { log.Fatalln("Could not access configured bucket", err) @@ -239,24 +251,29 @@ func prepareBucket(ctx *context.Context, bucket string) *storage.BucketHandle { var manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/(\w+)$`) var layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) -func main() { - bucketName := os.Getenv("BUCKET") - if bucketName == "" { - log.Fatalln("GCS bucket for layer storage must be specified") +func getConfig(key, desc string) string { + value := os.Getenv(key) + if value == "" { + log.Fatalln(desc + " must be specified") } - port := os.Getenv("PORT") - if port == "" { - port = "5726" + return value +} + +func main() { + cfg := &config{ + bucket: getConfig("BUCKET", "GCS bucket for layer storage"), + builder: getConfig("NIX_BUILDER", "Nix image builder code"), + web: getConfig("WEB_DIR", "Static web file dir"), + port: getConfig("PORT", "HTTP port"), } ctx := context.Background() - bucket := prepareBucket(&ctx, bucketName) + bucket := prepareBucket(&ctx, cfg) - log.Printf("Starting Kubernetes Nix controller on port %s\n", port) + log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.port) - log.Fatal(http.ListenAndServe(":"+port, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // When running on AppEngine, HTTP traffic should be redirected to HTTPS. + log.Fatal(http.ListenAndServe(":"+cfg.port, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // // This is achieved here by enforcing HSTS (with a one week duration) on responses. if r.Header.Get("X-Forwarded-Proto") == "http" && strings.Contains(r.Host, "appspot.com") { @@ -265,7 +282,7 @@ func main() { // Serve an index page to anyone who visits the registry's base URL: if r.RequestURI == "/" { - index, _ := ioutil.ReadFile("experimental/users/tazjin/nixery/index.html") + index, _ := ioutil.ReadFile(cfg.web + "/index.html") w.Header().Add("Content-Type", "text/html") w.Write(index) return @@ -283,14 +300,14 @@ func main() { imageName := manifestMatches[1] log.Printf("Requesting manifest for image '%s'", imageName) image := imageFromName(manifestMatches[1]) - manifest, err := buildImage(&image, &ctx, bucket) + manifest, err := buildImage(&ctx, cfg, &image, bucket) if err != nil { log.Println("Failed to build image manifest", err) return } - w.Header().Add("Content-Type", ManifestMediaType) + w.Header().Add("Content-Type", manifestMediaType) w.Write(manifest) return } @@ -300,7 +317,7 @@ func main() { layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) if len(layerMatches) == 3 { digest := layerMatches[2] - layerRedirect(w, bucketName, digest) + layerRedirect(w, cfg, digest) return } From 83145681997d549461e9f3ff15daf819fb1b3e3b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 22:56:18 +0100 Subject: [PATCH 005/223] style(main): Reflow comments to 80 characters maximum --- tools/nixery/main.go | 93 ++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 5db200fd3..044d79151 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -40,31 +40,31 @@ type config struct { port string // Port on which to launch HTTP server } -// ManifestMediaType is the Content-Type used for the manifest itself. -// This corresponds to the "Image Manifest V2, Schema 2" described on -// this page: +// ManifestMediaType is the Content-Type used for the manifest itself. This +// corresponds to the "Image Manifest V2, Schema 2" described on this page: // // https://docs.docker.com/registry/spec/manifest-v2-2/ const manifestMediaType string = "application/vnd.docker.distribution.manifest.v2+json" -// Image represents the information necessary for building a container image. This can -// be either a list of package names (corresponding to keys in the nixpkgs set) or a -// Nix expression that results in a *list* of derivations. +// Image represents the information necessary for building a container image. +// This can be either a list of package names (corresponding to keys in the +// nixpkgs set) or a Nix expression that results in a *list* of derivations. type image struct { // Name of the container image. name string - // Names of packages to include in the image. These must correspond directly to - // top-level names of Nix packages in the nixpkgs tree. + // Names of packages to include in the image. These must correspond + // directly to top-level names of Nix packages in the nixpkgs tree. packages []string } -// BuildResult represents the output of calling the Nix derivation responsible for building -// registry images. +// BuildResult represents the output of calling the Nix derivation responsible +// for building registry images. // -// The `layerLocations` field contains the local filesystem paths to each individual image layer -// that will need to be served, while the `manifest` field contains the JSON-representation of -// the manifest that needs to be served to the client. +// The `layerLocations` field contains the local filesystem paths to each +// individual image layer that will need to be served, while the `manifest` +// field contains the JSON-representation of the manifest that needs to be +// served to the client. // // The later field is simply treated as opaque JSON and passed through. type BuildResult struct { @@ -78,7 +78,8 @@ type BuildResult struct { // imageFromName parses an image name into the corresponding structure which can // be used to invoke Nix. // -// It will expand convenience names under the hood (see the `convenienceNames` function below). +// It will expand convenience names under the hood (see the `convenienceNames` +// function below). func imageFromName(name string) image { packages := strings.Split(name, "/") return image{ @@ -87,15 +88,15 @@ func imageFromName(name string) image { } } -// convenienceNames expands convenience package names defined by Nixery which let users -// include commonly required sets of tools in a container quickly. +// convenienceNames expands convenience package names defined by Nixery which +// let users include commonly required sets of tools in a container quickly. // // Convenience names must be specified as the first package in an image. // // Currently defined convenience names are: // // * `shell`: Includes bash, coreutils and other common command-line tools -// * `builder`: Includes the standard build environment, as well as everything from `shell` +// * `builder`: All of the above and the standard build environment func convenienceNames(packages []string) []string { shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} builderPackages := append(shellPackages, "stdenv") @@ -109,8 +110,8 @@ func convenienceNames(packages []string) []string { } } -// Call out to Nix and request that an image be built. Nix will, upon success, return -// a manifest for the container image. +// Call out to Nix and request that an image be built. Nix will, upon success, +// return a manifest for the container image. func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage.BucketHandle) ([]byte, error) { packages, err := json.Marshal(image.packages) if err != nil { @@ -139,7 +140,7 @@ func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage log.Println("Error starting nix-build:", err) return nil, err } - log.Printf("Started Nix image build for ''%s'", image.name) + log.Printf("Started Nix image build for '%s'", image.name) stdout, _ := ioutil.ReadAll(outpipe) stderr, _ := ioutil.ReadAll(errpipe) @@ -157,8 +158,9 @@ func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage return nil, err } - // The build output returned by Nix is deserialised to add all contained layers to the - // bucket. Only the manifest itself is re-serialised to JSON and returned. + // The build output returned by Nix is deserialised to add all + // contained layers to the bucket. Only the manifest itself is + // re-serialised to JSON and returned. var result BuildResult err = json.Unmarshal(buildOutput, &result) if err != nil { @@ -175,19 +177,20 @@ func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage return json.Marshal(result.Manifest) } -// uploadLayer uploads a single layer to Cloud Storage bucket. Before writing any data -// the bucket is probed to see if the file already exists. +// uploadLayer uploads a single layer to Cloud Storage bucket. Before writing +// any data the bucket is probed to see if the file already exists. // -// If the file does exist, its MD5 hash is verified to ensure that the stored file is -// not - for example - a fragment of a previous, incomplete upload. +// If the file does exist, its MD5 hash is verified to ensure that the stored +// file is not - for example - a fragment of a previous, incomplete upload. func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer string, path string, md5 []byte) error { layerKey := fmt.Sprintf("layers/%s", layer) obj := bucket.Object(layerKey) - // Before uploading a layer to the bucket, probe whether it already exists. + // Before uploading a layer to the bucket, probe whether it already + // exists. // - // If it does and the MD5 checksum matches the expected one, the layer upload - // can be skipped. + // If it does and the MD5 checksum matches the expected one, the layer + // upload can be skipped. attrs, err := obj.Attrs(*ctx) if err == nil && bytes.Equal(attrs.MD5, md5) { @@ -215,11 +218,11 @@ func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer strin return nil } -// layerRedirect constructs the public URL of the layer object in the Cloud Storage bucket -// and redirects the client there. +// layerRedirect constructs the public URL of the layer object in the Cloud +// Storage bucket and redirects the client there. // -// The Docker client is known to follow redirects, but this might not -// be true for all other registry clients. +// The Docker client is known to follow redirects, but this might not be true +// for all other registry clients. func layerRedirect(w http.ResponseWriter, cfg *config, digest string) { log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, cfg.bucket) url := fmt.Sprintf("https://storage.googleapis.com/%s/layers/%s", cfg.bucket, digest) @@ -227,12 +230,13 @@ func layerRedirect(w http.ResponseWriter, cfg *config, digest string) { w.WriteHeader(303) } -// prepareBucket configures the handle to a Cloud Storage bucket in which individual layers will be -// stored after Nix builds. Nixery does not directly serve layers to registry clients, instead it -// redirects them to the public URLs of the Cloud Storage bucket. +// prepareBucket configures the handle to a Cloud Storage bucket in which +// individual layers will be stored after Nix builds. Nixery does not directly +// serve layers to registry clients, instead it redirects them to the public +// URLs of the Cloud Storage bucket. // -// The bucket is required for Nixery to function correctly, hence -// fatal errors are generated in case it fails to be set up correctly. +// The bucket is required for Nixery to function correctly, hence fatal errors +// are generated in case it fails to be set up correctly. func prepareBucket(ctx *context.Context, cfg *config) *storage.BucketHandle { client, err := storage.NewClient(*ctx) if err != nil { @@ -274,13 +278,17 @@ func main() { log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.port) log.Fatal(http.ListenAndServe(":"+cfg.port, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // When running on AppEngine, HTTP traffic should be redirected + // to HTTPS. // - // This is achieved here by enforcing HSTS (with a one week duration) on responses. + // This is achieved here by enforcing HSTS (with a one week + // duration) on responses. if r.Header.Get("X-Forwarded-Proto") == "http" && strings.Contains(r.Host, "appspot.com") { w.Header().Add("Strict-Transport-Security", "max-age=604800") } - // Serve an index page to anyone who visits the registry's base URL: + // Serve an index page to anyone who visits the registry's base + // URL: if r.RequestURI == "/" { index, _ := ioutil.ReadFile(cfg.web + "/index.html") w.Header().Add("Content-Type", "text/html") @@ -312,8 +320,9 @@ func main() { return } - // Serve an image layer. For this we need to first ask Nix for the - // manifest, then proceed to extract the correct layer from it. + // Serve an image layer. For this we need to first ask Nix for + // the manifest, then proceed to extract the correct layer from + // it. layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) if len(layerMatches) == 3 { digest := layerMatches[2] From 5f471392cf074156857ce913002071ed0ac0fce2 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 23:22:18 +0100 Subject: [PATCH 006/223] feat(build): Add wrapper script & container image setup Introduces a wrapper script which automatically sets the paths to the required runtime data dependencies. Additionally configures a container image derivation which will output a derivation with Nixery, Nix and other dependencies. --- tools/nixery/default.nix | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 19e7df963..ebfb41bf6 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -38,6 +38,31 @@ rec { # for the demo instance running at nixery.appspot.com and provides # some background information for what Nixery is. nixery-static = runCommand "nixery-static" {} '' - cp -r ${./static} $out + mkdir $out + cp ${./static}/* $out ''; + + # Wrapper script running the Nixery server with the above two data + # dependencies configured. + # + # In most cases, this will be the derivation a user wants if they + # are installing Nixery directly. + nixery-bin = writeShellScriptBin "nixery" '' + export NIX_BUILDER="${nixery-builder}" + export WEB_DIR="${nixery-static}" + exec ${nixery-server}/bin/nixery + ''; + + # Container image containing Nixery and Nix itself. This image can + # be run on Kubernetes, published on AppEngine or whatever else is + # desired. + nixery-image = dockerTools.buildLayeredImage { + name = "nixery"; + contents = [ + bashInteractive + coreutils + nix + nixery-bin + ]; + }; } From 23260e59d9ca52cb695d14a4722d6a6ee23b163c Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 23:32:56 +0100 Subject: [PATCH 007/223] chore: Add license scaffolding & contribution guidelines --- tools/nixery/CONTRIBUTING.md | 28 ++++ tools/nixery/LICENSE | 202 ++++++++++++++++++++++++++ tools/nixery/build-registry-image.nix | 14 ++ tools/nixery/default.nix | 13 ++ tools/nixery/main.go | 14 ++ 5 files changed, 271 insertions(+) create mode 100644 tools/nixery/CONTRIBUTING.md create mode 100644 tools/nixery/LICENSE diff --git a/tools/nixery/CONTRIBUTING.md b/tools/nixery/CONTRIBUTING.md new file mode 100644 index 000000000..939e5341e --- /dev/null +++ b/tools/nixery/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google.com/conduct/). diff --git a/tools/nixery/LICENSE b/tools/nixery/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/tools/nixery/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index 11030d38a..5c5579fa2 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -1,3 +1,17 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # This file contains a modified version of dockerTools.buildImage that, instead # of outputting a single tarball which can be imported into a running Docker # daemon, builds a manifest file that can be used for serving the image over a diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index ebfb41bf6..c7f284f03 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -1,3 +1,16 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { pkgs ? import {} }: with pkgs; diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 044d79151..c0a57bc6d 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + // Package main provides the implementation of a container registry that // transparently builds container images based on Nix derivations. // From 28bb3924ff35b1adba8e058d7b17bd1e58bacef1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 23:33:22 +0100 Subject: [PATCH 008/223] chore: Add gitignore to ignore Nix build results --- tools/nixery/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tools/nixery/.gitignore diff --git a/tools/nixery/.gitignore b/tools/nixery/.gitignore new file mode 100644 index 000000000..b2dec4547 --- /dev/null +++ b/tools/nixery/.gitignore @@ -0,0 +1,2 @@ +result +result-bin From 18b4ae9f28ae4b56df1529eaca8039e326df64e1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 23 Jul 2019 22:37:40 +0000 Subject: [PATCH 009/223] chore: Remove AppEngine configuration file --- tools/nixery/app.yaml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 tools/nixery/app.yaml diff --git a/tools/nixery/app.yaml b/tools/nixery/app.yaml deleted file mode 100644 index 223fa7582..000000000 --- a/tools/nixery/app.yaml +++ /dev/null @@ -1,14 +0,0 @@ -env: flex -runtime: custom - -resources: - cpu: 2 - memory_gb: 4 - disk_size_gb: 50 - -automatic_scaling: - max_num_instances: 3 - cool_down_period_sec: 60 - -env_variables: - BUCKET: "nixery-layers" From 948f308025e5d1a3a4575b41d4b20d97f363c5c2 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 24 Jul 2019 17:46:39 +0000 Subject: [PATCH 010/223] feat(build): Configure Nixery image builder to set up env correctly When running Nix inside of a container image, there are several environment-specific details that need to be configured appropriately. Most importantly, since one of the recent Nix 2.x releases, sandboxing during builds is enabled by default. This, however, requires kernel privileges which commonly aren't available to containers. Nixery's demo instance (for instance, hehe) is deployed on AppEngine where this type of container configuration is difficult, hence this change. Specifically the following were changed: * additional tools (such as tar/gzip) were introduced into the image because the builtins-toolset in Nix does not reference these tools via their store paths, which leads to them not being included automatically * Nix sandboxing was disabled in the container image * the users/groups required by Nix were added to the container setup. Note that these are being configured manually instead of via the tools from the 'shadow'-package, because the latter requires some user information (such as root) to be present already, which is not the case inside of the container --- tools/nixery/default.nix | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index c7f284f03..64cb061c3 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -36,7 +36,7 @@ rec { meta = { description = "Container image build serving Nix-backed images"; homepage = "https://github.com/google/nixery"; - license = lib.licenses.ascl20; + license = lib.licenses.asl20; maintainers = [ lib.maintainers.tazjin ]; }; }; @@ -69,13 +69,37 @@ rec { # Container image containing Nixery and Nix itself. This image can # be run on Kubernetes, published on AppEngine or whatever else is # desired. - nixery-image = dockerTools.buildLayeredImage { + nixery-image = let + # Wrapper script for the wrapper script (meta!) which configures + # the container environment appropriately. + # + # Most importantly, sandboxing is disabled to avoid privilege + # issues in containers. + nixery-launch-script = writeShellScriptBin "nixery" '' + set -e + export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt + mkdir /tmp + + # Create the build user/group required by Nix + echo 'nixbld:x:30000:nixbld' >> /etc/group + echo 'nixbld:x:30000:30000:nixbld:/tmp:/bin/bash' >> /etc/passwd + + # Disable sandboxing to avoid running into privilege issues + mkdir -p /etc/nix + echo 'sandbox = false' >> /etc/nix/nix.conf + + exec ${nixery-bin}/bin/nixery + ''; + in dockerTools.buildLayeredImage { name = "nixery"; contents = [ bashInteractive + cacert coreutils nix - nixery-bin + nixery-launch-script + gnutar + gzip ]; }; } From 6dd0ac3189559fa4934fabe3bf56850f4865e77f Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 24 Jul 2019 17:53:08 +0000 Subject: [PATCH 011/223] feat(nix): Import nixpkgs from a configured Nix channel Instead of using whatever the current system default is, import a Nix channel when building an image. This will use Nix' internal caching behaviour for tarballs fetched without a SHA-hash. For now the downloaded channel is pinned to nixos-19.03. --- tools/nixery/build-registry-image.nix | 10 ++++++++-- tools/nixery/static/index.html | 13 +++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index 5c5579fa2..25d1f59e7 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -34,10 +34,16 @@ # plenty of room for extension. I believe the actual maximum is # 128. maxLayers ? 24, - # Nix package set to use - pkgs ? (import {}) + # Nix channel to use + channel ? "nixos-19.03" }: +# Import the specified channel directly from Github. +let + channelUrl = "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; + pkgs = import (builtins.fetchTarball channelUrl) {}; +in + # Since this is essentially a re-wrapping of some of the functionality that is # implemented in the dockerTools, we need all of its components in our top-level # namespace. diff --git a/tools/nixery/static/index.html b/tools/nixery/static/index.html index ebec9968c..908fb3821 100644 --- a/tools/nixery/static/index.html +++ b/tools/nixery/static/index.html @@ -75,10 +75,15 @@
  • Which revision of nixpkgs is used?
    - Currently whatever was HEAD at the time I deployed this. One idea I've had is - to let users specify tags on images that correspond to commits in nixpkgs, however there is - some potential for abuse there (e.g. by triggering lots of builds on commits that have - broken Hydra builds) and I don't want to deal with that yet. + Nixery imports a Nix channel + via builtins.fetchTarball. Currently the channel + to which this instance is pinned is NixOS 19.03. +
    + One idea I've had is to let users specify tags on images that + correspond to commits in nixpkgs, however there is some + potential for abuse there (e.g. by triggering lots of builds + on commits that have broken Hydra builds) and I don't want to + deal with that yet.
  • Who made this? From 620778243488df5ab28d7af6ce61c30ed3de5510 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 26 Jul 2019 10:22:31 +0000 Subject: [PATCH 012/223] fix(build): Specify default command for Nixery's own image When running on AppEngine, the image is expected to be configured with a default entry point / command. This sets the command to the wrapper script, so that the image can actually run properly when deployed. --- tools/nixery/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 64cb061c3..23c776e11 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -92,6 +92,7 @@ rec { ''; in dockerTools.buildLayeredImage { name = "nixery"; + config.Cmd = ["${nixery-launch-script}/bin/nixery"]; contents = [ bashInteractive cacert From 93a3985298c31168836a2b3f0585a47889db65ea Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 29 Jul 2019 21:02:46 +0100 Subject: [PATCH 013/223] docs(README): Remove known issues from README These issues have been moved to the issue tracker. --- tools/nixery/README.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 6b1db4696..5a4d766b0 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -44,26 +44,6 @@ bash-4.4# curl --version curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.0.2q zlib/1.2.11 libssh2/1.8.0 nghttp2/1.35.1 ``` -## Known issues - -* Initial build times for an image can be somewhat slow while Nixery retrieves - the required derivations from the Nix cache under-the-hood. - - Due to how the Docker Registry API works, there is no way to provide - feedback to the user during this period - hence the UX (in interactive mode) - is currently that "nothing is happening" for a while after the `Unable to - find image` message is printed. - -* For some reason these images do not currently work in GKE clusters. - Launching a Kubernetes pod that uses a Nixery image results in an error - stating `unable to convert a nil pointer to a runtime API image: - ImageInspectError`. - - This error comes from - [here](https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/dockershim/convert.go#L35) - and it occurs *after* the Kubernetes node has retrieved the image from - Nixery (as per the Nixery logs). - ## Kubernetes integration (in the future) **Note**: The Kubernetes integration is not yet implemented. From 33d876fda87e98477de487cff3b553941eca30db Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 29 Jul 2019 21:03:04 +0100 Subject: [PATCH 014/223] docs(README): Update roadmap information Adds information about Kubernetes integration & custom repository support as well as links to the relevant tracking issues. --- tools/nixery/README.md | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 5a4d766b0..72185047e 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -44,35 +44,25 @@ bash-4.4# curl --version curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.0.2q zlib/1.2.11 libssh2/1.8.0 nghttp2/1.35.1 ``` -## Kubernetes integration (in the future) +## Roadmap -**Note**: The Kubernetes integration is not yet implemented. +### Custom Nix repository support -The basic idea of the Kubernetes integration is to provide a way for users to -specify the contents of a container image as an API object in Kubernetes which -will be transparently built by Nix when the container is started up. +One part of the Nixery vision is support for a custom Nix repository that +provides, for example, the internal packages of an organisation. -For example, given a resource that looks like this: +It should be possible to configure Nixery to build images from such a repository +and serve them in order to make container images themselves close to invisible +to the user. -```yaml ---- -apiVersion: k8s.nixos.org/v1alpha -kind: NixImage -metadata: - name: curl-and-jq -data: - tag: v1 - contents: - - curl - - jq - - bash -``` +See [issue #3](https://github.com/google/nixery/issues/3). -One could create a container that references the `curl-and-jq` image, which will -then be created by Nix when the container image is pulled. +### Kubernetes integration (in the future) -The controller itself runs as a daemonset on every node in the cluster, -providing a host-mounted `/nix/store`-folder for caching purposes. +It should be trivial to deploy Nixery inside of a Kubernetes cluster with +correct caching behaviour, addressing and so on. + +See [issue #4](https://github.com/google/nixery/issues/4). [Nix]: https://nixos.org/ [gist]: https://gist.github.com/tazjin/08f3d37073b3590aacac424303e6f745 From 90948a48e1cbc4b43970ce0a6d55064c0a5eb3e4 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 29 Jul 2019 21:06:47 +0100 Subject: [PATCH 015/223] docs(CONTRIBUTING): Mention commit message format --- tools/nixery/CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/nixery/CONTRIBUTING.md b/tools/nixery/CONTRIBUTING.md index 939e5341e..ecad21b04 100644 --- a/tools/nixery/CONTRIBUTING.md +++ b/tools/nixery/CONTRIBUTING.md @@ -15,6 +15,11 @@ You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. +## Commit messages + +Commits in this repository follow the [Angular commit message +guidelines][commits]. + ## Code reviews All submissions, including submissions by project members, require review. We @@ -26,3 +31,5 @@ information on using pull requests. This project follows [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). + +[commits]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit From fc2e508ab882b10b324ddbf430fcf6365e79b264 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 30 Jul 2019 12:39:29 +0100 Subject: [PATCH 016/223] feat(build): Add Travis configuration to build everything The default Travis build command for Nix is `nix-build`, which will build all derivations specified in the default.nix. --- tools/nixery/.gitignore | 2 +- tools/nixery/.travis.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tools/nixery/.travis.yml diff --git a/tools/nixery/.gitignore b/tools/nixery/.gitignore index b2dec4547..750baebf4 100644 --- a/tools/nixery/.gitignore +++ b/tools/nixery/.gitignore @@ -1,2 +1,2 @@ result -result-bin +result-* diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml new file mode 100644 index 000000000..d8cc8efa4 --- /dev/null +++ b/tools/nixery/.travis.yml @@ -0,0 +1 @@ +language: nix From 02eba0336e30714c859f6344ffc7fd641148d2c1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 30 Jul 2019 13:15:44 +0100 Subject: [PATCH 017/223] refactor(main): Introduce more flexible request routing Instead of just dispatching on URL regexes, use handlers to split the routes into registry-related handlers and otherwise(tm). For now the otherwise(tm) consists of a file server serving the static directory, rather than just a plain match on the index route. --- tools/nixery/main.go | 119 ++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index c0a57bc6d..63004c0ce 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -266,8 +266,13 @@ func prepareBucket(ctx *context.Context, cfg *config) *storage.BucketHandle { return bkt } -var manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/(\w+)$`) -var layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) +// Regexes matching the V2 Registry API routes. This only includes the +// routes required for serving images, since pushing and other such +// functionality is not available. +var ( + manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/(\w+)$`) + layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) +) func getConfig(key, desc string) string { value := os.Getenv(key) @@ -278,12 +283,51 @@ func getConfig(key, desc string) string { return value } +type registryHandler struct { + cfg *config + ctx *context.Context + bucket *storage.BucketHandle +} + +func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Serve the manifest (straight from Nix) + manifestMatches := manifestRegex.FindStringSubmatch(r.RequestURI) + if len(manifestMatches) == 3 { + imageName := manifestMatches[1] + log.Printf("Requesting manifest for image '%s'", imageName) + image := imageFromName(manifestMatches[1]) + manifest, err := buildImage(h.ctx, h.cfg, &image, h.bucket) + + if err != nil { + log.Println("Failed to build image manifest", err) + return + } + + w.Header().Add("Content-Type", manifestMediaType) + w.Write(manifest) + return + } + + // Serve an image layer. For this we need to first ask Nix for + // the manifest, then proceed to extract the correct layer from + // it. + layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) + if len(layerMatches) == 3 { + digest := layerMatches[2] + layerRedirect(w, h.cfg, digest) + return + } + + log.Printf("Unsupported registry route: %s\n", r.RequestURI) + w.WriteHeader(404) +} + func main() { cfg := &config{ - bucket: getConfig("BUCKET", "GCS bucket for layer storage"), + bucket: getConfig("BUCKET", "GCS bucket for layer storage"), builder: getConfig("NIX_BUILDER", "Nix image builder code"), - web: getConfig("WEB_DIR", "Static web file dir"), - port: getConfig("PORT", "HTTP port"), + web: getConfig("WEB_DIR", "Static web file dir"), + port: getConfig("PORT", "HTTP port"), } ctx := context.Background() @@ -291,59 +335,20 @@ func main() { log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.port) - log.Fatal(http.ListenAndServe(":"+cfg.port, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // When running on AppEngine, HTTP traffic should be redirected - // to HTTPS. - // - // This is achieved here by enforcing HSTS (with a one week - // duration) on responses. - if r.Header.Get("X-Forwarded-Proto") == "http" && strings.Contains(r.Host, "appspot.com") { - w.Header().Add("Strict-Transport-Security", "max-age=604800") - } + // Acknowledge that we speak V2 + http.HandleFunc("/v2", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w) + }) - // Serve an index page to anyone who visits the registry's base - // URL: - if r.RequestURI == "/" { - index, _ := ioutil.ReadFile(cfg.web + "/index.html") - w.Header().Add("Content-Type", "text/html") - w.Write(index) - return - } + // All other /v2/ requests belong to the registry handler. + http.Handle("/v2/", ®istryHandler{ + cfg: cfg, + ctx: &ctx, + bucket: bucket, + }) - // Acknowledge that we speak V2 - if r.RequestURI == "/v2/" { - fmt.Fprintln(w) - return - } + // All other roots are served by the static file server. + http.Handle("/", http.FileServer(http.Dir(cfg.web))) - // Serve the manifest (straight from Nix) - manifestMatches := manifestRegex.FindStringSubmatch(r.RequestURI) - if len(manifestMatches) == 3 { - imageName := manifestMatches[1] - log.Printf("Requesting manifest for image '%s'", imageName) - image := imageFromName(manifestMatches[1]) - manifest, err := buildImage(&ctx, cfg, &image, bucket) - - if err != nil { - log.Println("Failed to build image manifest", err) - return - } - - w.Header().Add("Content-Type", manifestMediaType) - w.Write(manifest) - return - } - - // Serve an image layer. For this we need to first ask Nix for - // the manifest, then proceed to extract the correct layer from - // it. - layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) - if len(layerMatches) == 3 { - digest := layerMatches[2] - layerRedirect(w, cfg, digest) - return - } - - w.WriteHeader(404) - }))) + log.Fatal(http.ListenAndServe(":"+cfg.port, nil)) } From 1b37d8ecf62e1f192352b1fc2cc1aafa31325d53 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 30 Jul 2019 13:23:31 +0100 Subject: [PATCH 018/223] feat(static): Add logo & favicon resources --- tools/nixery/.gitignore | 1 + tools/nixery/static/favicon.ico | Bin 0 -> 157995 bytes tools/nixery/static/nixery-logo.png | Bin 0 -> 79360 bytes 3 files changed, 1 insertion(+) create mode 100644 tools/nixery/static/favicon.ico create mode 100644 tools/nixery/static/nixery-logo.png diff --git a/tools/nixery/.gitignore b/tools/nixery/.gitignore index 750baebf4..1e5c28c01 100644 --- a/tools/nixery/.gitignore +++ b/tools/nixery/.gitignore @@ -1,2 +1,3 @@ result result-* +.envrc diff --git a/tools/nixery/static/favicon.ico b/tools/nixery/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..405dbc175b4e94cffb1d4da46c292c3a5fcabf89 GIT binary patch literal 157995 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ð}= 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%0A!&o!X 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 dVPI%z{$v0C^U_@Qn&063>cb`_=#e literal 0 HcmV?d00001 From 9753df9255552b8e37aace321717262919696da3 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 30 Jul 2019 13:25:04 +0100 Subject: [PATCH 019/223] docs(README): Add logo & build status --- tools/nixery/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 72185047e..8d4f84598 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -1,7 +1,13 @@ -# Nixery +
    + +
    -This package implements a Docker-compatible container registry that is capable -of transparently building and serving container images using [Nix][]. +----------------- + +[![Build Status](https://travis-ci.org/google/nixery.svg?branch=master)](https://travis-ci.org/google/nixery) + +**Nixery** is a Docker-compatible container registry that is capable of +transparently building and serving container images using [Nix][]. The project started out with the intention of becoming a Kubernetes controller that can serve declarative image specifications specified in CRDs as container From 4802727408b7afeb8b5245e698053a700ebf6775 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 30 Jul 2019 13:35:30 +0100 Subject: [PATCH 020/223] docs(static): Update index page with post-launch information Points people at the repository and removes some outdated information. --- tools/nixery/static/index.html | 69 ++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/tools/nixery/static/index.html b/tools/nixery/static/index.html index 908fb3821..8cbda7360 100644 --- a/tools/nixery/static/index.html +++ b/tools/nixery/static/index.html @@ -14,6 +14,10 @@ padding: 010px } + .logo { + max-width: 650px; + } + h1, h2, h3 { line-height: 1.2 } @@ -21,56 +25,55 @@
    -

    Nixery

    +
    + +
    +
    -

    What is this?

    +

    - Nixery provides the ability to pull ad-hoc container images from a Docker-compatible registry - server. The image names specify the contents the image should contain, which are then - retrieved and built by the Nix package manager. + This is an instance + of Nixery, which + provides the ability to pull ad-hoc container images from a + Docker-compatible registry server. The image names specify the + contents the image should contain, which are then retrieved and + built by the Nix package manager.

    - Nix is also responsible for the creation of the container images themselves. To do this it - uses an interesting layering strategy described in + Nix is also responsible for the creation of the container images + themselves. To do this it uses an interesting layering strategy + described in this blog post.

    How does it work?

    - Simply point your local Docker installation (or other compatible registry client) at Nixery - and ask for an image with the contents you desire. Image contents are path separated in the - name, so for example if you needed an image that contains a shell and emacs you - could pull it as such: + Simply point your local Docker installation (or other compatible + registry client) at Nixery and ask for an image with the + contents you desire. Image contents are path separated in the + name, so for example if you needed an image that contains a + shell and emacs you could pull it as such:

    nixery.appspot.com/shell/emacs25-nox

    - Image tags are currently ignored. Every package name needs to correspond to a key in the + Image tags are currently ignored. Every package name needs to + correspond to a key in the nixpkgs package set.

    - There are some special meta-packages which you must specify as the - first package in an image. These are: -

    -
      -
    • shell: Provides default packages you would expect in an interactive environment
    • -
    • builder: Provides the above as well as Nix's standard build environment
    • -
    -

    - Hence if you needed an interactive image with, for example, htop installed you - could run docker run -ti nixery.appspot.com/shell/htop bash. + The special meta-package shell provides default packages + you would expect in an interactive environment (such as an + interactively configured bash). If you use this package + you must specify it as the first package in an image.

    FAQ

    -

    - Technically speaking none of these are frequently-asked questions (because no questions have - been asked so far), but I'm going to take a guess at a few anyways: -

    • Where is the source code for this?
      - Not yet public, sorry. Check back later(tm). + Over on Github.
    • Which revision of nixpkgs is used? @@ -78,17 +81,17 @@ Nixery imports a Nix channel via builtins.fetchTarball. Currently the channel to which this instance is pinned is NixOS 19.03. +
    • +
    • + Is this an official Google project?
      - One idea I've had is to let users specify tags on images that - correspond to commits in nixpkgs, however there is some - potential for abuse there (e.g. by triggering lots of builds - on commits that have broken Hydra builds) and I don't want to - deal with that yet. + No. Nixery is not officially supported by + Google.
    • Who made this?
      - @tazjin + tazjin
    From 2e4b1f85eef04c0abd83f5f47e1aa93c8ea84cb9 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 30 Jul 2019 23:55:28 +0100 Subject: [PATCH 021/223] fix(nix): Add empty image config to allow k8s usage Introduce an empty runtime configuration object in each built layer. This is required because Kubernetes expects the configuration to be present (even if it's just empty values). Providing an empty configuration will make Docker's API return a full configuration struct with default (i.e. empty) values rather than `null`, which works for Kubernetes. This fixes issue #1. See the issue for additional details. --- tools/nixery/build-registry-image.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index 25d1f59e7..0d61f3b71 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -133,6 +133,8 @@ let os = "linux"; rootfs.type = "layers"; rootfs.diff_ids = map (layer: "sha256:${layer.sha256}") allLayers; + # Required to let Kubernetes import Nixery images + config = {}; }; configJson = writeText "${baseName}-config.json" (builtins.toJSON config); configMetadata = with builtins; fromJSON (readFile (runCommand "config-meta" { From a83701a14b4b620d9b24656e638d31e746f3a06e Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 31 Jul 2019 00:39:31 +0100 Subject: [PATCH 022/223] feat(build): Add dependencies for custom repo clones Adds git & SSH as part of the Nixery image, which are required to use Nix's builtins.fetchGit. The dependency on interactive tools is dropped, as it was only required during development when debugging the image building process itself. --- tools/nixery/default.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 23c776e11..0f2891166 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -94,13 +94,13 @@ rec { name = "nixery"; config.Cmd = ["${nixery-launch-script}/bin/nixery"]; contents = [ - bashInteractive cacert - coreutils - nix - nixery-launch-script + git gnutar gzip + nix + nixery-launch-script + openssh ]; }; } From 2db92243e748bba726e9d829f89f65dd5bf16afd Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 31 Jul 2019 01:45:09 +0100 Subject: [PATCH 023/223] feat(nix): Support package set imports from different sources This extends the package set import mechanism in build-registry-image.nix with several different options: 1. Importing a nixpkgs channel from Github (the default, pinned to nixos-19.03) 2. Importing a custom Nix git repository. This uses builtins.fetchGit and can thus rely on git/SSH configuration in the environment (such as keys) 3. Importing a local filesystem path As long as the repository pointed at is either a checkout of nixpkgs, or nixpkgs overlaid with custom packages this will work. A special syntax has been defined for how these three options are passed in, but users should not need to concern themselves with it as it will be taken care of by the server component. This relates to #3. --- tools/nixery/build-registry-image.nix | 62 ++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index 0d61f3b71..931507113 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -34,14 +34,66 @@ # plenty of room for extension. I believe the actual maximum is # 128. maxLayers ? 24, - # Nix channel to use - channel ? "nixos-19.03" + + # Configuration for which package set to use when building. + # + # Both channels of the public nixpkgs repository as well as imports + # from private repositories are supported. + # + # This setting can be invoked with three different formats: + # + # 1. nixpkgs!$channel (e.g. nixpkgs!nixos-19.03) + # 2. git!$repo!$rev (e.g. git!git@github.com:NixOS/nixpkgs.git!master) + # 3. path!$path (e.g. path!/var/local/nixpkgs) + # + # '!' was chosen as the separator because `builtins.split` does not + # support regex escapes and there are few other candidates. It + # doesn't matter much because this is invoked by the server. + pkgSource ? "nixpkgs!nixos-19.03" }: -# Import the specified channel directly from Github. let - channelUrl = "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; - pkgs = import (builtins.fetchTarball channelUrl) {}; + # If a nixpkgs channel is requested, it is retrieved from Github (as + # a tarball) and imported. + fetchImportChannel = channel: + let url = "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; + in import (builtins.fetchTarball url) {}; + + # If a git repository is requested, it is retrieved via + # builtins.fetchGit which defaults to the git configuration of the + # outside environment. This means that user-configured SSH + # credentials etc. are going to work as expected. + fetchImportGit = url: rev: + let + # builtins.fetchGit needs to know whether 'rev' is a reference + # (e.g. a branch/tag) or a revision (i.e. a commit hash) + # + # Since this data is being extrapolated from the supplied image + # tag, we have to guess if we want to avoid specifying a format. + # + # There are some additional caveats around whether the default + # branch contains the specified revision, which need to be + # explained to users. + spec = if (builtins.stringLength rev) == 40 then { + inherit url rev; + } else { + inherit url; + ref = rev; + }; + in import (builtins.fetchGit spec) {}; + + importPath = path: import (builtins.toPath path) {}; + + source = builtins.split "!" pkgSource; + sourceType = builtins.elemAt source 0; + pkgs = with builtins; + if sourceType == "nixpkgs" + then fetchImportChannel (elemAt source 2) + else if sourceType == "git" + then fetchImportGit (elemAt source 2) (elemAt source 4) + else if sourceType == "path" + then importPath (elemAt source 2) + else builtins.throw("Invalid package set source specification: ${pkgSource}"); in # Since this is essentially a re-wrapping of some of the functionality that is From 3bc04530a7fc27a0196ed3640611554c197843cb Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 31 Jul 2019 14:17:11 +0100 Subject: [PATCH 024/223] feat(go): Add environment configuration for package set sources Adds environment variables with which users can configure the package set source to use. Not setting a source lets Nix default to a recent NixOS channel (currently nixos-19.03). --- tools/nixery/main.go | 85 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 63004c0ce..faee731b4 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -46,12 +46,65 @@ import ( "cloud.google.com/go/storage" ) +// pkgSource represents the source from which the Nix package set used +// by Nixery is imported. Users configure the source by setting one of +// the supported environment variables. +type pkgSource struct { + srcType string + args string +} + +// Convert the package source into the representation required by Nix. +func (p *pkgSource) renderSource(tag string) string { + // The 'git' source requires a tag to be present. + if p.srcType == "git" { + if tag == "latest" || tag == "" { + tag = "master" + } + + return fmt.Sprintf("git!%s!%s", p.args, tag) + } + + return fmt.Sprintf("%s!%s", p.srcType, p.args) +} + +// Retrieve a package source from the environment. If no source is +// specified, the Nix code will default to a recent NixOS channel. +func pkgSourceFromEnv() *pkgSource { + if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { + log.Printf("Using Nix package set from Nix channel %q\n", channel) + return &pkgSource{ + srcType: "nixpkgs", + args: channel, + } + } + + if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { + log.Printf("Using Nix package set from git repository at %q\n", git) + return &pkgSource{ + srcType: "git", + args: git, + } + } + + if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { + log.Printf("Using Nix package set from path %q\n", path) + return &pkgSource{ + srcType: "path", + args: path, + } + } + + return nil +} + // config holds the Nixery configuration options. type config struct { - bucket string // GCS bucket to cache & serve layers - builder string // Nix derivation for building images - web string // Static files to serve over HTTP - port string // Port on which to launch HTTP server + bucket string // GCS bucket to cache & serve layers + builder string // Nix derivation for building images + web string // Static files to serve over HTTP + port string // Port on which to launch HTTP server + pkgs *pkgSource // Source for Nix package set } // ManifestMediaType is the Content-Type used for the manifest itself. This @@ -67,6 +120,9 @@ type image struct { // Name of the container image. name string + // Tag requested (only relevant for package sets from git repositories) + tag string + // Names of packages to include in the image. These must correspond // directly to top-level names of Nix packages in the nixpkgs tree. packages []string @@ -94,10 +150,11 @@ type BuildResult struct { // // It will expand convenience names under the hood (see the `convenienceNames` // function below). -func imageFromName(name string) image { +func imageFromName(name string, tag string) image { packages := strings.Split(name, "/") return image{ name: name, + tag: tag, packages: convenienceNames(packages), } } @@ -132,13 +189,17 @@ func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage return nil, err } - cmd := exec.Command( - "nix-build", + args := []string{ "--no-out-link", "--show-trace", "--argstr", "name", image.name, "--argstr", "packages", string(packages), cfg.builder, - ) + } + + if cfg.pkgs != nil { + args = append(args, "--argstr", "pkgSource", cfg.pkgs.renderSource(image.tag)) + } + cmd := exec.Command("nix-build", args...) outpipe, err := cmd.StdoutPipe() if err != nil { @@ -270,7 +331,7 @@ func prepareBucket(ctx *context.Context, cfg *config) *storage.BucketHandle { // routes required for serving images, since pushing and other such // functionality is not available. var ( - manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/(\w+)$`) + manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) ) @@ -294,8 +355,9 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { manifestMatches := manifestRegex.FindStringSubmatch(r.RequestURI) if len(manifestMatches) == 3 { imageName := manifestMatches[1] - log.Printf("Requesting manifest for image '%s'", imageName) - image := imageFromName(manifestMatches[1]) + imageTag := manifestMatches[2] + log.Printf("Requesting manifest for image %q at tag %q", imageName, imageTag) + image := imageFromName(imageName, imageTag) manifest, err := buildImage(h.ctx, h.cfg, &image, h.bucket) if err != nil { @@ -328,6 +390,7 @@ func main() { builder: getConfig("NIX_BUILDER", "Nix image builder code"), web: getConfig("WEB_DIR", "Static web file dir"), port: getConfig("PORT", "HTTP port"), + pkgs: pkgSourceFromEnv(), } ctx := context.Background() From ec8e9eed5db5dc76d161257fb26b463326ec9c81 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 31 Jul 2019 14:36:32 +0100 Subject: [PATCH 025/223] docs(README): Revamp with updated information on package sources Adds documentation for configuration options and supported features. --- tools/nixery/README.md | 87 ++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 8d4f84598..f100cb1b6 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -9,28 +9,20 @@ **Nixery** is a Docker-compatible container registry that is capable of transparently building and serving container images using [Nix][]. -The project started out with the intention of becoming a Kubernetes controller -that can serve declarative image specifications specified in CRDs as container -images. The design for this is outlined in [a public gist][gist]. - -Currently it focuses on the ad-hoc creation of container images as outlined -below with an example instance available at -[nixery.appspot.com](https://nixery.appspot.com). - -This is not an officially supported Google project. - -## Ad-hoc container images - -Nixery supports building images on-demand based on the *image name*. Every -package that the user intends to include in the image is specified as a path -component of the image name. +Images are built on-demand based on the *image name*. Every package that the +user intends to include in the image is specified as a path component of the +image name. The path components refer to top-level keys in `nixpkgs` and are used to build a container image using Nix's [buildLayeredImage][] functionality. -The special meta-package `shell` provides an image base with many core -components (such as `bash` and `coreutils`) that users commonly expect in -interactive images. +The project started out with the intention of becoming a Kubernetes controller +that can serve declarative image specifications specified in CRDs as container +images. The design for this is outlined in [a public gist][gist]. + +An example instance is available at [nixery.appspot.com][demo]. + +This is not an officially supported Google project. ## Usage example @@ -50,19 +42,54 @@ bash-4.4# curl --version curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.0.2q zlib/1.2.11 libssh2/1.8.0 nghttp2/1.35.1 ``` +The special meta-package `shell` provides an image base with many core +components (such as `bash` and `coreutils`) that users commonly expect in +interactive images. + +## Feature overview + +* Serve container images on-demand using image names as content specifications + + Specify package names as path components and Nixery will create images, using + the most efficient caching strategy it can to share data between different + images. + +* Use private package sets from various sources + + In addition to building images from the publicly available Nix/NixOS channels, + a private Nixery instance can be configured to serve images built from a + package set hosted in a custom git repository or filesystem path. + + When using this feature with custom git repositories, Nixery will forward the + specified image tags as git references. + + For example, if a company used a custom repository overlaying their packages + on the Nix package set, images could be built from a git tag `release-v2`: + + `docker pull nixery.thecompany.website/custom-service:release-v2` + +* Efficient serving of image layers from Google Cloud Storage + + After building an image, Nixery stores all of its layers in a GCS bucket and + forwards requests to retrieve layers to the bucket. This enables efficient + serving of layers, as well as sharing of image layers between redundant + instances. + +## Configuration + +Nixery supports the following configuration options, provided via environment +variables: + +* `BUCKET`: [Google Cloud Storage][gcs] bucket to store & serve image layers +* `PORT`: HTTP port on which Nixery should listen +* `NIXERY_CHANNEL`: The name of a Nix/NixOS channel to use for building +* `NIXERY_PKGS_REPO`: URL of a git repository containing a package set (uses + locally configured SSH/git credentials) +* `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use + for building + ## Roadmap -### Custom Nix repository support - -One part of the Nixery vision is support for a custom Nix repository that -provides, for example, the internal packages of an organisation. - -It should be possible to configure Nixery to build images from such a repository -and serve them in order to make container images themselves close to invisible -to the user. - -See [issue #3](https://github.com/google/nixery/issues/3). - ### Kubernetes integration (in the future) It should be trivial to deploy Nixery inside of a Kubernetes cluster with @@ -73,3 +100,5 @@ See [issue #4](https://github.com/google/nixery/issues/4). [Nix]: https://nixos.org/ [gist]: https://gist.github.com/tazjin/08f3d37073b3590aacac424303e6f745 [buildLayeredImage]: https://grahamc.com/blog/nix-and-layered-docker-images +[demo]: https://nixery.appspot.com +[gcs]: https://cloud.google.com/storage/ From 3070d88051674bc5562412b8a98dbd90e92959f0 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 31 Jul 2019 21:23:44 +0100 Subject: [PATCH 026/223] feat(nix): Return structured errors if packages are not found Changes the return format of Nixery's build procedure to return a JSON structure that can indicate which errors have occured. The server can use this information to send appropriate status codes back to clients. --- tools/nixery/build-registry-image.nix | 47 +++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index 931507113..d06c9f3bf 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -99,6 +99,7 @@ in # Since this is essentially a re-wrapping of some of the functionality that is # implemented in the dockerTools, we need all of its components in our top-level # namespace. +with builtins; with pkgs; with dockerTools; @@ -115,16 +116,29 @@ let # For example, `deepFetch pkgs "xorg.xev"` retrieves `pkgs.xorg.xev`. deepFetch = s: n: let path = lib.strings.splitString "." n; - err = builtins.throw "Could not find '${n}' in package set"; + err = { error = "not_found"; pkg = n; }; in lib.attrsets.attrByPath path err s; # allContents is the combination of all derivations and store paths passed in # directly, as well as packages referred to by name. - allContents = contents ++ (map (deepFetch pkgs) (builtins.fromJSON packages)); + # + # It accumulates potential errors about packages that could not be found to + # return this information back to the server. + allContents = + # Folds over the results of 'deepFetch' on all requested packages to + # separate them into errors and content. This allows the program to + # terminate early and return only the errors if any are encountered. + let splitter = attrs: res: + if hasAttr "error" res + then attrs // { errors = attrs.errors ++ [ res ]; } + else attrs // { contents = attrs.contents ++ [ res ]; }; + init = { inherit contents; errors = []; }; + fetched = (map (deepFetch pkgs) (fromJSON packages)); + in foldl' splitter init fetched; contentsEnv = symlinkJoin { name = "bulk-layers"; - paths = allContents; + paths = allContents.contents; }; # The image build infrastructure expects to be outputting a slightly different @@ -176,7 +190,7 @@ let cat fs-layers | jq -s -c '.' > $out ''; - allLayers = builtins.fromJSON (builtins.readFile allLayersJson); + allLayers = fromJSON (readFile allLayersJson); # Image configuration corresponding to the OCI specification for the file type # 'application/vnd.oci.image.config.v1+json' @@ -188,8 +202,8 @@ let # Required to let Kubernetes import Nixery images config = {}; }; - configJson = writeText "${baseName}-config.json" (builtins.toJSON config); - configMetadata = with builtins; fromJSON (readFile (runCommand "config-meta" { + configJson = writeText "${baseName}-config.json" (toJSON config); + configMetadata = fromJSON (readFile (runCommand "config-meta" { buildInputs = [ jq openssl ]; } '' size=$(wc -c ${configJson} | cut -d ' ' -f1) @@ -228,7 +242,7 @@ let path = configJson; md5 = configMetadata.md5; }; - } // (builtins.listToAttrs (map (layer: { + } // (listToAttrs (map (layer: { name = "${layer.sha256}"; value = { path = layer.path; @@ -236,6 +250,19 @@ let }; }) allLayers)); -in writeText "manifest-output.json" (builtins.toJSON { - inherit manifest layerLocations; -}) + # Final output structure returned to the controller in the case of a + # successful build. + manifestOutput = { + inherit manifest layerLocations; + }; + + # Output structure returned if errors occured during the build. Currently the + # only error type that is returned in a structured way is 'not_found'. + errorOutput = { + error = "not_found"; + pkgs = map (err: err.pkg) allContents.errors; + }; +in writeText "manifest-output.json" (if (length allContents.errors) == 0 + then toJSON (trace manifestOutput manifestOutput) + else toJSON (trace errorOutput errorOutput) +) From 2f1bc55597c382496930f4ba8f4ef25914fb9748 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 31 Jul 2019 21:25:36 +0100 Subject: [PATCH 027/223] fix(go): Return response code 500 if Nix builds fail --- tools/nixery/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index faee731b4..a4e0b9a80 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -362,6 +362,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("Failed to build image manifest", err) + w.WriteHeader(500) return } From 119af77b43775999e39b6e88911d46b5cc60b93d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 31 Jul 2019 21:36:25 +0100 Subject: [PATCH 028/223] feat(go): Return errors with correct status codes to clients Uses the structured errors feature introduced in the Nix code to return more sensible errors to clients. For now this is quite limited, but already a lot better than before: * packages that could not be found result in 404s * all other errors result in 500s This way the registry clients will not attempt to interpret the returned garbage data/empty response as something useful. --- tools/nixery/main.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index a4e0b9a80..91a9e6d49 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -138,6 +138,9 @@ type image struct { // // The later field is simply treated as opaque JSON and passed through. type BuildResult struct { + Error string `json:"error` + Pkgs []string `json:"pkgs"` + Manifest json.RawMessage `json:"manifest"` LayerLocations map[string]struct { Path string `json:"path"` @@ -183,7 +186,7 @@ func convenienceNames(packages []string) []string { // Call out to Nix and request that an image be built. Nix will, upon success, // return a manifest for the container image. -func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage.BucketHandle) ([]byte, error) { +func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage.BucketHandle) (*BuildResult, error) { packages, err := json.Marshal(image.packages) if err != nil { return nil, err @@ -249,7 +252,7 @@ func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage } } - return json.Marshal(result.Manifest) + return &result, nil } // uploadLayer uploads a single layer to Cloud Storage bucket. Before writing @@ -358,7 +361,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { imageTag := manifestMatches[2] log.Printf("Requesting manifest for image %q at tag %q", imageName, imageTag) image := imageFromName(imageName, imageTag) - manifest, err := buildImage(h.ctx, h.cfg, &image, h.bucket) + buildResult, err := buildImage(h.ctx, h.cfg, &image, h.bucket) if err != nil { log.Println("Failed to build image manifest", err) @@ -366,6 +369,17 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + // Some error types have special handling, which is applied + // here. + if buildResult.Error == "not_found" { + log.Printf("Could not find packages: %v\n", buildResult.Pkgs) + w.WriteHeader(404) + return + } + + // This marshaling error is ignored because we know that this + // field represents valid JSON data. + manifest, _ := json.Marshal(buildResult.Manifest) w.Header().Add("Content-Type", manifestMediaType) w.Write(manifest) return From 3d0596596ac03957db6646836e6b4ec0d222e23a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 2 Aug 2019 00:45:22 +0100 Subject: [PATCH 029/223] feat(go): Return error responses in registry format The registry specifies a format for how errors should be returned and this commit implements it: https://docs.docker.com/registry/spec/api/#errors --- tools/nixery/main.go | 46 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 91a9e6d49..9574a3a68 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -138,7 +138,7 @@ type image struct { // // The later field is simply treated as opaque JSON and passed through. type BuildResult struct { - Error string `json:"error` + Error string `json:"error"` Pkgs []string `json:"pkgs"` Manifest json.RawMessage `json:"manifest"` @@ -338,13 +338,29 @@ var ( layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) ) -func getConfig(key, desc string) string { - value := os.Getenv(key) - if value == "" { - log.Fatalln(desc + " must be specified") - } +// Error format corresponding to the registry protocol V2 specification. This +// allows feeding back errors to clients in a way that can be presented to +// users. +type registryError struct { + Code string `json:"code"` + Message string `json:"message"` +} - return value +type registryErrors struct { + Errors []registryError `json:"errors"` +} + +func writeError(w http.ResponseWriter, status int, code, message string) { + err := registryErrors{ + Errors: []registryError{ + {code, message}, + }, + } + json, _ := json.Marshal(err) + + w.WriteHeader(status) + w.Header().Add("Content-Type", "application/json") + w.Write(json) } type registryHandler struct { @@ -364,16 +380,17 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { buildResult, err := buildImage(h.ctx, h.cfg, &image, h.bucket) if err != nil { + writeError(w, 500, "UNKNOWN", "image build failure") log.Println("Failed to build image manifest", err) - w.WriteHeader(500) return } // Some error types have special handling, which is applied // here. if buildResult.Error == "not_found" { - log.Printf("Could not find packages: %v\n", buildResult.Pkgs) - w.WriteHeader(404) + s := fmt.Sprintf("Could not find Nix packages: %v", buildResult.Pkgs) + writeError(w, 404, "MANIFEST_UNKNOWN", s) + log.Println(s) return } @@ -399,6 +416,15 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) } +func getConfig(key, desc string) string { + value := os.Getenv(key) + if value == "" { + log.Fatalln(desc + " must be specified") + } + + return value +} + func main() { cfg := &config{ bucket: getConfig("BUCKET", "GCS bucket for layer storage"), From bf34bb327ccdd4a8f1ff5dd10a9197e5114b7379 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 2 Aug 2019 01:02:40 +0100 Subject: [PATCH 030/223] fix(nix): Calculate MD5 sum of config layer correctly The MD5 sum is used for verifying contents in the layer cache before accidentally re-uploading, but the syntax of the hash invocation was incorrect leading to a cache-bust on the manifest layer on every single build (even for identical images). --- tools/nixery/build-registry-image.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index d06c9f3bf..20eb6d9e9 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -208,7 +208,7 @@ let } '' size=$(wc -c ${configJson} | cut -d ' ' -f1) sha256=$(sha256sum ${configJson} | cut -d ' ' -f1) - md5=$(openssl dgst -md5 -binary $layerPath | openssl enc -base64) + md5=$(openssl dgst -md5 -binary ${configJson} | openssl enc -base64) jq -n -c --arg size $size --arg sha256 $sha256 --arg md5 $md5 \ '{ size: ($size | tonumber), sha256: $sha256, md5: $md5 }' \ >> $out From 92f1758014f49ecc2edcf883e6294bec636e69aa Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 2 Aug 2019 01:11:26 +0100 Subject: [PATCH 031/223] docs(static): Note that the demo instance is just a demo People should not start depending on the demo instance. There have been discussions around making a NixOS-official instance, but the project needs to mature a little bit first. --- tools/nixery/static/index.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/nixery/static/index.html b/tools/nixery/static/index.html index 8cbda7360..dc37eebc2 100644 --- a/tools/nixery/static/index.html +++ b/tools/nixery/static/index.html @@ -88,6 +88,16 @@ No. Nixery is not officially supported by Google.
  • +
  • + Can I depend on the demo instance in production? +
    + No. The demo instance is just a demo. It + might go down, move, or disappear entirely at any point. +
    + To make use of Nixery in your project, please deploy a private + instance. Stay tuned for instructions for how to do this on + GKE. +
  • Who made this?
    From 02dfff393a2afa3b0be3fe134d12aebce9d47c27 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 2 Aug 2019 01:28:55 +0100 Subject: [PATCH 032/223] fix(build): coreutils are still required by launch script Mea culpa! --- tools/nixery/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 0f2891166..2b3ec0ef4 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -95,6 +95,7 @@ rec { config.Cmd = ["${nixery-launch-script}/bin/nixery"]; contents = [ cacert + coreutils git gnutar gzip From 1f885e43b67dd26ec0b1bcfff9411bddc2674bea Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 2 Aug 2019 17:00:32 +0100 Subject: [PATCH 033/223] style(static): Update Nixery logo to a healthier version This might not yet be the final version, but it's going in the right direction. Additionally the favicon has been reduced to just the coloured Nix logo, because details are pretty much invisible at that size anyways. --- tools/nixery/static/favicon.ico | Bin 157995 -> 140163 bytes tools/nixery/static/nixery-logo.png | Bin 79360 -> 194153 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/nixery/static/favicon.ico b/tools/nixery/static/favicon.ico index 405dbc175b4e94cffb1d4da46c292c3a5fcabf89..fbaad005001fb83a089069bdb301f6376660078f 100644 GIT binary patch literal 140163 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ð}= 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∓vKfbqlg&Sjtg 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 z%0D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.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 literal 79360 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%0A!&o!X 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 dVPI%z{$v0C^U_@Qn&063>cb`_=#e From da5df525c8e7ca9e3907a4f90412883dfc1b28a7 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 2 Aug 2019 17:05:33 +0100 Subject: [PATCH 034/223] docs: Update all nixery.appspot.com references to nixery.dev Shiny, new domain is much better and eliminates the TLS redirect issue because there is a HSTS preload for the entire .dev TLD (which, by the way, is awesome!) --- tools/nixery/README.md | 12 ++++++------ tools/nixery/default.nix | 4 ++-- tools/nixery/static/index.html | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index f100cb1b6..e0e634c3c 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -20,24 +20,24 @@ The project started out with the intention of becoming a Kubernetes controller that can serve declarative image specifications specified in CRDs as container images. The design for this is outlined in [a public gist][gist]. -An example instance is available at [nixery.appspot.com][demo]. +An example instance is available at [nixery.dev][demo]. This is not an officially supported Google project. ## Usage example -Using the publicly available Nixery instance at `nixery.appspot.com`, one could +Using the publicly available Nixery instance at `nixery.dev`, one could retrieve a container image containing `curl` and an interactive shell like this: ```shell -tazjin@tazbox:~$ sudo docker run -ti nixery.appspot.com/shell/curl bash -Unable to find image 'nixery.appspot.com/shell/curl:latest' locally +tazjin@tazbox:~$ sudo docker run -ti nixery.dev/shell/curl bash +Unable to find image 'nixery.dev/shell/curl:latest' locally latest: Pulling from shell/curl 7734b79e1ba1: Already exists b0d2008d18cd: Pull complete < ... some layers omitted ...> Digest: sha256:178270bfe84f74548b6a43347d73524e5c2636875b673675db1547ec427cf302 -Status: Downloaded newer image for nixery.appspot.com/shell/curl:latest +Status: Downloaded newer image for nixery.dev/shell/curl:latest bash-4.4# curl --version curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.0.2q zlib/1.2.11 libssh2/1.8.0 nghttp2/1.35.1 ``` @@ -100,5 +100,5 @@ See [issue #4](https://github.com/google/nixery/issues/4). [Nix]: https://nixos.org/ [gist]: https://gist.github.com/tazjin/08f3d37073b3590aacac424303e6f745 [buildLayeredImage]: https://grahamc.com/blog/nix-and-layered-docker-images -[demo]: https://nixery.appspot.com +[demo]: https://nixery.dev [gcs]: https://cloud.google.com/storage/ diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 2b3ec0ef4..8a7ce8b34 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -48,8 +48,8 @@ rec { ''; # Static files to serve on the Nixery index. This is used primarily - # for the demo instance running at nixery.appspot.com and provides - # some background information for what Nixery is. + # for the demo instance running at nixery.dev and provides some + # background information for what Nixery is. nixery-static = runCommand "nixery-static" {} '' mkdir $out cp ${./static}/* $out diff --git a/tools/nixery/static/index.html b/tools/nixery/static/index.html index dc37eebc2..24aa879a5 100644 --- a/tools/nixery/static/index.html +++ b/tools/nixery/static/index.html @@ -55,7 +55,7 @@ shell and emacs you could pull it as such:

    - nixery.appspot.com/shell/emacs25-nox + nixery.dev/shell/emacs25-nox

    Image tags are currently ignored. Every package name needs to From a4c0d3e8d30b69d3e1d5e3a2665475c6a6a72634 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 3 Aug 2019 00:38:32 +0100 Subject: [PATCH 035/223] chore(go): Remove 'builder' metapackage This metapackage isn't actually particularly useful (stdenv is rarely what users want). --- tools/nixery/main.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 9574a3a68..54dd8ab4d 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -173,15 +173,12 @@ func imageFromName(name string, tag string) image { // * `builder`: All of the above and the standard build environment func convenienceNames(packages []string) []string { shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} - builderPackages := append(shellPackages, "stdenv") if packages[0] == "shell" { return append(packages[1:], shellPackages...) - } else if packages[0] == "builder" { - return append(packages[1:], builderPackages...) - } else { - return packages } + + return packages } // Call out to Nix and request that an image be built. Nix will, upon success, From c84543a9b5be48c55924a0f6f948cecb6e138808 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 3 Aug 2019 00:44:32 +0100 Subject: [PATCH 036/223] style(static): Fix favicon background colour --- tools/nixery/static/favicon.ico | Bin 140163 -> 167584 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/nixery/static/favicon.ico b/tools/nixery/static/favicon.ico index fbaad005001fb83a089069bdb301f6376660078f..7523e8513950d695daee7106bed83d7bebd9eaa9 100644 GIT binary patch literal 167584 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 z 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 KM=3W* Date: Sat, 3 Aug 2019 01:10:04 +0100 Subject: [PATCH 037/223] =?UTF-8?q?fix(static):=20Fix=20logo=20nitpick=20(?= =?UTF-8?q?smoothened=20=CE=BB=20edges)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/nixery/static/nixery-logo.png | Bin 194153 -> 194098 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/nixery/static/nixery-logo.png b/tools/nixery/static/nixery-logo.png index 7ef2f25ee68a89a7a4a11231fb055cc1462d6858..fcf77df3d6a97b4a142fac3cc2061695cab2de3e 100644 GIT binary patch delta 171634 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

    X$( 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

    {59df)RxS%^I#Iaf9f#5; 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 z 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 dp*05EAM(o+#P2?Qs0&^=|w>;1B=+ From ecee1ec1b888bb2ee745e4255158c829b78c3d3d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 3 Aug 2019 01:11:05 +0100 Subject: [PATCH 038/223] chore: Prevent accidental key leaks via gitignore --- tools/nixery/.gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/nixery/.gitignore b/tools/nixery/.gitignore index 1e5c28c01..4203fee19 100644 --- a/tools/nixery/.gitignore +++ b/tools/nixery/.gitignore @@ -1,3 +1,9 @@ result result-* .envrc +debug/ + +# Just to be sure, since we're occasionally handling test keys: +*.pem +*.p12 +*.json From 3347c38ba7b79f0251ca16c332867d4488976ac5 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 3 Aug 2019 01:18:14 +0100 Subject: [PATCH 039/223] fix(go): Registry API acknowledgement URI has a trailing slash Previously the acknowledgement calls from Docker were receiving a 404 (which apparently doesn't bother it?!). This corrects the URL, which meant that acknowledgement had to move inside of the registryHandler. --- tools/nixery/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 54dd8ab4d..a78250d4c 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -367,6 +367,11 @@ type registryHandler struct { } func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Acknowledge that we speak V2 with an empty response + if r.RequestURI == "/v2/" { + return + } + // Serve the manifest (straight from Nix) manifestMatches := manifestRegex.FindStringSubmatch(r.RequestURI) if len(manifestMatches) == 3 { @@ -436,12 +441,7 @@ func main() { log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.port) - // Acknowledge that we speak V2 - http.HandleFunc("/v2", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w) - }) - - // All other /v2/ requests belong to the registry handler. + // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ cfg: cfg, ctx: &ctx, From 07ef06dcfadef108cfb43df7610db710568a1c45 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 3 Aug 2019 01:21:21 +0100 Subject: [PATCH 040/223] feat(go): Support signed GCS URLs with static keys Google Cloud Storage supports granting access to protected objects via time-restricted URLs that are cryptographically signed. This makes it possible to store private data in buckets and to distribute it to eligible clients without having to make those clients aware of GCS authentication methods. Nixery now uses this feature to sign URLs for GCS buckets when returning layer URLs to clients on image pulls. This means that a private Nixery instance can run a bucket with restricted access just fine. Under the hood Nixery uses a key provided via environment variables to sign the URL with a 5 minute expiration time. This can be set up by adding the following two environment variables: * GCS_SIGNING_KEY: Path to the PEM file containing the signing key. * GCS_SIGNING_ACCOUNT: Account ("e-mail" address) to use for signing. If the variables are not set, the previous behaviour is not modified. --- tools/nixery/main.go | 77 ++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index a78250d4c..291cdf52d 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -23,10 +23,6 @@ // request and a Nix-build is initiated that eventually responds with the // manifest as well as information linking each layer digest to a local // filesystem path. -// -// Nixery caches the filesystem paths and returns the manifest to the client. -// Subsequent requests for layer content per digest are then fulfilled by -// serving the files from disk. package main import ( @@ -42,6 +38,7 @@ import ( "os/exec" "regexp" "strings" + "time" "cloud.google.com/go/storage" ) @@ -98,13 +95,37 @@ func pkgSourceFromEnv() *pkgSource { return nil } +// Load (optional) GCS bucket signing data from the GCS_SIGNING_KEY and +// GCS_SIGNING_ACCOUNT envvars. +func signingOptsFromEnv() *storage.SignedURLOptions { + path := os.Getenv("GCS_SIGNING_KEY") + id := os.Getenv("GCS_SIGNING_ACCOUNT") + + if path == "" || id == "" { + log.Println("GCS URL signing disabled") + return nil + } + + log.Printf("GCS URL signing enabled with account %q\n", id) + k, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("Failed to read GCS signing key: %s\n", err) + } + + return &storage.SignedURLOptions{ + GoogleAccessID: id, + PrivateKey: k, + Method: "GET", + } +} + // config holds the Nixery configuration options. type config struct { - bucket string // GCS bucket to cache & serve layers - builder string // Nix derivation for building images - web string // Static files to serve over HTTP - port string // Port on which to launch HTTP server - pkgs *pkgSource // Source for Nix package set + bucket string // GCS bucket to cache & serve layers + signing *storage.SignedURLOptions // Signing options to use for GCS URLs + builder string // Nix derivation for building images + port string // Port on which to launch HTTP server + pkgs *pkgSource // Source for Nix package set } // ManifestMediaType is the Content-Type used for the manifest itself. This @@ -117,10 +138,7 @@ const manifestMediaType string = "application/vnd.docker.distribution.manifest.v // This can be either a list of package names (corresponding to keys in the // nixpkgs set) or a Nix expression that results in a *list* of derivations. type image struct { - // Name of the container image. name string - - // Tag requested (only relevant for package sets from git repositories) tag string // Names of packages to include in the image. These must correspond @@ -294,15 +312,24 @@ func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer strin } // layerRedirect constructs the public URL of the layer object in the Cloud -// Storage bucket and redirects the client there. +// Storage bucket, signs it and redirects the user there. +// +// Signing the URL allows unauthenticated clients to retrieve objects from the +// bucket. // // The Docker client is known to follow redirects, but this might not be true // for all other registry clients. -func layerRedirect(w http.ResponseWriter, cfg *config, digest string) { +func constructLayerUrl(cfg *config, digest string) (string, error) { log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, cfg.bucket) - url := fmt.Sprintf("https://storage.googleapis.com/%s/layers/%s", cfg.bucket, digest) - w.Header().Set("Location", url) - w.WriteHeader(303) + object := "layers/" + digest + + if cfg.signing != nil { + opts := *cfg.signing + opts.Expires = time.Now().Add(5 * time.Minute) + return storage.SignedURL(cfg.bucket, object, &opts) + } else { + return ("https://storage.googleapis.com" + cfg.bucket + "/" + object), nil + } } // prepareBucket configures the handle to a Cloud Storage bucket in which @@ -410,7 +437,16 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) if len(layerMatches) == 3 { digest := layerMatches[2] - layerRedirect(w, h.cfg, digest) + url, err := constructLayerUrl(h.cfg, digest) + + if err != nil { + log.Printf("Failed to sign GCS URL: %s\n", err) + writeError(w, 500, "UNKNOWN", "could not serve layer") + return + } + + w.Header().Set("Location", url) + w.WriteHeader(303) return } @@ -431,9 +467,9 @@ func main() { cfg := &config{ bucket: getConfig("BUCKET", "GCS bucket for layer storage"), builder: getConfig("NIX_BUILDER", "Nix image builder code"), - web: getConfig("WEB_DIR", "Static web file dir"), port: getConfig("PORT", "HTTP port"), pkgs: pkgSourceFromEnv(), + signing: signingOptsFromEnv(), } ctx := context.Background() @@ -449,7 +485,8 @@ func main() { }) // All other roots are served by the static file server. - http.Handle("/", http.FileServer(http.Dir(cfg.web))) + webDir := http.Dir(getConfig("WEB_DIR", "Static web file dir")) + http.Handle("/", http.FileServer(webDir)) log.Fatal(http.ListenAndServe(":"+cfg.port, nil)) } From aa260af1fff8a07a34c776e5b4be2b5c63b96826 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 3 Aug 2019 01:25:21 +0100 Subject: [PATCH 041/223] docs: Add GCS signing envvars to README --- tools/nixery/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index e0e634c3c..bcb8e40ec 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -87,6 +87,10 @@ variables: locally configured SSH/git credentials) * `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use for building +* `GCS_SIGNING_KEY`: A Google service account key (in PEM format) that can be + used to sign Cloud Storage URLs +* `GCS_SIGNING_ACCOUNT`: Google service account ID that the signing key belongs + to ## Roadmap From 20103640fa6ab0678c72d3ba8a3ddc29ac264973 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 3 Aug 2019 18:27:26 +0100 Subject: [PATCH 042/223] fix(nix): Support retrieving differently cased top-level attributes As described in issue #14, the registry API does not allow image names with uppercase-characters in them. However, the Nix package set has several top-level keys with uppercase characters in them which could previously not be retrieved using Nixery. This change implements a method for retrieving those keys, but it is explicitly only working for the top-level package set as nested sets (such as `haskellPackages`) often contain packages that differ in case only. --- tools/nixery/build-registry-image.nix | 33 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index 20eb6d9e9..6a5312b44 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -113,11 +113,36 @@ let # For top-level items, the name of the key yields the result directly. Nested # items are fetched by using dot-syntax, as in Nix itself. # - # For example, `deepFetch pkgs "xorg.xev"` retrieves `pkgs.xorg.xev`. - deepFetch = s: n: - let path = lib.strings.splitString "." n; + # Due to a restriction of the registry API specification it is not possible to + # pass uppercase characters in an image name, however the Nix package set + # makes use of camelCasing repeatedly (for example for `haskellPackages`). + # + # To work around this, if no value is found on the top-level a second lookup + # is done on the package set using lowercase-names. This is not done for + # nested sets, as they often have keys that only differ in case. + # + # For example, `deepFetch pkgs "xorg.xev"` retrieves `pkgs.xorg.xev` and + # `deepFetch haskellpackages.stylish-haskell` retrieves + # `haskellPackages.stylish-haskell`. + deepFetch = with lib; s: n: + let path = splitString "." n; err = { error = "not_found"; pkg = n; }; - in lib.attrsets.attrByPath path err s; + # The most efficient way I've found to do a lookup against + # case-differing versions of an attribute is to first construct a + # mapping of all lowercased attribute names to their differently cased + # equivalents. + # + # This map is then used for a second lookup if the top-level + # (case-sensitive) one does not yield a result. + hasUpper = str: (match ".*[A-Z].*" str) != null; + allUpperKeys = filter hasUpper (attrNames s); + lowercased = listToAttrs (map (k: { + name = toLower k; + value = k; + }) allUpperKeys); + caseAmendedPath = map (v: if hasAttr v lowercased then lowercased."${v}" else v) path; + fetchLower = attrByPath caseAmendedPath err s; + in attrByPath path fetchLower s; # allContents is the combination of all derivations and store paths passed in # directly, as well as packages referred to by name. From a0d7d693d373569b61597440537bedb1f1384450 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 4 Aug 2019 00:48:52 +0100 Subject: [PATCH 043/223] feat(build): Support additional pre-launch commands in image This makes it possible for users to hook basically arbitrary things into the Nixery container image. --- tools/nixery/default.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 8a7ce8b34..7c7ad0b6c 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -11,7 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{ pkgs ? import {} }: +{ pkgs ? import {} +, preLaunch ? "" }: with pkgs; @@ -88,6 +89,8 @@ rec { mkdir -p /etc/nix echo 'sandbox = false' >> /etc/nix/nix.conf + ${preLaunch} + exec ${nixery-bin}/bin/nixery ''; in dockerTools.buildLayeredImage { From 099c99b7ad4fa93d1b5c8b7bfef0416f79edad59 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 4 Aug 2019 01:30:24 +0100 Subject: [PATCH 044/223] feat(build): Configure Cachix for build caching in CI The CI setup is configured with an appropriate key to enable pushes to the nixery.cachix.org binary cache. --- tools/nixery/.travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index d8cc8efa4..96f1ec0fc 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -1 +1,6 @@ language: nix +before_script: + - nix-env -iA nixpkgs.cachix + - cachix use nixery +script: + - nix-build | cachix push nixery From 7c41a7a8723c8bd6606c0c2f3fed3a546f1efb24 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 4 Aug 2019 22:38:51 +0100 Subject: [PATCH 045/223] docs: Replace static page with mdBook site Uses mdBook[1] to generate a documentation overview page instead of the previous HTML site. This makes it possible to add more elaborate documentation without having to deal with finicky markup. [1]: https://github.com/rust-lang-nursery/mdBook --- tools/nixery/.gitattributes | 2 + tools/nixery/docs/.gitignore | 1 + tools/nixery/docs/book.toml | 8 ++ tools/nixery/docs/src/SUMMARY.md | 4 + tools/nixery/docs/src/nix-1p.md | 2 + .../{static => docs/src}/nixery-logo.png | Bin tools/nixery/docs/src/nixery.md | 77 +++++++++++++ tools/nixery/docs/theme/favicon.png | Bin 0 -> 16053 bytes tools/nixery/docs/theme/nixery.css | 3 + tools/nixery/static/favicon.ico | Bin 167584 -> 0 bytes tools/nixery/static/index.html | 108 ------------------ 11 files changed, 97 insertions(+), 108 deletions(-) create mode 100644 tools/nixery/.gitattributes create mode 100644 tools/nixery/docs/.gitignore create mode 100644 tools/nixery/docs/book.toml create mode 100644 tools/nixery/docs/src/SUMMARY.md create mode 100644 tools/nixery/docs/src/nix-1p.md rename tools/nixery/{static => docs/src}/nixery-logo.png (100%) create mode 100644 tools/nixery/docs/src/nixery.md create mode 100644 tools/nixery/docs/theme/favicon.png create mode 100644 tools/nixery/docs/theme/nixery.css delete mode 100644 tools/nixery/static/favicon.ico delete mode 100644 tools/nixery/static/index.html diff --git a/tools/nixery/.gitattributes b/tools/nixery/.gitattributes new file mode 100644 index 000000000..74464db94 --- /dev/null +++ b/tools/nixery/.gitattributes @@ -0,0 +1,2 @@ +# Ignore stylesheet modifications for the book in Linguist stats +*.css linguist-detectable=false diff --git a/tools/nixery/docs/.gitignore b/tools/nixery/docs/.gitignore new file mode 100644 index 000000000..7585238ef --- /dev/null +++ b/tools/nixery/docs/.gitignore @@ -0,0 +1 @@ +book diff --git a/tools/nixery/docs/book.toml b/tools/nixery/docs/book.toml new file mode 100644 index 000000000..bf6ccbb27 --- /dev/null +++ b/tools/nixery/docs/book.toml @@ -0,0 +1,8 @@ +[book] +authors = ["Vincent Ambo "] +language = "en" +multilingual = false +src = "src" + +[output.html] +additional-css = ["theme/nixery.css"] diff --git a/tools/nixery/docs/src/SUMMARY.md b/tools/nixery/docs/src/SUMMARY.md new file mode 100644 index 000000000..5d680b82e --- /dev/null +++ b/tools/nixery/docs/src/SUMMARY.md @@ -0,0 +1,4 @@ +# Summary + +- [Nixery](./nixery.md) +- [Nix, the language](./nix-1p.md) diff --git a/tools/nixery/docs/src/nix-1p.md b/tools/nixery/docs/src/nix-1p.md new file mode 100644 index 000000000..a21234150 --- /dev/null +++ b/tools/nixery/docs/src/nix-1p.md @@ -0,0 +1,2 @@ +This page is a placeholder. During the build process, it is replaced by the +actual `nix-1p` guide from https://github.com/tazjin/nix-1p diff --git a/tools/nixery/static/nixery-logo.png b/tools/nixery/docs/src/nixery-logo.png similarity index 100% rename from tools/nixery/static/nixery-logo.png rename to tools/nixery/docs/src/nixery-logo.png diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md new file mode 100644 index 000000000..d3d1911d2 --- /dev/null +++ b/tools/nixery/docs/src/nixery.md @@ -0,0 +1,77 @@ +![Nixery](./nixery-logo.png) + +------------ + +Welcome to this instance of [Nixery][]. It provides ad-hoc container images that +contain packages from the [Nix][] package manager. Images with arbitrary +packages can be requested via the image name. + +Nix not only provides the packages to include in the images, but also builds the +images themselves by using an interesting layering strategy described in [this +blog post][layers]. + +## Quick start + +Simply pull an image from this registry, separating each package you want +included by a slash: + + docker pull nixery.dev/shell/git/htop + +This gives you an image with `git`, `htop` and an interactively configured +shell. You could run it like this: + + docker run -ti nixery.dev/shell/git/htop bash + +Each path segment corresponds either to a key in the Nix package set, or a +meta-package that automatically expands to several other packages. + +Meta-packages **must** be the first path component if they are used. Currently +the only meta-package is `shell`, which provides a `bash`-shell with interactive +configuration and standard tools like `coreutils`. + +**Tip:** When pulling from a private Nixery instance, replace `nixery.dev` in +the above examples with your registry address. + +## FAQ + +If you have a question that is not answered here, feel free to file an issue on +Github so that we can get it included in this section. The volume of questions +is quite low, thus by definition your question is already frequently asked. + +### Where is the source code for this? + +Over [on Github][Nixery]. It is licensed under the Apache 2.0 license. Consult +the documentation entries in the sidebar for information on how to set up your +own instance of Nixery. + +### Which revision of `nixpkgs` is used for the builds? + +The instance at `nixery.dev` tracks a recent NixOS channel, currently NixOS +19.03. The channel is updated several times a day. + +Private registries might be configured to track a different channel (such as +`nixos-unstable`) or even track a git repository with custom packages. + +### Is this an official Google project? + +**No.** Nixery is not officially supported by Google. + +### Should I depend on `nixery.dev` in production? + +While we appreciate the enthusiasm, if you would like to use Nixery in your +production project we recommend setting up a private instance. The public Nixery +at `nixery.dev` is run on a best-effort basis and we make no guarantees about +availability. + +### Who made this? + +Nixery was written mostly by [tazjin][]. + +[grahamc][] authored the image layering strategy. Many people have contributed +to Nix over time, maybe you could become one of them? + +[Nixery]: https://github.com/google/nixery +[Nix]: https://nixos.org/nix +[layers]: https://grahamc.com/blog/nix-and-layered-docker-images +[tazjin]: https://github.com/tazjin +[grahamc]: https://github.com/grahamc diff --git a/tools/nixery/docs/theme/favicon.png b/tools/nixery/docs/theme/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..f510bde197acab5f1d1f4d74a80acfd30d7d5fc8 GIT binary patch literal 16053 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 literal 0 HcmV?d00001 diff --git a/tools/nixery/docs/theme/nixery.css b/tools/nixery/docs/theme/nixery.css new file mode 100644 index 000000000..c240e693d --- /dev/null +++ b/tools/nixery/docs/theme/nixery.css @@ -0,0 +1,3 @@ +h2, h3 { + margin-top: 1em; +} diff --git a/tools/nixery/static/favicon.ico b/tools/nixery/static/favicon.ico deleted file mode 100644 index 7523e8513950d695daee7106bed83d7bebd9eaa9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167584 zcmZQzU}Rut00Bk@1%|2;28J>Q28Me-5U&(GdSj+U!V%Q;^hu*o&RL4oB|WEe|h%LJc3C3BOx zyD#;szukII{&`qw>AF{T+jnvQ`h8cvB=m0eyL->yZ2cZ}-)qS#i=M+3yPEjFG4MS$ z;8Ww{K9Fd$wlIQ$?{Q}DVWt8J|7;nthQk$6k2f&L9J?&l&icS$-ZcvyhW3wJjwLc$ z^ei_ePRj=NY6f<$&@(m*8yObul!@do((S0SZja>MeZBdm?X&F+Dg0gOe>k3*{9Nwd zoy_;_fbli%XZ2=RZNwNt1riVM$XnTM`ny8R#Y1))|EGYdA}`*qTgs($bDL`HcZ;8l zE=9lWoMz2>s$G$>!}@1j&)&;^@^u1dZX7@9C9u(5-uY>few$(XjN?oWEk_>e?-RWD zvqk%-%BJn+#)~Cio%Y*S|9zX&d9m{@WX|7X0@w_n_tRsEd&re!ora*pqLC$KCsLz5dZZM&HXaezHz#;eY@C z$p1IyVWg9SHSV9L%h;WnW*?ZD$lR> z>sem-b?Ny86@{iV2?ob9cHiCgWb%Q1OP&fGs}W^jh~K3sm$_oQ{@Z%4MH=ioK68Fw z``?3MN3EFFnbNSx_eTCX3_YIy7A7Q6qEEQ~2(;vo^Y zcRdQ~O)ijYo>_dxp7F$#>H7b)JYCaQpImWhdqq*mzN%oRGoK>gvxhKFx&J>1k2XciFk`>vZQIJY)H6CW8f| z;J?4?zC3HcoRwI1`~MZuEZ1Mp>Q(sj#SYs}V@&I)P@fZ1b5?q*9phx-=g(xOYR{Fg z(XRc$C{gTi+E4V@j<2t+!fWT9n)-L=l;W7R+t%-i*_LtzbZtG>4UPw)UT3+8VU8d3EOmF?xu;{bbp3b+u@H!>zYwG-o8v`ru z{nLpLt=N}y@5-+~>5OOIF<7z~%x6)!J#W+5-5cWhwVQ=QxJnNHeVH#` zUqWy1+qt*$o~Lo9u)XNL?rI*l{Xg3Zjv0^t|H%EZSw3iO{(s-B=PRPP>pwL=xwrcO zdzsQ%%V)g|DOaAi8SebGY)RAt=KUvqN?z(ewp{4K+i+>N{+8qOm%I&|_eC}EcbB(` z@3+hRM|qw{O|Lg=SCm{FIOA}q$@3ZZObiS^9<87Baa&aJf<2oxycpj_+nZ0(P+&?v z{r<1VyxogALbX1|Zg0Q;*UF&UD@3N@T>rk=cM7?uo?^E$c)lX4JO5wlkInLD^}R~! zI2@ks*!$G*`(f)zx9V>0+r2mK;NsQ!UdN(}T|4dG$Naa{zu=~9EnL)49J6lAx<7k* za^}uuT*YN}X~p*aUo5qzeG>E*{89IM(|M2DB)PyvpPd{7I&Tx(dR!!e_ZqOzpdh8m;L3@pB@~uT<9`)^Xh)heIIj!cg}p>vMf{czvm;?`itEEVYWGkHRWCl+u8oWMba_N z+vmPr8+l}|h52dI$D#X6l5NC`6W9WFJ-AzTG?2r3XW*h^84}y{eqZsq@6WQJ^5yca zm)i_~=3I5@uu6Nn>XP+6^RJ?xnLfKQFmxQ+UU7eC-6Ioyru&>(s=MCZ`_g8=#KwF| z#(#&Gf4WV0J}+_6zWU&3I{rtZA=txA~H9k_J@blQ!m(KMI zk_^rL!bFcv(ciV>_xsA$N;L)_frFgxAF}6VzJ7a$k1xNGnV1`W`+AL~`77ZD!&@uZ^=nrCfAjpw{G}JF&uo5} zbZKAJdG0@|%rZ-N1}@rn^cm;RPrZiE8iZ6CH1@y#8}iy&?_|fSU)RcSg=TM=EU)U- zW0D#7Ei>WxUyH?d_TTj<{F$b?x$IWi)mfKjpPN7H;-15MWK0+WC64_*|5e4l)%J7u z-5(BBcWX1#a=r+-%;aBP@~~Q=Xn~iYt}_JTwwnnoVT@^p(9}96uTeo=eCA3 zT~Kip`?mR;pw^Tk?+8J6$A#Iy&!wM9W<0TFifQ*x-sRO_|NKc>v_j;?srcj3(#0;F zRqAu2DvrM15_o>HknYr5@w+eO-t*Wu-|k<;@7vddc20_(QY7~6^RMkUOpiO>vAO!s z_{`&l$(PRTez5c2q`JP(i(PD+b9whZYLh4yxIE*j&%V!pZ45e0+@s73W}9ut_OD+o_SxmJ^lJF|_2(?V^1S3sJ~$n; z8Sj}qeRkNoDH&5jN!BP zoAmF(RyUYWFe-iEx%buP{8O*aFs_o%&M)kQ-MgC_D8*Y#O{rtlV37@>d!ia zwjQ4sP;piIQ;*Qu+zPi@y_as^OAV7_eg4e)=jPfd_G*Tmipk3N{xbdVI2fZD*zNoH z)705XXB^j5Z|}KtbWe%oqaOlF5t}y5Uwpk}<@x0;pDxe6c~H(F?}Wz1Mx(v2E910J z8GCJ6?{a^y>fgg`59hgerlmd+n|AM;Ugmbbr9YUQCQZ5jS5ZskS^kalc^|I`f>P@G z%4Oesls_+V=@hWu`(~HrO4V6YCtAO+dA}t1v(BnIg@kKim+k+aIpiHb(^X9N4dYX* zoz1olb0)fW#(k-=KcCH|#ro@P^GpACUMsIp(NH^mBDUn?!k{R3(YDj#^?QE5UoJK; zi)ljPxAT$7aTR+`8ai-roxd=3f1mL6-VJY_%19_3{2V9pB-$_KLCdIvw)p&-J`}$&!|VJ0g3F&+9K|2-x={ zxpLaX+x!BGeU%p{U8=Ub^7o@=d$Z;RzqBt&OrLe)t~rRf>p$V1|GIVKv#(b+9r>A^ z@3-yBRf9{h$2=Qmp0)V?$*AqBW%U=sqo4Jr$XI8e5zAR}RcG(hw`mU_EqAzaKCe*V z){?Z`D*2mA^&h`Z;MgJ_8lE^KOh;i^X?6zJjF=O6(z9w12M<-t#}^{fWfKZtlnJ3-;yQo)xY0{LH~MbNgaz&m349KKc3C zm*M9X`3_y1WSX`AiTFom=~TAdzAp!TS8dLD=NI`m*-7(6@#Uaa(-|4BGm4*Y^*C@V zcO^&0woSXZc@_#fHU9rA_TfRo%R}F<{C~IJLZaogsZWNQz;@d=%b(xcFC$W$vW`_j zyX4<%_B;7D#@iXp0dZDNTN|X`m7CjU%rE9?y1wt` z|C8S*SHC;6uOHlP$Nr_22!Kd$mqk{OI&NpCg>odV60#vaItd=<8bgsrCK5#p@@#SZ8kxFWuoj zBbf2Tmbvfm&bqVx-qiHl6U*eB8Cg?qc`vukWMz4!7SFUoLyPs!#%r~&!`M5@zkTM? zuMVAOIk~!h!^JMYgsZApBWn(&S#Vb$@+Q}_G*`J+`W zw<=(TgX{tYw#o-gJ6QA@yA$M7c6ra2h-q3|8o!KZQ$vS`<^t`!j_p^cHa>Edzf|OU z$t8o`@RYnuhgI3niA&4($-k(*!Oz%W^K1L9J4*fkcnz-Csy#o)CIIR@Z9h4A@ul!n zx*0zOnw?l(6!_mT^Y~8O_F$XhT4$>O_HPNRHZX6fO^`n+Vf8&|i`Y6wt}FITM@=SQK7=+))>@@8js#zgg!34=4M}Z7(Xx)a*GHd^jvnEbhe{r}W++ms$MnlTvNw)rEa5 z&1b8c`*DRr^A^4Szw!nQZQM^6+8ZzbU!y0>SNw$i#EJR-HQK*E%AO3)_#M8YL3ag% zfr{G?3#t9fpY>+kZ%dKg!WuMzQKy4()|JB62OOFV8v&bT`|^Dk;uItjk4(~C|nN!s&amig6xfAXvSYq<{Gs=M*`9ghdI zK`dWLl1SLC&F@>@&Xx`<(F<9SBNTo|K;4hI|Jibe7f(WrbnhRyCXl;$;%dV(-b1?= z>IyV6-%vUbyVS$j*!*$Ll!#vU91aD~la@(JMhx7Wm4g_x+~zhii8OHCJZl`_;QAma z_1Ki;J5II}7`D%Pajx-2&{&!8}VpWXXZ|k_v^$Yl}eExd#v;K)~ljh%bIB*rjHNyLvO5Y25G&USyEEd9J9c z-^mTj=6PM?jbYGOxk~I*h^)a{w_BN3Ne-SzI}9AzPtQ5B<7OK_?{V>K+4?KPg;&g; z8tSknae2wbqiG)}?c7*f`bB)#&!`&-MPL2@o!tEChHU3cXS!GKXJGhgz!;(^sKO?ZGSO$jN8?-HQn##Ux4iM`$u4n)=r2rV3p6*)-?T1r z-uWW|tsHD(zqGH3^e$DIrY`r!r$@!cHmS2^Pe;pKFTCdZJ!&L)ZZVii2c8|{*R~r-h?gtHDVvn_*N+;pxAfjQtI6-d)wu9 zk_|S0>=)cnEpc%a^K_T{SNyq0{#;}Hj_phn0_Vs7j{Wq&O;RV=iho-@BZHok;R zmo3VM4g$-&r-}DPgoYelcKk4Jf9WHSm{rFFIF1P{P;&9nYqZ$V=pCqG6s@l5<+Pvo zkagSQsj}5UGIQj_!;dp+yH;=How4Z^4c4}y%%_ckD_~2LZ4?3L}rZi2Gye-w8 zA>_FE*Ax8UGMQOy?o2y)d@j&@qUY4 z911y}xd)7=9ou#!Ia>Ch#)C$g#LcDKXMZrS`)z117qa(oNEL{jcPwlR1y zx4>(?l0t}sPXC{!dH)pmC~a6CBhSj7k>9|X@qqP$Tv*~uQQldB^S`etmj9I`ai&t^ zcMd!Ew}&C-vA)s&Z(c~QTl!+^4ep@iDLq1*9-$sGJEzRKY;tYK;!Gbc77fWS`XAOD z|2k3SjYXP~T%}#-<;8wF{5-!sm2ys~{yOlsS0zYePlH#eaQm&yov~MWMP*8pG7}cQ z7h-+5(0bNdH!l~*iHs$)*45vz{`JI5ZCm#H{H2}082j_RV^#_H2kR@Zk!VozTB01J zSmzpHx&CT+soEXgP_Gu1hYMMmr^g>=ZvLt5q@{4}+1+>L=kI;Kbl*^Uu7 zJV`1W`|^Dqw%o1_TR8i=YwIS>DQ9j>+i|(<42#nhF@cT*9mk1kNww>~-9G*yvN39?OmzPVSk@YhzSzjgY*3X%&#Www-0WegSJdbw-s+tvFcex9gr^w8g- zQ29fDV%zTP|H^l0`IiI$ATItn)dBSpH#d+y}6aKxt`cU!N_p^(PI1RX% z1x{?gSlfEBUu=(b&Ppd1`vfM{ONsW2dKO%Hw>Mn0!6;iHx}(8qk1Myh@Wiev#{5`$ z+kXzTeW%|Ea9nt1tK<2lQS!b=GTm4%rX>Egy;~<*!OGRO?6Sx5bmg>EO`G{)^Mf?Q zcm%K6Ub(qP&!lZzyskF~lVjxEz>NXZTuiQ-y;d@n&*rf;*4gU$ZOcl&ns;8if8DUT zn<)8p=Uex8+rkgB8TVBvd*saf87x(m*}vxDpZ=*mzj}gV>S7<=c5eSA;vh0Fh*!fn z$bUP_Cy)3)HxE8Jdv%}m-Xh*r%-mhxVIkt{SMcetOxykD$*b@Nru|QTv-wcCqV-}UQs z`=M{^Z@%P<@W0Ob&V1kQlc#E1beHg6^7Z1;S}}P+#`HC7QsyT<``EcLE!$>uR_BWS z+ZIIVtXgzAv-i6Da=EruVq8o2-CB_NSmox1ZHBw%ZC1^;(VBAd^X|^`s{%rmPHoAa zo^@lAMG(Ut^+%~u6?;0;>K48^t94>t>@j7_n+u>V=`>bE<&X>+w7*%}XY&h`Go^I_8vMLEA)*W}Hdgz#Ue5G0vI_`JUSnwHD;z3r`uEwjMPD^E zgnW;_*I48Jm%HY5;qHlM$AlS{u2Q{l#N*POf*aX?8o5?6Z!^nKcf0hg;@01^Ss`T% zN^9(v2EE+wB5{A3O_hK4^=yYV%No5yg?ks8ewcr?-M@ot?%X$xIdcD{9@%zBEX#a0uft5szQ<+D87Fl3ti-|PZJf_ z)0e3&O1)@w|MNskmSy`VWHL56&Fr_?x1}OA@v7(3Qw?vg&6^QY?IO~!@yQ&ujnlM_ z>3`76GHVR}ue4c8SRUhj9V{`@ze$>PEx7cGXAxAqG* zOUBIDE4BCm!@~}nPxg#w-hG+D_~{FCN0VfK-o*cZ_nz4PWQ#OUfk2$rqB9|SJLA8& z%YV8Xx_~2uO-DpQQEXe^8s*tPQZ>IzniZToS}5TfC{emdLudNxr$JL4R(j9zp4s!B z^@zyv!o{W=PlvgjX^^_EslIlR{EVa8T~B9)zxzC6<>jJ)DXYvhq&!U*mvpQQFv?WQ zxuIVmVtGD2Su?n(M=^OyHit=jJ##RpoZZ&O*Mw>^+&-;1RCI!Fh;_*GFUisnQA?O9PguxKb3Mn@o$d%e4C}KC`|FWLW9VmH(6+;i4i2|Ei1g}DlxXj8u(ziZ2< zjsJK3pOP`{ZPC+*!v;JGhv_DL${IP$wao&mRn$tG(Ds<)c+RPR?E4C_Y#^Wu; zC(Z3k-ZzIwu3wY2srUE~O?JVVIl-(9f-hI|UR`DSJSJ^+p7h-*DifcJwzZ2fuHs6| zE00Qke7j>;m%!UE3?5e_uWNce@}K+u%$9=4g%KNBj%>_{x$QT3-9agl8%w3ynVbTi z6rByec&uWQ+HTFH>6)&A(YJghqhxgAB$srqtKKkq=lPzc$$qOV7*{*Zu`7sHe#K$P zvE{YWNwdOdYmUrb_1WZWsN%*Wt5h!-eLUCEt^X=SY}q#F$ye4F|NURpTK>oLcQSAK zmFLsm@3?)RA^+Kx=W-5Wp6&jB!sEUzlv(?*IY4sV@&Hk#eV^RxKfQhZf2Rb`Dz1=| z9hz<_!V7l0>^`ZXHMOX|{(ghcvy43Hn=?x2C5h?P8zzVs1Uyigq#?54^b>)%YnKTKJ_?8yK3Boh>b-mS z?hd{9;HWJ&3>Rel<@c?=x%SLb22ID%-E0%JZ07$Gi#osD@AB2{mZ4X#gg@^V_6%u| znAN2h{ysMGzvbK_7S-e}as2cCFWR^N|Ccvg6<9gey{#*6+xD*6GJ$c1*L2I*W);nq zk%xaCXOgvP3{pS0VduMP+WY@3-mmjxtIpl7zgM)&6N2uG{myDWnsl?XSWi_+$?NhF ziStqX3&M8ad}qQakSTLqVMW&JyE$8>cUOK{GU-R=YMsIbT>1+ZWE?4-8*}aX!B)qB z2Nsjcl;=kMJ-R7!&!UjGpS4WSTlP8|wtd_8%Q&LnX8xi8*JbZ(udK2bJTft-%BIK4 z>ZxHt;>n||Mox2nN*QdpFfEJCVPQ^8(~iX{tX?e2d|Db6CO7;`mCb{8dzB;wd4#Tr z@R8qfU#_|-wo)X;?9_HEfz!A4U+0Z)4nK3e;wi&Jvy9nddyLkHH2%qLXby4=4Gp;} zRsDXkdCbSd|6fV=dxRc{%kq|W;H_l->pbr{S(&!MKCN$0s4 zmSoP^l~R+W5?oytH*d+S9`0_T-!o4JuDZeM&3!CD=;Suvz}++X_}I2O`p=oR>RN=< zT^U1Xm*z!=(=NI6EZoOhw|k=B_9>g{--pSCB-XC_GPCUN$rOv|XFfMRWTS5HDtw$f-c|w%c-}Iu^+YD z9{X;^g!kL^{`b^BD?Y)WR>Q|}BI|iz<)itDmiDjQ951Ap%ud$}ui=_f9UjOixcTSX zyZ8SbUmtUU@Ab8+RbFm6E18?LY|d}KX7$`f<3p`o^Vm$C^WsF zt^3+q^5kWE5rO0+6~CmjOHNdUtSZ{AcRO`?_SFormQ8P71mzm!^(^VX`^;VU>XlVS zj<1#Z8pH#ZYtB8^SC^!elre4p`haUpKREr?X`9`@^upg|M@U}$*Pn;H;}14-f2-w4 zx$^u+e#+m}4>cKnrnM&(`hL!^UwUblOz(qA9fq4pp-21ke_hZ2_vyX6+B_XrQ-L`h zU(XyUcp!fIZQ1U-E7N2C-T2|LGe)%VY?$rlyz+f9<%>fNdJL^4BA2|%xuunzwcES? zm3~W@?Om_LrRRf>{@BLRuw++i?Q#7LT8{#@-DVJd{qv55|DwLxMMBI8&QDi`1_rXY znoYYD!6q)2#Vp5>lW}_T!zTt0e}=39dAwauEjRq~4EDZ`_GuA?R(q66Ic+O~8!f!O z+Ll;tvz%+wT{dS|M)8?Y_TH}@5@!Xyf+d{)KP-(qY#P+`{`9r8YxV2a{fhs0e4l2Z z62qqx@ukTNi!Ro8JhWP|=Ao$k>whzZpRHy{Syefw#P0vz|6eZ8*5}_7wDX4kBLx?? zb}@0St4$qMTNQjKYrGQ(6e(Y{V{&cHb(Sd>oww$u?F>pBU9nsC%&^?xX6D|zG3D1%HC~1#ox4JM zpE{kiSkCaLq4(yx+7*ZNCoH*>>G_P&V^;0DSfS+?D-_l-EA*~3QmC_h7-Ss%dB&Xc zdaLDYxpNjCQp&gAG)V;F>m8` z{a4%K&se=l&sX3$bFrTF_WXR83pQIBH%&0w`NmrQ_xIU)y5hfkH$IYK`1W{#6dUW~ z7;}%yQLASd>1FEAIqgzgl2Y>e#*Y&xUQ9FbU3PC-(xtG}pMo zcU^V&3U5=5iO(mma_Tu^=W%25Gl|@q8_CROS8`mI?K;c0@RgjYSSX9CR7}!o({J1< zwS2cD-Ruo7DE=);eWGZu!`W$gE6?2i|INMj)AKfNci)_pZzau#oex~in%2mf?d#zWknmWrg@+!~O1sNS4EXmP}SGO2Xz_O^7(Cbe!_b?tTXIm@)Ht+Q{&t$Ve}IN`mxx{|1WXoswW z!c>h3H|}S?KdOeZLpiFmAUhLvRjx5AA_Kk?)Hx#%hEKaXPNb?@}(988f-97 zIKZ88``C%9DQZbwx#Hg^8%(+3|7!6@EuQz;`?wh1wyoyb@KrKkde)U`uLQ2Psl2>! z!%a(V%gnp>A>03!%i3A#Zd;oh`z`bTndk5H96TJtZu6hmb^gGtJ5%QC$ox9dA9O!* zFGu^k#Gsn<3tYZTt^eQsTW2W$KlC(a^+^v#rs%*$ZanIIS$$bqb2e*CY z6x6l7^_^SeTO;qRWRZqr5$`@9PCwRK7u;vI&iJa``zh(~ayVBsdA$3d>~n`p`c3|w z+vm1R&QgfT@cSi{BcE0?>AcM6H!LTVWX(nnYLHTX1ruV|a*Y~(zZ)q5`E>-9FD z(gO__W)(zVSAC@5*FL@aNCBruz?xMbHGBh@Y>&NMzFN6HEM8*D>b0x)M^xNgUGebI z+x7z?8@KyjKg9j%_CMZ9Gp_&9DjlJF|?0!nG`l4+MmU<~D z`OQgv{M&KsC36#lJNL~EZrGnW;~1dB<|@d(AVkOb{X-tnYgdGP19bz^Rla^(S0bd` zdsXCiYu1`ecjw$7OQ1WW5}UMFu;3Aw0teTZX3}wia;Ee27b~CD|C9aQ>8`#}fI9!*?KbjehL`xh z&Odhg|Bm0Hwg+si8I8_gsqMe#F?l0HPO{+hNdHh*R~PN4U0ofkp8wnz9&lDmQ=l>B z)LJf|s!iL9=O!GSveDa1C${|kKDMW#mkuf)Upy^;%@M;_$}9$; zmx)(zqh6rd5izY5UpB`tum~~Jm=eiSci^x$_qFGpxgURc_%H~TMrDfZT9C1*FWdaG z$u!>nt)UMu>Fv(9-DJ0P_v;RwK*!db{U?{)_!@B`Z`%s39`{Ek8XLX0?9LYdf z#j%iwCj{oaUy%A#o_($9%OCPnf6uc2>G<(#{;O^M>a3EH^)vhKPtyL%$*|{FePi>z ztL?M>nHB^ny>jXO8ZB?|W~J7~rpZ;cd%|QBo~5l8JSw|@-^FLiDkVM7$7Rc7Ik-B# zrF%I;71c`DoD;I%G+Dd(irAqek{r!fM68eal_}MKGcj;K67=RZ1IMX1tRmZ5I2=14 zX((Qp^1E}!q=;UH?(z@ZA|jsZ#y5Yr{aKI_vc>Gl)>odBGM;0cz$EeZ(#}Z> zR~~p-baqbb4K{LUF}ZeZ#%kV~K8)EtJN6~+ ze!#)svO;zC^<#nVn}XsOuy_gZ2P`fL&HNJ)9%Uv4s>mAa_lZooI3@Yhjr;Gn-ngyf z$mZ$M9cM22{o3kR9iKfJHo2M@ZA{#=@8&EYh8BrHnLO#cJ}xuoUC!_eSzXJLwN1)X zidjPX{;Np)(v`)U#wvW5$~2>2Y`gMIp+#uQiSE6tW()E7>V8}0lWu4h9J9}gN%2+2 z6~Q#Eu#?;#cfOgI&obJ0Hnlpk^uUzg6fcQ+MtrFAR_ zFgonbowFijqo+=y^|Y7P>o>ff!qDIT!l)UVa`tNZ=>h53H2dB2$$KE3$wl%2c%yer=f zp5G@|oR3Sg<8)Z|zvlJy|JyIhY5qtRRGn8;xT-@rDKxiyQ*nclo5t@Pk@k6CxtC^5 zTJp9^G5T*n(Oji!&+RML3T>S5^4*l*2CAaHhT_|%NGz2S*Y@eLxN`Aq<~D(7;S*o# z=Dtb4xUPGN0(0Stev8-dC;VBu>g_V^Fdajw$DUI8{0kJAKBq`dJ*lwM>7+sMtd5N` zA~&lt7$C8O^PFF6fh&)fQ6`C2;b%nb_Lh0^;fJh;6y zwEte?&)<@)zmE6gM0chOozbaxmp`nmytS|J%xZhDr@~b!zx@*LaWt$*HZs3gclPV{ z*gzhwMN9(KFH<;+bj7rnCb`bZHc=0n?(_Y}sw@E!_TS4GI%WSSqGddPz{1X46cXi^~#9161$2N5xTNp9_QNga&em)x? zE6oovKFD@@U2tOCS5M{AmD^sQlixNo_MSwkR@5p<{RMu`lOj6j1}UvPACUS_Ew}Dm ztNdD-jX|QHZroSu{e1b~<_+^h_k8-#Se5$QFR_lJVU^KLzbil9@%~S_A><+YM*WRy z$V->)Bk=$fv@!P;=(I|`=4l;GG35d+-~tVZs*51hi(5H%umnp?Q?&EWK7oPN9V2W`QKGFZC-KyU*dfUEfL?X_dnZwt0`OA6>M+7uq}_n-OTmq zrr0fuGTy8!4M;fMd+5TpLl?q=bKZrW*nFkcYyV2Vs5FfS>}&iwSLQdKm0_xzsLgy# zBC2Le>;|XyX|Erdt_$L=`|wyl|JJ7&m4DW!KPa7HxM{=ugU>&2`d+TwUDflmkSXQV znYsLbW3DuEIH`49;B5$GQuUm3^|Rt5jl8mKQKiexoLyzh-&OD1YWAjOqt6dhcGDCY zk7W@H-c8S4&$4T}RcJ!@LDyxLikWNXouA4$`DS;k&`;LcS3fMfd%dsxrKN9|7ypt2pUb-_tGOGR^)!EKf3*ROnTtyyKvpu)AsYjO_Hgm>%8 zHfcpIo^&LsR`2SJ?*R{YMt?P9K4_UTudyd8vuD==kD0H{tDBds$S*Eb$yssd_|mNH zmrh>$nz;1*v4vZtG^MkyUM^FfTcWAptKzGwA(dvL9#nX$tbJPassy+D6P7zgC0SJI@O&G*yh$KlRIO`mtByJ1!S^uHedvp3bAQ?bsqir>}xqzpDOw zJYDzo-A(s)y?f!Uo83w?V$kIglAYn51~ z_v*w0rQNsHr^9kWYa?t)D5+&1NAlP@PE({h&=Jzo58b9k$z%|o3x&tLQ} z-mvKUmFIlhGxGL5b8z?Md9Y(@y8PZhmUFk9brJBhc0F_IU(&=QlW)9E`=Yf|<3*O^ z<%J4Co@=c(zuq~|<>szN!;afh+sgC~-8t*fA5wlNFWdQ;{sgZyhsok^7MP`*RfX-~ z>e{e0xK#dzfaI;UZH_G)-kPm{ofC1p(E8d{r6!BDUv4%g#?MJxtNB~JBC_*k(or6H z!H{2T{JwE+loLIb^H=s!+{p&s8G&mG7ao$Vo_$PVa@th}o7LNMKOB=fX3QQI-zBlm zd8=^c)A=R)E7lpaEPaXCsX3)PZgf6+^KY)e*3y((qfLr;x7ocl*>1ggPea?ZTL+Jp z6>NMPzvJ=Fg&LtZKcC$BZt7ZpYo5S~rPpqptvxyC!tN_4PO6+rZBy-5ZvT=||1xWT{Y2Mr^S}wg2h48-JZCqn zPT?~MZdG`&*q-s+9M0nKsPugt3`U{Pe#rlM$Lp_GFu6PK&b@71N$J|Y!Ea_by$F>~ z2{y~z_Pu=lOI3y&YTK@;=qu`Pe7fvoQYW)#xpI`2#?>A9jT_|*7vDMF%JqKBMDydZ zLd;zz7eB7m>nqviyggu{Q)`C%_o!!YrDAec|BcZozP~~9d+z=5ulEGw zq*ZUc4rX*n;iPO0gTL{{JO$G3&dBx0Ua+b!)BNPf8K0DswzHD6CwTq!-w*%i8u zuZ;Kazr@+HNeZmHeZ;$E-rag3dtve0w!eF(iOro}eeAKFp??m8ph5fof2n^azYi1V z+qL0oWo&HfdU@rf^sd|myAP%x+2767aHHQxjFqK8V52;3B#Vxqp@1~2>Rt8}KECiLmcxzjJ$_Hit^>-Qkx znEbXICTa5=uRM@bd!JAf_jUT+*XMH+x9;USz_VK6EMv!nWpDLkUrB%7A+CNQ=-Ru> zXA)X2XV$T@EB+RAxNXaD<6K48XBDn*lUSHbx@#kD)jmkxdH+b_Dpp0d|(Z@pER z9qnCs`}66-mHV6DrmdT{I&NdmW-Gy^tKM!YTahuPmb=0|Fhrs`b+vb`^St6+FFzKX zo;>lZ#%YZkJIuax$4GB@wBhMV4ZiYV_gfV%Jt}U?A1>Oa8y4ZRc-sb5Gc6`1#!jv1 zYo)?Aw;r`_H7VTuyy=9}s}HOC1K&nY5J)LLt#a>S^qF}q+2@%i1ZK1|&0#M7efzpv z`CYj$`WXqu*Y{3UZGJDkDoKTJL4y6f5``D**tWbDi4D0C{OA0-?djY1e_@u2$~wHP zD`VlAy2SfT4r_YPPW^R^YvMNd?n$#=EowVu;(jINsQ=Bax0f}|n4xgFhHvHNgGUcu z5W2tgRbg2mrVMhr_)rTwz@t) ze)$HuP`2Gi&C@siT^)4gOXrleD;A%tSh#1w)XefTBHLn@&zz=Zmb3Gji_u}*vP}j(5jqXn!}9Ug5kli%sn1v*V9- zTBdQ%Ws!dUY|7ubU2je=uIIfz>ta&yf+C@tU1=Ae8}ATaCCEF+`)KIy&<$nk4CfBc z_;!8&r9Wr>AK$u<`R#<_2MR&YPZ+FGz1Z|2<(GWO_pLG>s$APH|2ZVYzHG|7T_;S> zo|U;2$t5_EZK}np-;X?G+3Sq%ZaaA=v99s4$EK8@vHSAfzq%jv5mxprwpsfnnqh6# zEeZc_(O}sPdVztwT{bl?AC54;ean2$Zbs&20SyP9`$Ex9X-pka`kNo`mM`woJ^S~z z2J4&5$8oazwb#$yfBLMchpgxo1l3tm?N5&v2%?`j$VR=plS}lO5A8+2fNUuG!y-+_mZj zyU9M&ySwa^mgzhzDd?PT+N?9B;`T3%67Z;~X;A%v~ms zug=(Up@(zR{euFtq8abrZ9X``{og5`y*Zn_?|+;4PJ=-y!SCIkwV(Iq2W>4|vF4wr zzyzT_kG)y9y|Xl#XU)ny$hW&$==a3HQ(e(B6;$+ZPX2#mN>obBExqUQ^*L+-3s2VB zZjSb-4tms?kURazn^&%jJy*Zm6cwfSmQ78vzvu0%oyQghJlil;?3CJX)rn$WIkrWe z#}=kNZ+ zfbn;2x5s;3{WT`e_358uFRYr^J5TGX^9fFaTBQbltF@6_>YFl{Cf&GQ?s8$>%f4!cjUVO- z9pMYLFu#0tc~j8T-{MiPgEt;(_I`Uv^xlJ@*)F%%dCR=|A$C2JL)t5;H)<8v^hJh` z%3f!hO`c}RaN0PfpI=NwX2Is8rL%eS<8M!9TELaOT2~#tek}jpI9^E(5 zNn0gt>XBDxx~89b!);Q`d0spvyCf}`nPL5e(C)_K4R)1`sdw3;O1D4#xWw{Vhrxym zJo=w=90H@a*-hLcU47x5Xk>VD=#-kHXKd=&-}^27U~~6_>)aijDa%q4f*4#Fw(S4Z z`sE?}$_Oiy?e#ucj@@%={g&I<<#7b4inHx}dvm6kiSqwj2i>>NblBu?WHDRH#Uv+F zY~ES6mONo~vk%;Nrv_*gR~`^h*1n$lP2$<&$OEfBb~OBJl3B!dfbaY11FD}+-1qx` zH^r&l@a5zFZR_`TW!JXPdb|6xPD9AYPb-Wf_@fl1!#v;Qaap|oA6v91-TCK)CXPeP z&eS~C{Vth&QKr0b$=h8nxrS`V4lM1sWhC0b%fKbN;pH@~S+`!6J50NKAVpVq%^7ya z0QU6@qU+bp-fwnwS6a1O@Ne$_%hn!Sc;MH2*_mQnbUw!2U3K+!{t|Y;1lC?f72K9oI&|YJw|(To8@A=$I*4- z{~oD_tdy$9h`XO@t97+U z>&QXOCNRfZcpy~y3#CV#zXci<~ijByEPaU z4rrP_cCNiYM`rT@k*#&>ZT|J&?4KigD?9d+=YxQ!h2@Nld>R`2tCKd^h#gtdwxI7) zn4eL>iP?v4ezSLeYybK8vd*CJ!wYp*C2A>Kb{~8hpTRD6`(NVb2ORpoTkIZPVu`!J zTP4uA@krV$ffGSY4h}j$ZYr~C2+AI^xgc`opj73bc!7LYNwx{=YE>#f)M<(FN8T@LTd28dS#qI;twiUhMMjG>s;WLuzGt4N zUG4U~kww5!R&?6IFh&EHH>*rrr|%1TE!@73(Lk1Gi|72y>tcSqSfr9Dxuw z;-Qt*=J)U3=U`o_5_d^U^=#24o!tf1GAz=D{}i*h(x3QSZGLO-y+J1K-P9}}hdUG3 zzMiCU<<A!DHt^4+6`8(%xr{?+X*r2(#(0t#~w;IJxZzl9M zeJ$QTt-dP$=I2npTFHP9ulAq3q`y0t_w@Up0{prTlVqCs9_(#sKZtE=iGM+erJ5P zQ1a%4H$vUB0+0DIIw!22zkBJQ56cdG=S!4SXjWO&x_;Ti>#2r<&xGgyV43rHUmdUY z$FutvCVianwdrl0o5QJHYC3D?XxK${xp{Xkk6a>_nWg%1)1ecudb%1nPpT05t;ePE z&19>B!R8gGUnzNeDJkfG^?BKG@Ue}a=`7)A0ag;5vtA}vr`@sNbjm(tO-c1uqs4b8 zm^kiC+mym!b|us6PT=%KmnZlI&w9*v;DD01kJ1LQV;4f2{-1lCxI8K3q|q!*TdPNN zH!JD=x}E)5)=S|)BXjX7(^~u&P387ETu0=8e6o0QS1 zdo6-BXT{}$+t-%gEik{US?IdZ?GY1$^NQnNcA0*xuRK^$#9_sDLh0p)iSo~r|EFr{ z-Ku#cJpV)n(D zm$q_+)}d)GxtZ6Onkvrlk>Hpuv~7vyyO3?|EGeSeJ+0ys#5ZtF@=)JqIen2;$|~kI z-bn>jsxC~N90H3S5~V-z3RFD^?*K?qw3Tpmt6u5A%a>)SBTpJ9@VyO~Pn zn%cwL@^7rl+IVVJW_41Pk>Nzo1#8YJXRP}1?(fmfVQs(T)fcIkUr^}$d1HUe{)(!o zGh1pHLItMk@3PqWwfu7j|J}qshK3VbF}sXj+W$P{zd!fuENi))Zx7r5E9O=9lWnw` zBDej?j{2IT_j&kqjbiurby;NpxxW94|LnIHxBB0E`CB!#Gy2%~1f8`}bx)eJFQ@HJ ze0y*LBNJohqPfp}X3oDGpEmuQZZtXJ#=d6Ej zIiwx0{*`k^Wsp&Z`DD{uIXAfV4!kmDs52=%{enr^UB9a@Qbu<3i8YaB?i;?o`@wm5 z&JLF=ng?=1mOhw%|HGyKRu{G(**SybmB3cdQ0~V1H-Xt~zt}EcS^4UY?WHw40=mka zXFV=bSY7X*(7aH!EMcN-OwMnyGD+jgo8AD7+v z>x6Px)a#|0v%ao=^HgV-xBcUkMSk0(puCqM2JEg@kT})SR?@y=P?I%7@ zY~}v;;%c|)ksb}5_%DvZ>l{AL+`aG7gBd%e*&I|ZY>MAi|7K#g{4c?Vmpp=#1E&gy z*}wlXIbQattdg2SPI>i&#*CYbRMpqo)t&qhblS}KvfN5%mkA-ac{BTVZ&&!>*8aZ|(p`+k4+t|Rt~OfrMW`TZJye)dnc&VS9Sd`0!L%Zi943no1OI&9*1!IA-QIX(hVaU&r|0kg?hRj8b8yk632Cl{yA>GpZ63ev zxA`caEIfP7$GM9woXmXG*F|W^?5%nFi1l#vakp)KeJXlO-cDW3#}awmXvLz-Q!K9J zF<+Y}XWX+wH!*k<*YeCoj-8VOChxdCU*g8WjB^qT-zgsv={Mp`)eu$`jSBN-%Q5cd zYm_lG@{pDPJby*aoxIt3Z~QKbE1qBd?`3@3|9`exBGmz_`a)W`TA#eTJ$d*0B?>2e z*+LS1e>TgTr$hz5{`7m_!_C{XE4rL)zin$xp1=F|>iW`W^4+U$$~|ye6r{;-`&=^r z-#z{RH>2ir6tX)ucImrn>IS=J-boQevAf_Ih3kd~{bQo~g(Esa@CtTf@6LfufVI z-W4cR*X2B8tnYbw(?ZE<>t8(-VO;buoOyTtu8r1=32m#&FRi?Ha{1RZk6D?olI3fv z=S?>aJMP|{U$NTuYb3+Esu#b%_uK7SaAW>EwS}U7v1eD;yq-9J@23;ub2h$svWD5W z{F@7dpXIxy+wc8u_KlX`XjZUtnc;lum|v)62p4!pyB)j4+R z264eluVjAKz4o5({cdkJd;6zC3jxK>ii3w9?Y?*Zfbdt|_?uVJiWZLV}a+IxW-*`Z!b4m&cUU)!S&!G4Ug7Gj~cct zc2nn?>s`7@DzN;~Z_}SzwR<*zfqW4 zCUEe`wvvQ}rgEwi&D_5iTwZZ|zw+#z7E3x;HAI+ZYx(J9K9@XS(;Ob5@Ms4An*0NE z63(5h6P=J5`%iMN#NvJbYPQvUFgbH>HQ~{WQgN&dZN~w;t7=HmBsg^L5s`$Rj&LHt%zDS#`U6l3$iN%kn6#=p=Wq zsYN@Z^wM4Lx$Ihco2U53Gq=UAkr~TY6$j~vdZjAQa(WoKIp^h}&Z^{h{EaKtMqgLG zw%p{Rt!S%~lTcRiozP#r4AZk#o|-sy#bTFRcUe!Yxx0n$h1oZoFNWI}i!jdqF>UtY z_W42DA-P=AqxRD%R`+)t%M4_`+rY`QY;GN8v zptsah%k(hc@!eW8b_RXzbZA&%QK;k>+F_!tDSA98--N|4BqTKS^wXrBSt5~l{mYcN z+kdmO|C%%Vb;K#3=e@VIs{M`Bu591A)?X#jdrw2w;k1KB>qCsW*n>j9?tL(iz24kt zTjsQs1CEF9a4{Ii+}iaeSYW#J*~A{RSN}er{~G_vi^0C;QQK_uyOAHgjEp~>>b&yj zl=%Dq*BXytny7v6N(yjS>W~M*W zys}BQ`_=q?pS9iuWX@su5-K*W%QRFmY3`RCKhH)F^TZwwt*CWu^Y-t}V=AqbS86hx zq9W54=w`TbQIO_Ttu-F%rW&Vu0-xV`;PAFXmHV=@&a@)+5@ltMBa;4hGnIVis9cnn z+xa$uzhK!6ZN`9BLnFIe|4xao2x)t=zxA0TD|_>nHp|qcfY7Py4lNEA5ny~0G*`^|{MLO99+_Uwv+gx| z`)4;(SOo@pg|M0Rcel2r=t z%6OF@waf2k|9pgJ#%jebhK$;W#hWsjKY8s?bds61^t9LeKMroSaep81YsH;B`!7dv zLh#Nf^Rf+Gij@_@z{W~?wrg~^keKhfOnB1mNt4(XRO#J$CTItDarIEf1&jbX8ZatQF>z9jq z)>&@j1V0uBmSao!@5%prVO@D^U(S-NT64=8`S}=sT(90|bxHDjh1jq2-xyb2`Sa=Q z?00{j9ky4zVDigjHy78Yt6V-^jTTE*&c4zb@LIdVPA%DWxqe2_(lBGSAVFQx?x}Y9 zL7#8d1*Bx(l`y!y;c0G7%wxCstrj}Qxhy8vPozyhcG+p_raH-0Id5E&mt2&wzL7jf z@RxJ8`3shZTwTN~w{rdNH}}>jPZH{V6ehC0 zgV*8Al&9wfIyxu29Lc=4e?^e4h_+7C+^zLY2e;ksTov*9E5De!?$MXC`lC)4zR0bS zd41jOY}J|*9_m|Vbl${!T3_32zvbdm&N(}-sIpfZ7(4Z|I*8_&yxF4npQnkTa_VUV zH|ehrTJ^u4vtmC`em$=I^t0+y#>?!!w}iyZkNx&tnSgnj3gT&K~_n# zr?d7KWG24!s{Yf-9i|gKX_xaX!CS0ZyFLX;of`xZn5t{wn^@#Vb05DZm}4$>+I)N*wr5;fQYYWKl1V8_ zotc?4XUV-XTCn7hOTiF=ESnv$k4A^5!+eY3$-6VQchs z>~DS17uo+HWNvXlBgY!kWl??Z!8yzN-|1KBFgU#4 zbpFH%vx>O9E9nxml&4MC+tIm*m$mKc~%choCgkQ6?W|?(Xca>Lc`n4w<9WkpC z)fumR-}_H7{_0zo|I98ax7J34u(6#!VkBzBA|PGVS^MbW3PVAD9?`y-V4Fu8dZDrp zjyO*3=#X?d;-hrwdbr)Q7gO8K7JFQHYyM(Q`W_zta{q#`(!bm8x7+_!e0n2ycI4p= zH5ZTC>|i*@c|@T8%a!sam9qK`D?;{Mx~t3|_xVEqS!RJ@%Mn5$gIs zAwc7j-xqs_GXm{p*6TvJ>K07SfANnqXiN5WuWDz%*Q{g>J$p(y-9^~AG%9n_vVHG$ z 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 - - - - - Nixery - - - -

    -
    - -
    -
    -
    -
    - -

    - This is an instance - of Nixery, which - provides the ability to pull ad-hoc container images from a - Docker-compatible registry server. The image names specify the - contents the image should contain, which are then retrieved and - built by the Nix package manager. -

    -

    - Nix is also responsible for the creation of the container images - themselves. To do this it uses an interesting layering strategy - described in - this blog post. -

    -

    How does it work?

    -

    - Simply point your local Docker installation (or other compatible - registry client) at Nixery and ask for an image with the - contents you desire. Image contents are path separated in the - name, so for example if you needed an image that contains a - shell and emacs you could pull it as such: -

    -

    - nixery.dev/shell/emacs25-nox -

    -

    - Image tags are currently ignored. Every package name needs to - correspond to a key in the - nixpkgs package set. -

    -

    - The special meta-package shell provides default packages - you would expect in an interactive environment (such as an - interactively configured bash). If you use this package - you must specify it as the first package in an image. -

    -

    FAQ

    -
      -
    • - Where is the source code for this? -
      - Over on Github. -
    • -
    • - Which revision of nixpkgs is used? -
      - Nixery imports a Nix channel - via builtins.fetchTarball. Currently the channel - to which this instance is pinned is NixOS 19.03. -
    • -
    • - Is this an official Google project? -
      - No. Nixery is not officially supported by - Google. -
    • -
    • - Can I depend on the demo instance in production? -
      - No. The demo instance is just a demo. It - might go down, move, or disappear entirely at any point. -
      - To make use of Nixery in your project, please deploy a private - instance. Stay tuned for instructions for how to do this on - GKE. -
    • -
    • - Who made this? -
      - tazjin -
    • -
    - - From 85e8d760fcf16e50ba8e055561ac418f4f5bce58 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 4 Aug 2019 22:43:22 +0100 Subject: [PATCH 046/223] feat(build): Add mdBook 0.3.1 to build environment Upstream nixpkgs currently only has an older versin of mdBook. Until that changes, we keep a different version in here. --- tools/nixery/default.nix | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 7c7ad0b6c..28b94af5b 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -55,6 +55,24 @@ rec { mkdir $out cp ${./static}/* $out ''; + # nixpkgs currently has an old version of mdBook. A new version is + # built here, but eventually the update will be upstreamed + # (nixpkgs#65890) + mdbook = rustPlatform.buildRustPackage rec { + name = "mdbook-${version}"; + version = "0.3.1"; + doCheck = false; + + src = fetchFromGitHub { + owner = "rust-lang-nursery"; + repo = "mdBook"; + rev = "v${version}"; + sha256 = "0py69267jbs6b7zw191hcs011cm1v58jz8mglqx3ajkffdfl3ghw"; + }; + + cargoSha256 = "0qwhc42a86jpvjcaysmfcw8kmwa150lmz01flmlg74g6qnimff5m"; + }; + # Wrapper script running the Nixery server with the above two data # dependencies configured. From 2bef0ba2403f20826fbd615b0abb91cb2aff0350 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 4 Aug 2019 22:45:23 +0100 Subject: [PATCH 047/223] feat(build): Build Nixery book and embed it into Nixery image Executes the previously added mdBook on the previously added book source to yield a directory that can be served by Nixery on its index page. This is one of those 'I <3 Nix' things due to how easy it is to do. --- tools/nixery/default.nix | 18 ++++++++++-------- tools/nixery/docs/default.nix | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 tools/nixery/docs/default.nix diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 28b94af5b..4f0b14c90 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -48,13 +48,6 @@ rec { cat ${./build-registry-image.nix} > $out ''; - # Static files to serve on the Nixery index. This is used primarily - # for the demo instance running at nixery.dev and provides some - # background information for what Nixery is. - nixery-static = runCommand "nixery-static" {} '' - mkdir $out - cp ${./static}/* $out - ''; # nixpkgs currently has an old version of mdBook. A new version is # built here, but eventually the update will be upstreamed # (nixpkgs#65890) @@ -73,6 +66,10 @@ rec { cargoSha256 = "0qwhc42a86jpvjcaysmfcw8kmwa150lmz01flmlg74g6qnimff5m"; }; + # Use mdBook to build a static asset page which Nixery can then + # serve. This is primarily used for the public instance at + # nixery.dev. + nixery-book = callPackage ./docs { inherit mdbook; }; # Wrapper script running the Nixery server with the above two data # dependencies configured. @@ -81,7 +78,7 @@ rec { # are installing Nixery directly. nixery-bin = writeShellScriptBin "nixery" '' export NIX_BUILDER="${nixery-builder}" - export WEB_DIR="${nixery-static}" + export WEB_DIR="${nixery-book}" exec ${nixery-server}/bin/nixery ''; @@ -107,6 +104,11 @@ rec { mkdir -p /etc/nix echo 'sandbox = false' >> /etc/nix/nix.conf + # In some cases users building their own image might want to + # customise something on the inside (e.g. set up an environment + # for keys or whatever). + # + # This can be achieved by setting a 'preLaunch' script. ${preLaunch} exec ${nixery-bin}/bin/nixery diff --git a/tools/nixery/docs/default.nix b/tools/nixery/docs/default.nix new file mode 100644 index 000000000..ba652cef9 --- /dev/null +++ b/tools/nixery/docs/default.nix @@ -0,0 +1,36 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Builds the documentation page using the Rust project's 'mdBook' +# tool. +# +# Some of the documentation is pulled in and included from other +# sources. + +{ fetchFromGitHub, mdbook, runCommand }: + +let + nix-1p = fetchFromGitHub { + owner = "tazjin"; + repo = "nix-1p"; + rev = "aab846cd3d79fcd092b1bfea1346c587b2a56095"; + sha256 = "12dl0xrwgg2d4wyv9zxgdn0hzqnanczjg23vqn3356rywxlzzwak"; + }; +in runCommand "nixery-book" {} '' + mkdir -p $out + cp -r ${./.}/* . + chmod -R a+w src + cp ${nix-1p}/README.md src/nix-1p.md + ${mdbook}/bin/mdbook build -d $out +'' From a3f6278913d756c74d4f5c636c88b91a1b3748f6 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 4 Aug 2019 23:42:57 +0100 Subject: [PATCH 048/223] docs: Add an "under-the-hood" page explaining the build process This page describes the various steps that Nixery goes through when "procuring" an image. The intention is to give users some more visibility into what is going on and to make it clear that this is not just an image storage service. --- tools/nixery/docs/src/SUMMARY.md | 1 + tools/nixery/docs/src/nixery.md | 8 +- tools/nixery/docs/src/under-the-hood.md | 105 ++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 tools/nixery/docs/src/under-the-hood.md diff --git a/tools/nixery/docs/src/SUMMARY.md b/tools/nixery/docs/src/SUMMARY.md index 5d680b82e..f5ba3e9b0 100644 --- a/tools/nixery/docs/src/SUMMARY.md +++ b/tools/nixery/docs/src/SUMMARY.md @@ -1,4 +1,5 @@ # Summary - [Nixery](./nixery.md) +- [Under the hood](./under-the-hood.md) - [Nix, the language](./nix-1p.md) diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index d3d1911d2..83e1aac52 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -52,10 +52,6 @@ The instance at `nixery.dev` tracks a recent NixOS channel, currently NixOS Private registries might be configured to track a different channel (such as `nixos-unstable`) or even track a git repository with custom packages. -### Is this an official Google project? - -**No.** Nixery is not officially supported by Google. - ### Should I depend on `nixery.dev` in production? While we appreciate the enthusiasm, if you would like to use Nixery in your @@ -63,6 +59,10 @@ production project we recommend setting up a private instance. The public Nixery at `nixery.dev` is run on a best-effort basis and we make no guarantees about availability. +### Is this an official Google project? + +**No.** Nixery is not officially supported by Google. + ### Who made this? Nixery was written mostly by [tazjin][]. diff --git a/tools/nixery/docs/src/under-the-hood.md b/tools/nixery/docs/src/under-the-hood.md new file mode 100644 index 000000000..3791707b1 --- /dev/null +++ b/tools/nixery/docs/src/under-the-hood.md @@ -0,0 +1,105 @@ +# Under the hood + +This page serves as a quick explanation of what happens under-the-hood when an +image is requested from Nixery. + + + +- [1. The image manifest is requested](#1-the-image-manifest-is-requested) +- [2. Nix builds the image](#2-nix-builds-the-image) +- [3. Layers are uploaded to Nixery's storage](#3-layers-are-uploaded-to-nixerys-storage) +- [4. The image manifest is sent back](#4-the-image-manifest-is-sent-back) +- [5. Image layers are requested](#5-image-layers-are-requested) + + + +-------- + +## 1. The image manifest is requested + +When container registry clients such as Docker pull an image, the first thing +they do is ask for the image manifest. This is a JSON document describing which +layers are contained in an image, as well as some additional auxiliary +information. + +This request is of the form `GET /v2/$imageName/manifests/$imageTag`. + +Nixery receives this request and begins by splitting the image name into its +path components and substituting meta-packages (such as `shell`) for their +contents. + +For example, requesting `shell/htop/git` results in Nixery expanding the image +name to `["bashInteractive", "coreutils", "htop", "git"]`. + +If Nixery is configured with a private Nix repository, it also looks at the +image tag and substitutes `latest` with `master`. + +It then invokes Nix with three parameters: + +1. image contents (as above) +2. image tag +3. configured package set source + +## 2. Nix builds the image + +Using the parameters above, Nix imports the package set and begins by mapping +the image names to attributes in the package set. + +A special case during this process is packages with uppercase characters in +their name, for example anything under `haskellPackages`. The registry protocol +does not allow uppercase characters, so the Nix code will translate something +like `haskellpackages` (lowercased) to the correct attribute name. + +After identifying all contents, Nix determines the contents of each layer while +optimising for the best possible cache efficiency. + +Finally it builds each layer, assembles the image manifest as JSON structure, +and yields this manifest back to the web server. + +*Note:* While this step is running (which can take some time in the case of +large first-time image builds), the registry client is left hanging waiting for +an HTTP response. Unfortunately the registry protocol does not allow for any +feedback back to the user at this point, so from the user's perspective things +just ... hang, for a moment. + +## 3. Layers are uploaded to Nixery's storage + +Nixery inspects the returned manifest and uploads each layer to the configured +[Google Cloud Storage][gcs] bucket. To avoid unnecessary uploading, it will +first check whether layers are already present in the bucket and - just to be +safe - compare their MD5-hashes against what was built. + +## 4. The image manifest is sent back + +If everything went well at this point, Nixery responds to the registry client +with the image manifest. + +The client now inspects the manifest and basically sees a list of SHA256-hashes, +each corresponding to one layer of the image. Most clients will now consult +their local layer storage and determine which layers they are missing. + +Each of the missing layers is then requested from Nixery. + +## 5. Image layers are requested + +For each image layer that it needs to retrieve, the registry client assembles a +request that looks like this: + +`GET /v2/${imageName}/blob/sha256:${layerHash}` + +Nixery receives these requests and *rewrites* them to Google Cloud Storage URLs, +responding with an `HTTP 303 See Other` status code and the actual download URL +of the layer. + +Nixery supports using private buckets which are not generally world-readable, in +which case [signed URLs][] are constructed using a private key. These allow the +registry client to download each layer without needing to care about how the +underlying authentication works. + +--------- + +That's it. After these five steps the registry client has retrieved all it needs +to run the image produced by Nixery. + +[gcs]: https://cloud.google.com/storage/ +[signed URLs]: https://cloud.google.com/storage/docs/access-control/signed-urls From 6293d69fd94f709d029c5c121956900cad3d24c1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 5 Aug 2019 00:27:39 +0100 Subject: [PATCH 049/223] docs: Add a section on running your own Nixery --- tools/nixery/docs/src/SUMMARY.md | 6 +- tools/nixery/docs/src/run-your-own.md | 141 ++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tools/nixery/docs/src/run-your-own.md diff --git a/tools/nixery/docs/src/SUMMARY.md b/tools/nixery/docs/src/SUMMARY.md index f5ba3e9b0..677f32897 100644 --- a/tools/nixery/docs/src/SUMMARY.md +++ b/tools/nixery/docs/src/SUMMARY.md @@ -1,5 +1,7 @@ # Summary - [Nixery](./nixery.md) -- [Under the hood](./under-the-hood.md) -- [Nix, the language](./nix-1p.md) + - [Under the hood](./under-the-hood.md) + - [Run your own Nixery](./run-your-own.md) +- [Nix](./nix.md) + - [Nix, the language](./nix-1p.md) diff --git a/tools/nixery/docs/src/run-your-own.md b/tools/nixery/docs/src/run-your-own.md new file mode 100644 index 000000000..0539ab02f --- /dev/null +++ b/tools/nixery/docs/src/run-your-own.md @@ -0,0 +1,141 @@ +## Run your own Nixery + + + +- [0. Prerequisites](#0-prerequisites) +- [1. Choose a package set](#1-choose-a-package-set) +- [2. Build Nixery itself](#2-build-nixery-itself) +- [3. Prepare configuration](#3-prepare-configuration) +- [4. Deploy Nixery](#4-deploy-nixery) +- [5. Productionise](#5-productionise) + + + + +--------- + +⚠ This page is still under construction! ⚠ + +-------- + +Running your own Nixery is not difficult, but requires some setup. Follow the +steps below to get up & running. + +*Note:* Nixery can be run inside of a [GKE][] cluster, providing a local service +from which images can be requested. Documentation for how to set this up is +forthcoming, please see [nixery#4][]. + +## 0. Prerequisites + +To run Nixery, you must have: + +* [Nix][] (to build Nixery itself) +* Somewhere to run it (your own server, Google AppEngine, a Kubernetes cluster, + whatever!) +* A [Google Cloud Storage][gcs] bucket in which to store & serve layers + +## 1. Choose a package set + +When running your own Nixery you need to decide which package set you want to +serve. By default, Nixery builds packages from a recent NixOS channel which +ensures that most packages are cached upstream and no expensive builds need to +be performed for trivial things. + +However if you are running a private Nixery, chances are high that you intend to +use it with your own packages. There are three options available: + +1. Specify an upstream Nix/NixOS channel[^1], such as `nixos-19.03` or + `nixos-unstable`. +2. Specify your own git-repository with a custom package set[^2]. This makes it + possible to pull different tags, branches or commits by modifying the image + tag. +3. Specify a local file path containing a Nix package set. Where this comes from + or what it contains is up to you. + +## 2. Build Nixery itself + +Building Nixery creates a container image. This section assumes that the +container runtime used is Docker, please modify instructions correspondingly if +you are using something else. + +With a working Nix installation, building Nixery is done by invoking `nix-build +-A nixery-image` from a checkout of the [Nixery repository][repo]. + +This will create a `result`-symlink which points to a tarball containing the +image. In Docker, this tarball can be loaded by using `docker load -i result`. + +## 3. Prepare configuration + +Nixery is configured via environment variables. + +You must set *all* of these: + +* `BUCKET`: [Google Cloud Storage][gcs] bucket to store & serve image layers +* `PORT`: HTTP port on which Nixery should listen + +You may set *one* of these, if unset Nixery defaults to `nixos-19.03`: + +* `NIXERY_CHANNEL`: The name of a Nix/NixOS channel to use for building +* `NIXERY_PKGS_REPO`: URL of a git repository containing a package set (uses + locally configured SSH/git credentials) +* `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use + for building + +You may set *both* of these: + +* `GCS_SIGNING_KEY`: A Google service account key (in PEM format) that can be + used to [sign Cloud Storage URLs][signed-urls] +* `GCS_SIGNING_ACCOUNT`: Google service account ID that the signing key belongs + to + +To authenticate to the configured GCS bucket, Nixery uses Google's [Application +Default Credentials][ADC]. Depending on your environment this may require +additional configuration. + +## 4. Deploy Nixery + +With the above environment variables configured, you can run the image that was +built in step 2. + +How this works depends on the environment you are using and is, for now, outside +of the scope of this tutorial. + +Once Nixery is running you can immediately start requesting images from it. + +## 5. Productionise + +(⚠ Here be dragons! ⚠) + +Nixery is still an early project and has not yet been deployed in any production +environments and some caveats apply. + +Notably, Nixery currently does not support any authentication methods, so anyone +with network access to the registry can retrieve images. + +Running a Nixery inside of a fenced-off environment (such as internal to a +Kubernetes cluster) should be fine, but you should consider to do all of the +following: + +* Issue a TLS certificate for the hostname you are assigning to Nixery. In fact, + Docker will refuse to pull images from registries that do not use TLS (with + the exception of `.local` domains). +* Configure signed GCS URLs to avoid having to make your bucket world-readable. +* Configure request timeouts for Nixery if you have your own web server in front + of it. This will be natively supported by Nixery in the future. + +------- + +[^1]: Nixery will not work with Nix channels older than `nixos-19.03`. + +[^2]: This documentation will be updated with instructions on how to best set up + a custom Nix repository. Nixery expects custom package sets to be a superset + of `nixpkgs`, as it uses `lib` and other features from `nixpkgs` + extensively. + +[GKE]: https://cloud.google.com/kubernetes-engine/ +[nixery#4]: https://github.com/google/nixery/issues/4 +[Nix]: https://nixos.org/nix +[gcs]: https://cloud.google.com/storage/ +[repo]: https://github.com/google/nixery +[signed-urls]: under-the-hood.html#5-image-layers-are-requested +[ADC]: https://cloud.google.com/docs/authentication/production#finding_credentials_automatically From d87662b7b562d68561775adeefd29fffb065465c Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 5 Aug 2019 00:27:47 +0100 Subject: [PATCH 050/223] docs: Add a section on Nix itself --- tools/nixery/docs/src/nix.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tools/nixery/docs/src/nix.md diff --git a/tools/nixery/docs/src/nix.md b/tools/nixery/docs/src/nix.md new file mode 100644 index 000000000..2bfd75a69 --- /dev/null +++ b/tools/nixery/docs/src/nix.md @@ -0,0 +1,31 @@ +# Nix + +These sections are designed to give some background information on what Nix is. +If you've never heard of Nix before looking at Nixery, this might just be the +page for you! + +[Nix][] is a functional package-manager that comes with a number of advantages +over traditional package managers, such as side-by-side installs of different +package versions, atomic updates, easy customisability, simple binary caching +and much more. Feel free to explore the [Nix website][Nix] for an overview of +Nix itself. + +Nix uses a custom programming language also called Nix, which is explained here +[on its own page][nix-1p]. + +In addition to the package manager and language, the Nix project also maintains +[NixOS][] - a Linux distribution built entirely on Nix. On NixOS, users can +declaratively describe the *entire* configuration of their system and perform +updates/rollbacks to other system configurations with ease. + +Most Nix packages are tracked in the [Nix package set][nixpkgs], usually simply +referred to as `nixpkgs`. It contains tens of thousands of packages already! + +Nixery (which you are looking at!) provides an easy & simple way to get started +with Nix, in fact you don't even need to know that you're using Nix to make use +of Nixery. + +[Nix]: https://nixos.org/nix/ +[nix-1p]: nix-1p.html +[NixOS]: https://nixos.org/ +[nixpkgs]: https://github.com/nixos/nixpkgs From 12a853fab74de9d146580891d8b70a7c216d2c83 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 5 Aug 2019 01:20:52 +0100 Subject: [PATCH 051/223] docs: Minor fixes to README after new website release --- tools/nixery/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index bcb8e40ec..9c323a57f 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -1,5 +1,5 @@
    - +
    ----------------- @@ -16,12 +16,13 @@ image name. The path components refer to top-level keys in `nixpkgs` and are used to build a container image using Nix's [buildLayeredImage][] functionality. +A public instance as well as additional documentation is available at +[nixery.dev][public]. + The project started out with the intention of becoming a Kubernetes controller that can serve declarative image specifications specified in CRDs as container images. The design for this is outlined in [a public gist][gist]. -An example instance is available at [nixery.dev][demo]. - This is not an officially supported Google project. ## Usage example @@ -94,7 +95,7 @@ variables: ## Roadmap -### Kubernetes integration (in the future) +### Kubernetes integration It should be trivial to deploy Nixery inside of a Kubernetes cluster with correct caching behaviour, addressing and so on. @@ -104,5 +105,5 @@ See [issue #4](https://github.com/google/nixery/issues/4). [Nix]: https://nixos.org/ [gist]: https://gist.github.com/tazjin/08f3d37073b3590aacac424303e6f745 [buildLayeredImage]: https://grahamc.com/blog/nix-and-layered-docker-images -[demo]: https://nixery.dev +[public]: https://nixery.dev [gcs]: https://cloud.google.com/storage/ From 993fda337755e02e4505b0a85f14581baa8ed1d0 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 8 Aug 2019 17:47:46 +0100 Subject: [PATCH 052/223] fix(go): Fix breakage in unsigned URLs This affected the public instance which is still running without URL signing. Should add some monitoring! --- tools/nixery/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 291cdf52d..d20ede2eb 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -328,7 +328,7 @@ func constructLayerUrl(cfg *config, digest string) (string, error) { opts.Expires = time.Now().Add(5 * time.Minute) return storage.SignedURL(cfg.bucket, object, &opts) } else { - return ("https://storage.googleapis.com" + cfg.bucket + "/" + object), nil + return ("https://storage.googleapis.com/" + cfg.bucket + "/" + object), nil } } From c727b3ca9ea44e5dbc2f4c7b280af26f8c53a526 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 8 Aug 2019 20:57:11 +0100 Subject: [PATCH 053/223] chore(nix): Increase maximum number of layers to 96 This uses a significantly larger percentage of the total available layers (125) than before, which means that cache hits for layers become more likely between images. --- tools/nixery/build-registry-image.nix | 9 ++++----- tools/nixery/default.nix | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-registry-image.nix index 6a5312b44..255f1ca9b 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-registry-image.nix @@ -29,11 +29,10 @@ packages ? "[]", # Optional bash script to run on the files prior to fixturizing the layer. extraCommands ? "", uid ? 0, gid ? 0, - # Docker's lowest maximum layer limit is 42-layers for an old - # version of the AUFS graph driver. We pick 24 to ensure there is - # plenty of room for extension. I believe the actual maximum is - # 128. - maxLayers ? 24, + # Docker's modern image storage mechanisms have a maximum of 125 + # layers. To allow for some extensibility (via additional layers), + # the default here is set to something a little less than that. + maxLayers ? 96, # Configuration for which package set to use when building. # diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 4f0b14c90..092c76e9c 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -30,7 +30,6 @@ rec { # or similar (as other required files will not be included), but # buildGoPackage requires a package path. goPackagePath = "github.com/google/nixery"; - goDeps = ./go-deps.nix; src = ./.; @@ -116,6 +115,7 @@ rec { in dockerTools.buildLayeredImage { name = "nixery"; config.Cmd = ["${nixery-launch-script}/bin/nixery"]; + maxLayers = 96; contents = [ cacert coreutils From 3e385dc379de4a1e8dd619a7a47714925e99490a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 8 Aug 2019 21:02:08 +0100 Subject: [PATCH 054/223] docs: Update embedded nix-1p The new commit has an operator table, which is nice to have! --- tools/nixery/docs/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/docs/default.nix b/tools/nixery/docs/default.nix index ba652cef9..deebdffd7 100644 --- a/tools/nixery/docs/default.nix +++ b/tools/nixery/docs/default.nix @@ -24,8 +24,8 @@ let nix-1p = fetchFromGitHub { owner = "tazjin"; repo = "nix-1p"; - rev = "aab846cd3d79fcd092b1bfea1346c587b2a56095"; - sha256 = "12dl0xrwgg2d4wyv9zxgdn0hzqnanczjg23vqn3356rywxlzzwak"; + rev = "3cd0f7d7b4f487d04a3f1e3ca8f2eb1ab958c49b"; + sha256 = "02lpda03q580gyspkbmlgnb2cbm66rrcgqsv99aihpbwyjym81af"; }; in runCommand "nixery-book" {} '' mkdir -p $out From ce31598f4264a3c8af749b3fd533a905dcd69edc Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 11 Aug 2019 16:44:05 +0100 Subject: [PATCH 055/223] feat(group-layers): Implement first half of new layering strategy The strategy is described in-depth in the comment at the top of the implementation file, as well as in the design document: https://storage.googleapis.com/nixdoc/nixery-layers.html --- tools/nixery/group-layers/group-layers.go | 267 ++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 tools/nixery/group-layers/group-layers.go diff --git a/tools/nixery/group-layers/group-layers.go b/tools/nixery/group-layers/group-layers.go new file mode 100644 index 000000000..1ee67433d --- /dev/null +++ b/tools/nixery/group-layers/group-layers.go @@ -0,0 +1,267 @@ +// This program reads an export reference graph (i.e. a graph representing the +// runtime dependencies of a set of derivations) created by Nix and groups them +// in a way that is likely to match the grouping for other derivation sets with +// overlapping dependencies. +// +// This is used to determine which derivations to include in which layers of a +// container image. +// +// # Inputs +// +// * a graph of Nix runtime dependencies, generated via exportReferenceGraph +// * a file containing absolute popularity values of packages in the +// Nix package set (in the form of a direct reference count) +// * a maximum number of layers to allocate for the image (the "layer budget") +// +// # Algorithm +// +// It works by first creating a (directed) dependency tree: +// +// img (root node) +// │ +// ├───> A ─────┐ +// │ v +// ├───> B ───> E +// │ ^ +// ├───> C ─────┘ +// │ │ +// │ v +// └───> D ───> F +// │ +// └────> G +// +// Each node (i.e. package) is then visited to determine how important +// it is to separate this node into its own layer, specifically: +// +// 1. Is the node within a certain threshold percentile of absolute +// popularity within all of nixpkgs? (e.g. `glibc`, `openssl`) +// +// 2. Is the node's runtime closure above a threshold size? (e.g. 100MB) +// +// In either case, a bit is flipped for this node representing each +// condition and an edge to it is inserted directly from the image +// root, if it does not already exist. +// +// For the rest of the example we assume 'G' is above the threshold +// size and 'E' is popular. +// +// This tree is then transformed into a dominator tree: +// +// img +// │ +// ├───> A +// ├───> B +// ├───> C +// ├───> E +// ├───> D ───> F +// └───> G +// +// Specifically this means that the paths to A, B, C, E, G, and D +// always pass through the root (i.e. are dominated by it), whilst F +// is dominated by D (all paths go through it). +// +// The top-level subtrees are considered as the initially selected +// layers. +// +// If the list of layers fits within the layer budget, it is returned. +// +// Otherwise layers are merged together in this order: +// +// * layers whose root meets neither condition above +// * layers whose root is popular +// * layers whose root is big +// * layers whose root meets both conditions +// +// # Threshold values +// +// Threshold values for the partitioning conditions mentioned above +// have not yet been determined, but we will make a good first guess +// based on gut feeling and proceed to measure their impact on cache +// hits/misses. +// +// # Example +// +// Using the logic described above as well as the example presented in +// the introduction, this program would create the following layer +// groupings (assuming no additional partitioning): +// +// Layer budget: 1 +// Layers: { A, B, C, D, E, F, G } +// +// Layer budget: 2 +// Layers: { G }, { A, B, C, D, E, F } +// +// Layer budget: 3 +// Layers: { G }, { E }, { A, B, C, D, F } +// +// Layer budget: 4 +// Layers: { G }, { E }, { D, F }, { A, B, C } +// +// ... +// +// Layer budget: 10 +// Layers: { E }, { D, F }, { A }, { B }, { C } +package main + +import ( + "encoding/json" + "flag" + "io/ioutil" + "log" + "fmt" + "regexp" + "os" + + "gonum.org/v1/gonum/graph/simple" + "gonum.org/v1/gonum/graph/flow" + "gonum.org/v1/gonum/graph/encoding/dot" +) + +// closureGraph represents the structured attributes Nix outputs when asking it +// for the exportReferencesGraph of a list of derivations. +type exportReferences struct { + References struct { + Graph []string `json:"graph"` + } `json:"exportReferencesGraph"` + + Graph []struct { + Size uint64 `json:"closureSize` + Path string `json:"path"` + Refs []string `json:"references"` + } `json:"graph"` +} + +// closure as pointed to by the graph nodes. +type closure struct { + GraphID int64 + Path string + Size uint64 + Refs []string + // TODO(tazjin): popularity and other funny business +} + +func (c *closure) ID() int64 { + return c.GraphID +} + +var nixRegexp = regexp.MustCompile(`^/nix/store/[a-z0-9]+-`) +func (c *closure) DOTID() string { + return nixRegexp.ReplaceAllString(c.Path, "") +} + +func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *closure) { + for _, c := range node.Refs { + // Nix adds a self reference to each node, which + // should not be inserted. + if c != node.Path { + edge := graph.NewEdge(node, (*cmap)[c]) + graph.SetEdge(edge) + } + } +} + +// Create a graph structure from the references supplied by Nix. +func buildGraph(refs *exportReferences) *simple.DirectedGraph { + cmap := make(map[string]*closure) + graph := simple.NewDirectedGraph() + + // Insert all closures into the graph, as well as a fake root + // closure which serves as the top of the tree. + // + // A map from store paths to IDs is kept to actually insert + // edges below. + root := &closure { + GraphID: 0, + Path: "image_root", + } + graph.AddNode(root) + + for idx, c := range refs.Graph { + node := &closure { + GraphID: int64(idx + 1), // inc because of root node + Path: c.Path, + Size: c.Size, + Refs: c.Refs, + } + + graph.AddNode(node) + cmap[c.Path] = node + } + + // Insert the top-level closures with edges from the root + // node, then insert all edges for each closure. + for _, p := range refs.References.Graph { + edge := graph.NewEdge(root, cmap[p]) + graph.SetEdge(edge) + } + + for _, c := range cmap { + insertEdges(graph, &cmap, c) + } + + gv, err := dot.Marshal(graph, "deps", "", "") + if err != nil { + log.Fatalf("Could not encode graph: %s\n", err) + } + fmt.Print(string(gv)) + os.Exit(0) + + return graph +} + +// Calculate the dominator tree of the entire package set and group +// each top-level subtree into a layer. +func dominate(graph *simple.DirectedGraph) { + dt := flow.Dominators(graph.Node(0), graph) + + // convert dominator tree back into encodable graph + dg := simple.NewDirectedGraph() + + for nodes := graph.Nodes(); nodes.Next(); { + dg.AddNode(nodes.Node()) + } + + for nodes := dg.Nodes(); nodes.Next(); { + node := nodes.Node() + for _, child := range dt.DominatedBy(node.ID()) { + edge := dg.NewEdge(node, child) + dg.SetEdge(edge) + } + } + + gv, err := dot.Marshal(dg, "deps", "", "") + if err != nil { + log.Fatalf("Could not encode graph: %s\n", err) + } + fmt.Print(string(gv)) + + // fmt.Printf("%v edges in the graph\n", graph.Edges().Len()) + // top := 0 + // for _, n := range dt.DominatedBy(0) { + // fmt.Printf("%q is top-level\n", n.(*closure).Path) + // top++ + // } + // fmt.Printf("%v total top-level nodes\n", top) + // root := dt.Root().(*closure) + // fmt.Printf("dominator tree root is %q\n", root.Path) + // fmt.Printf("%v nodes can reach to 1\n", nodes.Len()) +} + +func main() { + inputFile := flag.String("input", ".attrs.json", "Input file containing graph") + flag.Parse() + + file, err := ioutil.ReadFile(*inputFile) + if err != nil { + log.Fatalf("Failed to load input: %s\n", err) + } + + var refs exportReferences + err = json.Unmarshal(file, &refs) + if err != nil { + log.Fatalf("Failed to deserialise input: %s\n", err) + } + + graph := buildGraph(&refs) + dominate(graph) +} From 92078527dbe8f853d984a4038e718c662c016741 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 11 Aug 2019 20:05:12 +0100 Subject: [PATCH 056/223] feat(group-layers): Add preliminary size & popularity considerations As described in the design document, this adds considerations for closure size and popularity. All closures meeting a certain threshold for either value will have an extra edge from the image root to themselves inserted in the graph, which will cause them to be considered for inclusion in a separate layer. This is preliminary because popularity is considered as a boolean toggle (the input I generated only contains the top ~200 most popular packages), but it should be using either absolute popularity values or percentiles (needs some experimentation). --- tools/nixery/group-layers/group-layers.go | 92 ++++++++++++++++------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/tools/nixery/group-layers/group-layers.go b/tools/nixery/group-layers/group-layers.go index 1ee67433d..7be89e4e6 100644 --- a/tools/nixery/group-layers/group-layers.go +++ b/tools/nixery/group-layers/group-layers.go @@ -108,9 +108,7 @@ import ( "flag" "io/ioutil" "log" - "fmt" "regexp" - "os" "gonum.org/v1/gonum/graph/simple" "gonum.org/v1/gonum/graph/flow" @@ -131,12 +129,20 @@ type exportReferences struct { } `json:"graph"` } +// Popularity data for each Nix package that was calculated in advance. +// +// Popularity is a number from 1-100 that represents the +// popularity percentile in which this package resides inside +// of the nixpkgs tree. +type pkgsMetadata = map[string]int + // closure as pointed to by the graph nodes. type closure struct { GraphID int64 Path string Size uint64 Refs []string + Popularity int // TODO(tazjin): popularity and other funny business } @@ -149,7 +155,36 @@ func (c *closure) DOTID() string { return nixRegexp.ReplaceAllString(c.Path, "") } -func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *closure) { +// bigOrPopular checks whether this closure should be considered for +// separation into its own layer, even if it would otherwise only +// appear in a subtree of the dominator tree. +func (c *closure) bigOrPopular(pkgs *pkgsMetadata) bool { + const sizeThreshold = 100 * 1000000 // 100MB + + if c.Size > sizeThreshold { + return true + } + + // TODO(tazjin): After generating the full data, this should + // be changed to something other than a simple inclusion + // (currently the test-data only contains the top 200 + // packages). + pop, ok := (*pkgs)[c.DOTID()] + if ok { + log.Printf("%q is popular!\n", c.DOTID()) + } + c.Popularity = pop + return ok +} + +func insertEdges(graph *simple.DirectedGraph, pop *pkgsMetadata, cmap *map[string]*closure, node *closure) { + // Big or popular nodes get a separate edge from the top to + // flag them for their own layer. + if node.bigOrPopular(pop) && !graph.HasEdgeFromTo(0, node.ID()) { + edge := graph.NewEdge(graph.Node(0), node) + graph.SetEdge(edge) + } + for _, c := range node.Refs { // Nix adds a self reference to each node, which // should not be inserted. @@ -161,7 +196,7 @@ func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *c } // Create a graph structure from the references supplied by Nix. -func buildGraph(refs *exportReferences) *simple.DirectedGraph { +func buildGraph(refs *exportReferences, pop *pkgsMetadata) *simple.DirectedGraph { cmap := make(map[string]*closure) graph := simple.NewDirectedGraph() @@ -196,15 +231,15 @@ func buildGraph(refs *exportReferences) *simple.DirectedGraph { } for _, c := range cmap { - insertEdges(graph, &cmap, c) + insertEdges(graph, pop, &cmap, c) } - gv, err := dot.Marshal(graph, "deps", "", "") - if err != nil { - log.Fatalf("Could not encode graph: %s\n", err) - } - fmt.Print(string(gv)) - os.Exit(0) + // gv, err := dot.Marshal(graph, "deps", "", "") + // if err != nil { + // log.Fatalf("Could not encode graph: %s\n", err) + // } + // fmt.Print(string(gv)) + // os.Exit(0) return graph } @@ -233,25 +268,16 @@ func dominate(graph *simple.DirectedGraph) { if err != nil { log.Fatalf("Could not encode graph: %s\n", err) } - fmt.Print(string(gv)) - - // fmt.Printf("%v edges in the graph\n", graph.Edges().Len()) - // top := 0 - // for _, n := range dt.DominatedBy(0) { - // fmt.Printf("%q is top-level\n", n.(*closure).Path) - // top++ - // } - // fmt.Printf("%v total top-level nodes\n", top) - // root := dt.Root().(*closure) - // fmt.Printf("dominator tree root is %q\n", root.Path) - // fmt.Printf("%v nodes can reach to 1\n", nodes.Len()) + ioutil.WriteFile("graph.dot", gv, 0644) } func main() { - inputFile := flag.String("input", ".attrs.json", "Input file containing graph") + graphFile := flag.String("graph", ".attrs.json", "Input file containing graph") + popFile := flag.String("pop", "popularity.json", "Package popularity data") flag.Parse() - file, err := ioutil.ReadFile(*inputFile) + // Parse graph data + file, err := ioutil.ReadFile(*graphFile) if err != nil { log.Fatalf("Failed to load input: %s\n", err) } @@ -262,6 +288,20 @@ func main() { log.Fatalf("Failed to deserialise input: %s\n", err) } - graph := buildGraph(&refs) + // Parse popularity data + popBytes, err := ioutil.ReadFile(*popFile) + if err != nil { + log.Fatalf("Failed to load input: %s\n", err) + } + + var pop pkgsMetadata + err = json.Unmarshal(popBytes, &pop) + if err != nil { + log.Fatalf("Failed to deserialise input: %s\n", err) + } + + log.Printf("%v\n", pop) + + graph := buildGraph(&refs, &pop) dominate(graph) } From 590ce994bb18b15c9b654c2aa426866750c3c494 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 11 Aug 2019 20:10:03 +0100 Subject: [PATCH 057/223] feat(group-layers): Add initial popcount scripts This script generates an entry in a text file for each time a derivation is referred to by another in nixpkgs. For initial testing, this output can be turned into group-layers compatible JSON with this *trivial* invocation: cat output | awk '{ print "{\"" $2 "\":" $1 "}"}' | jq -s '. | add | with_entries(.key |= sub("/nix/store/[a-z0-9]+-";""))' > test-data.json --- tools/nixery/group-layers/popcount | 13 +++++++ tools/nixery/group-layers/popcount.nix | 51 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100755 tools/nixery/group-layers/popcount create mode 100644 tools/nixery/group-layers/popcount.nix diff --git a/tools/nixery/group-layers/popcount b/tools/nixery/group-layers/popcount new file mode 100755 index 000000000..83baf3045 --- /dev/null +++ b/tools/nixery/group-layers/popcount @@ -0,0 +1,13 @@ +#!/bin/bash +set -ueo pipefail + +function graphsFor() { + local pkg="${1}" + local graphs=$(nix-build --timeout 2 --argstr target "${pkg}" popcount.nix || echo -n 'empty.json') + cat $graphs | jq -r -cM '.[] | .references[]' +} + +for pkg in $(cat all-top-level.json | jq -r '.[]'); do + graphsFor "${pkg}" 2>/dev/null + echo "Printed refs for ${pkg}" >&2 +done diff --git a/tools/nixery/group-layers/popcount.nix b/tools/nixery/group-layers/popcount.nix new file mode 100644 index 000000000..e21d73677 --- /dev/null +++ b/tools/nixery/group-layers/popcount.nix @@ -0,0 +1,51 @@ +{ pkgs ? import { config.allowUnfree = false; } +, target }: + +let + inherit (pkgs) coreutils runCommand writeText; + inherit (builtins) replaceStrings readFile toFile fromJSON toJSON foldl' listToAttrs; + + path = [ pkgs."${target}" ]; + + # graphJSON abuses feature in Nix that makes structured runtime + # closure information available to builders. This data is imported + # back via IFD to process it for layering data. + graphJSON = + path: + runCommand "build-graph" { + __structuredAttrs = true; + exportReferencesGraph.graph = path; + PATH = "${coreutils}/bin"; + builder = toFile "builder" '' + . .attrs.sh + cat .attrs.json > ''${outputs[out]} + ''; + } ""; + + buildClosures = paths: (fromJSON (readFile (graphJSON paths))); + + buildGraph = paths: listToAttrs (map (c: { + name = c.path; + value = { + inherit (c) closureSize references; + }; + }) (buildClosures paths)); + + # Nix does not allow attrbute set keys to refer to store paths, but + # we need them to for the purpose of the calculation. To work around + # it, the store path prefix is replaced with the string 'closure/' + # and later replaced again. + fromStorePath = replaceStrings [ "/nix/store" ] [ "closure/" ]; + toStorePath = replaceStrings [ "closure/" ] [ "/nix/store/" ]; + + buildTree = paths: + let + graph = buildGraph paths; + top = listToAttrs (map (p: { + name = fromStorePath (toString p); + value = {}; + }) paths); + in top; + + outputJson = thing: writeText "the-thing.json" (builtins.toJSON thing); +in outputJson (buildClosures path).graph From 56a426952c5ce4e20b4673a0087e6a2c5604fdf5 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 01:41:17 +0100 Subject: [PATCH 058/223] feat(group-layers): Finish layering algorithm implementation This commit adds the actual logic for extracting layer groupings and merging them until the layer budget is satisfied. The implementation conforms to the design doc as of the time of this commit. --- tools/nixery/group-layers/group-layers.go | 161 ++++++++++++++-------- 1 file changed, 103 insertions(+), 58 deletions(-) diff --git a/tools/nixery/group-layers/group-layers.go b/tools/nixery/group-layers/group-layers.go index 7be89e4e6..93f2e520a 100644 --- a/tools/nixery/group-layers/group-layers.go +++ b/tools/nixery/group-layers/group-layers.go @@ -65,12 +65,11 @@ // // If the list of layers fits within the layer budget, it is returned. // -// Otherwise layers are merged together in this order: +// Otherwise, a merge rating is calculated for each layer. This is the +// product of the layer's total size and its root node's popularity. // -// * layers whose root meets neither condition above -// * layers whose root is popular -// * layers whose root is big -// * layers whose root meets both conditions +// Layers are then merged in ascending order of merge ratings until +// they fit into the layer budget. // // # Threshold values // @@ -109,10 +108,10 @@ import ( "io/ioutil" "log" "regexp" + "sort" - "gonum.org/v1/gonum/graph/simple" "gonum.org/v1/gonum/graph/flow" - "gonum.org/v1/gonum/graph/encoding/dot" + "gonum.org/v1/gonum/graph/simple" ) // closureGraph represents the structured attributes Nix outputs when asking it @@ -123,7 +122,7 @@ type exportReferences struct { } `json:"exportReferencesGraph"` Graph []struct { - Size uint64 `json:"closureSize` + Size uint64 `json:"closureSize"` Path string `json:"path"` Refs []string `json:"references"` } `json:"graph"` @@ -136,14 +135,26 @@ type exportReferences struct { // of the nixpkgs tree. type pkgsMetadata = map[string]int +// layer represents the data returned for each layer that Nix should +// build for the container image. +type layer struct { + Contents []string `json:"contents"` + mergeRating uint64 +} + +func (a layer) merge(b layer) layer { + a.Contents = append(a.Contents, b.Contents...) + a.mergeRating += b.mergeRating + return a +} + // closure as pointed to by the graph nodes. type closure struct { - GraphID int64 - Path string - Size uint64 - Refs []string + GraphID int64 + Path string + Size uint64 + Refs []string Popularity int - // TODO(tazjin): popularity and other funny business } func (c *closure) ID() int64 { @@ -151,6 +162,7 @@ func (c *closure) ID() int64 { } var nixRegexp = regexp.MustCompile(`^/nix/store/[a-z0-9]+-`) + func (c *closure) DOTID() string { return nixRegexp.ReplaceAllString(c.Path, "") } @@ -158,29 +170,30 @@ func (c *closure) DOTID() string { // bigOrPopular checks whether this closure should be considered for // separation into its own layer, even if it would otherwise only // appear in a subtree of the dominator tree. -func (c *closure) bigOrPopular(pkgs *pkgsMetadata) bool { +func (c *closure) bigOrPopular() bool { const sizeThreshold = 100 * 1000000 // 100MB if c.Size > sizeThreshold { return true } - // TODO(tazjin): After generating the full data, this should - // be changed to something other than a simple inclusion - // (currently the test-data only contains the top 200 - // packages). - pop, ok := (*pkgs)[c.DOTID()] - if ok { - log.Printf("%q is popular!\n", c.DOTID()) + // The threshold value used here is currently roughly the + // minimum number of references that only 1% of packages in + // the entire package set have. + // + // TODO(tazjin): Do this more elegantly by calculating + // percentiles for each package and using those instead. + if c.Popularity >= 1000 { + return true } - c.Popularity = pop - return ok + + return false } -func insertEdges(graph *simple.DirectedGraph, pop *pkgsMetadata, cmap *map[string]*closure, node *closure) { +func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *closure) { // Big or popular nodes get a separate edge from the top to // flag them for their own layer. - if node.bigOrPopular(pop) && !graph.HasEdgeFromTo(0, node.ID()) { + if node.bigOrPopular() && !graph.HasEdgeFromTo(0, node.ID()) { edge := graph.NewEdge(graph.Node(0), node) graph.SetEdge(edge) } @@ -205,18 +218,24 @@ func buildGraph(refs *exportReferences, pop *pkgsMetadata) *simple.DirectedGraph // // A map from store paths to IDs is kept to actually insert // edges below. - root := &closure { + root := &closure{ GraphID: 0, - Path: "image_root", + Path: "image_root", } graph.AddNode(root) for idx, c := range refs.Graph { - node := &closure { + node := &closure{ GraphID: int64(idx + 1), // inc because of root node - Path: c.Path, - Size: c.Size, - Refs: c.Refs, + Path: c.Path, + Size: c.Size, + Refs: c.Refs, + } + + if p, ok := (*pop)[node.DOTID()]; ok { + node.Popularity = p + } else { + node.Popularity = 1 } graph.AddNode(node) @@ -231,49 +250,74 @@ func buildGraph(refs *exportReferences, pop *pkgsMetadata) *simple.DirectedGraph } for _, c := range cmap { - insertEdges(graph, pop, &cmap, c) + insertEdges(graph, &cmap, c) } - // gv, err := dot.Marshal(graph, "deps", "", "") - // if err != nil { - // log.Fatalf("Could not encode graph: %s\n", err) - // } - // fmt.Print(string(gv)) - // os.Exit(0) - return graph } +// Extracts a subgraph starting at the specified root from the +// dominator tree. The subgraph is converted into a flat list of +// layers, each containing the store paths and merge rating. +func groupLayer(dt *flow.DominatorTree, root *closure) layer { + size := root.Size + contents := []string{root.Path} + children := dt.DominatedBy(root.ID()) + + // This iteration does not use 'range' because the list being + // iterated is modified during the iteration (yes, I'm sorry). + for i := 0; i < len(children); i++ { + child := children[i].(*closure) + size += child.Size + contents = append(contents, child.Path) + children = append(children, dt.DominatedBy(child.ID())...) + } + + return layer{ + Contents: contents, + // TODO(tazjin): The point of this is to factor in + // both the size and the popularity when making merge + // decisions, but there might be a smarter way to do + // it than a plain multiplication. + mergeRating: uint64(root.Popularity) * size, + } +} + // Calculate the dominator tree of the entire package set and group // each top-level subtree into a layer. -func dominate(graph *simple.DirectedGraph) { +// +// Layers are merged together until they fit into the layer budget, +// based on their merge rating. +func dominate(budget int, graph *simple.DirectedGraph) []layer { dt := flow.Dominators(graph.Node(0), graph) - // convert dominator tree back into encodable graph - dg := simple.NewDirectedGraph() - - for nodes := graph.Nodes(); nodes.Next(); { - dg.AddNode(nodes.Node()) + var layers []layer + for _, n := range dt.DominatedBy(dt.Root().ID()) { + layers = append(layers, groupLayer(&dt, n.(*closure))) } - for nodes := dg.Nodes(); nodes.Next(); { - node := nodes.Node() - for _, child := range dt.DominatedBy(node.ID()) { - edge := dg.NewEdge(node, child) - dg.SetEdge(edge) - } + sort.Slice(layers, func(i, j int) bool { + return layers[i].mergeRating < layers[j].mergeRating + }) + + if len(layers) > budget { + log.Printf("Ideal image has %v layers, but budget is %v\n", len(layers), budget) } - gv, err := dot.Marshal(dg, "deps", "", "") - if err != nil { - log.Fatalf("Could not encode graph: %s\n", err) + for len(layers) > budget { + merged := layers[0].merge(layers[1]) + layers[1] = merged + layers = layers[1:] } - ioutil.WriteFile("graph.dot", gv, 0644) + + return layers } func main() { graphFile := flag.String("graph", ".attrs.json", "Input file containing graph") popFile := flag.String("pop", "popularity.json", "Package popularity data") + outFile := flag.String("out", "layers.json", "File to write layers to") + layerBudget := flag.Int("budget", 94, "Total layer budget available") flag.Parse() // Parse graph data @@ -300,8 +344,9 @@ func main() { log.Fatalf("Failed to deserialise input: %s\n", err) } - log.Printf("%v\n", pop) - graph := buildGraph(&refs, &pop) - dominate(graph) + layers := dominate(*layerBudget, graph) + + j, _ := json.Marshal(layers) + ioutil.WriteFile(*outFile, j, 0644) } From d699f7f91cb0b90f15199f6d6884442594cb6c56 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 14:59:41 +0100 Subject: [PATCH 059/223] chore(build): Update Go dependencies & add gonum --- tools/nixery/go-deps.nix | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/nixery/go-deps.nix b/tools/nixery/go-deps.nix index 57d9ecd64..ebd1576db 100644 --- a/tools/nixery/go-deps.nix +++ b/tools/nixery/go-deps.nix @@ -5,8 +5,8 @@ fetch = { type = "git"; url = "https://code.googlesource.com/gocloud"; - rev = "edd0968ab5054ee810843a77774d81069989494b"; - sha256 = "1mh8i72h6a1z9lp4cy9bwa2j87bm905zcsvmqwskdqi8z58cif4a"; + rev = "77f6a3a292a7dbf66a5329de0d06326f1906b450"; + sha256 = "1c9pkx782nbcp8jnl5lprcbzf97van789ky5qsncjgywjyymhigi"; }; } { @@ -68,8 +68,8 @@ fetch = { type = "git"; url = "https://go.googlesource.com/sys"; - rev = "fae7ac547cb717d141c433a2a173315e216b64c4"; - sha256 = "11pl0dycm5d8ar7g1l1w5q2cx0lms8i15n8mxhilhkdd2xpmh8f0"; + rev = "51ab0e2deafac1f46c46ad59cf0921be2f180c3d"; + sha256 = "0xdhpckbql3bsqkpc2k5b1cpnq3q1qjqjjq2j3p707rfwb8nm91a"; }; } { @@ -81,6 +81,15 @@ sha256 = "0flv9idw0jm5nm8lx25xqanbkqgfiym6619w575p7nrdh0riqwqh"; }; } + { + goPackagePath = "gonum.org/v1/gonum"; + fetch = { + type = "git"; + url = "https://github.com/gonum/gonum"; + rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; + sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; + }; + } { goPackagePath = "google.golang.org/api"; fetch = { From 1fa93fe6f640bfdbb5e9ecedb2dbf2cacc5e8945 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 15:14:24 +0100 Subject: [PATCH 060/223] refactor: Move registry server to a subfolder --- tools/nixery/default.nix | 22 ++-------------------- tools/nixery/server/default.nix | 16 ++++++++++++++++ tools/nixery/{ => server}/go-deps.nix | 9 --------- tools/nixery/{ => server}/main.go | 0 4 files changed, 18 insertions(+), 29 deletions(-) create mode 100644 tools/nixery/server/default.nix rename tools/nixery/{ => server}/go-deps.nix (92%) rename tools/nixery/{ => server}/main.go (100%) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 092c76e9c..dee5713c6 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -23,29 +23,11 @@ rec { # Users will usually not want to use this directly, instead see the # 'nixery' derivation below, which automatically includes runtime # data dependencies. - nixery-server = buildGoPackage { - name = "nixery-server"; - - # Technically people should not be building Nixery through 'go get' - # or similar (as other required files will not be included), but - # buildGoPackage requires a package path. - goPackagePath = "github.com/google/nixery"; - goDeps = ./go-deps.nix; - src = ./.; - - meta = { - description = "Container image build serving Nix-backed images"; - homepage = "https://github.com/google/nixery"; - license = lib.licenses.asl20; - maintainers = [ lib.maintainers.tazjin ]; - }; - }; + nixery-server = callPackage ./server {}; # Nix expression (unimported!) which is used by Nixery to build # container images. - nixery-builder = runCommand "build-registry-image.nix" {} '' - cat ${./build-registry-image.nix} > $out - ''; + nixery-builder = ./build-registry-image.nix; # nixpkgs currently has an old version of mdBook. A new version is # built here, but eventually the update will be upstreamed diff --git a/tools/nixery/server/default.nix b/tools/nixery/server/default.nix new file mode 100644 index 000000000..394d2b27b --- /dev/null +++ b/tools/nixery/server/default.nix @@ -0,0 +1,16 @@ +{ buildGoPackage, lib }: + +buildGoPackage { + name = "nixery-server"; + goDeps = ./go-deps.nix; + src = ./.; + + goPackagePath = "github.com/google/nixery"; + + meta = { + description = "Container image builder serving Nix-backed images"; + homepage = "https://github.com/google/nixery"; + license = lib.licenses.asl20; + maintainers = [ lib.maintainers.tazjin ]; + }; +} diff --git a/tools/nixery/go-deps.nix b/tools/nixery/server/go-deps.nix similarity index 92% rename from tools/nixery/go-deps.nix rename to tools/nixery/server/go-deps.nix index ebd1576db..a223ef0a7 100644 --- a/tools/nixery/go-deps.nix +++ b/tools/nixery/server/go-deps.nix @@ -81,15 +81,6 @@ sha256 = "0flv9idw0jm5nm8lx25xqanbkqgfiym6619w575p7nrdh0riqwqh"; }; } - { - goPackagePath = "gonum.org/v1/gonum"; - fetch = { - type = "git"; - url = "https://github.com/gonum/gonum"; - rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; - sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; - }; - } { goPackagePath = "google.golang.org/api"; fetch = { diff --git a/tools/nixery/main.go b/tools/nixery/server/main.go similarity index 100% rename from tools/nixery/main.go rename to tools/nixery/server/main.go From 819b4602788195cacde48cf8bb36ab242d240512 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 17:10:34 +0100 Subject: [PATCH 061/223] chore(docs): Move mdBook derivation to docs/default.nix --- tools/nixery/default.nix | 23 +---------------------- tools/nixery/docs/default.nix | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index dee5713c6..fe5afdb8e 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -25,32 +25,11 @@ rec { # data dependencies. nixery-server = callPackage ./server {}; - # Nix expression (unimported!) which is used by Nixery to build - # container images. - nixery-builder = ./build-registry-image.nix; - - # nixpkgs currently has an old version of mdBook. A new version is - # built here, but eventually the update will be upstreamed - # (nixpkgs#65890) - mdbook = rustPlatform.buildRustPackage rec { - name = "mdbook-${version}"; - version = "0.3.1"; - doCheck = false; - - src = fetchFromGitHub { - owner = "rust-lang-nursery"; - repo = "mdBook"; - rev = "v${version}"; - sha256 = "0py69267jbs6b7zw191hcs011cm1v58jz8mglqx3ajkffdfl3ghw"; - }; - - cargoSha256 = "0qwhc42a86jpvjcaysmfcw8kmwa150lmz01flmlg74g6qnimff5m"; - }; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at # nixery.dev. - nixery-book = callPackage ./docs { inherit mdbook; }; + nixery-book = callPackage ./docs {}; # Wrapper script running the Nixery server with the above two data # dependencies configured. diff --git a/tools/nixery/docs/default.nix b/tools/nixery/docs/default.nix index deebdffd7..6a31be4fd 100644 --- a/tools/nixery/docs/default.nix +++ b/tools/nixery/docs/default.nix @@ -18,9 +18,27 @@ # Some of the documentation is pulled in and included from other # sources. -{ fetchFromGitHub, mdbook, runCommand }: +{ fetchFromGitHub, mdbook, runCommand, rustPlatform }: let + # nixpkgs currently has an old version of mdBook. A new version is + # built here, but eventually the update will be upstreamed + # (nixpkgs#65890) + mdbook = rustPlatform.buildRustPackage rec { + name = "mdbook-${version}"; + version = "0.3.1"; + doCheck = false; + + src = fetchFromGitHub { + owner = "rust-lang-nursery"; + repo = "mdBook"; + rev = "v${version}"; + sha256 = "0py69267jbs6b7zw191hcs011cm1v58jz8mglqx3ajkffdfl3ghw"; + }; + + cargoSha256 = "0qwhc42a86jpvjcaysmfcw8kmwa150lmz01flmlg74g6qnimff5m"; + }; + nix-1p = fetchFromGitHub { owner = "tazjin"; repo = "nix-1p"; From 6d718bf2713a7e2209197247976390b878f51313 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 17:14:00 +0100 Subject: [PATCH 062/223] refactor(server): Use wrapper script to avoid path dependency Instead of requiring the server component to be made aware of the location of the Nix builder via environment variables, this commit introduces a wrapper script for the builder that can simply exist on the builders $PATH. This is one step towards a slightly nicer out-of-the-box experience when using `nix-build -A nixery-bin`. --- .../build-image.nix} | 4 +- tools/nixery/build-image/default.nix | 40 +++++++++++++++++++ tools/nixery/build-image/go-deps.nix | 12 ++++++ .../group-layers.go | 0 tools/nixery/default.nix | 4 +- tools/nixery/server/default.nix | 14 +++++++ tools/nixery/server/main.go | 8 +--- 7 files changed, 73 insertions(+), 9 deletions(-) rename tools/nixery/{build-registry-image.nix => build-image/build-image.nix} (99%) create mode 100644 tools/nixery/build-image/default.nix create mode 100644 tools/nixery/build-image/go-deps.nix rename tools/nixery/{group-layers => build-image}/group-layers.go (100%) diff --git a/tools/nixery/build-registry-image.nix b/tools/nixery/build-image/build-image.nix similarity index 99% rename from tools/nixery/build-registry-image.nix rename to tools/nixery/build-image/build-image.nix index 255f1ca9b..37156905f 100644 --- a/tools/nixery/build-registry-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -287,6 +287,6 @@ let pkgs = map (err: err.pkg) allContents.errors; }; in writeText "manifest-output.json" (if (length allContents.errors) == 0 - then toJSON (trace manifestOutput manifestOutput) - else toJSON (trace errorOutput errorOutput) + then toJSON manifestOutput + else toJSON errorOutput ) diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix new file mode 100644 index 000000000..4962e07de --- /dev/null +++ b/tools/nixery/build-image/default.nix @@ -0,0 +1,40 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file builds the tool used to calculate layer distribution and +# moves the files needed to call the Nix builds at runtime in the +# correct locations. + +{ buildGoPackage, lib, nix, writeShellScriptBin }: + +let + group-layers = buildGoPackage { + name = "group-layers"; + goDeps = ./go-deps.nix; + src = ./.; + + goPackagePath = "github.com/google/nixery/group-layers"; + + meta = { + description = "Tool to group a set of packages into container image layers"; + license = lib.licenses.asl20; + maintainers = [ lib.maintainers.tazjin ]; + }; + }; + + # Wrapper script which is called by the Nixery server to trigger an + # actual image build. +in writeShellScriptBin "nixery-build-image" '' + exec ${nix}/bin/nix-build --show-trace --no-out-link "$@" ${./build-image.nix} +'' diff --git a/tools/nixery/build-image/go-deps.nix b/tools/nixery/build-image/go-deps.nix new file mode 100644 index 000000000..235c3c4c6 --- /dev/null +++ b/tools/nixery/build-image/go-deps.nix @@ -0,0 +1,12 @@ +# This file was generated by https://github.com/kamilchm/go2nix v1.3.0 +[ + { + goPackagePath = "gonum.org/v1/gonum"; + fetch = { + type = "git"; + url = "https://github.com/gonum/gonum"; + rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; + sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; + }; + } +] diff --git a/tools/nixery/group-layers/group-layers.go b/tools/nixery/build-image/group-layers.go similarity index 100% rename from tools/nixery/group-layers/group-layers.go rename to tools/nixery/build-image/group-layers.go diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index fe5afdb8e..7d201869d 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -25,6 +25,8 @@ rec { # data dependencies. nixery-server = callPackage ./server {}; + # Implementation of the image building & layering logic + nixery-build-image = callPackage ./build-image {}; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at @@ -37,7 +39,6 @@ rec { # In most cases, this will be the derivation a user wants if they # are installing Nixery directly. nixery-bin = writeShellScriptBin "nixery" '' - export NIX_BUILDER="${nixery-builder}" export WEB_DIR="${nixery-book}" exec ${nixery-server}/bin/nixery ''; @@ -84,6 +85,7 @@ rec { gnutar gzip nix + nixery-build-image nixery-launch-script openssh ]; diff --git a/tools/nixery/server/default.nix b/tools/nixery/server/default.nix index 394d2b27b..0d0c056a5 100644 --- a/tools/nixery/server/default.nix +++ b/tools/nixery/server/default.nix @@ -1,3 +1,17 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + { buildGoPackage, lib }: buildGoPackage { diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index d20ede2eb..3e015e858 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -123,7 +123,6 @@ func signingOptsFromEnv() *storage.SignedURLOptions { type config struct { bucket string // GCS bucket to cache & serve layers signing *storage.SignedURLOptions // Signing options to use for GCS URLs - builder string // Nix derivation for building images port string // Port on which to launch HTTP server pkgs *pkgSource // Source for Nix package set } @@ -208,16 +207,14 @@ func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage } args := []string{ - "--no-out-link", - "--show-trace", "--argstr", "name", image.name, - "--argstr", "packages", string(packages), cfg.builder, + "--argstr", "packages", string(packages), } if cfg.pkgs != nil { args = append(args, "--argstr", "pkgSource", cfg.pkgs.renderSource(image.tag)) } - cmd := exec.Command("nix-build", args...) + cmd := exec.Command("nixery-build-image", args...) outpipe, err := cmd.StdoutPipe() if err != nil { @@ -466,7 +463,6 @@ func getConfig(key, desc string) string { func main() { cfg := &config{ bucket: getConfig("BUCKET", "GCS bucket for layer storage"), - builder: getConfig("NIX_BUILDER", "Nix image builder code"), port: getConfig("PORT", "HTTP port"), pkgs: pkgSourceFromEnv(), signing: signingOptsFromEnv(), From 6035bf36eb93bc30db6ac40739913358e71d1121 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 17:47:27 +0100 Subject: [PATCH 063/223] feat(popcount): Clean up popularity counting script Adds the script used to generate the popularity information for all of nixpkgs. The README lists the (currently somewhat rough) usage instructions. --- tools/nixery/group-layers/popcount.nix | 51 ------------------ tools/nixery/popcount/README.md | 39 ++++++++++++++ tools/nixery/popcount/empty.json | 1 + .../{group-layers => popcount}/popcount | 0 tools/nixery/popcount/popcount.nix | 53 +++++++++++++++++++ 5 files changed, 93 insertions(+), 51 deletions(-) delete mode 100644 tools/nixery/group-layers/popcount.nix create mode 100644 tools/nixery/popcount/README.md create mode 100644 tools/nixery/popcount/empty.json rename tools/nixery/{group-layers => popcount}/popcount (100%) create mode 100644 tools/nixery/popcount/popcount.nix diff --git a/tools/nixery/group-layers/popcount.nix b/tools/nixery/group-layers/popcount.nix deleted file mode 100644 index e21d73677..000000000 --- a/tools/nixery/group-layers/popcount.nix +++ /dev/null @@ -1,51 +0,0 @@ -{ pkgs ? import { config.allowUnfree = false; } -, target }: - -let - inherit (pkgs) coreutils runCommand writeText; - inherit (builtins) replaceStrings readFile toFile fromJSON toJSON foldl' listToAttrs; - - path = [ pkgs."${target}" ]; - - # graphJSON abuses feature in Nix that makes structured runtime - # closure information available to builders. This data is imported - # back via IFD to process it for layering data. - graphJSON = - path: - runCommand "build-graph" { - __structuredAttrs = true; - exportReferencesGraph.graph = path; - PATH = "${coreutils}/bin"; - builder = toFile "builder" '' - . .attrs.sh - cat .attrs.json > ''${outputs[out]} - ''; - } ""; - - buildClosures = paths: (fromJSON (readFile (graphJSON paths))); - - buildGraph = paths: listToAttrs (map (c: { - name = c.path; - value = { - inherit (c) closureSize references; - }; - }) (buildClosures paths)); - - # Nix does not allow attrbute set keys to refer to store paths, but - # we need them to for the purpose of the calculation. To work around - # it, the store path prefix is replaced with the string 'closure/' - # and later replaced again. - fromStorePath = replaceStrings [ "/nix/store" ] [ "closure/" ]; - toStorePath = replaceStrings [ "closure/" ] [ "/nix/store/" ]; - - buildTree = paths: - let - graph = buildGraph paths; - top = listToAttrs (map (p: { - name = fromStorePath (toString p); - value = {}; - }) paths); - in top; - - outputJson = thing: writeText "the-thing.json" (builtins.toJSON thing); -in outputJson (buildClosures path).graph diff --git a/tools/nixery/popcount/README.md b/tools/nixery/popcount/README.md new file mode 100644 index 000000000..8485a4d30 --- /dev/null +++ b/tools/nixery/popcount/README.md @@ -0,0 +1,39 @@ +popcount +======== + +This script is used to count the popularity for each package in `nixpkgs`, by +determining how many other packages depend on it. + +It skips over all packages that fail to build, are not cached or are unfree - +but these omissions do not meaningfully affect the statistics. + +It currently does not evaluate nested attribute sets (such as +`haskellPackages`). + +## Usage + +1. Generate a list of all top-level attributes in `nixpkgs`: + + ```shell + nix eval '(with builtins; toJSON (attrNames (import {})))' | jq -r | jq > all-top-level.json + ``` + +2. Run `./popcount > all-runtime-deps.txt` + +3. Collect and count the results with the following magic incantation: + + ```shell + cat all-runtime-deps.txt \ + | sed -r 's|/nix/store/[a-z0-9]+-||g' \ + | sort \ + | uniq -c \ + | sort -n -r \ + | awk '{ print "{\"" $2 "\":" $1 "}"}' \ + | jq -c -s '. | add | with_entries(select(.value > 1))' \ + > your-output-file + ``` + + In essence, this will trim Nix's store paths and hashes from the output, + count the occurences of each package and return the output as JSON. All + packages that have no references other than themselves are removed from the + output. diff --git a/tools/nixery/popcount/empty.json b/tools/nixery/popcount/empty.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tools/nixery/popcount/empty.json @@ -0,0 +1 @@ +[] diff --git a/tools/nixery/group-layers/popcount b/tools/nixery/popcount/popcount similarity index 100% rename from tools/nixery/group-layers/popcount rename to tools/nixery/popcount/popcount diff --git a/tools/nixery/popcount/popcount.nix b/tools/nixery/popcount/popcount.nix new file mode 100644 index 000000000..54fd2ad58 --- /dev/null +++ b/tools/nixery/popcount/popcount.nix @@ -0,0 +1,53 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script, given a target attribute in `nixpkgs`, builds the +# target derivations' runtime closure and returns its reference graph. +# +# This is invoked by popcount.sh for each package in nixpkgs to +# collect all package references, so that package popularity can be +# tracked. +# +# Check out build-image/group-layers.go for an in-depth explanation of +# what the popularity counts are used for. + +{ pkgs ? import { config.allowUnfree = false; }, target }: + +let + inherit (pkgs) coreutils runCommand writeText; + inherit (builtins) readFile toFile fromJSON toJSON listToAttrs; + + # graphJSON abuses feature in Nix that makes structured runtime + # closure information available to builders. This data is imported + # back via IFD to process it for layering data. + graphJSON = path: + runCommand "build-graph" { + __structuredAttrs = true; + exportReferencesGraph.graph = path; + PATH = "${coreutils}/bin"; + builder = toFile "builder" '' + . .attrs.sh + cat .attrs.json > ''${outputs[out]} + ''; + } ""; + + buildClosures = paths: (fromJSON (readFile (graphJSON paths))); + + buildGraph = paths: + listToAttrs (map (c: { + name = c.path; + value = { inherit (c) closureSize references; }; + }) (buildClosures paths)); +in writeText "${target}-graph" +(toJSON (buildClosures [ pkgs."${target}" ]).graph) From f60f702274191f87b23dab5393420b27a50952fc Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 17:53:46 +0100 Subject: [PATCH 064/223] feat: Add shell.nix for running a local Nixery --- tools/nixery/shell.nix | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tools/nixery/shell.nix diff --git a/tools/nixery/shell.nix b/tools/nixery/shell.nix new file mode 100644 index 000000000..49d0e5581 --- /dev/null +++ b/tools/nixery/shell.nix @@ -0,0 +1,27 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Configures a shell environment that builds required local packages to +# run Nixery. +{pkgs ? import {} }: + +let nixery = import ./default.nix { inherit pkgs; }; +in pkgs.stdenv.mkDerivation { + name = "nixery-dev-shell"; + + buildInputs = with pkgs;[ + jq + nixery.nixery-build-image + ]; +} From 7214d0aa4f05d1de25911ec6b99d3feb3dcbd1b5 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Aug 2019 19:02:13 +0100 Subject: [PATCH 065/223] feat(build-image): Introduce a terrifying hack to build group-layers The issue is described in detail in a comment in `build-image/default.nix`, please read it. --- tools/nixery/build-image/build-image.nix | 26 ++++++++- tools/nixery/build-image/default.nix | 71 ++++++++++++++++++++---- tools/nixery/default.nix | 2 +- 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 37156905f..e5b195a63 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -22,6 +22,8 @@ name, # Image tag, the Nix's output hash will be used if null tag ? null, + # Tool used to determine layer grouping + groupLayers, # Files to put on the image (a nix store path or list of paths). contents ? [], # Packages to install by name (which must refer to top-level attributes of @@ -48,7 +50,8 @@ # '!' was chosen as the separator because `builtins.split` does not # support regex escapes and there are few other candidates. It # doesn't matter much because this is invoked by the server. - pkgSource ? "nixpkgs!nixos-19.03" + pkgSource ? "nixpkgs!nixos-19.03", + ... }: let @@ -165,6 +168,25 @@ let paths = allContents.contents; }; + # Before actually creating any image layers, the store paths that need to be + # included in the image must be sorted into the layers that they should go + # into. + # + # How contents are allocated to each layer is decided by the `group-layers.go` + # program. The mechanism used is described at the top of the program's source + # code, or alternatively in the layering design document: + # + # https://storage.googleapis.com/nixdoc/nixery-layers.html + # + # To invoke the program, a graph of all runtime references is created via + # Nix's exportReferencesGraph feature - the resulting layers are read back + # into Nix using import-from-derivation. + groupedLayers = runCommand "grouped-layers.json" { + buildInputs = [ groupLayers ]; + } '' + group-layers --fnorg + ''; + # The image build infrastructure expects to be outputting a slightly different # format than the one we serve over the registry protocol. To work around its # expectations we need to provide an empty JSON file that it can write some @@ -287,6 +309,6 @@ let pkgs = map (err: err.pkg) allContents.errors; }; in writeText "manifest-output.json" (if (length allContents.errors) == 0 - then toJSON manifestOutput + then toJSON groupedLayers # manifestOutput else toJSON errorOutput ) diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix index 4962e07de..cff403995 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/build-image/default.nix @@ -16,25 +16,76 @@ # moves the files needed to call the Nix builds at runtime in the # correct locations. -{ buildGoPackage, lib, nix, writeShellScriptBin }: +{ pkgs ? import { }, self ? ./. -let - group-layers = buildGoPackage { + # Because of the insanity occuring below, this function must mirror + # all arguments of build-image.nix. +, tag ? null, name ? null, packages ? null, maxLayers ? null, pkgSource ? null +}@args: + +with pkgs; rec { + groupLayers = buildGoPackage { name = "group-layers"; goDeps = ./go-deps.nix; - src = ./.; - goPackagePath = "github.com/google/nixery/group-layers"; + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # WARNING: HERE BE DRAGONS! # + # # + # The hack below is used to break evaluation purity. The issue is # + # that Nixery's build instructions (the default.nix in the folder # + # above this one) must build a program that can invoke Nix at # + # runtime, with a derivation that needs a program tracked in this # + # source tree (`group-layers`). # + # # + # Simply installing that program in the $PATH of Nixery does not # + # work, because the runtime Nix builds use their own isolated # + # environment. # + # # + # I first attempted to naively copy the sources into the Nix # + # store, so that Nixery could build `group-layers` when it starts # + # up - however those sources are not available to a nested Nix # + # build because they're not part of the context of the nested # + # invocation. # + # # + # Nix has several primitives under `builtins.` that can break # + # evaluation purity, these (namely readDir and readFile) are used # + # below to break out of the isolated environment and reconstruct # + # the source tree for `group-layers`. # + # # + # There might be a better way to do this, but I don't know what # + # it is. # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + src = runCommand "group-layers-srcs" { } '' + mkdir -p $out + ${with builtins; + let + files = + (attrNames (lib.filterAttrs (_: t: t != "symlink") (readDir self))); + commands = + map (f: "cp ${toFile f (readFile "${self}/${f}")} $out/${f}") files; + in lib.concatStringsSep "\n" commands} + ''; + meta = { - description = "Tool to group a set of packages into container image layers"; + description = + "Tool to group a set of packages into container image layers"; license = lib.licenses.asl20; maintainers = [ lib.maintainers.tazjin ]; }; }; + buildImage = import ./build-image.nix + ({ inherit groupLayers; } // (lib.filterAttrs (_: v: v != null) args)); + # Wrapper script which is called by the Nixery server to trigger an - # actual image build. -in writeShellScriptBin "nixery-build-image" '' - exec ${nix}/bin/nix-build --show-trace --no-out-link "$@" ${./build-image.nix} -'' + # actual image build. This exists to avoid having to specify the + # location of build-image.nix at runtime. + wrapper = writeShellScriptBin "nixery-build-image" '' + exec ${nix}/bin/nix-build \ + --show-trace \ + --no-out-link "$@" \ + --argstr self "${./.}" \ + -A buildImage ${./.} + ''; +} diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 7d201869d..926ab0d19 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -26,7 +26,7 @@ rec { nixery-server = callPackage ./server {}; # Implementation of the image building & layering logic - nixery-build-image = callPackage ./build-image {}; + nixery-build-image = (import ./build-image { inherit pkgs; }).wrapper; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at From 6285cd8dbfb77c287fc5b30263bbfd5770b47413 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 13 Aug 2019 00:29:08 +0100 Subject: [PATCH 066/223] feat(build-image): Use new image layering algorithm for images Removes usage of the old layering algorithm and replaces it with the new one. Apart from the new layer layout this means that each layer is now built in a separate derivation, which hopefully leads to better cacheability. --- tools/nixery/build-image/build-image.nix | 85 +++++++++++++----------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index e5b195a63..d68ed6d37 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -54,12 +54,12 @@ ... }: -let +with builtins; let # If a nixpkgs channel is requested, it is retrieved from Github (as # a tarball) and imported. fetchImportChannel = channel: let url = "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; - in import (builtins.fetchTarball url) {}; + in import (fetchTarball url) {}; # If a git repository is requested, it is retrieved via # builtins.fetchGit which defaults to the git configuration of the @@ -76,32 +76,31 @@ let # There are some additional caveats around whether the default # branch contains the specified revision, which need to be # explained to users. - spec = if (builtins.stringLength rev) == 40 then { + spec = if (stringLength rev) == 40 then { inherit url rev; } else { inherit url; ref = rev; }; - in import (builtins.fetchGit spec) {}; + in import (fetchGit spec) {}; - importPath = path: import (builtins.toPath path) {}; + importPath = path: import (toPath path) {}; - source = builtins.split "!" pkgSource; - sourceType = builtins.elemAt source 0; - pkgs = with builtins; + source = split "!" pkgSource; + sourceType = elemAt source 0; + pkgs = if sourceType == "nixpkgs" then fetchImportChannel (elemAt source 2) else if sourceType == "git" then fetchImportGit (elemAt source 2) (elemAt source 4) else if sourceType == "path" then importPath (elemAt source 2) - else builtins.throw("Invalid package set source specification: ${pkgSource}"); + else throw("Invalid package set source specification: ${pkgSource}"); in # Since this is essentially a re-wrapping of some of the functionality that is # implemented in the dockerTools, we need all of its components in our top-level # namespace. -with builtins; with pkgs; with dockerTools; @@ -168,6 +167,11 @@ let paths = allContents.contents; }; + popularity = builtins.fetchurl { + url = "https://storage.googleapis.com/nixery-layers/popularity/nixos-19.03-20190812.json"; + sha256 = "16sxd49vqqg2nrhwynm36ba6bc2yff5cd5hf83wi0hanw5sx3svk"; + }; + # Before actually creating any image layers, the store paths that need to be # included in the image must be sorted into the layers that they should go # into. @@ -181,31 +185,37 @@ let # To invoke the program, a graph of all runtime references is created via # Nix's exportReferencesGraph feature - the resulting layers are read back # into Nix using import-from-derivation. - groupedLayers = runCommand "grouped-layers.json" { - buildInputs = [ groupLayers ]; + groupedLayers = fromJSON (readFile (runCommand "grouped-layers.json" { + __structuredAttrs = true; + exportReferencesGraph.graph = allContents.contents; + PATH = "${groupLayers}/bin"; + builder = toFile "builder" '' + . .attrs.sh + group-layers --budget ${toString (maxLayers - 1)} --pop ${popularity} --out ''${outputs[out]} + ''; + } "")); + + # Given a list of store paths, create an image layer tarball with + # their contents. + pathsToLayer = paths: runCommand "layer.tar" { } '' - group-layers --fnorg + tar --no-recursion -rf "$out" \ + --mtime="@$SOURCE_DATE_EPOCH" \ + --owner=0 --group=0 /nix /nix/store + + tar -rpf "$out" --hard-dereference --sort=name \ + --mtime="@$SOURCE_DATE_EPOCH" \ + --owner=0 --group=0 ${lib.concatStringsSep " " paths} ''; - # The image build infrastructure expects to be outputting a slightly different - # format than the one we serve over the registry protocol. To work around its - # expectations we need to provide an empty JSON file that it can write some - # fun data into. - emptyJson = writeText "empty.json" "{}"; - - bulkLayers = mkManyPureLayers { - name = baseName; - configJson = emptyJson; - closure = writeText "closure" "${contentsEnv} ${emptyJson}"; - # One layer will be taken up by the customisationLayer, so - # take up one less. - maxLayers = maxLayers - 1; - }; + bulkLayers = writeText "bulk-layers" + (lib.concatStringsSep "\n" (map (layer: pathsToLayer layer.contents) + groupedLayers)); customisationLayer = mkCustomisationLayer { name = baseName; contents = contentsEnv; - baseJson = emptyJson; + baseJson = writeText "empty.json" "{}"; inherit uid gid extraCommands; }; @@ -214,23 +224,22 @@ let # # This computes both an MD5 and a SHA256 hash of each layer, which are used # for different purposes. See the registry server implementation for details. - # - # Some of this logic is copied straight from `buildLayeredImage`. allLayersJson = runCommand "fs-layer-list.json" { buildInputs = [ coreutils findutils jq openssl ]; } '' - find ${bulkLayers} -mindepth 1 -maxdepth 1 | sort -t/ -k5 -n > layer-list - echo ${customisationLayer} >> layer-list + cat ${bulkLayers} | sort -t/ -k5 -n > layer-list + echo -n layer-list: + cat layer-list + echo ${customisationLayer}/layer.tar >> layer-list for layer in $(cat layer-list); do - layerPath="$layer/layer.tar" - layerSha256=$(sha256sum $layerPath | cut -d ' ' -f1) + layerSha256=$(sha256sum $layer | cut -d ' ' -f1) # The server application compares binary MD5 hashes and expects base64 # encoding instead of hex. - layerMd5=$(openssl dgst -md5 -binary $layerPath | openssl enc -base64) - layerSize=$(wc -c $layerPath | cut -d ' ' -f1) + layerMd5=$(openssl dgst -md5 -binary $layer | openssl enc -base64) + layerSize=$(wc -c $layer | cut -d ' ' -f1) - jq -n -c --arg sha256 $layerSha256 --arg md5 $layerMd5 --arg size $layerSize --arg path $layerPath \ + jq -n -c --arg sha256 $layerSha256 --arg md5 $layerMd5 --arg size $layerSize --arg path $layer \ '{ size: ($size | tonumber), sha256: $sha256, md5: $md5, path: $path }' >> fs-layers done @@ -309,6 +318,6 @@ let pkgs = map (err: err.pkg) allContents.errors; }; in writeText "manifest-output.json" (if (length allContents.errors) == 0 - then toJSON groupedLayers # manifestOutput + then toJSON manifestOutput else toJSON errorOutput ) From 3939722063f3d08a547fa98e17aac609f7f765ac Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 13 Aug 2019 00:35:42 +0100 Subject: [PATCH 067/223] style: Apply nixfmt to trivial Nix files ALl the ones except for build-image.nix are considered trivial. On the latter, nixfmt makes some useful changes but by-and-large it is not ready for that code yet. --- tools/nixery/build-image/go-deps.nix | 20 +++++++++----------- tools/nixery/default.nix | 9 ++++----- tools/nixery/docs/default.nix | 8 ++++---- tools/nixery/server/default.nix | 4 ++-- tools/nixery/shell.nix | 7 ++----- 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/tools/nixery/build-image/go-deps.nix b/tools/nixery/build-image/go-deps.nix index 235c3c4c6..0f22a7088 100644 --- a/tools/nixery/build-image/go-deps.nix +++ b/tools/nixery/build-image/go-deps.nix @@ -1,12 +1,10 @@ # This file was generated by https://github.com/kamilchm/go2nix v1.3.0 -[ - { - goPackagePath = "gonum.org/v1/gonum"; - fetch = { - type = "git"; - url = "https://github.com/gonum/gonum"; - rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; - sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; - }; - } -] +[{ + goPackagePath = "gonum.org/v1/gonum"; + fetch = { + type = "git"; + url = "https://github.com/gonum/gonum"; + rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; + sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; + }; +}] diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 926ab0d19..686c23055 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{ pkgs ? import {} -, preLaunch ? "" }: +{ pkgs ? import { }, preLaunch ? "" }: with pkgs; @@ -23,7 +22,7 @@ rec { # Users will usually not want to use this directly, instead see the # 'nixery' derivation below, which automatically includes runtime # data dependencies. - nixery-server = callPackage ./server {}; + nixery-server = callPackage ./server { }; # Implementation of the image building & layering logic nixery-build-image = (import ./build-image { inherit pkgs; }).wrapper; @@ -31,7 +30,7 @@ rec { # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at # nixery.dev. - nixery-book = callPackage ./docs {}; + nixery-book = callPackage ./docs { }; # Wrapper script running the Nixery server with the above two data # dependencies configured. @@ -76,7 +75,7 @@ rec { ''; in dockerTools.buildLayeredImage { name = "nixery"; - config.Cmd = ["${nixery-launch-script}/bin/nixery"]; + config.Cmd = [ "${nixery-launch-script}/bin/nixery" ]; maxLayers = 96; contents = [ cacert diff --git a/tools/nixery/docs/default.nix b/tools/nixery/docs/default.nix index 6a31be4fd..aae2fdde4 100644 --- a/tools/nixery/docs/default.nix +++ b/tools/nixery/docs/default.nix @@ -40,12 +40,12 @@ let }; nix-1p = fetchFromGitHub { - owner = "tazjin"; - repo = "nix-1p"; - rev = "3cd0f7d7b4f487d04a3f1e3ca8f2eb1ab958c49b"; + owner = "tazjin"; + repo = "nix-1p"; + rev = "3cd0f7d7b4f487d04a3f1e3ca8f2eb1ab958c49b"; sha256 = "02lpda03q580gyspkbmlgnb2cbm66rrcgqsv99aihpbwyjym81af"; }; -in runCommand "nixery-book" {} '' +in runCommand "nixery-book" { } '' mkdir -p $out cp -r ${./.}/* . chmod -R a+w src diff --git a/tools/nixery/server/default.nix b/tools/nixery/server/default.nix index 0d0c056a5..05ad64261 100644 --- a/tools/nixery/server/default.nix +++ b/tools/nixery/server/default.nix @@ -15,9 +15,9 @@ { buildGoPackage, lib }: buildGoPackage { - name = "nixery-server"; + name = "nixery-server"; goDeps = ./go-deps.nix; - src = ./.; + src = ./.; goPackagePath = "github.com/google/nixery"; diff --git a/tools/nixery/shell.nix b/tools/nixery/shell.nix index 49d0e5581..93cd1f4ce 100644 --- a/tools/nixery/shell.nix +++ b/tools/nixery/shell.nix @@ -14,14 +14,11 @@ # Configures a shell environment that builds required local packages to # run Nixery. -{pkgs ? import {} }: +{ pkgs ? import { } }: let nixery = import ./default.nix { inherit pkgs; }; in pkgs.stdenv.mkDerivation { name = "nixery-dev-shell"; - buildInputs = with pkgs;[ - jq - nixery.nixery-build-image - ]; + buildInputs = with pkgs; [ jq nixery.nixery-build-image ]; } From d9168e3e4d8ee0be01cbe994d171d933af215f2c Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 13 Aug 2019 23:03:16 +0100 Subject: [PATCH 068/223] refactor(build-image): Extract package set loading into helper Some upcoming changes might require the Nix build to be split into multiple separate nix-build invocations of different expressions, thus splitting this out is useful. It also fixes an issue where `build-image/default.nix` might be called in an environment where no Nix channels are configured. --- tools/nixery/build-image/build-image.nix | 64 ++------------------- tools/nixery/build-image/default.nix | 11 ++-- tools/nixery/build-image/load-pkgs.nix | 73 ++++++++++++++++++++++++ tools/nixery/default.nix | 4 +- 4 files changed, 87 insertions(+), 65 deletions(-) create mode 100644 tools/nixery/build-image/load-pkgs.nix diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index d68ed6d37..b67fef6ce 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -18,9 +18,11 @@ # registry API. { + # Package set to used (this will usually be loaded by load-pkgs.nix) + pkgs, # Image Name name, - # Image tag, the Nix's output hash will be used if null + # Image tag, the Nix output's hash will be used if null tag ? null, # Tool used to determine layer grouping groupLayers, @@ -36,71 +38,13 @@ # the default here is set to something a little less than that. maxLayers ? 96, - # Configuration for which package set to use when building. - # - # Both channels of the public nixpkgs repository as well as imports - # from private repositories are supported. - # - # This setting can be invoked with three different formats: - # - # 1. nixpkgs!$channel (e.g. nixpkgs!nixos-19.03) - # 2. git!$repo!$rev (e.g. git!git@github.com:NixOS/nixpkgs.git!master) - # 3. path!$path (e.g. path!/var/local/nixpkgs) - # - # '!' was chosen as the separator because `builtins.split` does not - # support regex escapes and there are few other candidates. It - # doesn't matter much because this is invoked by the server. - pkgSource ? "nixpkgs!nixos-19.03", ... }: -with builtins; let - # If a nixpkgs channel is requested, it is retrieved from Github (as - # a tarball) and imported. - fetchImportChannel = channel: - let url = "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; - in import (fetchTarball url) {}; - - # If a git repository is requested, it is retrieved via - # builtins.fetchGit which defaults to the git configuration of the - # outside environment. This means that user-configured SSH - # credentials etc. are going to work as expected. - fetchImportGit = url: rev: - let - # builtins.fetchGit needs to know whether 'rev' is a reference - # (e.g. a branch/tag) or a revision (i.e. a commit hash) - # - # Since this data is being extrapolated from the supplied image - # tag, we have to guess if we want to avoid specifying a format. - # - # There are some additional caveats around whether the default - # branch contains the specified revision, which need to be - # explained to users. - spec = if (stringLength rev) == 40 then { - inherit url rev; - } else { - inherit url; - ref = rev; - }; - in import (fetchGit spec) {}; - - importPath = path: import (toPath path) {}; - - source = split "!" pkgSource; - sourceType = elemAt source 0; - pkgs = - if sourceType == "nixpkgs" - then fetchImportChannel (elemAt source 2) - else if sourceType == "git" - then fetchImportGit (elemAt source 2) (elemAt source 4) - else if sourceType == "path" - then importPath (elemAt source 2) - else throw("Invalid package set source specification: ${pkgSource}"); -in - # Since this is essentially a re-wrapping of some of the functionality that is # implemented in the dockerTools, we need all of its components in our top-level # namespace. +with builtins; with pkgs; with dockerTools; diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix index cff403995..0d3002cb4 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/build-image/default.nix @@ -16,14 +16,17 @@ # moves the files needed to call the Nix builds at runtime in the # correct locations. -{ pkgs ? import { }, self ? ./. +{ pkgs ? null, self ? ./. # Because of the insanity occuring below, this function must mirror # all arguments of build-image.nix. -, tag ? null, name ? null, packages ? null, maxLayers ? null, pkgSource ? null +, pkgSource ? "nixpkgs!nixos-19.03" +, tag ? null, name ? null, packages ? null, maxLayers ? null }@args: -with pkgs; rec { +let pkgs = import ./load-pkgs.nix { inherit pkgSource; }; +in with pkgs; rec { + groupLayers = buildGoPackage { name = "group-layers"; goDeps = ./go-deps.nix; @@ -76,7 +79,7 @@ with pkgs; rec { }; buildImage = import ./build-image.nix - ({ inherit groupLayers; } // (lib.filterAttrs (_: v: v != null) args)); + ({ inherit pkgs groupLayers; } // (lib.filterAttrs (_: v: v != null) args)); # Wrapper script which is called by the Nixery server to trigger an # actual image build. This exists to avoid having to specify the diff --git a/tools/nixery/build-image/load-pkgs.nix b/tools/nixery/build-image/load-pkgs.nix new file mode 100644 index 000000000..3e8b450c4 --- /dev/null +++ b/tools/nixery/build-image/load-pkgs.nix @@ -0,0 +1,73 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Load a Nix package set from a source specified in one of the following +# formats: +# +# 1. nixpkgs!$channel (e.g. nixpkgs!nixos-19.03) +# 2. git!$repo!$rev (e.g. git!git@github.com:NixOS/nixpkgs.git!master) +# 3. path!$path (e.g. path!/var/local/nixpkgs) +# +# '!' was chosen as the separator because `builtins.split` does not +# support regex escapes and there are few other candidates. It +# doesn't matter much because this is invoked by the server. +{ pkgSource, args ? { } }: + +with builtins; +let + # If a nixpkgs channel is requested, it is retrieved from Github (as + # a tarball) and imported. + fetchImportChannel = channel: + let + url = + "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; + in import (fetchTarball url) args; + + # If a git repository is requested, it is retrieved via + # builtins.fetchGit which defaults to the git configuration of the + # outside environment. This means that user-configured SSH + # credentials etc. are going to work as expected. + fetchImportGit = url: rev: + let + # builtins.fetchGit needs to know whether 'rev' is a reference + # (e.g. a branch/tag) or a revision (i.e. a commit hash) + # + # Since this data is being extrapolated from the supplied image + # tag, we have to guess if we want to avoid specifying a format. + # + # There are some additional caveats around whether the default + # branch contains the specified revision, which need to be + # explained to users. + spec = if (stringLength rev) == 40 then { + inherit url rev; + } else { + inherit url; + ref = rev; + }; + in import (fetchGit spec) args; + + # No special handling is used for paths, so users are expected to pass one + # that will work natively with Nix. + importPath = path: import (toPath path) args; + + source = split "!" pkgSource; + sourceType = elemAt source 0; +in if sourceType == "nixpkgs" then + fetchImportChannel (elemAt source 2) +else if sourceType == "git" then + fetchImportGit (elemAt source 2) (elemAt source 4) +else if sourceType == "path" then + importPath (elemAt source 2) +else + throw ("Invalid package set source specification: ${pkgSource}") diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 686c23055..734a72d57 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -25,7 +25,9 @@ rec { nixery-server = callPackage ./server { }; # Implementation of the image building & layering logic - nixery-build-image = (import ./build-image { inherit pkgs; }).wrapper; + nixery-build-image = (import ./build-image { + pkgSource = "path!${}"; + }).wrapper; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at From 58380e331340d5fb19726531e1a5b50999b260dc Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 14 Aug 2019 17:20:01 +0100 Subject: [PATCH 069/223] refactor(server): Extract build logic into separate module This module is going to get more complex as the implementation of #32 progresses. --- tools/nixery/server/builder/builder.go | 208 ++++++++++++++++ tools/nixery/server/config/config.go | 131 ++++++++++ tools/nixery/server/main.go | 330 ++----------------------- 3 files changed, 365 insertions(+), 304 deletions(-) create mode 100644 tools/nixery/server/builder/builder.go create mode 100644 tools/nixery/server/config/config.go diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go new file mode 100644 index 000000000..c53b702e0 --- /dev/null +++ b/tools/nixery/server/builder/builder.go @@ -0,0 +1,208 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Package builder implements the code required to build images via Nix. Image +// build data is cached for up to 24 hours to avoid duplicated calls to Nix +// (which are costly even if no building is performed). +package builder + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + + "cloud.google.com/go/storage" + "github.com/google/nixery/config" +) + +// Image represents the information necessary for building a container image. +// This can be either a list of package names (corresponding to keys in the +// nixpkgs set) or a Nix expression that results in a *list* of derivations. +type Image struct { + Name string + Tag string + + // Names of packages to include in the image. These must correspond + // directly to top-level names of Nix packages in the nixpkgs tree. + Packages []string +} + +// ImageFromName parses an image name into the corresponding structure which can +// be used to invoke Nix. +// +// It will expand convenience names under the hood (see the `convenienceNames` +// function below). +func ImageFromName(name string, tag string) Image { + packages := strings.Split(name, "/") + return Image{ + Name: name, + Tag: tag, + Packages: convenienceNames(packages), + } +} + +// BuildResult represents the output of calling the Nix derivation responsible +// for building registry images. +// +// The `layerLocations` field contains the local filesystem paths to each +// individual image layer that will need to be served, while the `manifest` +// field contains the JSON-representation of the manifest that needs to be +// served to the client. +// +// The later field is simply treated as opaque JSON and passed through. +type BuildResult struct { + Error string `json:"error"` + Pkgs []string `json:"pkgs"` + + Manifest json.RawMessage `json:"manifest"` + LayerLocations map[string]struct { + Path string `json:"path"` + Md5 []byte `json:"md5"` + } `json:"layerLocations"` +} + +// convenienceNames expands convenience package names defined by Nixery which +// let users include commonly required sets of tools in a container quickly. +// +// Convenience names must be specified as the first package in an image. +// +// Currently defined convenience names are: +// +// * `shell`: Includes bash, coreutils and other common command-line tools +func convenienceNames(packages []string) []string { + shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} + + if packages[0] == "shell" { + return append(packages[1:], shellPackages...) + } + + return packages +} + +// Call out to Nix and request that an image be built. Nix will, upon success, +// return a manifest for the container image. +func BuildImage(ctx *context.Context, cfg *config.Config, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { + packages, err := json.Marshal(image.Packages) + if err != nil { + return nil, err + } + + args := []string{ + "--argstr", "name", image.Name, + "--argstr", "packages", string(packages), + } + + if cfg.Pkgs != nil { + args = append(args, "--argstr", "pkgSource", cfg.Pkgs.Render(image.Tag)) + } + cmd := exec.Command("nixery-build-image", args...) + + outpipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + + errpipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + + if err = cmd.Start(); err != nil { + log.Println("Error starting nix-build:", err) + return nil, err + } + log.Printf("Started Nix image build for '%s'", image.Name) + + stdout, _ := ioutil.ReadAll(outpipe) + stderr, _ := ioutil.ReadAll(errpipe) + + if err = cmd.Wait(); err != nil { + // TODO(tazjin): Propagate errors upwards in a usable format. + log.Printf("nix-build execution error: %s\nstdout: %s\nstderr: %s\n", err, stdout, stderr) + return nil, err + } + + log.Println("Finished Nix image build") + + buildOutput, err := ioutil.ReadFile(strings.TrimSpace(string(stdout))) + if err != nil { + return nil, err + } + + // The build output returned by Nix is deserialised to add all + // contained layers to the bucket. Only the manifest itself is + // re-serialised to JSON and returned. + var result BuildResult + err = json.Unmarshal(buildOutput, &result) + if err != nil { + return nil, err + } + + for layer, meta := range result.LayerLocations { + err = uploadLayer(ctx, bucket, layer, meta.Path, meta.Md5) + if err != nil { + return nil, err + } + } + + return &result, nil +} + +// uploadLayer uploads a single layer to Cloud Storage bucket. Before writing +// any data the bucket is probed to see if the file already exists. +// +// If the file does exist, its MD5 hash is verified to ensure that the stored +// file is not - for example - a fragment of a previous, incomplete upload. +func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer string, path string, md5 []byte) error { + layerKey := fmt.Sprintf("layers/%s", layer) + obj := bucket.Object(layerKey) + + // Before uploading a layer to the bucket, probe whether it already + // exists. + // + // If it does and the MD5 checksum matches the expected one, the layer + // upload can be skipped. + attrs, err := obj.Attrs(*ctx) + + if err == nil && bytes.Equal(attrs.MD5, md5) { + log.Printf("Layer sha256:%s already exists in bucket, skipping upload", layer) + } else { + writer := obj.NewWriter(*ctx) + file, err := os.Open(path) + + if err != nil { + return fmt.Errorf("failed to open layer %s from path %s: %v", layer, path, err) + } + + size, err := io.Copy(writer, file) + if err != nil { + return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) + } + + if err = writer.Close(); err != nil { + return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) + } + + log.Printf("Uploaded layer sha256:%s (%v bytes written)\n", layer, size) + } + + return nil +} diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go new file mode 100644 index 000000000..4e3b70dcd --- /dev/null +++ b/tools/nixery/server/config/config.go @@ -0,0 +1,131 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Package config implements structures to store Nixery's configuration at +// runtime as well as the logic for instantiating this configuration from the +// environment. +package config + +import ( + "fmt" + "io/ioutil" + "log" + "os" + + "cloud.google.com/go/storage" +) + +// pkgSource represents the source from which the Nix package set used +// by Nixery is imported. Users configure the source by setting one of +// the supported environment variables. +type PkgSource struct { + srcType string + args string +} + +// Convert the package source into the representation required by Nix. +func (p *PkgSource) Render(tag string) string { + // The 'git' source requires a tag to be present. + if p.srcType == "git" { + if tag == "latest" || tag == "" { + tag = "master" + } + + return fmt.Sprintf("git!%s!%s", p.args, tag) + } + + return fmt.Sprintf("%s!%s", p.srcType, p.args) +} + +// Retrieve a package source from the environment. If no source is +// specified, the Nix code will default to a recent NixOS channel. +func pkgSourceFromEnv() *PkgSource { + if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { + log.Printf("Using Nix package set from Nix channel %q\n", channel) + return &PkgSource{ + srcType: "nixpkgs", + args: channel, + } + } + + if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { + log.Printf("Using Nix package set from git repository at %q\n", git) + return &PkgSource{ + srcType: "git", + args: git, + } + } + + if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { + log.Printf("Using Nix package set from path %q\n", path) + return &PkgSource{ + srcType: "path", + args: path, + } + } + + return nil +} + +// Load (optional) GCS bucket signing data from the GCS_SIGNING_KEY and +// GCS_SIGNING_ACCOUNT envvars. +func signingOptsFromEnv() *storage.SignedURLOptions { + path := os.Getenv("GCS_SIGNING_KEY") + id := os.Getenv("GCS_SIGNING_ACCOUNT") + + if path == "" || id == "" { + log.Println("GCS URL signing disabled") + return nil + } + + log.Printf("GCS URL signing enabled with account %q\n", id) + k, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("Failed to read GCS signing key: %s\n", err) + } + + return &storage.SignedURLOptions{ + GoogleAccessID: id, + PrivateKey: k, + Method: "GET", + } +} + +func getConfig(key, desc string) string { + value := os.Getenv(key) + if value == "" { + log.Fatalln(desc + " must be specified") + } + + return value +} + +// config holds the Nixery configuration options. +type Config struct { + Bucket string // GCS bucket to cache & serve layers + Signing *storage.SignedURLOptions // Signing options to use for GCS URLs + Port string // Port on which to launch HTTP server + Pkgs *PkgSource // Source for Nix package set + WebDir string +} + +func FromEnv() *Config { + return &Config{ + Bucket: getConfig("BUCKET", "GCS bucket for layer storage"), + Port: getConfig("PORT", "HTTP port"), + Pkgs: pkgSourceFromEnv(), + Signing: signingOptsFromEnv(), + WebDir: getConfig("WEB_DIR", "Static web file dir"), + } +} diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 3e015e858..5d7dcd2ad 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -12,8 +12,8 @@ // License for the specific language governing permissions and limitations under // the License. -// Package main provides the implementation of a container registry that -// transparently builds container images based on Nix derivations. +// The nixery server implements a container registry that transparently builds +// container images based on Nix derivations. // // The Nix derivation used for image creation is responsible for creating // objects that are compatible with the registry API. The targeted registry @@ -26,287 +26,32 @@ package main import ( - "bytes" "context" "encoding/json" "fmt" - "io" - "io/ioutil" "log" "net/http" - "os" - "os/exec" "regexp" - "strings" "time" "cloud.google.com/go/storage" + "github.com/google/nixery/builder" + "github.com/google/nixery/config" ) -// pkgSource represents the source from which the Nix package set used -// by Nixery is imported. Users configure the source by setting one of -// the supported environment variables. -type pkgSource struct { - srcType string - args string -} - -// Convert the package source into the representation required by Nix. -func (p *pkgSource) renderSource(tag string) string { - // The 'git' source requires a tag to be present. - if p.srcType == "git" { - if tag == "latest" || tag == "" { - tag = "master" - } - - return fmt.Sprintf("git!%s!%s", p.args, tag) - } - - return fmt.Sprintf("%s!%s", p.srcType, p.args) -} - -// Retrieve a package source from the environment. If no source is -// specified, the Nix code will default to a recent NixOS channel. -func pkgSourceFromEnv() *pkgSource { - if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { - log.Printf("Using Nix package set from Nix channel %q\n", channel) - return &pkgSource{ - srcType: "nixpkgs", - args: channel, - } - } - - if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { - log.Printf("Using Nix package set from git repository at %q\n", git) - return &pkgSource{ - srcType: "git", - args: git, - } - } - - if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { - log.Printf("Using Nix package set from path %q\n", path) - return &pkgSource{ - srcType: "path", - args: path, - } - } - - return nil -} - -// Load (optional) GCS bucket signing data from the GCS_SIGNING_KEY and -// GCS_SIGNING_ACCOUNT envvars. -func signingOptsFromEnv() *storage.SignedURLOptions { - path := os.Getenv("GCS_SIGNING_KEY") - id := os.Getenv("GCS_SIGNING_ACCOUNT") - - if path == "" || id == "" { - log.Println("GCS URL signing disabled") - return nil - } - - log.Printf("GCS URL signing enabled with account %q\n", id) - k, err := ioutil.ReadFile(path) - if err != nil { - log.Fatalf("Failed to read GCS signing key: %s\n", err) - } - - return &storage.SignedURLOptions{ - GoogleAccessID: id, - PrivateKey: k, - Method: "GET", - } -} - -// config holds the Nixery configuration options. -type config struct { - bucket string // GCS bucket to cache & serve layers - signing *storage.SignedURLOptions // Signing options to use for GCS URLs - port string // Port on which to launch HTTP server - pkgs *pkgSource // Source for Nix package set -} - // ManifestMediaType is the Content-Type used for the manifest itself. This // corresponds to the "Image Manifest V2, Schema 2" described on this page: // // https://docs.docker.com/registry/spec/manifest-v2-2/ const manifestMediaType string = "application/vnd.docker.distribution.manifest.v2+json" -// Image represents the information necessary for building a container image. -// This can be either a list of package names (corresponding to keys in the -// nixpkgs set) or a Nix expression that results in a *list* of derivations. -type image struct { - name string - tag string - - // Names of packages to include in the image. These must correspond - // directly to top-level names of Nix packages in the nixpkgs tree. - packages []string -} - -// BuildResult represents the output of calling the Nix derivation responsible -// for building registry images. -// -// The `layerLocations` field contains the local filesystem paths to each -// individual image layer that will need to be served, while the `manifest` -// field contains the JSON-representation of the manifest that needs to be -// served to the client. -// -// The later field is simply treated as opaque JSON and passed through. -type BuildResult struct { - Error string `json:"error"` - Pkgs []string `json:"pkgs"` - - Manifest json.RawMessage `json:"manifest"` - LayerLocations map[string]struct { - Path string `json:"path"` - Md5 []byte `json:"md5"` - } `json:"layerLocations"` -} - -// imageFromName parses an image name into the corresponding structure which can -// be used to invoke Nix. -// -// It will expand convenience names under the hood (see the `convenienceNames` -// function below). -func imageFromName(name string, tag string) image { - packages := strings.Split(name, "/") - return image{ - name: name, - tag: tag, - packages: convenienceNames(packages), - } -} - -// convenienceNames expands convenience package names defined by Nixery which -// let users include commonly required sets of tools in a container quickly. -// -// Convenience names must be specified as the first package in an image. -// -// Currently defined convenience names are: -// -// * `shell`: Includes bash, coreutils and other common command-line tools -// * `builder`: All of the above and the standard build environment -func convenienceNames(packages []string) []string { - shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} - - if packages[0] == "shell" { - return append(packages[1:], shellPackages...) - } - - return packages -} - -// Call out to Nix and request that an image be built. Nix will, upon success, -// return a manifest for the container image. -func buildImage(ctx *context.Context, cfg *config, image *image, bucket *storage.BucketHandle) (*BuildResult, error) { - packages, err := json.Marshal(image.packages) - if err != nil { - return nil, err - } - - args := []string{ - "--argstr", "name", image.name, - "--argstr", "packages", string(packages), - } - - if cfg.pkgs != nil { - args = append(args, "--argstr", "pkgSource", cfg.pkgs.renderSource(image.tag)) - } - cmd := exec.Command("nixery-build-image", args...) - - outpipe, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - errpipe, err := cmd.StderrPipe() - if err != nil { - return nil, err - } - - if err = cmd.Start(); err != nil { - log.Println("Error starting nix-build:", err) - return nil, err - } - log.Printf("Started Nix image build for '%s'", image.name) - - stdout, _ := ioutil.ReadAll(outpipe) - stderr, _ := ioutil.ReadAll(errpipe) - - if err = cmd.Wait(); err != nil { - // TODO(tazjin): Propagate errors upwards in a usable format. - log.Printf("nix-build execution error: %s\nstdout: %s\nstderr: %s\n", err, stdout, stderr) - return nil, err - } - - log.Println("Finished Nix image build") - - buildOutput, err := ioutil.ReadFile(strings.TrimSpace(string(stdout))) - if err != nil { - return nil, err - } - - // The build output returned by Nix is deserialised to add all - // contained layers to the bucket. Only the manifest itself is - // re-serialised to JSON and returned. - var result BuildResult - err = json.Unmarshal(buildOutput, &result) - if err != nil { - return nil, err - } - - for layer, meta := range result.LayerLocations { - err = uploadLayer(ctx, bucket, layer, meta.Path, meta.Md5) - if err != nil { - return nil, err - } - } - - return &result, nil -} - -// uploadLayer uploads a single layer to Cloud Storage bucket. Before writing -// any data the bucket is probed to see if the file already exists. -// -// If the file does exist, its MD5 hash is verified to ensure that the stored -// file is not - for example - a fragment of a previous, incomplete upload. -func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer string, path string, md5 []byte) error { - layerKey := fmt.Sprintf("layers/%s", layer) - obj := bucket.Object(layerKey) - - // Before uploading a layer to the bucket, probe whether it already - // exists. - // - // If it does and the MD5 checksum matches the expected one, the layer - // upload can be skipped. - attrs, err := obj.Attrs(*ctx) - - if err == nil && bytes.Equal(attrs.MD5, md5) { - log.Printf("Layer sha256:%s already exists in bucket, skipping upload", layer) - } else { - writer := obj.NewWriter(*ctx) - file, err := os.Open(path) - - if err != nil { - return fmt.Errorf("failed to open layer %s from path %s: %v", layer, path, err) - } - - size, err := io.Copy(writer, file) - if err != nil { - return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) - } - - if err = writer.Close(); err != nil { - return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) - } - - log.Printf("Uploaded layer sha256:%s (%v bytes written)\n", layer, size) - } - - return nil -} +// Regexes matching the V2 Registry API routes. This only includes the +// routes required for serving images, since pushing and other such +// functionality is not available. +var ( + manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) + layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) +) // layerRedirect constructs the public URL of the layer object in the Cloud // Storage bucket, signs it and redirects the user there. @@ -316,16 +61,16 @@ func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer strin // // The Docker client is known to follow redirects, but this might not be true // for all other registry clients. -func constructLayerUrl(cfg *config, digest string) (string, error) { - log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, cfg.bucket) +func constructLayerUrl(cfg *config.Config, digest string) (string, error) { + log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, cfg.Bucket) object := "layers/" + digest - if cfg.signing != nil { - opts := *cfg.signing + if cfg.Signing != nil { + opts := *cfg.Signing opts.Expires = time.Now().Add(5 * time.Minute) - return storage.SignedURL(cfg.bucket, object, &opts) + return storage.SignedURL(cfg.Bucket, object, &opts) } else { - return ("https://storage.googleapis.com/" + cfg.bucket + "/" + object), nil + return ("https://storage.googleapis.com/" + cfg.Bucket + "/" + object), nil } } @@ -336,13 +81,13 @@ func constructLayerUrl(cfg *config, digest string) (string, error) { // // The bucket is required for Nixery to function correctly, hence fatal errors // are generated in case it fails to be set up correctly. -func prepareBucket(ctx *context.Context, cfg *config) *storage.BucketHandle { +func prepareBucket(ctx *context.Context, cfg *config.Config) *storage.BucketHandle { client, err := storage.NewClient(*ctx) if err != nil { log.Fatalln("Failed to set up Cloud Storage client:", err) } - bkt := client.Bucket(cfg.bucket) + bkt := client.Bucket(cfg.Bucket) if _, err := bkt.Attrs(*ctx); err != nil { log.Fatalln("Could not access configured bucket", err) @@ -351,14 +96,6 @@ func prepareBucket(ctx *context.Context, cfg *config) *storage.BucketHandle { return bkt } -// Regexes matching the V2 Registry API routes. This only includes the -// routes required for serving images, since pushing and other such -// functionality is not available. -var ( - manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) - layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) -) - // Error format corresponding to the registry protocol V2 specification. This // allows feeding back errors to clients in a way that can be presented to // users. @@ -385,7 +122,7 @@ func writeError(w http.ResponseWriter, status int, code, message string) { } type registryHandler struct { - cfg *config + cfg *config.Config ctx *context.Context bucket *storage.BucketHandle } @@ -402,8 +139,8 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { imageName := manifestMatches[1] imageTag := manifestMatches[2] log.Printf("Requesting manifest for image %q at tag %q", imageName, imageTag) - image := imageFromName(imageName, imageTag) - buildResult, err := buildImage(h.ctx, h.cfg, &image, h.bucket) + image := builder.ImageFromName(imageName, imageTag) + buildResult, err := builder.BuildImage(h.ctx, h.cfg, &image, h.bucket) if err != nil { writeError(w, 500, "UNKNOWN", "image build failure") @@ -451,27 +188,12 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) } -func getConfig(key, desc string) string { - value := os.Getenv(key) - if value == "" { - log.Fatalln(desc + " must be specified") - } - - return value -} - func main() { - cfg := &config{ - bucket: getConfig("BUCKET", "GCS bucket for layer storage"), - port: getConfig("PORT", "HTTP port"), - pkgs: pkgSourceFromEnv(), - signing: signingOptsFromEnv(), - } - + cfg := config.FromEnv() ctx := context.Background() bucket := prepareBucket(&ctx, cfg) - log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.port) + log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.Port) // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ @@ -481,8 +203,8 @@ func main() { }) // All other roots are served by the static file server. - webDir := http.Dir(getConfig("WEB_DIR", "Static web file dir")) + webDir := http.Dir(cfg.WebDir) http.Handle("/", http.FileServer(webDir)) - log.Fatal(http.ListenAndServe(":"+cfg.port, nil)) + log.Fatal(http.ListenAndServe(":"+cfg.Port, nil)) } From cf227c153f0eb55b2d931c780d3f7b020e8844bb Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 14 Aug 2019 20:02:52 +0100 Subject: [PATCH 070/223] feat(builder): Implement build cache for manifests & layers Implements a cache that keeps track of: a) Manifests that have already been built (for up to 6 hours) b) Layers that have already been seen (and uploaded to GCS) This significantly speeds up response times for images that are full or partial matches with previous images served by an instance. --- tools/nixery/server/builder/builder.go | 108 ++++++++++++++----------- tools/nixery/server/builder/cache.go | 95 ++++++++++++++++++++++ tools/nixery/server/main.go | 5 +- 3 files changed, 159 insertions(+), 49 deletions(-) create mode 100644 tools/nixery/server/builder/cache.go diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index c53b702e0..e241d3d0b 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -69,10 +69,10 @@ func ImageFromName(name string, tag string) Image { // // The later field is simply treated as opaque JSON and passed through. type BuildResult struct { - Error string `json:"error"` - Pkgs []string `json:"pkgs"` + Error string `json:"error"` + Pkgs []string `json:"pkgs"` + Manifest json.RawMessage `json:"manifest"` - Manifest json.RawMessage `json:"manifest"` LayerLocations map[string]struct { Path string `json:"path"` Md5 []byte `json:"md5"` @@ -99,50 +99,57 @@ func convenienceNames(packages []string) []string { // Call out to Nix and request that an image be built. Nix will, upon success, // return a manifest for the container image. -func BuildImage(ctx *context.Context, cfg *config.Config, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { - packages, err := json.Marshal(image.Packages) - if err != nil { - return nil, err +func BuildImage(ctx *context.Context, cfg *config.Config, cache *BuildCache, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { + resultFile, cached := cache.manifestFromCache(image) + + if !cached { + packages, err := json.Marshal(image.Packages) + if err != nil { + return nil, err + } + + args := []string{ + "--argstr", "name", image.Name, + "--argstr", "packages", string(packages), + } + + if cfg.Pkgs != nil { + args = append(args, "--argstr", "pkgSource", cfg.Pkgs.Render(image.Tag)) + } + cmd := exec.Command("nixery-build-image", args...) + + outpipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + + errpipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + + if err = cmd.Start(); err != nil { + log.Println("Error starting nix-build:", err) + return nil, err + } + log.Printf("Started Nix image build for '%s'", image.Name) + + stdout, _ := ioutil.ReadAll(outpipe) + stderr, _ := ioutil.ReadAll(errpipe) + + if err = cmd.Wait(); err != nil { + // TODO(tazjin): Propagate errors upwards in a usable format. + log.Printf("nix-build execution error: %s\nstdout: %s\nstderr: %s\n", err, stdout, stderr) + return nil, err + } + + log.Println("Finished Nix image build") + + resultFile = strings.TrimSpace(string(stdout)) + cache.cacheManifest(image, resultFile) } - args := []string{ - "--argstr", "name", image.Name, - "--argstr", "packages", string(packages), - } - - if cfg.Pkgs != nil { - args = append(args, "--argstr", "pkgSource", cfg.Pkgs.Render(image.Tag)) - } - cmd := exec.Command("nixery-build-image", args...) - - outpipe, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - errpipe, err := cmd.StderrPipe() - if err != nil { - return nil, err - } - - if err = cmd.Start(); err != nil { - log.Println("Error starting nix-build:", err) - return nil, err - } - log.Printf("Started Nix image build for '%s'", image.Name) - - stdout, _ := ioutil.ReadAll(outpipe) - stderr, _ := ioutil.ReadAll(errpipe) - - if err = cmd.Wait(); err != nil { - // TODO(tazjin): Propagate errors upwards in a usable format. - log.Printf("nix-build execution error: %s\nstdout: %s\nstderr: %s\n", err, stdout, stderr) - return nil, err - } - - log.Println("Finished Nix image build") - - buildOutput, err := ioutil.ReadFile(strings.TrimSpace(string(stdout))) + buildOutput, err := ioutil.ReadFile(resultFile) if err != nil { return nil, err } @@ -151,15 +158,20 @@ func BuildImage(ctx *context.Context, cfg *config.Config, image *Image, bucket * // contained layers to the bucket. Only the manifest itself is // re-serialised to JSON and returned. var result BuildResult + err = json.Unmarshal(buildOutput, &result) if err != nil { return nil, err } for layer, meta := range result.LayerLocations { - err = uploadLayer(ctx, bucket, layer, meta.Path, meta.Md5) - if err != nil { - return nil, err + if !cache.hasSeenLayer(layer) { + err = uploadLayer(ctx, bucket, layer, meta.Path, meta.Md5) + if err != nil { + return nil, err + } + + cache.sawLayer(layer) } } diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go new file mode 100644 index 000000000..0014789af --- /dev/null +++ b/tools/nixery/server/builder/cache.go @@ -0,0 +1,95 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +package builder + +import ( + "sync" + "time" +) + +// recencyThreshold is the amount of time that a manifest build will be cached +// for. When using the channel mechanism for retrieving nixpkgs, Nix will +// occasionally re-fetch the channel so things can in fact change while the +// instance is running. +const recencyThreshold = time.Duration(6) * time.Hour + +type manifestEntry struct { + built time.Time + path string +} + +type void struct{} + +type BuildCache struct { + mmtx sync.RWMutex + mcache map[string]manifestEntry + + lmtx sync.RWMutex + lcache map[string]void +} + +func NewCache() BuildCache { + return BuildCache{ + mcache: make(map[string]manifestEntry), + lcache: make(map[string]void), + } +} + +// Has this layer hash already been seen by this Nixery instance? If +// yes, we can skip upload checking and such because it has already +// been done. +func (c *BuildCache) hasSeenLayer(hash string) bool { + c.lmtx.RLock() + defer c.lmtx.RUnlock() + _, seen := c.lcache[hash] + return seen +} + +// Layer has now been seen and should be stored. +func (c *BuildCache) sawLayer(hash string) { + c.lmtx.Lock() + defer c.lmtx.Unlock() + c.lcache[hash] = void{} +} + +// Has this manifest been built already? If yes, we can reuse the +// result given that the build happened recently enough. +func (c *BuildCache) manifestFromCache(image *Image) (string, bool) { + c.mmtx.RLock() + + entry, ok := c.mcache[image.Name+image.Tag] + c.mmtx.RUnlock() + + if !ok { + return "", false + } + + if time.Since(entry.built) > recencyThreshold { + return "", false + } + + return entry.path, true +} + +// Adds the result of a manifest build to the cache. +func (c *BuildCache) cacheManifest(image *Image, path string) { + entry := manifestEntry{ + built: time.Now(), + path: path, + } + + c.mmtx.Lock() + c.mcache[image.Name+image.Tag] = entry + c.mmtx.Unlock() +} diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 5d7dcd2ad..fd307f79d 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -125,6 +125,7 @@ type registryHandler struct { cfg *config.Config ctx *context.Context bucket *storage.BucketHandle + cache *builder.BuildCache } func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -140,7 +141,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { imageTag := manifestMatches[2] log.Printf("Requesting manifest for image %q at tag %q", imageName, imageTag) image := builder.ImageFromName(imageName, imageTag) - buildResult, err := builder.BuildImage(h.ctx, h.cfg, &image, h.bucket) + buildResult, err := builder.BuildImage(h.ctx, h.cfg, h.cache, &image, h.bucket) if err != nil { writeError(w, 500, "UNKNOWN", "image build failure") @@ -192,6 +193,7 @@ func main() { cfg := config.FromEnv() ctx := context.Background() bucket := prepareBucket(&ctx, cfg) + cache := builder.NewCache() log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.Port) @@ -200,6 +202,7 @@ func main() { cfg: cfg, ctx: &ctx, bucket: bucket, + cache: &cache, }) // All other roots are served by the static file server. From 36d50d1f19f8c55e1eb707639fe73906d8dd30e8 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 14 Aug 2019 20:08:40 +0100 Subject: [PATCH 071/223] fix(server): Print correct project name during startup They grow up so fast :') --- tools/nixery/server/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index fd307f79d..da181249b 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -195,7 +195,7 @@ func main() { bucket := prepareBucket(&ctx, cfg) cache := builder.NewCache() - log.Printf("Starting Kubernetes Nix controller on port %s\n", cfg.Port) + log.Printf("Starting Nixery on port %s\n", cfg.Port) // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ From 85b9c30749efb734c02e85a0460b563d9292344f Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 14 Aug 2019 20:13:35 +0100 Subject: [PATCH 072/223] chore(server): Add 'go vet' to build process --- tools/nixery/server/default.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/nixery/server/default.nix b/tools/nixery/server/default.nix index 05ad64261..9df527218 100644 --- a/tools/nixery/server/default.nix +++ b/tools/nixery/server/default.nix @@ -21,6 +21,14 @@ buildGoPackage { goPackagePath = "github.com/google/nixery"; + # Enable checks and configure check-phase to include vet: + doCheck = true; + preCheck = '' + for pkg in $(getGoDirs ""); do + buildGoDir vet "$pkg" + done + ''; + meta = { description = "Container image builder serving Nix-backed images"; homepage = "https://github.com/google/nixery"; From ca1ffb397d979239b0246c1c72c73b74bc96635e Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 14 Aug 2019 23:46:21 +0100 Subject: [PATCH 073/223] feat(build): Add an integration test that runs on Travis This test, after performing the usual Nixery build, loads the built image into Docker, runs it, pulls an image from Nixery and runs that image. To make this work, there is some configuration on the Travis side. Most importantly, the following environment variables have special values: * `GOOGLE_KEY`: This is set to a base64-encoded service account key to be used in the test. * `GCS_SIGNING_PEM`: This is set to a base64-encoded signing key (in PEM) that is used for signing URLs. Both of these are available to all branches in the Nixery repository. --- tools/nixery/.travis.yml | 49 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 96f1ec0fc..950de21f7 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -1,6 +1,51 @@ language: nix +services: + - docker before_script: - - nix-env -iA nixpkgs.cachix - - cachix use nixery + - | + mkdir test-files + echo ${GOOGLE_KEY} | base64 -d > test-files/key.json + echo ${GCS_SIGNING_PEM} | base64 -d > test-files/gcs.pem + nix-env -iA nixpkgs.cachix -A nixpkgs.go + cachix use nixery script: + - test -z $(gofmt -l server/ build-image/) - nix-build | cachix push nixery + + # This integration test makes sure that the container image built + # for Nixery itself runs fine in Docker, and that images pulled + # from it work in Docker. + # + # Output from the Nixery container is printed at the end of the + # test regardless of test status. + - IMG=$(docker load -q -i $(nix-build -A nixery-image) | awk '{ print $3 }') + - echo "Loaded Nixery image as ${IMG}" + + - | + docker run -d -p 8080:8080 --name nixery \ + -v ${PWD}/test-files:/var/nixery \ + -e PORT=8080 \ + -e BUCKET=nixery-layers \ + -e GOOGLE_CLOUD_PROJECT=nixery \ + -e GOOGLE_APPLICATION_CREDENTIALS=/var/nixery/key.json \ + ${IMG} + + # print all of the container's logs regardless of success + - | + function print_logs { + echo "Nixery container logs:" + docker logs nixery + } + trap print_logs EXIT + + # Give the container ~20 seconds to come up + - | + attempts=0 + echo -n "Waiting for Nixery to start ..." + until $(curl --fail --silent "http://localhost:8080/v2/"); do + [[ attempts -eq 30 ]] && echo "Nixery container failed to start!" && exit 1 + ((attempts++)) + echo -n "." + sleep 1 + done + - docker run --rm localhost:8080/hello hello From 0ec369d76c2b151fa82839230e6eb4d58015b1dc Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 15 Aug 2019 11:50:23 +0100 Subject: [PATCH 074/223] docs(book): Update information on new layering strategy --- tools/nixery/docs/src/nixery.md | 15 ++++++++------- tools/nixery/docs/src/under-the-hood.md | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index 83e1aac52..3eaeb6be4 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -7,8 +7,11 @@ contain packages from the [Nix][] package manager. Images with arbitrary packages can be requested via the image name. Nix not only provides the packages to include in the images, but also builds the -images themselves by using an interesting layering strategy described in [this -blog post][layers]. +images themselves by using a special [layering strategy][] that optimises for +cache efficiency. + +For general information on why using Nix makes sense for container images, check +out [this blog post][layers]. ## Quick start @@ -65,13 +68,11 @@ availability. ### Who made this? -Nixery was written mostly by [tazjin][]. - -[grahamc][] authored the image layering strategy. Many people have contributed -to Nix over time, maybe you could become one of them? +Nixery was written by [tazjin][], but many people have contributed to Nix over +time, maybe you could become one of them? [Nixery]: https://github.com/google/nixery [Nix]: https://nixos.org/nix +[layering-strategy]: https://storage.googleapis.com/nixdoc/nixery-layers.html [layers]: https://grahamc.com/blog/nix-and-layered-docker-images [tazjin]: https://github.com/tazjin -[grahamc]: https://github.com/grahamc diff --git a/tools/nixery/docs/src/under-the-hood.md b/tools/nixery/docs/src/under-the-hood.md index 3791707b1..6b5e5e9bb 100644 --- a/tools/nixery/docs/src/under-the-hood.md +++ b/tools/nixery/docs/src/under-the-hood.md @@ -51,7 +51,8 @@ does not allow uppercase characters, so the Nix code will translate something like `haskellpackages` (lowercased) to the correct attribute name. After identifying all contents, Nix determines the contents of each layer while -optimising for the best possible cache efficiency. +optimising for the best possible cache efficiency (see the [layering design +doc][] for details). Finally it builds each layer, assembles the image manifest as JSON structure, and yields this manifest back to the web server. @@ -103,3 +104,4 @@ to run the image produced by Nixery. [gcs]: https://cloud.google.com/storage/ [signed URLs]: https://cloud.google.com/storage/docs/access-control/signed-urls +[layering design doc]: https://storage.googleapis.com/nixdoc/nixery-layers.html From 3f232e017075f5b45c7d11cdf27225a51f47c085 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 15 Aug 2019 12:00:36 +0100 Subject: [PATCH 075/223] docs: Add asciinema demo to README & book --- tools/nixery/README.md | 21 ++++++--------------- tools/nixery/docs/src/nixery.md | 4 ++++ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 9c323a57f..8225e430b 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -25,23 +25,14 @@ images. The design for this is outlined in [a public gist][gist]. This is not an officially supported Google project. -## Usage example +## Demo -Using the publicly available Nixery instance at `nixery.dev`, one could -retrieve a container image containing `curl` and an interactive shell like this: +Click the image to see an example in which an image containing an interactive +shell and GNU `hello` is downloaded. -```shell -tazjin@tazbox:~$ sudo docker run -ti nixery.dev/shell/curl bash -Unable to find image 'nixery.dev/shell/curl:latest' locally -latest: Pulling from shell/curl -7734b79e1ba1: Already exists -b0d2008d18cd: Pull complete -< ... some layers omitted ...> -Digest: sha256:178270bfe84f74548b6a43347d73524e5c2636875b673675db1547ec427cf302 -Status: Downloaded newer image for nixery.dev/shell/curl:latest -bash-4.4# curl --version -curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.0.2q zlib/1.2.11 libssh2/1.8.0 nghttp2/1.35.1 -``` +[![asciicast](https://asciinema.org/a/262583.png)](https://asciinema.org/a/262583?autoplay=1) + +To try it yourself, head to [nixery.dev][public]! The special meta-package `shell` provides an image base with many core components (such as `bash` and `coreutils`) that users commonly expect in diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index 3eaeb6be4..edea53bab 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -13,6 +13,10 @@ cache efficiency. For general information on why using Nix makes sense for container images, check out [this blog post][layers]. +## Demo + + + ## Quick start Simply pull an image from this registry, separating each package you want From 501e6ded5f80215879ad7467cd7ca250f902422d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 15 Aug 2019 12:02:32 +0100 Subject: [PATCH 076/223] fix(build): Ensure GCS signing is used in CI --- tools/nixery/.travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 950de21f7..46c2a9778 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -28,6 +28,8 @@ script: -e BUCKET=nixery-layers \ -e GOOGLE_CLOUD_PROJECT=nixery \ -e GOOGLE_APPLICATION_CREDENTIALS=/var/nixery/key.json \ + -e GCS_SIGNING_ACCOUNT="${GCS_SIGNING_ACCOUNT}" \ + -e GCS_SIGNING_KEY=/var/nixery/gcs.pem \ ${IMG} # print all of the container's logs regardless of success From 3b65fc8c726a91eb155ff2e5bf116e5aeee488aa Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Fri, 16 Aug 2019 21:18:43 +0200 Subject: [PATCH 077/223] feat(server): add iana-etc and cacert to the shell convenience package These probably should be part of every container image by default, but adding it to the "shell" convenience name probably is our best bet for now. --- tools/nixery/server/builder/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index e241d3d0b..46301d6c6 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -88,7 +88,7 @@ type BuildResult struct { // // * `shell`: Includes bash, coreutils and other common command-line tools func convenienceNames(packages []string) []string { - shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} + shellPackages := []string{"bashInteractive", "cacert", "coreutils", "iana-etc", "moreutils", "nano"} if packages[0] == "shell" { return append(packages[1:], shellPackages...) From 0ee239874b76bea75a3d3a90201118b3c4294576 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 17 Aug 2019 10:07:46 +0100 Subject: [PATCH 078/223] docs(README): Update links to layering strategy --- tools/nixery/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 8225e430b..cdae23bc4 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -14,14 +14,16 @@ user intends to include in the image is specified as a path component of the image name. The path components refer to top-level keys in `nixpkgs` and are used to build a -container image using Nix's [buildLayeredImage][] functionality. +container image using a [layering strategy][] that optimises for caching popular +and/or large dependencies. A public instance as well as additional documentation is available at [nixery.dev][public]. -The project started out with the intention of becoming a Kubernetes controller -that can serve declarative image specifications specified in CRDs as container -images. The design for this is outlined in [a public gist][gist]. +The project started out inspired by the [buildLayeredImage][] blog post with the +intention of becoming a Kubernetes controller that can serve declarative image +specifications specified in CRDs as container images. The design for this was +outlined in [a public gist][gist]. This is not an officially supported Google project. @@ -94,6 +96,7 @@ correct caching behaviour, addressing and so on. See [issue #4](https://github.com/google/nixery/issues/4). [Nix]: https://nixos.org/ +[layering strategy]: https://storage.googleapis.com/nixdoc/nixery-layers.html [gist]: https://gist.github.com/tazjin/08f3d37073b3590aacac424303e6f745 [buildLayeredImage]: https://grahamc.com/blog/nix-and-layered-docker-images [public]: https://nixery.dev From 9a95c4124f911dbc07d4eabc0c4b8fc2d44c74d6 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 17 Aug 2019 10:18:15 +0100 Subject: [PATCH 079/223] fix(server): Sort requested packages in image name & spec Before this change, Nixery would pass on the image name unmodified to Nix which would lead it to cache-bust the manifest and configuration layers for images that are content-identical but have different package ordering. This fixes #38. --- tools/nixery/server/builder/builder.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 46301d6c6..a249384d9 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -27,6 +27,7 @@ import ( "log" "os" "os/exec" + "sort" "strings" "cloud.google.com/go/storage" @@ -50,12 +51,21 @@ type Image struct { // // It will expand convenience names under the hood (see the `convenienceNames` // function below). +// +// Once assembled the image structure uses a sorted representation of +// the name. This is to avoid unnecessarily cache-busting images if +// only the order of requested packages has changed. func ImageFromName(name string, tag string) Image { - packages := strings.Split(name, "/") + pkgs := strings.Split(name, "/") + expanded := convenienceNames(pkgs) + + sort.Strings(pkgs) + sort.Strings(expanded) + return Image{ - Name: name, + Name: strings.Join(pkgs, "/"), Tag: tag, - Packages: convenienceNames(packages), + Packages: expanded, } } From 745b7ce0b821b1d46b7259c8ba704bf767ad31d6 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 17 Aug 2019 09:29:56 +0000 Subject: [PATCH 080/223] fix(build): Ensure root user is known inside of container This is required by git in cases where Nixery is configured with a custom git repository. I've also added a shell back into the image to make debugging a running Nixery easier. It turns out some of the dependencies already pull in bash anyways, so this is just surfacing it to $PATH. --- tools/nixery/default.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 734a72d57..194cf5460 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -61,6 +61,8 @@ rec { # Create the build user/group required by Nix echo 'nixbld:x:30000:nixbld' >> /etc/group echo 'nixbld:x:30000:30000:nixbld:/tmp:/bin/bash' >> /etc/passwd + echo 'root:x:0:0:root:/root:/bin/bash' >> /etc/passwd + echo 'root:x:0:' >> /etc/group # Disable sandboxing to avoid running into privilege issues mkdir -p /etc/nix @@ -80,6 +82,7 @@ rec { config.Cmd = [ "${nixery-launch-script}/bin/nixery" ]; maxLayers = 96; contents = [ + bashInteractive cacert coreutils git @@ -89,6 +92,7 @@ rec { nixery-build-image nixery-launch-script openssh + zlib ]; }; } From ffae282eac86f4df48a5c39f61d82dd88ccca1ec Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 18 Aug 2019 02:44:34 +0100 Subject: [PATCH 081/223] fix(docs): Correct link to layering strategy --- tools/nixery/docs/src/nixery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index edea53bab..6cc16431c 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -77,6 +77,6 @@ time, maybe you could become one of them? [Nixery]: https://github.com/google/nixery [Nix]: https://nixos.org/nix -[layering-strategy]: https://storage.googleapis.com/nixdoc/nixery-layers.html +[layering strategy]: https://storage.googleapis.com/nixdoc/nixery-layers.html [layers]: https://grahamc.com/blog/nix-and-layered-docker-images [tazjin]: https://github.com/tazjin From e7d7f73f7d191c4149409a0be83c924127972b2a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 19 Aug 2019 01:10:21 +0100 Subject: [PATCH 082/223] feat(build): Add 'extraPackages' parameter This makes it possible to inject additional programs (e.g. Cachix) into a Nixery container. --- tools/nixery/default.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 194cf5460..31b15396a 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -11,7 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{ pkgs ? import { }, preLaunch ? "" }: +{ pkgs ? import { } +, preLaunch ? "" +, extraPackages ? [] }: with pkgs; @@ -93,6 +95,6 @@ rec { nixery-launch-script openssh zlib - ]; + ] ++ extraPackages; }; } From ccf6a95f94b4abad2fed94e613888a4407f3ec93 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 19 Aug 2019 01:36:32 +0100 Subject: [PATCH 083/223] chore(build): Pin nixpkgs to a specific commit This is the same commit for which Nixery has popularity data, but that isn't particularly relevant. --- tools/nixery/.travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 46c2a9778..864eeaa90 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -1,6 +1,8 @@ language: nix services: - docker +env: + - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/88d9f776091896cfe57dc6fbdf246e7d27d5f105.tar.gz before_script: - | mkdir test-files From daa6196c2a5575a9394cf6e389aee8e050df08ec Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 19 Aug 2019 01:34:20 +0100 Subject: [PATCH 084/223] fix(build): Force nix-env to use NIX_PATH Thanks to clever! --- tools/nixery/.travis.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 864eeaa90..471037f36 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -4,12 +4,11 @@ services: env: - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/88d9f776091896cfe57dc6fbdf246e7d27d5f105.tar.gz before_script: - - | - mkdir test-files - echo ${GOOGLE_KEY} | base64 -d > test-files/key.json - echo ${GCS_SIGNING_PEM} | base64 -d > test-files/gcs.pem - nix-env -iA nixpkgs.cachix -A nixpkgs.go - cachix use nixery + - mkdir test-files + - echo ${GOOGLE_KEY} | base64 -d > test-files/key.json + - echo ${GCS_SIGNING_PEM} | base64 -d > test-files/gcs.pem + - nix-env -f '' -iA cachix -A go + - cachix use nixery script: - test -z $(gofmt -l server/ build-image/) - nix-build | cachix push nixery From bb5427a47a29161f854bd9b57e388849ea26e818 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 21 Aug 2019 10:21:45 +0100 Subject: [PATCH 085/223] chore(docs): Update embedded nix-1p version The new version of the document has syntactic fixes that render pipes in code blocks in tables correctly across dialects. Fixes #44 --- tools/nixery/docs/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/docs/default.nix b/tools/nixery/docs/default.nix index aae2fdde4..11eda3ff7 100644 --- a/tools/nixery/docs/default.nix +++ b/tools/nixery/docs/default.nix @@ -42,8 +42,8 @@ let nix-1p = fetchFromGitHub { owner = "tazjin"; repo = "nix-1p"; - rev = "3cd0f7d7b4f487d04a3f1e3ca8f2eb1ab958c49b"; - sha256 = "02lpda03q580gyspkbmlgnb2cbm66rrcgqsv99aihpbwyjym81af"; + rev = "e0a051a016b9118bea90ec293d6cd346b9707e77"; + sha256 = "0d1lfkxg03lki8dc3229g1cgqiq3nfrqgrknw99p6w0zk1pjd4dj"; }; in runCommand "nixery-book" { } '' mkdir -p $out From 306e12787a9977334d44f215eece8f4ae89fe03f Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 21 Aug 2019 10:22:44 +0100 Subject: [PATCH 086/223] chore(build): Add iana-etc to Nixery's own image This package is used by a variety of programs that users may want to embed into Nixery in addition, for example cachix, but those packages don't refer to it explicitly. --- tools/nixery/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 31b15396a..1f908b609 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -90,6 +90,7 @@ rec { git gnutar gzip + iana-etc nix nixery-build-image nixery-launch-script From 92270fcbe472c2cef3cbd8f3f92b950aa78bc777 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 1 Sep 2019 23:28:57 +0100 Subject: [PATCH 087/223] refactor(build-image): Simplify customisation layer builder Moves the relevant parts of the customisation layer construction from dockerTools.mkCustomisationLayer into the Nixery code base. The version in dockerTools builds additional files (including via hashing of potentially large files) which are not required when serving an image over the registry protocol. --- tools/nixery/build-image/build-image.nix | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index b67fef6ce..7b0f2cac9 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -31,8 +31,6 @@ # Packages to install by name (which must refer to top-level attributes of # nixpkgs). This is passed in as a JSON-array in string form. packages ? "[]", - # Optional bash script to run on the files prior to fixturizing the layer. - extraCommands ? "", uid ? 0, gid ? 0, # Docker's modern image storage mechanisms have a maximum of 125 # layers. To allow for some extensibility (via additional layers), # the default here is set to something a little less than that. @@ -106,11 +104,6 @@ let fetched = (map (deepFetch pkgs) (fromJSON packages)); in foldl' splitter init fetched; - contentsEnv = symlinkJoin { - name = "bulk-layers"; - paths = allContents.contents; - }; - popularity = builtins.fetchurl { url = "https://storage.googleapis.com/nixery-layers/popularity/nixos-19.03-20190812.json"; sha256 = "16sxd49vqqg2nrhwynm36ba6bc2yff5cd5hf83wi0hanw5sx3svk"; @@ -156,13 +149,23 @@ let (lib.concatStringsSep "\n" (map (layer: pathsToLayer layer.contents) groupedLayers)); - customisationLayer = mkCustomisationLayer { - name = baseName; - contents = contentsEnv; - baseJson = writeText "empty.json" "{}"; - inherit uid gid extraCommands; + # Create a symlink forest into all top-level store paths. + contentsEnv = symlinkJoin { + name = "bulk-layers"; + paths = allContents.contents; }; + # This customisation layer which contains the symlink forest + # required at container runtime is assembled with a simplified + # version of dockerTools.mkCustomisationLayer. + # + # No metadata creation (such as layer hashing) is required when + # serving images over the API. + customisationLayer = runCommand "customisation-layer.tar" {} '' + cp -r ${contentsEnv}/ ./layer + tar --transform='s|^\./||' -C layer --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 -cf $out . + ''; + # Inspect the returned bulk layers to determine which layers belong to the # image and how to serve them. # @@ -172,9 +175,7 @@ let buildInputs = [ coreutils findutils jq openssl ]; } '' cat ${bulkLayers} | sort -t/ -k5 -n > layer-list - echo -n layer-list: - cat layer-list - echo ${customisationLayer}/layer.tar >> layer-list + echo ${customisationLayer} >> layer-list for layer in $(cat layer-list); do layerSha256=$(sha256sum $layer | cut -d ' ' -f1) From ce8635833b226df8497be34a8009d0e47cb0399e Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 1 Sep 2019 23:55:34 +0100 Subject: [PATCH 088/223] refactor(build-image): Remove implicit import of entire package set Explicitly refer to where things come from, and also don't import dockerTools as it is no longer used for anything. --- tools/nixery/build-image/build-image.nix | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 7b0f2cac9..cd0ef91b3 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -39,14 +39,11 @@ ... }: -# Since this is essentially a re-wrapping of some of the functionality that is -# implemented in the dockerTools, we need all of its components in our top-level -# namespace. with builtins; -with pkgs; -with dockerTools; let + inherit (pkgs) lib runCommand writeText; + tarLayer = "application/vnd.docker.image.rootfs.diff.tar"; baseName = baseNameOf name; @@ -150,7 +147,7 @@ let groupedLayers)); # Create a symlink forest into all top-level store paths. - contentsEnv = symlinkJoin { + contentsEnv = pkgs.symlinkJoin { name = "bulk-layers"; paths = allContents.contents; }; @@ -172,7 +169,7 @@ let # This computes both an MD5 and a SHA256 hash of each layer, which are used # for different purposes. See the registry server implementation for details. allLayersJson = runCommand "fs-layer-list.json" { - buildInputs = [ coreutils findutils jq openssl ]; + buildInputs = with pkgs; [ coreutils jq openssl ]; } '' cat ${bulkLayers} | sort -t/ -k5 -n > layer-list echo ${customisationLayer} >> layer-list @@ -204,7 +201,7 @@ let }; configJson = writeText "${baseName}-config.json" (toJSON config); configMetadata = fromJSON (readFile (runCommand "config-meta" { - buildInputs = [ jq openssl ]; + buildInputs = with pkgs; [ jq openssl ]; } '' size=$(wc -c ${configJson} | cut -d ' ' -f1) sha256=$(sha256sum ${configJson} | cut -d ' ' -f1) From 32b9b5099eaeabc5672b6236e26743736b85cc41 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 2 Sep 2019 23:32:36 +0100 Subject: [PATCH 089/223] feat(server): Add configuration option for Nix build timeouts Adds a NIX_TIMEOUT environment variable which can be set to a number of seconds that is the maximum allowed time each Nix builder can run. By default this is set to 60 seconds, which should be plenty for most use-cases as Nixery is not expected to be performing builds of uncached binaries in most production cases. Currently the errors Nix throws on a build timeout are not separated from other types of errors, meaning that users will see a generic 500 server error in case of a timeout. This fixes #47 --- tools/nixery/server/builder/builder.go | 1 + tools/nixery/server/config/config.go | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index a249384d9..35a2c2f71 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -119,6 +119,7 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *BuildCache, ima } args := []string{ + "--timeout", cfg.Timeout, "--argstr", "name", image.Name, "--argstr", "packages", string(packages), } diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index 4e3b70dcd..5fba0e658 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -102,10 +102,12 @@ func signingOptsFromEnv() *storage.SignedURLOptions { } } -func getConfig(key, desc string) string { +func getConfig(key, desc, def string) string { value := os.Getenv(key) - if value == "" { + if value == "" && def == "" { log.Fatalln(desc + " must be specified") + } else if value == "" { + return def } return value @@ -117,15 +119,17 @@ type Config struct { Signing *storage.SignedURLOptions // Signing options to use for GCS URLs Port string // Port on which to launch HTTP server Pkgs *PkgSource // Source for Nix package set - WebDir string + Timeout string // Timeout for a single Nix builder (seconds) + WebDir string // Directory with static web assets } func FromEnv() *Config { return &Config{ - Bucket: getConfig("BUCKET", "GCS bucket for layer storage"), - Port: getConfig("PORT", "HTTP port"), + Bucket: getConfig("BUCKET", "GCS bucket for layer storage", ""), + Port: getConfig("PORT", "HTTP port", ""), Pkgs: pkgSourceFromEnv(), Signing: signingOptsFromEnv(), - WebDir: getConfig("WEB_DIR", "Static web file dir"), + Timeout: getConfig("NIX_TIMEOUT", "Nix builder timeout", "60"), + WebDir: getConfig("WEB_DIR", "Static web file dir", ""), } } From 496a4ab84742279fb7bbd1c8ac9d4ebd1a44b148 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 2 Sep 2019 23:35:50 +0100 Subject: [PATCH 090/223] docs: Add information about NIX_TIMEOUT variable --- tools/nixery/README.md | 2 ++ tools/nixery/docs/src/run-your-own.md | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index cdae23bc4..3bc6df845 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -81,6 +81,8 @@ variables: locally configured SSH/git credentials) * `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use for building +* `NIX_TIMEOUT`: Number of seconds that any Nix builder is allowed to run + (defaults to 60 * `GCS_SIGNING_KEY`: A Google service account key (in PEM format) that can be used to sign Cloud Storage URLs * `GCS_SIGNING_ACCOUNT`: Google service account ID that the signing key belongs diff --git a/tools/nixery/docs/src/run-your-own.md b/tools/nixery/docs/src/run-your-own.md index 0539ab02f..7a294f560 100644 --- a/tools/nixery/docs/src/run-your-own.md +++ b/tools/nixery/docs/src/run-your-own.md @@ -81,8 +81,10 @@ You may set *one* of these, if unset Nixery defaults to `nixos-19.03`: * `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use for building -You may set *both* of these: +You may set *all* of these: +* `NIX_TIMEOUT`: Number of seconds that any Nix builder is allowed to run + (defaults to 60) * `GCS_SIGNING_KEY`: A Google service account key (in PEM format) that can be used to [sign Cloud Storage URLs][signed-urls] * `GCS_SIGNING_ACCOUNT`: Google service account ID that the signing key belongs From 980f5e218761fa340b746e6336db62abf63c953a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 8 Sep 2019 21:53:22 +0100 Subject: [PATCH 091/223] refactor(server): Move package source management logic to server Introduces three new types representing each of the possible package sources and moves the logic for specifying the package source to the server. Concrete changes: * Determining whether a specified git reference is a commit vs. a branch/tag is now done in the server, and is done more precisely by using a regular expression. * Package sources now have a new `CacheKey` function which can be used to retrieve a key under which a build manifest can be cached *if* the package source is not a moving target (i.e. a full git commit hash of either nixpkgs or a private repository). This function is not yet used. * Users *must* now specify a package source, Nixery no longer defaults to anything and will fail to launch if no source is configured. --- tools/nixery/.travis.yml | 1 + tools/nixery/build-image/default.nix | 5 +- tools/nixery/build-image/load-pkgs.nix | 54 ++------- tools/nixery/default.nix | 3 +- tools/nixery/server/builder/builder.go | 7 +- tools/nixery/server/config/config.go | 66 ++-------- tools/nixery/server/config/pkgsource.go | 155 ++++++++++++++++++++++++ tools/nixery/server/main.go | 6 +- 8 files changed, 192 insertions(+), 105 deletions(-) create mode 100644 tools/nixery/server/config/pkgsource.go diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 471037f36..f670ab0e2 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -31,6 +31,7 @@ script: -e GOOGLE_APPLICATION_CREDENTIALS=/var/nixery/key.json \ -e GCS_SIGNING_ACCOUNT="${GCS_SIGNING_ACCOUNT}" \ -e GCS_SIGNING_KEY=/var/nixery/gcs.pem \ + -e NIXERY_CHANNEL=nixos-unstable \ ${IMG} # print all of the container's logs regardless of success diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix index 0d3002cb4..6b1cea6f0 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/build-image/default.nix @@ -20,11 +20,12 @@ # Because of the insanity occuring below, this function must mirror # all arguments of build-image.nix. -, pkgSource ? "nixpkgs!nixos-19.03" +, srcType ? "nixpkgs" +, srcArgs ? "nixos-19.03" , tag ? null, name ? null, packages ? null, maxLayers ? null }@args: -let pkgs = import ./load-pkgs.nix { inherit pkgSource; }; +let pkgs = import ./load-pkgs.nix { inherit srcType srcArgs; }; in with pkgs; rec { groupLayers = buildGoPackage { diff --git a/tools/nixery/build-image/load-pkgs.nix b/tools/nixery/build-image/load-pkgs.nix index 3e8b450c4..cceebfc14 100644 --- a/tools/nixery/build-image/load-pkgs.nix +++ b/tools/nixery/build-image/load-pkgs.nix @@ -12,17 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Load a Nix package set from a source specified in one of the following -# formats: -# -# 1. nixpkgs!$channel (e.g. nixpkgs!nixos-19.03) -# 2. git!$repo!$rev (e.g. git!git@github.com:NixOS/nixpkgs.git!master) -# 3. path!$path (e.g. path!/var/local/nixpkgs) -# -# '!' was chosen as the separator because `builtins.split` does not -# support regex escapes and there are few other candidates. It -# doesn't matter much because this is invoked by the server. -{ pkgSource, args ? { } }: +# Load a Nix package set from one of the supported source types +# (nixpkgs, git, path). +{ srcType, srcArgs, importArgs ? { } }: with builtins; let @@ -32,42 +24,22 @@ let let url = "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; - in import (fetchTarball url) args; + in import (fetchTarball url) importArgs; # If a git repository is requested, it is retrieved via # builtins.fetchGit which defaults to the git configuration of the # outside environment. This means that user-configured SSH # credentials etc. are going to work as expected. - fetchImportGit = url: rev: - let - # builtins.fetchGit needs to know whether 'rev' is a reference - # (e.g. a branch/tag) or a revision (i.e. a commit hash) - # - # Since this data is being extrapolated from the supplied image - # tag, we have to guess if we want to avoid specifying a format. - # - # There are some additional caveats around whether the default - # branch contains the specified revision, which need to be - # explained to users. - spec = if (stringLength rev) == 40 then { - inherit url rev; - } else { - inherit url; - ref = rev; - }; - in import (fetchGit spec) args; + fetchImportGit = spec: import (fetchGit spec) importArgs; # No special handling is used for paths, so users are expected to pass one # that will work natively with Nix. - importPath = path: import (toPath path) args; - - source = split "!" pkgSource; - sourceType = elemAt source 0; -in if sourceType == "nixpkgs" then - fetchImportChannel (elemAt source 2) -else if sourceType == "git" then - fetchImportGit (elemAt source 2) (elemAt source 4) -else if sourceType == "path" then - importPath (elemAt source 2) + importPath = path: import (toPath path) importArgs; +in if srcType == "nixpkgs" then + fetchImportChannel srcArgs +else if srcType == "git" then + fetchImportGit (fromJSON srcArgs) +else if srcType == "path" then + importPath srcArgs else - throw ("Invalid package set source specification: ${pkgSource}") + throw ("Invalid package set source specification: ${srcType} (${srcArgs})") diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 1f908b609..f7a2a1712 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -28,7 +28,8 @@ rec { # Implementation of the image building & layering logic nixery-build-image = (import ./build-image { - pkgSource = "path!${}"; + srcType = "path"; + srcArgs = ; }).wrapper; # Use mdBook to build a static asset page which Nixery can then diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 35a2c2f71..ce88d2dd8 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -118,15 +118,16 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *BuildCache, ima return nil, err } + srcType, srcArgs := cfg.Pkgs.Render(image.Tag) + args := []string{ "--timeout", cfg.Timeout, "--argstr", "name", image.Name, "--argstr", "packages", string(packages), + "--argstr", "srcType", srcType, + "--argstr", "srcArgs", srcArgs, } - if cfg.Pkgs != nil { - args = append(args, "--argstr", "pkgSource", cfg.Pkgs.Render(image.Tag)) - } cmd := exec.Command("nixery-build-image", args...) outpipe, err := cmd.StdoutPipe() diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index 5fba0e658..ea1bb1ab4 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -18,7 +18,6 @@ package config import ( - "fmt" "io/ioutil" "log" "os" @@ -26,58 +25,6 @@ import ( "cloud.google.com/go/storage" ) -// pkgSource represents the source from which the Nix package set used -// by Nixery is imported. Users configure the source by setting one of -// the supported environment variables. -type PkgSource struct { - srcType string - args string -} - -// Convert the package source into the representation required by Nix. -func (p *PkgSource) Render(tag string) string { - // The 'git' source requires a tag to be present. - if p.srcType == "git" { - if tag == "latest" || tag == "" { - tag = "master" - } - - return fmt.Sprintf("git!%s!%s", p.args, tag) - } - - return fmt.Sprintf("%s!%s", p.srcType, p.args) -} - -// Retrieve a package source from the environment. If no source is -// specified, the Nix code will default to a recent NixOS channel. -func pkgSourceFromEnv() *PkgSource { - if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { - log.Printf("Using Nix package set from Nix channel %q\n", channel) - return &PkgSource{ - srcType: "nixpkgs", - args: channel, - } - } - - if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { - log.Printf("Using Nix package set from git repository at %q\n", git) - return &PkgSource{ - srcType: "git", - args: git, - } - } - - if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { - log.Printf("Using Nix package set from path %q\n", path) - return &PkgSource{ - srcType: "path", - args: path, - } - } - - return nil -} - // Load (optional) GCS bucket signing data from the GCS_SIGNING_KEY and // GCS_SIGNING_ACCOUNT envvars. func signingOptsFromEnv() *storage.SignedURLOptions { @@ -118,18 +65,23 @@ type Config struct { Bucket string // GCS bucket to cache & serve layers Signing *storage.SignedURLOptions // Signing options to use for GCS URLs Port string // Port on which to launch HTTP server - Pkgs *PkgSource // Source for Nix package set + Pkgs PkgSource // Source for Nix package set Timeout string // Timeout for a single Nix builder (seconds) WebDir string // Directory with static web assets } -func FromEnv() *Config { +func FromEnv() (*Config, error) { + pkgs, err := pkgSourceFromEnv() + if err != nil { + return nil, err + } + return &Config{ Bucket: getConfig("BUCKET", "GCS bucket for layer storage", ""), Port: getConfig("PORT", "HTTP port", ""), - Pkgs: pkgSourceFromEnv(), + Pkgs: pkgs, Signing: signingOptsFromEnv(), Timeout: getConfig("NIX_TIMEOUT", "Nix builder timeout", "60"), WebDir: getConfig("WEB_DIR", "Static web file dir", ""), - } + }, nil } diff --git a/tools/nixery/server/config/pkgsource.go b/tools/nixery/server/config/pkgsource.go new file mode 100644 index 000000000..61bea33df --- /dev/null +++ b/tools/nixery/server/config/pkgsource.go @@ -0,0 +1,155 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +package config + +import ( + "crypto/sha1" + "encoding/json" + "fmt" + "log" + "os" + "regexp" + "strings" +) + +// PkgSource represents the source from which the Nix package set used +// by Nixery is imported. Users configure the source by setting one of +// the supported environment variables. +type PkgSource interface { + // Convert the package source into the representation required + // for calling Nix. + Render(tag string) (string, string) + + // Create a key by which builds for this source and iamge + // combination can be cached. + // + // The empty string means that this value is not cacheable due + // to the package source being a moving target (such as a + // channel). + CacheKey(pkgs []string, tag string) string +} + +type GitSource struct { + repository string +} + +// Regex to determine whether a git reference is a commit hash or +// something else (branch/tag). +// +// Used to check whether a git reference is cacheable, and to pass the +// correct git structure to Nix. +// +// Note: If a user creates a branch or tag with the name of a commit +// and references it intentionally, this heuristic will fail. +var commitRegex = regexp.MustCompile(`^[0-9a-f]{40}$`) + +func (g *GitSource) Render(tag string) (string, string) { + args := map[string]string{ + "url": g.repository, + } + + // The 'git' source requires a tag to be present. If the user + // has not specified one, it is assumed that the default + // 'master' branch should be used. + if tag == "latest" || tag == "" { + tag = "master" + } + + if commitRegex.MatchString(tag) { + args["rev"] = tag + } else { + args["ref"] = tag + } + + j, _ := json.Marshal(args) + + return "git", string(j) +} + +func (g *GitSource) CacheKey(pkgs []string, tag string) string { + // Only full commit hashes can be used for caching, as + // everything else is potentially a moving target. + if !commitRegex.MatchString(tag) { + return "" + } + + unhashed := strings.Join(pkgs, "") + tag + hashed := fmt.Sprintf("%x", sha1.Sum([]byte(unhashed))) + + return hashed +} + +type NixChannel struct { + channel string +} + +func (n *NixChannel) Render(tag string) (string, string) { + return "nixpkgs", n.channel +} + +func (n *NixChannel) CacheKey(pkgs []string, tag string) string { + // Since Nix channels are downloaded from the nixpkgs-channels + // Github, users can specify full commit hashes as the + // "channel", in which case builds are cacheable. + if !commitRegex.MatchString(n.channel) { + return "" + } + + unhashed := strings.Join(pkgs, "") + n.channel + hashed := fmt.Sprintf("%x", sha1.Sum([]byte(unhashed))) + + return hashed +} + +type PkgsPath struct { + path string +} + +func (p *PkgsPath) Render(tag string) (string, string) { + return "path", p.path +} + +func (p *PkgsPath) CacheKey(pkgs []string, tag string) string { + // Path-based builds are not currently cacheable because we + // have no local hash of the package folder's state easily + // available. + return "" +} + +// Retrieve a package source from the environment. If no source is +// specified, the Nix code will default to a recent NixOS channel. +func pkgSourceFromEnv() (PkgSource, error) { + if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { + log.Printf("Using Nix package set from Nix channel %q\n", channel) + return &NixChannel{ + channel: channel, + }, nil + } + + if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { + log.Printf("Using Nix package set from git repository at %q\n", git) + return &GitSource{ + repository: git, + }, nil + } + + if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { + log.Printf("Using Nix package set from path %q\n", path) + return &PkgsPath{ + path: path, + }, nil + } + + return nil, fmt.Errorf("no valid package source has been specified") +} diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index da181249b..49db1cdf8 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -190,7 +190,11 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func main() { - cfg := config.FromEnv() + cfg, err := config.FromEnv() + if err != nil { + log.Fatalln("Failed to load configuration", err) + } + ctx := context.Background() bucket := prepareBucket(&ctx, cfg) cache := builder.NewCache() From 051eb77b3de81d9393e5c5443c06b62b6abf1535 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 8 Sep 2019 22:21:14 +0100 Subject: [PATCH 092/223] refactor(server): Use package source specific cache keys Use the PackageSource.CacheKey function introduced in the previous commit to determine the key at which a manifest should be cached in the local cache. Due to this change, manifests for moving target sources are no longer cached and the recency threshold logic has been removed. --- tools/nixery/server/builder/builder.go | 4 +-- tools/nixery/server/builder/cache.go | 49 ++++++++++---------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index ce88d2dd8..cfe03511f 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -110,7 +110,7 @@ func convenienceNames(packages []string) []string { // Call out to Nix and request that an image be built. Nix will, upon success, // return a manifest for the container image. func BuildImage(ctx *context.Context, cfg *config.Config, cache *BuildCache, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { - resultFile, cached := cache.manifestFromCache(image) + resultFile, cached := cache.manifestFromCache(cfg.Pkgs, image) if !cached { packages, err := json.Marshal(image.Packages) @@ -158,7 +158,7 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *BuildCache, ima log.Println("Finished Nix image build") resultFile = strings.TrimSpace(string(stdout)) - cache.cacheManifest(image, resultFile) + cache.cacheManifest(cfg.Pkgs, image, resultFile) } buildOutput, err := ioutil.ReadFile(resultFile) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 0014789af..8ade88037 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -14,26 +14,15 @@ package builder import ( + "github.com/google/nixery/config" "sync" - "time" ) -// recencyThreshold is the amount of time that a manifest build will be cached -// for. When using the channel mechanism for retrieving nixpkgs, Nix will -// occasionally re-fetch the channel so things can in fact change while the -// instance is running. -const recencyThreshold = time.Duration(6) * time.Hour - -type manifestEntry struct { - built time.Time - path string -} - type void struct{} type BuildCache struct { mmtx sync.RWMutex - mcache map[string]manifestEntry + mcache map[string]string lmtx sync.RWMutex lcache map[string]void @@ -41,7 +30,7 @@ type BuildCache struct { func NewCache() BuildCache { return BuildCache{ - mcache: make(map[string]manifestEntry), + mcache: make(map[string]string), lcache: make(map[string]void), } } @@ -63,33 +52,33 @@ func (c *BuildCache) sawLayer(hash string) { c.lcache[hash] = void{} } -// Has this manifest been built already? If yes, we can reuse the -// result given that the build happened recently enough. -func (c *BuildCache) manifestFromCache(image *Image) (string, bool) { - c.mmtx.RLock() +// Retrieve a cached manifest if the build is cacheable and it exists. +func (c *BuildCache) manifestFromCache(src config.PkgSource, image *Image) (string, bool) { + key := src.CacheKey(image.Packages, image.Tag) + if key == "" { + return "", false + } - entry, ok := c.mcache[image.Name+image.Tag] + c.mmtx.RLock() + path, ok := c.mcache[key] c.mmtx.RUnlock() if !ok { return "", false } - if time.Since(entry.built) > recencyThreshold { - return "", false - } - - return entry.path, true + return path, true } -// Adds the result of a manifest build to the cache. -func (c *BuildCache) cacheManifest(image *Image, path string) { - entry := manifestEntry{ - built: time.Now(), - path: path, +// Adds the result of a manifest build to the cache, if the manifest +// is considered cacheable. +func (c *BuildCache) cacheManifest(src config.PkgSource, image *Image, path string) { + key := src.CacheKey(image.Packages, image.Tag) + if key == "" { + return } c.mmtx.Lock() - c.mcache[image.Name+image.Tag] = entry + c.mcache[key] = path c.mmtx.Unlock() } From 4a58b0ab4d21473723834dec651c876da2dec220 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 9 Sep 2019 16:41:52 +0100 Subject: [PATCH 093/223] feat(server): Cache built manifests to the GCS bucket Caches manifests under `manifests/$cacheKey` in the GCS bucket and introduces two-tiered retrieval of manifests from the caches (local first, bucket second). There is some cleanup to be done in this code, but the initial version works. --- tools/nixery/server/builder/builder.go | 6 +- tools/nixery/server/builder/cache.go | 113 ++++++++++++++++++++----- tools/nixery/server/main.go | 2 +- 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index cfe03511f..dd26ccc31 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -109,8 +109,8 @@ func convenienceNames(packages []string) []string { // Call out to Nix and request that an image be built. Nix will, upon success, // return a manifest for the container image. -func BuildImage(ctx *context.Context, cfg *config.Config, cache *BuildCache, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { - resultFile, cached := cache.manifestFromCache(cfg.Pkgs, image) +func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { + resultFile, cached := manifestFromCache(ctx, bucket, cfg.Pkgs, cache, image) if !cached { packages, err := json.Marshal(image.Packages) @@ -158,7 +158,7 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *BuildCache, ima log.Println("Finished Nix image build") resultFile = strings.TrimSpace(string(stdout)) - cache.cacheManifest(cfg.Pkgs, image, resultFile) + cacheManifest(ctx, bucket, cfg.Pkgs, cache, image, resultFile) } buildOutput, err := ioutil.ReadFile(resultFile) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 8ade88037..32f55e3a6 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -14,13 +14,21 @@ package builder import ( - "github.com/google/nixery/config" + "context" + "io" + "log" + "os" "sync" + + "cloud.google.com/go/storage" + "github.com/google/nixery/config" ) type void struct{} -type BuildCache struct { +// LocalCache implements the structure used for local caching of +// manifests and layer uploads. +type LocalCache struct { mmtx sync.RWMutex mcache map[string]string @@ -28,8 +36,8 @@ type BuildCache struct { lcache map[string]void } -func NewCache() BuildCache { - return BuildCache{ +func NewCache() LocalCache { + return LocalCache{ mcache: make(map[string]string), lcache: make(map[string]void), } @@ -38,7 +46,7 @@ func NewCache() BuildCache { // Has this layer hash already been seen by this Nixery instance? If // yes, we can skip upload checking and such because it has already // been done. -func (c *BuildCache) hasSeenLayer(hash string) bool { +func (c *LocalCache) hasSeenLayer(hash string) bool { c.lmtx.RLock() defer c.lmtx.RUnlock() _, seen := c.lcache[hash] @@ -46,19 +54,14 @@ func (c *BuildCache) hasSeenLayer(hash string) bool { } // Layer has now been seen and should be stored. -func (c *BuildCache) sawLayer(hash string) { +func (c *LocalCache) sawLayer(hash string) { c.lmtx.Lock() defer c.lmtx.Unlock() c.lcache[hash] = void{} } // Retrieve a cached manifest if the build is cacheable and it exists. -func (c *BuildCache) manifestFromCache(src config.PkgSource, image *Image) (string, bool) { - key := src.CacheKey(image.Packages, image.Tag) - if key == "" { - return "", false - } - +func (c *LocalCache) manifestFromLocalCache(key string) (string, bool) { c.mmtx.RLock() path, ok := c.mcache[key] c.mmtx.RUnlock() @@ -70,15 +73,85 @@ func (c *BuildCache) manifestFromCache(src config.PkgSource, image *Image) (stri return path, true } -// Adds the result of a manifest build to the cache, if the manifest -// is considered cacheable. -func (c *BuildCache) cacheManifest(src config.PkgSource, image *Image, path string) { - key := src.CacheKey(image.Packages, image.Tag) - if key == "" { - return - } - +// Adds the result of a manifest build to the local cache, if the +// manifest is considered cacheable. +func (c *LocalCache) localCacheManifest(key, path string) { c.mmtx.Lock() c.mcache[key] = path c.mmtx.Unlock() } + +// Retrieve a manifest from the cache(s). First the local cache is +// checked, then the GCS-bucket cache. +func manifestFromCache(ctx *context.Context, bucket *storage.BucketHandle, pkgs config.PkgSource, cache *LocalCache, image *Image) (string, bool) { + key := pkgs.CacheKey(image.Packages, image.Tag) + if key == "" { + return "", false + } + + path, cached := cache.manifestFromLocalCache(key) + if cached { + return path, true + } + + obj := bucket.Object("manifests/" + key) + + // Probe whether the file exists before trying to fetch it. + _, err := obj.Attrs(*ctx) + if err != nil { + return "", false + } + + r, err := obj.NewReader(*ctx) + if err != nil { + log.Printf("Failed to retrieve manifest '%s' from cache: %s\n", key, err) + return "", false + } + defer r.Close() + + path = os.TempDir() + "/" + key + f, _ := os.Create(path) + defer f.Close() + + _, err = io.Copy(f, r) + if err != nil { + log.Printf("Failed to read cached manifest for '%s': %s\n", key, err) + } + + log.Printf("Retrieved manifest for '%s' (%s) from GCS\n", image.Name, key) + cache.localCacheManifest(key, path) + + return path, true +} + +func cacheManifest(ctx *context.Context, bucket *storage.BucketHandle, pkgs config.PkgSource, cache *LocalCache, image *Image, path string) { + key := pkgs.CacheKey(image.Packages, image.Tag) + if key == "" { + return + } + + cache.localCacheManifest(key, path) + + obj := bucket.Object("manifests/" + key) + w := obj.NewWriter(*ctx) + + f, err := os.Open(path) + if err != nil { + log.Printf("failed to open '%s' manifest for cache upload: %s\n", image.Name, err) + return + } + defer f.Close() + + size, err := io.Copy(w, f) + if err != nil { + log.Printf("failed to cache manifest sha1:%s: %s\n", key, err) + return + } + + if err = w.Close(); err != nil { + log.Printf("failed to cache manifest sha1:%s: %s\n", key, err) + return + } + + log.Printf("Cached manifest sha1:%s (%v bytes written)\n", key, size) +} diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 49db1cdf8..9242a3731 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -125,7 +125,7 @@ type registryHandler struct { cfg *config.Config ctx *context.Context bucket *storage.BucketHandle - cache *builder.BuildCache + cache *builder.LocalCache } func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { From 5a002fe067e52d503062307515179670b5e3de13 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 10 Sep 2019 11:13:10 +0100 Subject: [PATCH 094/223] refactor(builder): Calculate image cache key only once --- tools/nixery/server/builder/builder.go | 13 +++++++++++-- tools/nixery/server/builder/cache.go | 19 ++++--------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index dd26ccc31..0ded94dfa 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -110,7 +110,13 @@ func convenienceNames(packages []string) []string { // Call out to Nix and request that an image be built. Nix will, upon success, // return a manifest for the container image. func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { - resultFile, cached := manifestFromCache(ctx, bucket, cfg.Pkgs, cache, image) + var resultFile string + cached := false + + key := cfg.Pkgs.CacheKey(image.Packages, image.Tag) + if key != "" { + resultFile, cached = manifestFromCache(ctx, cache, bucket, key) + } if !cached { packages, err := json.Marshal(image.Packages) @@ -158,7 +164,10 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, ima log.Println("Finished Nix image build") resultFile = strings.TrimSpace(string(stdout)) - cacheManifest(ctx, bucket, cfg.Pkgs, cache, image, resultFile) + + if key != "" { + cacheManifest(ctx, cache, bucket, key, resultFile) + } } buildOutput, err := ioutil.ReadFile(resultFile) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 32f55e3a6..52765293f 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -21,7 +21,6 @@ import ( "sync" "cloud.google.com/go/storage" - "github.com/google/nixery/config" ) type void struct{} @@ -83,12 +82,7 @@ func (c *LocalCache) localCacheManifest(key, path string) { // Retrieve a manifest from the cache(s). First the local cache is // checked, then the GCS-bucket cache. -func manifestFromCache(ctx *context.Context, bucket *storage.BucketHandle, pkgs config.PkgSource, cache *LocalCache, image *Image) (string, bool) { - key := pkgs.CacheKey(image.Packages, image.Tag) - if key == "" { - return "", false - } - +func manifestFromCache(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string) (string, bool) { path, cached := cache.manifestFromLocalCache(key) if cached { return path, true @@ -118,18 +112,13 @@ func manifestFromCache(ctx *context.Context, bucket *storage.BucketHandle, pkgs log.Printf("Failed to read cached manifest for '%s': %s\n", key, err) } - log.Printf("Retrieved manifest for '%s' (%s) from GCS\n", image.Name, key) + log.Printf("Retrieved manifest for sha1:%s from GCS\n", key) cache.localCacheManifest(key, path) return path, true } -func cacheManifest(ctx *context.Context, bucket *storage.BucketHandle, pkgs config.PkgSource, cache *LocalCache, image *Image, path string) { - key := pkgs.CacheKey(image.Packages, image.Tag) - if key == "" { - return - } - +func cacheManifest(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key, path string) { cache.localCacheManifest(key, path) obj := bucket.Object("manifests/" + key) @@ -137,7 +126,7 @@ func cacheManifest(ctx *context.Context, bucket *storage.BucketHandle, pkgs conf f, err := os.Open(path) if err != nil { - log.Printf("failed to open '%s' manifest for cache upload: %s\n", image.Name, err) + log.Printf("failed to open manifest sha1:%s for cache upload: %s\n", key, err) return } defer f.Close() From e4d03fdb17041ead530aa3e115f84988148a3b21 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Sep 2019 12:04:04 +0100 Subject: [PATCH 095/223] chore(docs): Remove mdbook override The change has been upstreamed in Nixpkgs. --- tools/nixery/docs/default.nix | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tools/nixery/docs/default.nix b/tools/nixery/docs/default.nix index 11eda3ff7..fdf0e6ff9 100644 --- a/tools/nixery/docs/default.nix +++ b/tools/nixery/docs/default.nix @@ -21,24 +21,6 @@ { fetchFromGitHub, mdbook, runCommand, rustPlatform }: let - # nixpkgs currently has an old version of mdBook. A new version is - # built here, but eventually the update will be upstreamed - # (nixpkgs#65890) - mdbook = rustPlatform.buildRustPackage rec { - name = "mdbook-${version}"; - version = "0.3.1"; - doCheck = false; - - src = fetchFromGitHub { - owner = "rust-lang-nursery"; - repo = "mdBook"; - rev = "v${version}"; - sha256 = "0py69267jbs6b7zw191hcs011cm1v58jz8mglqx3ajkffdfl3ghw"; - }; - - cargoSha256 = "0qwhc42a86jpvjcaysmfcw8kmwa150lmz01flmlg74g6qnimff5m"; - }; - nix-1p = fetchFromGitHub { owner = "tazjin"; repo = "nix-1p"; From 64f74abc4df6676e3cd4c7f34210fd2aea433f16 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Sep 2019 12:15:38 +0100 Subject: [PATCH 096/223] feat: Add configuration option for popularity data URL --- tools/nixery/README.md | 1 + tools/nixery/build-image/build-image.nix | 8 ++++---- tools/nixery/build-image/default.nix | 2 +- tools/nixery/server/builder/builder.go | 4 ++++ tools/nixery/server/config/config.go | 2 ++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 3bc6df845..3026451c7 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -83,6 +83,7 @@ variables: for building * `NIX_TIMEOUT`: Number of seconds that any Nix builder is allowed to run (defaults to 60 +* `NIX_POPULARITY_URL`: URL to a file containing popularity data for the package set (see `popcount/`) * `GCS_SIGNING_KEY`: A Google service account key (in PEM format) that can be used to sign Cloud Storage URLs * `GCS_SIGNING_ACCOUNT`: Google service account ID that the signing key belongs diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index cd0ef91b3..8f54f5326 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -35,6 +35,9 @@ # layers. To allow for some extensibility (via additional layers), # the default here is set to something a little less than that. maxLayers ? 96, + # Popularity data for layer solving is fetched from the URL passed + # in here. + popularityUrl ? "https://storage.googleapis.com/nixery-layers/popularity/popularity-19.03.173490.5271f8dddc0.json", ... }: @@ -101,10 +104,7 @@ let fetched = (map (deepFetch pkgs) (fromJSON packages)); in foldl' splitter init fetched; - popularity = builtins.fetchurl { - url = "https://storage.googleapis.com/nixery-layers/popularity/nixos-19.03-20190812.json"; - sha256 = "16sxd49vqqg2nrhwynm36ba6bc2yff5cd5hf83wi0hanw5sx3svk"; - }; + popularity = builtins.fetchurl popularityUrl; # Before actually creating any image layers, the store paths that need to be # included in the image must be sorted into the layers that they should go diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix index 6b1cea6f0..3bb5d62fb 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/build-image/default.nix @@ -22,7 +22,7 @@ # all arguments of build-image.nix. , srcType ? "nixpkgs" , srcArgs ? "nixos-19.03" -, tag ? null, name ? null, packages ? null, maxLayers ? null +, tag ? null, name ? null, packages ? null, maxLayers ? null, popularityUrl ? null }@args: let pkgs = import ./load-pkgs.nix { inherit srcType srcArgs; }; diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 0ded94dfa..b2e183b5a 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -134,6 +134,10 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, ima "--argstr", "srcArgs", srcArgs, } + if cfg.PopUrl != "" { + args = append(args, "--argstr", "popularityUrl", cfg.PopUrl) + } + cmd := exec.Command("nixery-build-image", args...) outpipe, err := cmd.StdoutPipe() diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index ea1bb1ab4..ac8820f23 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -68,6 +68,7 @@ type Config struct { Pkgs PkgSource // Source for Nix package set Timeout string // Timeout for a single Nix builder (seconds) WebDir string // Directory with static web assets + PopUrl string // URL to the Nix package popularity count } func FromEnv() (*Config, error) { @@ -83,5 +84,6 @@ func FromEnv() (*Config, error) { Signing: signingOptsFromEnv(), Timeout: getConfig("NIX_TIMEOUT", "Nix builder timeout", "60"), WebDir: getConfig("WEB_DIR", "Static web file dir", ""), + PopUrl: os.Getenv("NIX_POPULARITY_URL"), }, nil } From f0b69638e1c2354c91d2457998c17ee388665bd1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Sep 2019 12:18:04 +0100 Subject: [PATCH 097/223] chore(build): Bump nixpkgs version used in Travis This version matches the updated popularity URL. --- tools/nixery/.travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index f670ab0e2..5f0bbfd3b 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -2,7 +2,7 @@ language: nix services: - docker env: - - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/88d9f776091896cfe57dc6fbdf246e7d27d5f105.tar.gz + - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/5271f8dddc0f2e54f55bd2fc1868c09ff72ac980.tar.gz before_script: - mkdir test-files - echo ${GOOGLE_KEY} | base64 -d > test-files/key.json From da6fd1d79e03795e0432553c1875774a61267826 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Sep 2019 12:35:18 +0100 Subject: [PATCH 098/223] fix(build): Ensure nixery-build-image is on Nixery's PATH This is useful when running Nixery locally. --- tools/nixery/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index f7a2a1712..dd07d3493 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -44,6 +44,7 @@ rec { # are installing Nixery directly. nixery-bin = writeShellScriptBin "nixery" '' export WEB_DIR="${nixery-book}" + export PATH="${nixery-build-image}/bin:$PATH" exec ${nixery-server}/bin/nixery ''; From c391a7b7f81f30de59b3bf186bf0e51ad0e308ce Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Sep 2019 15:03:46 +0100 Subject: [PATCH 099/223] fix(build-image): Use absolute paths in tarballs --- tools/nixery/build-image/build-image.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 8f54f5326..9cfb806e4 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -133,11 +133,11 @@ let # their contents. pathsToLayer = paths: runCommand "layer.tar" { } '' - tar --no-recursion -rf "$out" \ + tar --no-recursion -Prf "$out" \ --mtime="@$SOURCE_DATE_EPOCH" \ --owner=0 --group=0 /nix /nix/store - tar -rpf "$out" --hard-dereference --sort=name \ + tar -Prpf "$out" --hard-dereference --sort=name \ --mtime="@$SOURCE_DATE_EPOCH" \ --owner=0 --group=0 ${lib.concatStringsSep " " paths} ''; From 0000b956bb2333cc09fb52fb063d383ec1fd90e3 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Sep 2019 15:04:13 +0100 Subject: [PATCH 100/223] feat(server): Log Nix output live during the builds Instead of dumping all Nix output as one at the end of the build process, stream it live as the lines come in. This is a lot more useful for debugging stuff like where manifest retrievals get stuck. --- tools/nixery/server/builder/builder.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index b2e183b5a..a5744a853 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -18,6 +18,7 @@ package builder import ( + "bufio" "bytes" "context" "encoding/json" @@ -107,6 +108,15 @@ func convenienceNames(packages []string) []string { return packages } +// logNix logs each output line from Nix. It runs in a goroutine per +// output channel that should be live-logged. +func logNix(name string, r io.ReadCloser) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + log.Printf("\x1b[31m[nix - %s]\x1b[39m %s\n", name, scanner.Text()) + } +} + // Call out to Nix and request that an image be built. Nix will, upon success, // return a manifest for the container image. func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { @@ -149,6 +159,7 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, ima if err != nil { return nil, err } + go logNix(image.Name, errpipe) if err = cmd.Start(); err != nil { log.Println("Error starting nix-build:", err) @@ -157,11 +168,9 @@ func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, ima log.Printf("Started Nix image build for '%s'", image.Name) stdout, _ := ioutil.ReadAll(outpipe) - stderr, _ := ioutil.ReadAll(errpipe) if err = cmd.Wait(); err != nil { - // TODO(tazjin): Propagate errors upwards in a usable format. - log.Printf("nix-build execution error: %s\nstdout: %s\nstderr: %s\n", err, stdout, stderr) + log.Printf("nix-build execution error: %s\nstdout: %s\n", err, stdout) return nil, err } From 21a17b33f49f3f016e35e535af0b81fdb53f0846 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Sep 2019 15:15:14 +0100 Subject: [PATCH 101/223] fix(build): Ensure launch script compatibility with other runtimes Fixes two launch script compatibility issues with other container runtimes (such as gvisor): * don't fail if /tmp already exists * don't fail if the environment becomes unset --- tools/nixery/default.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index dd07d3493..66155eefa 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -59,8 +59,9 @@ rec { # issues in containers. nixery-launch-script = writeShellScriptBin "nixery" '' set -e + export PATH=${coreutils}/bin:$PATH export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt - mkdir /tmp + mkdir -p /tmp # Create the build user/group required by Nix echo 'nixbld:x:30000:nixbld' >> /etc/group From 7b987530d1e1d25c74b6f8a85a9a25ef61a32c15 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 22 Sep 2019 17:45:44 +0100 Subject: [PATCH 102/223] refactor(build-image): Minor tweak to layer construction script --- tools/nixery/build-image/build-image.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 9cfb806e4..012cc7f36 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -179,7 +179,7 @@ let # The server application compares binary MD5 hashes and expects base64 # encoding instead of hex. layerMd5=$(openssl dgst -md5 -binary $layer | openssl enc -base64) - layerSize=$(wc -c $layer | cut -d ' ' -f1) + layerSize=$(stat --printf '%s' $layer) jq -n -c --arg sha256 $layerSha256 --arg md5 $layerMd5 --arg size $layerSize --arg path $layer \ '{ size: ($size | tonumber), sha256: $sha256, md5: $md5, path: $path }' >> fs-layers @@ -203,7 +203,7 @@ let configMetadata = fromJSON (readFile (runCommand "config-meta" { buildInputs = with pkgs; [ jq openssl ]; } '' - size=$(wc -c ${configJson} | cut -d ' ' -f1) + size=$(stat --printf '%s' ${configJson}) sha256=$(sha256sum ${configJson} | cut -d ' ' -f1) md5=$(openssl dgst -md5 -binary ${configJson} | openssl enc -base64) jq -n -c --arg size $size --arg sha256 $sha256 --arg md5 $md5 \ From ad9b3eb2629b0716d93ce07411831d859237edff Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 22 Sep 2019 17:51:39 +0100 Subject: [PATCH 103/223] refactor(build): Add group-layers to top-level Nix derivations This makes CI build the group-layers tool (and cache it to Cachix!) --- tools/nixery/default.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 66155eefa..95540e11d 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -17,7 +17,11 @@ with pkgs; -rec { +let buildImage = import ./build-image { + srcType = "path"; + srcArgs = ; +}; +in rec { # Go implementation of the Nixery server which implements the # container registry interface. # @@ -27,10 +31,8 @@ rec { nixery-server = callPackage ./server { }; # Implementation of the image building & layering logic - nixery-build-image = (import ./build-image { - srcType = "path"; - srcArgs = ; - }).wrapper; + nixery-build-image = buildImage.wrapper; + nixery-group-layers = buildImage.groupLayers; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at From 712b38cbbcb5671135dbf04492de3c4e97fa1b87 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 29 Sep 2019 22:49:50 +0100 Subject: [PATCH 104/223] refactor(build-image): Do not assemble image layers in Nix This is the first step towards a more granular build process where some of the build responsibility moves into the server component. Rather than assembling all layers inside of Nix, it will only create the symlink forest and return information about the runtime paths required by the image. The server is then responsible for grouping these paths into layers, and assembling the layers themselves. Relates to #50. --- tools/nixery/build-image/build-image.nix | 219 ++++++----------------- 1 file changed, 57 insertions(+), 162 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 012cc7f36..1d97ba59b 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -12,43 +12,38 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This file contains a modified version of dockerTools.buildImage that, instead -# of outputting a single tarball which can be imported into a running Docker -# daemon, builds a manifest file that can be used for serving the image over a -# registry API. +# This file contains a derivation that outputs structured information +# about the runtime dependencies of an image with a given set of +# packages. This is used by Nixery to determine the layer grouping and +# assemble each layer. +# +# In addition it creates and outputs a meta-layer with the symlink +# structure required for using the image together with the individual +# package layers. { - # Package set to used (this will usually be loaded by load-pkgs.nix) - pkgs, - # Image Name - name, - # Image tag, the Nix output's hash will be used if null - tag ? null, - # Tool used to determine layer grouping - groupLayers, - # Files to put on the image (a nix store path or list of paths). - contents ? [], + # Description of the package set to be used (will be loaded by load-pkgs.nix) + srcType ? "nixpkgs", + srcArgs ? "nixos-19.03", + importArgs ? { }, # Packages to install by name (which must refer to top-level attributes of # nixpkgs). This is passed in as a JSON-array in string form. - packages ? "[]", - # Docker's modern image storage mechanisms have a maximum of 125 - # layers. To allow for some extensibility (via additional layers), - # the default here is set to something a little less than that. - maxLayers ? 96, - # Popularity data for layer solving is fetched from the URL passed - # in here. - popularityUrl ? "https://storage.googleapis.com/nixery-layers/popularity/popularity-19.03.173490.5271f8dddc0.json", - - ... + packages ? "[]" }: -with builtins; - let + inherit (builtins) + foldl' + fromJSON + hasAttr + length + readFile + toFile + toJSON; + inherit (pkgs) lib runCommand writeText; - tarLayer = "application/vnd.docker.image.rootfs.diff.tar"; - baseName = baseNameOf name; + pkgs = import ./load-pkgs.nix { inherit srcType srcArgs importArgs; }; # deepFetch traverses the top-level Nix package set to retrieve an item via a # path specified in string form. @@ -87,11 +82,11 @@ let fetchLower = attrByPath caseAmendedPath err s; in attrByPath path fetchLower s; - # allContents is the combination of all derivations and store paths passed in - # directly, as well as packages referred to by name. + # allContents contains all packages successfully retrieved by name + # from the package set, as well as any errors encountered while + # attempting to fetch a package. # - # It accumulates potential errors about packages that could not be found to - # return this information back to the server. + # Accumulated error information is returned back to the server. allContents = # Folds over the results of 'deepFetch' on all requested packages to # separate them into errors and content. This allows the program to @@ -100,157 +95,57 @@ let if hasAttr "error" res then attrs // { errors = attrs.errors ++ [ res ]; } else attrs // { contents = attrs.contents ++ [ res ]; }; - init = { inherit contents; errors = []; }; + init = { contents = []; errors = []; }; fetched = (map (deepFetch pkgs) (fromJSON packages)); in foldl' splitter init fetched; - popularity = builtins.fetchurl popularityUrl; - - # Before actually creating any image layers, the store paths that need to be - # included in the image must be sorted into the layers that they should go - # into. + # Contains the export references graph of all retrieved packages, + # which has information about all runtime dependencies of the image. # - # How contents are allocated to each layer is decided by the `group-layers.go` - # program. The mechanism used is described at the top of the program's source - # code, or alternatively in the layering design document: - # - # https://storage.googleapis.com/nixdoc/nixery-layers.html - # - # To invoke the program, a graph of all runtime references is created via - # Nix's exportReferencesGraph feature - the resulting layers are read back - # into Nix using import-from-derivation. - groupedLayers = fromJSON (readFile (runCommand "grouped-layers.json" { + # This is used by Nixery to group closures into image layers. + runtimeGraph = runCommand "runtime-graph.json" { __structuredAttrs = true; exportReferencesGraph.graph = allContents.contents; - PATH = "${groupLayers}/bin"; + PATH = "${pkgs.coreutils}/bin"; builder = toFile "builder" '' . .attrs.sh - group-layers --budget ${toString (maxLayers - 1)} --pop ${popularity} --out ''${outputs[out]} + cp .attrs.json ''${outputs[out]} ''; - } "")); + } ""; - # Given a list of store paths, create an image layer tarball with - # their contents. - pathsToLayer = paths: runCommand "layer.tar" { - } '' - tar --no-recursion -Prf "$out" \ - --mtime="@$SOURCE_DATE_EPOCH" \ - --owner=0 --group=0 /nix /nix/store - - tar -Prpf "$out" --hard-dereference --sort=name \ - --mtime="@$SOURCE_DATE_EPOCH" \ - --owner=0 --group=0 ${lib.concatStringsSep " " paths} - ''; - - bulkLayers = writeText "bulk-layers" - (lib.concatStringsSep "\n" (map (layer: pathsToLayer layer.contents) - groupedLayers)); - - # Create a symlink forest into all top-level store paths. + # Create a symlink forest into all top-level store paths of the + # image contents. contentsEnv = pkgs.symlinkJoin { name = "bulk-layers"; paths = allContents.contents; }; - # This customisation layer which contains the symlink forest - # required at container runtime is assembled with a simplified - # version of dockerTools.mkCustomisationLayer. - # - # No metadata creation (such as layer hashing) is required when - # serving images over the API. - customisationLayer = runCommand "customisation-layer.tar" {} '' + # Image layer that contains the symlink forest created above. This + # must be included in the image to ensure that the filesystem has a + # useful layout at runtime. + symlinkLayer = runCommand "symlink-layer.tar" {} '' cp -r ${contentsEnv}/ ./layer tar --transform='s|^\./||' -C layer --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 -cf $out . ''; - # Inspect the returned bulk layers to determine which layers belong to the - # image and how to serve them. - # - # This computes both an MD5 and a SHA256 hash of each layer, which are used - # for different purposes. See the registry server implementation for details. - allLayersJson = runCommand "fs-layer-list.json" { + # Metadata about the symlink layer which is required for serving it. + # Two different hashes are computed for different usages (inclusion + # in manifest vs. content-checking in the layer cache). + symlinkLayerMeta = fromJSON (readFile (runCommand "symlink-layer-meta.json" { buildInputs = with pkgs; [ coreutils jq openssl ]; - } '' - cat ${bulkLayers} | sort -t/ -k5 -n > layer-list - echo ${customisationLayer} >> layer-list + }'' + layerSha256=$(sha256sum ${symlinkLayer} | cut -d ' ' -f1) + layerMd5=$(openssl dgst -md5 -binary ${symlinkLayer} | openssl enc -base64) + layerSize=$(stat --printf '%s' ${symlinkLayer}) - for layer in $(cat layer-list); do - layerSha256=$(sha256sum $layer | cut -d ' ' -f1) - # The server application compares binary MD5 hashes and expects base64 - # encoding instead of hex. - layerMd5=$(openssl dgst -md5 -binary $layer | openssl enc -base64) - layerSize=$(stat --printf '%s' $layer) - - jq -n -c --arg sha256 $layerSha256 --arg md5 $layerMd5 --arg size $layerSize --arg path $layer \ - '{ size: ($size | tonumber), sha256: $sha256, md5: $md5, path: $path }' >> fs-layers - done - - cat fs-layers | jq -s -c '.' > $out - ''; - allLayers = fromJSON (readFile allLayersJson); - - # Image configuration corresponding to the OCI specification for the file type - # 'application/vnd.oci.image.config.v1+json' - config = { - architecture = "amd64"; - os = "linux"; - rootfs.type = "layers"; - rootfs.diff_ids = map (layer: "sha256:${layer.sha256}") allLayers; - # Required to let Kubernetes import Nixery images - config = {}; - }; - configJson = writeText "${baseName}-config.json" (toJSON config); - configMetadata = fromJSON (readFile (runCommand "config-meta" { - buildInputs = with pkgs; [ jq openssl ]; - } '' - size=$(stat --printf '%s' ${configJson}) - sha256=$(sha256sum ${configJson} | cut -d ' ' -f1) - md5=$(openssl dgst -md5 -binary ${configJson} | openssl enc -base64) - jq -n -c --arg size $size --arg sha256 $sha256 --arg md5 $md5 \ - '{ size: ($size | tonumber), sha256: $sha256, md5: $md5 }' \ - >> $out + jq -n -c --arg sha256 $layerSha256 --arg md5 $layerMd5 --arg size $layerSize --arg path ${symlinkLayer} \ + '{ size: ($size | tonumber), sha256: $sha256, md5: $md5, path: $path }' >> $out '')); - # Corresponds to the manifest JSON expected by the Registry API. - # - # This is Docker's "Image Manifest V2, Schema 2": - # https://docs.docker.com/registry/spec/manifest-v2-2/ - manifest = { - schemaVersion = 2; - mediaType = "application/vnd.docker.distribution.manifest.v2+json"; - - config = { - mediaType = "application/vnd.docker.container.image.v1+json"; - size = configMetadata.size; - digest = "sha256:${configMetadata.sha256}"; - }; - - layers = map (layer: { - mediaType = tarLayer; - digest = "sha256:${layer.sha256}"; - size = layer.size; - }) allLayers; - }; - - # This structure maps each layer digest to the actual tarball that will need - # to be served. It is used by the controller to cache the paths during a pull. - layerLocations = { - "${configMetadata.sha256}" = { - path = configJson; - md5 = configMetadata.md5; - }; - } // (listToAttrs (map (layer: { - name = "${layer.sha256}"; - value = { - path = layer.path; - md5 = layer.md5; - }; - }) allLayers)); - - # Final output structure returned to the controller in the case of a - # successful build. - manifestOutput = { - inherit manifest layerLocations; + # Final output structure returned to Nixery if the build succeeded + buildOutput = { + runtimeGraph = fromJSON (readFile runtimeGraph); + symlinkLayer = symlinkLayerMeta; }; # Output structure returned if errors occured during the build. Currently the @@ -259,7 +154,7 @@ let error = "not_found"; pkgs = map (err: err.pkg) allContents.errors; }; -in writeText "manifest-output.json" (if (length allContents.errors) == 0 - then toJSON manifestOutput +in writeText "build-output.json" (if (length allContents.errors) == 0 + then toJSON buildOutput else toJSON errorOutput ) From 0898d8a96175958b82d1713d13304423f8b19f77 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 29 Sep 2019 22:58:52 +0100 Subject: [PATCH 105/223] chore(build-image): Simplify wrapper build & remove layer grouping Simplifies the wrapper script used to invoke Nix builds from Nixery to just contain the essentials, since the layer grouping logic is moving into the server itself. --- tools/nixery/build-image/build-image.nix | 4 +- tools/nixery/build-image/default.nix | 92 +----- tools/nixery/build-image/go-deps.nix | 10 - tools/nixery/build-image/group-layers.go | 352 ----------------------- tools/nixery/default.nix | 11 +- 5 files changed, 19 insertions(+), 450 deletions(-) delete mode 100644 tools/nixery/build-image/go-deps.nix delete mode 100644 tools/nixery/build-image/group-layers.go diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 1d97ba59b..33500dbb9 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -26,6 +26,8 @@ srcType ? "nixpkgs", srcArgs ? "nixos-19.03", importArgs ? { }, + # Path to load-pkgs.nix + loadPkgs ? ./load-pkgs.nix, # Packages to install by name (which must refer to top-level attributes of # nixpkgs). This is passed in as a JSON-array in string form. packages ? "[]" @@ -43,7 +45,7 @@ let inherit (pkgs) lib runCommand writeText; - pkgs = import ./load-pkgs.nix { inherit srcType srcArgs importArgs; }; + pkgs = import loadPkgs { inherit srcType srcArgs importArgs; }; # deepFetch traverses the top-level Nix package set to retrieve an item via a # path specified in string form. diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix index 3bb5d62fb..a61ac06bd 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/build-image/default.nix @@ -12,84 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This file builds the tool used to calculate layer distribution and -# moves the files needed to call the Nix builds at runtime in the -# correct locations. +# This file builds a wrapper script called by Nixery to ask for the +# content information for a given image. +# +# The purpose of using a wrapper script is to ensure that the paths to +# all required Nix files are set correctly at runtime. -{ pkgs ? null, self ? ./. +{ pkgs ? import {} }: - # Because of the insanity occuring below, this function must mirror - # all arguments of build-image.nix. -, srcType ? "nixpkgs" -, srcArgs ? "nixos-19.03" -, tag ? null, name ? null, packages ? null, maxLayers ? null, popularityUrl ? null -}@args: - -let pkgs = import ./load-pkgs.nix { inherit srcType srcArgs; }; -in with pkgs; rec { - - groupLayers = buildGoPackage { - name = "group-layers"; - goDeps = ./go-deps.nix; - goPackagePath = "github.com/google/nixery/group-layers"; - - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - # WARNING: HERE BE DRAGONS! # - # # - # The hack below is used to break evaluation purity. The issue is # - # that Nixery's build instructions (the default.nix in the folder # - # above this one) must build a program that can invoke Nix at # - # runtime, with a derivation that needs a program tracked in this # - # source tree (`group-layers`). # - # # - # Simply installing that program in the $PATH of Nixery does not # - # work, because the runtime Nix builds use their own isolated # - # environment. # - # # - # I first attempted to naively copy the sources into the Nix # - # store, so that Nixery could build `group-layers` when it starts # - # up - however those sources are not available to a nested Nix # - # build because they're not part of the context of the nested # - # invocation. # - # # - # Nix has several primitives under `builtins.` that can break # - # evaluation purity, these (namely readDir and readFile) are used # - # below to break out of the isolated environment and reconstruct # - # the source tree for `group-layers`. # - # # - # There might be a better way to do this, but I don't know what # - # it is. # - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - src = runCommand "group-layers-srcs" { } '' - mkdir -p $out - ${with builtins; - let - files = - (attrNames (lib.filterAttrs (_: t: t != "symlink") (readDir self))); - commands = - map (f: "cp ${toFile f (readFile "${self}/${f}")} $out/${f}") files; - in lib.concatStringsSep "\n" commands} - ''; - - meta = { - description = - "Tool to group a set of packages into container image layers"; - license = lib.licenses.asl20; - maintainers = [ lib.maintainers.tazjin ]; - }; - }; - - buildImage = import ./build-image.nix - ({ inherit pkgs groupLayers; } // (lib.filterAttrs (_: v: v != null) args)); - - # Wrapper script which is called by the Nixery server to trigger an - # actual image build. This exists to avoid having to specify the - # location of build-image.nix at runtime. - wrapper = writeShellScriptBin "nixery-build-image" '' - exec ${nix}/bin/nix-build \ - --show-trace \ - --no-out-link "$@" \ - --argstr self "${./.}" \ - -A buildImage ${./.} - ''; -} +pkgs.writeShellScriptBin "nixery-build-image" '' + exec ${pkgs.nix}/bin/nix-build \ + --show-trace \ + --no-out-link "$@" \ + --argstr loadPkgs ${./load-pkgs.nix} \ + ${./build-image.nix} +'' diff --git a/tools/nixery/build-image/go-deps.nix b/tools/nixery/build-image/go-deps.nix deleted file mode 100644 index 0f22a7088..000000000 --- a/tools/nixery/build-image/go-deps.nix +++ /dev/null @@ -1,10 +0,0 @@ -# This file was generated by https://github.com/kamilchm/go2nix v1.3.0 -[{ - goPackagePath = "gonum.org/v1/gonum"; - fetch = { - type = "git"; - url = "https://github.com/gonum/gonum"; - rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; - sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; - }; -}] diff --git a/tools/nixery/build-image/group-layers.go b/tools/nixery/build-image/group-layers.go deleted file mode 100644 index 93f2e520a..000000000 --- a/tools/nixery/build-image/group-layers.go +++ /dev/null @@ -1,352 +0,0 @@ -// This program reads an export reference graph (i.e. a graph representing the -// runtime dependencies of a set of derivations) created by Nix and groups them -// in a way that is likely to match the grouping for other derivation sets with -// overlapping dependencies. -// -// This is used to determine which derivations to include in which layers of a -// container image. -// -// # Inputs -// -// * a graph of Nix runtime dependencies, generated via exportReferenceGraph -// * a file containing absolute popularity values of packages in the -// Nix package set (in the form of a direct reference count) -// * a maximum number of layers to allocate for the image (the "layer budget") -// -// # Algorithm -// -// It works by first creating a (directed) dependency tree: -// -// img (root node) -// │ -// ├───> A ─────┐ -// │ v -// ├───> B ───> E -// │ ^ -// ├───> C ─────┘ -// │ │ -// │ v -// └───> D ───> F -// │ -// └────> G -// -// Each node (i.e. package) is then visited to determine how important -// it is to separate this node into its own layer, specifically: -// -// 1. Is the node within a certain threshold percentile of absolute -// popularity within all of nixpkgs? (e.g. `glibc`, `openssl`) -// -// 2. Is the node's runtime closure above a threshold size? (e.g. 100MB) -// -// In either case, a bit is flipped for this node representing each -// condition and an edge to it is inserted directly from the image -// root, if it does not already exist. -// -// For the rest of the example we assume 'G' is above the threshold -// size and 'E' is popular. -// -// This tree is then transformed into a dominator tree: -// -// img -// │ -// ├───> A -// ├───> B -// ├───> C -// ├───> E -// ├───> D ───> F -// └───> G -// -// Specifically this means that the paths to A, B, C, E, G, and D -// always pass through the root (i.e. are dominated by it), whilst F -// is dominated by D (all paths go through it). -// -// The top-level subtrees are considered as the initially selected -// layers. -// -// If the list of layers fits within the layer budget, it is returned. -// -// Otherwise, a merge rating is calculated for each layer. This is the -// product of the layer's total size and its root node's popularity. -// -// Layers are then merged in ascending order of merge ratings until -// they fit into the layer budget. -// -// # Threshold values -// -// Threshold values for the partitioning conditions mentioned above -// have not yet been determined, but we will make a good first guess -// based on gut feeling and proceed to measure their impact on cache -// hits/misses. -// -// # Example -// -// Using the logic described above as well as the example presented in -// the introduction, this program would create the following layer -// groupings (assuming no additional partitioning): -// -// Layer budget: 1 -// Layers: { A, B, C, D, E, F, G } -// -// Layer budget: 2 -// Layers: { G }, { A, B, C, D, E, F } -// -// Layer budget: 3 -// Layers: { G }, { E }, { A, B, C, D, F } -// -// Layer budget: 4 -// Layers: { G }, { E }, { D, F }, { A, B, C } -// -// ... -// -// Layer budget: 10 -// Layers: { E }, { D, F }, { A }, { B }, { C } -package main - -import ( - "encoding/json" - "flag" - "io/ioutil" - "log" - "regexp" - "sort" - - "gonum.org/v1/gonum/graph/flow" - "gonum.org/v1/gonum/graph/simple" -) - -// closureGraph represents the structured attributes Nix outputs when asking it -// for the exportReferencesGraph of a list of derivations. -type exportReferences struct { - References struct { - Graph []string `json:"graph"` - } `json:"exportReferencesGraph"` - - Graph []struct { - Size uint64 `json:"closureSize"` - Path string `json:"path"` - Refs []string `json:"references"` - } `json:"graph"` -} - -// Popularity data for each Nix package that was calculated in advance. -// -// Popularity is a number from 1-100 that represents the -// popularity percentile in which this package resides inside -// of the nixpkgs tree. -type pkgsMetadata = map[string]int - -// layer represents the data returned for each layer that Nix should -// build for the container image. -type layer struct { - Contents []string `json:"contents"` - mergeRating uint64 -} - -func (a layer) merge(b layer) layer { - a.Contents = append(a.Contents, b.Contents...) - a.mergeRating += b.mergeRating - return a -} - -// closure as pointed to by the graph nodes. -type closure struct { - GraphID int64 - Path string - Size uint64 - Refs []string - Popularity int -} - -func (c *closure) ID() int64 { - return c.GraphID -} - -var nixRegexp = regexp.MustCompile(`^/nix/store/[a-z0-9]+-`) - -func (c *closure) DOTID() string { - return nixRegexp.ReplaceAllString(c.Path, "") -} - -// bigOrPopular checks whether this closure should be considered for -// separation into its own layer, even if it would otherwise only -// appear in a subtree of the dominator tree. -func (c *closure) bigOrPopular() bool { - const sizeThreshold = 100 * 1000000 // 100MB - - if c.Size > sizeThreshold { - return true - } - - // The threshold value used here is currently roughly the - // minimum number of references that only 1% of packages in - // the entire package set have. - // - // TODO(tazjin): Do this more elegantly by calculating - // percentiles for each package and using those instead. - if c.Popularity >= 1000 { - return true - } - - return false -} - -func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *closure) { - // Big or popular nodes get a separate edge from the top to - // flag them for their own layer. - if node.bigOrPopular() && !graph.HasEdgeFromTo(0, node.ID()) { - edge := graph.NewEdge(graph.Node(0), node) - graph.SetEdge(edge) - } - - for _, c := range node.Refs { - // Nix adds a self reference to each node, which - // should not be inserted. - if c != node.Path { - edge := graph.NewEdge(node, (*cmap)[c]) - graph.SetEdge(edge) - } - } -} - -// Create a graph structure from the references supplied by Nix. -func buildGraph(refs *exportReferences, pop *pkgsMetadata) *simple.DirectedGraph { - cmap := make(map[string]*closure) - graph := simple.NewDirectedGraph() - - // Insert all closures into the graph, as well as a fake root - // closure which serves as the top of the tree. - // - // A map from store paths to IDs is kept to actually insert - // edges below. - root := &closure{ - GraphID: 0, - Path: "image_root", - } - graph.AddNode(root) - - for idx, c := range refs.Graph { - node := &closure{ - GraphID: int64(idx + 1), // inc because of root node - Path: c.Path, - Size: c.Size, - Refs: c.Refs, - } - - if p, ok := (*pop)[node.DOTID()]; ok { - node.Popularity = p - } else { - node.Popularity = 1 - } - - graph.AddNode(node) - cmap[c.Path] = node - } - - // Insert the top-level closures with edges from the root - // node, then insert all edges for each closure. - for _, p := range refs.References.Graph { - edge := graph.NewEdge(root, cmap[p]) - graph.SetEdge(edge) - } - - for _, c := range cmap { - insertEdges(graph, &cmap, c) - } - - return graph -} - -// Extracts a subgraph starting at the specified root from the -// dominator tree. The subgraph is converted into a flat list of -// layers, each containing the store paths and merge rating. -func groupLayer(dt *flow.DominatorTree, root *closure) layer { - size := root.Size - contents := []string{root.Path} - children := dt.DominatedBy(root.ID()) - - // This iteration does not use 'range' because the list being - // iterated is modified during the iteration (yes, I'm sorry). - for i := 0; i < len(children); i++ { - child := children[i].(*closure) - size += child.Size - contents = append(contents, child.Path) - children = append(children, dt.DominatedBy(child.ID())...) - } - - return layer{ - Contents: contents, - // TODO(tazjin): The point of this is to factor in - // both the size and the popularity when making merge - // decisions, but there might be a smarter way to do - // it than a plain multiplication. - mergeRating: uint64(root.Popularity) * size, - } -} - -// Calculate the dominator tree of the entire package set and group -// each top-level subtree into a layer. -// -// Layers are merged together until they fit into the layer budget, -// based on their merge rating. -func dominate(budget int, graph *simple.DirectedGraph) []layer { - dt := flow.Dominators(graph.Node(0), graph) - - var layers []layer - for _, n := range dt.DominatedBy(dt.Root().ID()) { - layers = append(layers, groupLayer(&dt, n.(*closure))) - } - - sort.Slice(layers, func(i, j int) bool { - return layers[i].mergeRating < layers[j].mergeRating - }) - - if len(layers) > budget { - log.Printf("Ideal image has %v layers, but budget is %v\n", len(layers), budget) - } - - for len(layers) > budget { - merged := layers[0].merge(layers[1]) - layers[1] = merged - layers = layers[1:] - } - - return layers -} - -func main() { - graphFile := flag.String("graph", ".attrs.json", "Input file containing graph") - popFile := flag.String("pop", "popularity.json", "Package popularity data") - outFile := flag.String("out", "layers.json", "File to write layers to") - layerBudget := flag.Int("budget", 94, "Total layer budget available") - flag.Parse() - - // Parse graph data - file, err := ioutil.ReadFile(*graphFile) - if err != nil { - log.Fatalf("Failed to load input: %s\n", err) - } - - var refs exportReferences - err = json.Unmarshal(file, &refs) - if err != nil { - log.Fatalf("Failed to deserialise input: %s\n", err) - } - - // Parse popularity data - popBytes, err := ioutil.ReadFile(*popFile) - if err != nil { - log.Fatalf("Failed to load input: %s\n", err) - } - - var pop pkgsMetadata - err = json.Unmarshal(popBytes, &pop) - if err != nil { - log.Fatalf("Failed to deserialise input: %s\n", err) - } - - graph := buildGraph(&refs, &pop) - layers := dominate(*layerBudget, graph) - - j, _ := json.Marshal(layers) - ioutil.WriteFile(*outFile, j, 0644) -} diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 95540e11d..f321b07a9 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -17,11 +17,7 @@ with pkgs; -let buildImage = import ./build-image { - srcType = "path"; - srcArgs = ; -}; -in rec { +rec { # Go implementation of the Nixery server which implements the # container registry interface. # @@ -30,9 +26,8 @@ in rec { # data dependencies. nixery-server = callPackage ./server { }; - # Implementation of the image building & layering logic - nixery-build-image = buildImage.wrapper; - nixery-group-layers = buildImage.groupLayers; + # Implementation of the Nix image building logic + nixery-build-image = import ./build-image { inherit pkgs; }; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at From 8c79d085ae7596b81f3e19e7a49443075ebccbb6 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 29 Sep 2019 23:00:20 +0100 Subject: [PATCH 106/223] chore(server): Import layer grouping logic into server component --- tools/nixery/server/go-deps.nix | 9 + tools/nixery/server/layers/grouping.go | 352 +++++++++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 tools/nixery/server/layers/grouping.go diff --git a/tools/nixery/server/go-deps.nix b/tools/nixery/server/go-deps.nix index a223ef0a7..7c40c6dde 100644 --- a/tools/nixery/server/go-deps.nix +++ b/tools/nixery/server/go-deps.nix @@ -108,4 +108,13 @@ sha256 = "05wig23l2sil3bfdv19gq62sya7hsabqj9l8pzr1sm57qsvj218d"; }; } + { + goPackagePath = "gonum.org/v1/gonum"; + fetch = { + type = "git"; + url = "https://github.com/gonum/gonum"; + rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; + sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; + }; + } ] diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go new file mode 100644 index 000000000..a75046e0f --- /dev/null +++ b/tools/nixery/server/layers/grouping.go @@ -0,0 +1,352 @@ +// This program reads an export reference graph (i.e. a graph representing the +// runtime dependencies of a set of derivations) created by Nix and groups them +// in a way that is likely to match the grouping for other derivation sets with +// overlapping dependencies. +// +// This is used to determine which derivations to include in which layers of a +// container image. +// +// # Inputs +// +// * a graph of Nix runtime dependencies, generated via exportReferenceGraph +// * a file containing absolute popularity values of packages in the +// Nix package set (in the form of a direct reference count) +// * a maximum number of layers to allocate for the image (the "layer budget") +// +// # Algorithm +// +// It works by first creating a (directed) dependency tree: +// +// img (root node) +// │ +// ├───> A ─────┐ +// │ v +// ├───> B ───> E +// │ ^ +// ├───> C ─────┘ +// │ │ +// │ v +// └───> D ───> F +// │ +// └────> G +// +// Each node (i.e. package) is then visited to determine how important +// it is to separate this node into its own layer, specifically: +// +// 1. Is the node within a certain threshold percentile of absolute +// popularity within all of nixpkgs? (e.g. `glibc`, `openssl`) +// +// 2. Is the node's runtime closure above a threshold size? (e.g. 100MB) +// +// In either case, a bit is flipped for this node representing each +// condition and an edge to it is inserted directly from the image +// root, if it does not already exist. +// +// For the rest of the example we assume 'G' is above the threshold +// size and 'E' is popular. +// +// This tree is then transformed into a dominator tree: +// +// img +// │ +// ├───> A +// ├───> B +// ├───> C +// ├───> E +// ├───> D ───> F +// └───> G +// +// Specifically this means that the paths to A, B, C, E, G, and D +// always pass through the root (i.e. are dominated by it), whilst F +// is dominated by D (all paths go through it). +// +// The top-level subtrees are considered as the initially selected +// layers. +// +// If the list of layers fits within the layer budget, it is returned. +// +// Otherwise, a merge rating is calculated for each layer. This is the +// product of the layer's total size and its root node's popularity. +// +// Layers are then merged in ascending order of merge ratings until +// they fit into the layer budget. +// +// # Threshold values +// +// Threshold values for the partitioning conditions mentioned above +// have not yet been determined, but we will make a good first guess +// based on gut feeling and proceed to measure their impact on cache +// hits/misses. +// +// # Example +// +// Using the logic described above as well as the example presented in +// the introduction, this program would create the following layer +// groupings (assuming no additional partitioning): +// +// Layer budget: 1 +// Layers: { A, B, C, D, E, F, G } +// +// Layer budget: 2 +// Layers: { G }, { A, B, C, D, E, F } +// +// Layer budget: 3 +// Layers: { G }, { E }, { A, B, C, D, F } +// +// Layer budget: 4 +// Layers: { G }, { E }, { D, F }, { A, B, C } +// +// ... +// +// Layer budget: 10 +// Layers: { E }, { D, F }, { A }, { B }, { C } +package layers + +import ( + "encoding/json" + "flag" + "io/ioutil" + "log" + "regexp" + "sort" + + "gonum.org/v1/gonum/graph/flow" + "gonum.org/v1/gonum/graph/simple" +) + +// closureGraph represents the structured attributes Nix outputs when asking it +// for the exportReferencesGraph of a list of derivations. +type exportReferences struct { + References struct { + Graph []string `json:"graph"` + } `json:"exportReferencesGraph"` + + Graph []struct { + Size uint64 `json:"closureSize"` + Path string `json:"path"` + Refs []string `json:"references"` + } `json:"graph"` +} + +// Popularity data for each Nix package that was calculated in advance. +// +// Popularity is a number from 1-100 that represents the +// popularity percentile in which this package resides inside +// of the nixpkgs tree. +type Popularity = map[string]int + +// layer represents the data returned for each layer that Nix should +// build for the container image. +type layer struct { + Contents []string `json:"contents"` + mergeRating uint64 +} + +func (a layer) merge(b layer) layer { + a.Contents = append(a.Contents, b.Contents...) + a.mergeRating += b.mergeRating + return a +} + +// closure as pointed to by the graph nodes. +type closure struct { + GraphID int64 + Path string + Size uint64 + Refs []string + Popularity int +} + +func (c *closure) ID() int64 { + return c.GraphID +} + +var nixRegexp = regexp.MustCompile(`^/nix/store/[a-z0-9]+-`) + +func (c *closure) DOTID() string { + return nixRegexp.ReplaceAllString(c.Path, "") +} + +// bigOrPopular checks whether this closure should be considered for +// separation into its own layer, even if it would otherwise only +// appear in a subtree of the dominator tree. +func (c *closure) bigOrPopular() bool { + const sizeThreshold = 100 * 1000000 // 100MB + + if c.Size > sizeThreshold { + return true + } + + // The threshold value used here is currently roughly the + // minimum number of references that only 1% of packages in + // the entire package set have. + // + // TODO(tazjin): Do this more elegantly by calculating + // percentiles for each package and using those instead. + if c.Popularity >= 1000 { + return true + } + + return false +} + +func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *closure) { + // Big or popular nodes get a separate edge from the top to + // flag them for their own layer. + if node.bigOrPopular() && !graph.HasEdgeFromTo(0, node.ID()) { + edge := graph.NewEdge(graph.Node(0), node) + graph.SetEdge(edge) + } + + for _, c := range node.Refs { + // Nix adds a self reference to each node, which + // should not be inserted. + if c != node.Path { + edge := graph.NewEdge(node, (*cmap)[c]) + graph.SetEdge(edge) + } + } +} + +// Create a graph structure from the references supplied by Nix. +func buildGraph(refs *exportReferences, pop *Popularity) *simple.DirectedGraph { + cmap := make(map[string]*closure) + graph := simple.NewDirectedGraph() + + // Insert all closures into the graph, as well as a fake root + // closure which serves as the top of the tree. + // + // A map from store paths to IDs is kept to actually insert + // edges below. + root := &closure{ + GraphID: 0, + Path: "image_root", + } + graph.AddNode(root) + + for idx, c := range refs.Graph { + node := &closure{ + GraphID: int64(idx + 1), // inc because of root node + Path: c.Path, + Size: c.Size, + Refs: c.Refs, + } + + if p, ok := (*pop)[node.DOTID()]; ok { + node.Popularity = p + } else { + node.Popularity = 1 + } + + graph.AddNode(node) + cmap[c.Path] = node + } + + // Insert the top-level closures with edges from the root + // node, then insert all edges for each closure. + for _, p := range refs.References.Graph { + edge := graph.NewEdge(root, cmap[p]) + graph.SetEdge(edge) + } + + for _, c := range cmap { + insertEdges(graph, &cmap, c) + } + + return graph +} + +// Extracts a subgraph starting at the specified root from the +// dominator tree. The subgraph is converted into a flat list of +// layers, each containing the store paths and merge rating. +func groupLayer(dt *flow.DominatorTree, root *closure) layer { + size := root.Size + contents := []string{root.Path} + children := dt.DominatedBy(root.ID()) + + // This iteration does not use 'range' because the list being + // iterated is modified during the iteration (yes, I'm sorry). + for i := 0; i < len(children); i++ { + child := children[i].(*closure) + size += child.Size + contents = append(contents, child.Path) + children = append(children, dt.DominatedBy(child.ID())...) + } + + return layer{ + Contents: contents, + // TODO(tazjin): The point of this is to factor in + // both the size and the popularity when making merge + // decisions, but there might be a smarter way to do + // it than a plain multiplication. + mergeRating: uint64(root.Popularity) * size, + } +} + +// Calculate the dominator tree of the entire package set and group +// each top-level subtree into a layer. +// +// Layers are merged together until they fit into the layer budget, +// based on their merge rating. +func dominate(budget int, graph *simple.DirectedGraph) []layer { + dt := flow.Dominators(graph.Node(0), graph) + + var layers []layer + for _, n := range dt.DominatedBy(dt.Root().ID()) { + layers = append(layers, groupLayer(&dt, n.(*closure))) + } + + sort.Slice(layers, func(i, j int) bool { + return layers[i].mergeRating < layers[j].mergeRating + }) + + if len(layers) > budget { + log.Printf("Ideal image has %v layers, but budget is %v\n", len(layers), budget) + } + + for len(layers) > budget { + merged := layers[0].merge(layers[1]) + layers[1] = merged + layers = layers[1:] + } + + return layers +} + +func main() { + graphFile := flag.String("graph", ".attrs.json", "Input file containing graph") + popFile := flag.String("pop", "popularity.json", "Package popularity data") + outFile := flag.String("out", "layers.json", "File to write layers to") + layerBudget := flag.Int("budget", 94, "Total layer budget available") + flag.Parse() + + // Parse graph data + file, err := ioutil.ReadFile(*graphFile) + if err != nil { + log.Fatalf("Failed to load input: %s\n", err) + } + + var refs exportReferences + err = json.Unmarshal(file, &refs) + if err != nil { + log.Fatalf("Failed to deserialise input: %s\n", err) + } + + // Parse popularity data + popBytes, err := ioutil.ReadFile(*popFile) + if err != nil { + log.Fatalf("Failed to load input: %s\n", err) + } + + var pop Popularity + err = json.Unmarshal(popBytes, &pop) + if err != nil { + log.Fatalf("Failed to deserialise input: %s\n", err) + } + + graph := buildGraph(&refs, &pop) + layers := dominate(*layerBudget, graph) + + j, _ := json.Marshal(layers) + ioutil.WriteFile(*outFile, j, 0644) +} From 9c3c622403cec6e1bf7e4b9aecfbc8bea1e1f6fd Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 29 Sep 2019 23:57:56 +0100 Subject: [PATCH 107/223] refactor(server): Expose layer grouping logic via a function Refactors the layer grouping package (which previously compiled to a separate binary) to expose the layer grouping logic via a function instead. This is the next step towards creating layers inside of the server component instead of in Nix. Relates to #50. --- tools/nixery/server/layers/grouping.go | 79 ++++++++------------------ 1 file changed, 24 insertions(+), 55 deletions(-) diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index a75046e0f..e8666bf4d 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -1,6 +1,6 @@ -// This program reads an export reference graph (i.e. a graph representing the -// runtime dependencies of a set of derivations) created by Nix and groups them -// in a way that is likely to match the grouping for other derivation sets with +// This package reads an export reference graph (i.e. a graph representing the +// runtime dependencies of a set of derivations) created by Nix and groups it in +// a way that is likely to match the grouping for other derivation sets with // overlapping dependencies. // // This is used to determine which derivations to include in which layers of a @@ -9,8 +9,8 @@ // # Inputs // // * a graph of Nix runtime dependencies, generated via exportReferenceGraph -// * a file containing absolute popularity values of packages in the -// Nix package set (in the form of a direct reference count) +// * popularity values of each package in the Nix package set (in the form of a +// direct reference count) // * a maximum number of layers to allocate for the image (the "layer budget") // // # Algorithm @@ -103,9 +103,6 @@ package layers import ( - "encoding/json" - "flag" - "io/ioutil" "log" "regexp" "sort" @@ -114,9 +111,11 @@ import ( "gonum.org/v1/gonum/graph/simple" ) -// closureGraph represents the structured attributes Nix outputs when asking it -// for the exportReferencesGraph of a list of derivations. -type exportReferences struct { +// RuntimeGraph represents structured information from Nix about the runtime +// dependencies of a derivation. +// +// This is generated in Nix by using the exportReferencesGraph feature. +type RuntimeGraph struct { References struct { Graph []string `json:"graph"` } `json:"exportReferencesGraph"` @@ -135,14 +134,14 @@ type exportReferences struct { // of the nixpkgs tree. type Popularity = map[string]int -// layer represents the data returned for each layer that Nix should +// Layer represents the data returned for each layer that Nix should // build for the container image. -type layer struct { +type Layer struct { Contents []string `json:"contents"` mergeRating uint64 } -func (a layer) merge(b layer) layer { +func (a Layer) merge(b Layer) Layer { a.Contents = append(a.Contents, b.Contents...) a.mergeRating += b.mergeRating return a @@ -209,7 +208,7 @@ func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *c } // Create a graph structure from the references supplied by Nix. -func buildGraph(refs *exportReferences, pop *Popularity) *simple.DirectedGraph { +func buildGraph(refs *RuntimeGraph, pop *Popularity) *simple.DirectedGraph { cmap := make(map[string]*closure) graph := simple.NewDirectedGraph() @@ -259,7 +258,7 @@ func buildGraph(refs *exportReferences, pop *Popularity) *simple.DirectedGraph { // Extracts a subgraph starting at the specified root from the // dominator tree. The subgraph is converted into a flat list of // layers, each containing the store paths and merge rating. -func groupLayer(dt *flow.DominatorTree, root *closure) layer { +func groupLayer(dt *flow.DominatorTree, root *closure) Layer { size := root.Size contents := []string{root.Path} children := dt.DominatedBy(root.ID()) @@ -273,7 +272,7 @@ func groupLayer(dt *flow.DominatorTree, root *closure) layer { children = append(children, dt.DominatedBy(child.ID())...) } - return layer{ + return Layer{ Contents: contents, // TODO(tazjin): The point of this is to factor in // both the size and the popularity when making merge @@ -288,10 +287,10 @@ func groupLayer(dt *flow.DominatorTree, root *closure) layer { // // Layers are merged together until they fit into the layer budget, // based on their merge rating. -func dominate(budget int, graph *simple.DirectedGraph) []layer { +func dominate(budget int, graph *simple.DirectedGraph) []Layer { dt := flow.Dominators(graph.Node(0), graph) - var layers []layer + var layers []Layer for _, n := range dt.DominatedBy(dt.Root().ID()) { layers = append(layers, groupLayer(&dt, n.(*closure))) } @@ -313,40 +312,10 @@ func dominate(budget int, graph *simple.DirectedGraph) []layer { return layers } -func main() { - graphFile := flag.String("graph", ".attrs.json", "Input file containing graph") - popFile := flag.String("pop", "popularity.json", "Package popularity data") - outFile := flag.String("out", "layers.json", "File to write layers to") - layerBudget := flag.Int("budget", 94, "Total layer budget available") - flag.Parse() - - // Parse graph data - file, err := ioutil.ReadFile(*graphFile) - if err != nil { - log.Fatalf("Failed to load input: %s\n", err) - } - - var refs exportReferences - err = json.Unmarshal(file, &refs) - if err != nil { - log.Fatalf("Failed to deserialise input: %s\n", err) - } - - // Parse popularity data - popBytes, err := ioutil.ReadFile(*popFile) - if err != nil { - log.Fatalf("Failed to load input: %s\n", err) - } - - var pop Popularity - err = json.Unmarshal(popBytes, &pop) - if err != nil { - log.Fatalf("Failed to deserialise input: %s\n", err) - } - - graph := buildGraph(&refs, &pop) - layers := dominate(*layerBudget, graph) - - j, _ := json.Marshal(layers) - ioutil.WriteFile(*outFile, j, 0644) +// GroupLayers applies the algorithm described above the its input and returns a +// list of layers, each consisting of a list of Nix store paths that it should +// contain. +func GroupLayers(refs *RuntimeGraph, pop *Popularity, budget int) []Layer { + graph := buildGraph(refs, pop) + return dominate(budget, graph) } From e60805c9b213a4054afe84d29c16058277014d0b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 30 Sep 2019 11:34:17 +0100 Subject: [PATCH 108/223] feat(server): Introduce function to hash contents of a layer This creates a cache key which can be used to check if a layer has already been built. --- tools/nixery/server/layers/grouping.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index e8666bf4d..f8259ab98 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -103,9 +103,12 @@ package layers import ( + "crypto/sha1" + "fmt" "log" "regexp" "sort" + "strings" "gonum.org/v1/gonum/graph/flow" "gonum.org/v1/gonum/graph/simple" @@ -141,6 +144,13 @@ type Layer struct { mergeRating uint64 } +// Hash the contents of a layer to create a deterministic identifier that can be +// used for caching. +func (l *Layer) Hash() string { + sum := sha1.Sum([]byte(strings.Join(l.Contents, ":"))) + return fmt.Sprintf("%x", sum) +} + func (a Layer) merge(b Layer) Layer { a.Contents = append(a.Contents, b.Contents...) a.mergeRating += b.mergeRating @@ -272,6 +282,9 @@ func groupLayer(dt *flow.DominatorTree, root *closure) Layer { children = append(children, dt.DominatedBy(child.ID())...) } + // Contents are sorted to ensure that hashing is consistent + sort.Strings(contents) + return Layer{ Contents: contents, // TODO(tazjin): The point of this is to factor in From 2c8ef634f67f91c8efc1cf6a58a271f4c44544dd Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 30 Sep 2019 11:51:36 +0100 Subject: [PATCH 109/223] docs(caching): Add information about Nixery's caching strategies --- tools/nixery/docs/src/SUMMARY.md | 1 + tools/nixery/docs/src/caching.md | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 tools/nixery/docs/src/caching.md diff --git a/tools/nixery/docs/src/SUMMARY.md b/tools/nixery/docs/src/SUMMARY.md index 677f32897..f1d68a3ac 100644 --- a/tools/nixery/docs/src/SUMMARY.md +++ b/tools/nixery/docs/src/SUMMARY.md @@ -2,6 +2,7 @@ - [Nixery](./nixery.md) - [Under the hood](./under-the-hood.md) + - [Caching](./caching.md) - [Run your own Nixery](./run-your-own.md) - [Nix](./nix.md) - [Nix, the language](./nix-1p.md) diff --git a/tools/nixery/docs/src/caching.md b/tools/nixery/docs/src/caching.md new file mode 100644 index 000000000..175fe04d7 --- /dev/null +++ b/tools/nixery/docs/src/caching.md @@ -0,0 +1,70 @@ +# Caching in Nixery + +This page gives a quick overview over the caching done by Nixery. All cache data +is written to Nixery's storage bucket and is based on deterministic identifiers +or content-addressing, meaning that cache entries under the same key *never +change*. + +## Manifests + +Manifests of builds are cached at `$BUCKET/manifests/$KEY`. The effect of this +cache is that multiple instances of Nixery do not need to rebuild the same +manifest from scratch. + +Since the manifest cache is populated only *after* layers are uploaded, Nixery +can immediately return the manifest to its clients without needing to check +whether layers have been uploaded already. + +`$KEY` is generated by creating a SHA1 hash of the requested content of a +manifest plus the package source specification. + +Manifests are *only* cached if the package source specification is *not* a +moving target. + +Manifest caching *only* applies in the following cases: + +* package source specification is a specific git commit +* package source specification is a specific NixOS/nixpkgs commit + +Manifest caching *never* applies in the following cases: + +* package source specification is a local file path (i.e. `NIXERY_PKGS_PATH`) +* package source specification is a NixOS channel (e.g. `NIXERY_CHANNEL=nixos-19.03`) +* package source specification is a git branch or tag (e.g. `staging`, `master` or `latest`) + +It is thus always preferable to request images from a fully-pinned package +source. + +Manifests can be removed from the manifest cache without negative consequences. + +## Layer tarballs + +Layer tarballs are the files that Nixery clients retrieve from the storage +bucket to download an image. + +They are stored content-addressably at `$BUCKET/layers/$SHA256HASH` and layer +requests sent to Nixery will redirect directly to this storage location. + +The effect of this cache is that Nixery does not need to upload identical layers +repeatedly. When Nixery notices that a layer already exists in GCS, it will use +the object metadata to compare its MD5-hash with the locally computed one and +skip uploading. + +Removing layers from the cache is *potentially problematic* if there are cached +manifests or layer builds referencing those layers. + +To clean up layers, a user must ensure that no other cached resources still +reference these layers. + +## Layer builds + +Layer builds are cached at `$BUCKET/builds/$HASH`, where `$HASH` is a SHA1 of +the Nix store paths included in the layer. + +The content of the cached entries is a JSON-object that contains the MD5 and +SHA256 hashes of the built layer. + +The effect of this cache is that different instances of Nixery will not build, +hash and upload layers that have identical contents across different instances. + +Layer builds can be removed from the cache without negative consequences. From 6262dec8aacf25ae6004de739353089cd635cea5 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 30 Sep 2019 14:19:11 +0100 Subject: [PATCH 110/223] feat(nix): Add derivation to create layer tars from a store path set This introduces a new Nix derivation that, given an attribute set of layer hashes mapped to store paths, will create a layer tarball for each of the store paths. This is going to be used by the builder to create layers that are not present in the cache. Relates to #50. --- tools/nixery/build-image/build-layers.nix | 47 +++++++++++++++++++++++ tools/nixery/build-image/default.nix | 24 ++++++++---- tools/nixery/default.nix | 7 +++- 3 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 tools/nixery/build-image/build-layers.nix diff --git a/tools/nixery/build-image/build-layers.nix b/tools/nixery/build-image/build-layers.nix new file mode 100644 index 000000000..8a8bfbe9e --- /dev/null +++ b/tools/nixery/build-image/build-layers.nix @@ -0,0 +1,47 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ + # Description of the package set to be used (will be loaded by load-pkgs.nix) + srcType ? "nixpkgs", + srcArgs ? "nixos-19.03", + importArgs ? { }, + # Path to load-pkgs.nix + loadPkgs ? ./load-pkgs.nix, + # Layers to assemble into tarballs + layers ? "{}" +}: + +let + inherit (builtins) fromJSON mapAttrs toJSON; + inherit (pkgs) lib runCommand; + + pkgs = import loadPkgs { inherit srcType srcArgs importArgs; }; + + # Given a list of store paths, create an image layer tarball with + # their contents. + pathsToLayer = paths: runCommand "layer.tar" { + } '' + tar --no-recursion -Prf "$out" \ + --mtime="@$SOURCE_DATE_EPOCH" \ + --owner=0 --group=0 /nix /nix/store + + tar -Prpf "$out" --hard-dereference --sort=name \ + --mtime="@$SOURCE_DATE_EPOCH" \ + --owner=0 --group=0 ${lib.concatStringsSep " " paths} + ''; + + + layerTarballs = mapAttrs (_: pathsToLayer ) (fromJSON layers); +in writeText "layer-tarballs.json" (toJSON layerTarballs) diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix index a61ac06bd..0800eb959 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/build-image/default.nix @@ -20,10 +20,20 @@ { pkgs ? import {} }: -pkgs.writeShellScriptBin "nixery-build-image" '' - exec ${pkgs.nix}/bin/nix-build \ - --show-trace \ - --no-out-link "$@" \ - --argstr loadPkgs ${./load-pkgs.nix} \ - ${./build-image.nix} -'' +{ + build-image = pkgs.writeShellScriptBin "nixery-build-image" '' + exec ${pkgs.nix}/bin/nix-build \ + --show-trace \ + --no-out-link "$@" \ + --argstr loadPkgs ${./load-pkgs.nix} \ + ${./build-image.nix} + ''; + + build-layers = pkgs.writeShellScriptBin "nixery-build-layers" '' + exec ${pkgs.nix}/bin/nix-build \ + --show-trace \ + --no-out-link "$@" \ + --argstr loadPkgs ${./load-pkgs.nix} \ + ${./build-layers.nix} + ''; +} diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index f321b07a9..925edbf6d 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -11,13 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + { pkgs ? import { } , preLaunch ? "" , extraPackages ? [] }: with pkgs; -rec { +let builders = import ./build-image { inherit pkgs; }; +in rec { # Go implementation of the Nixery server which implements the # container registry interface. # @@ -27,7 +29,8 @@ rec { nixery-server = callPackage ./server { }; # Implementation of the Nix image building logic - nixery-build-image = import ./build-image { inherit pkgs; }; + nixery-build-image = builders.build-image; + nixery-build-layers = builders.build-layers; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at From 6e2b84f475fb302bf6d8a43c2f7497040ad82cac Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 30 Sep 2019 14:25:55 +0100 Subject: [PATCH 111/223] feat(server): Add cache for layer builds in GCS & local cache This cache is going to be used for looking up whether a layer build has taken place already (based on a hash of the layer contents). See the caching section in the updated documentation for details. Relates to #50. --- tools/nixery/server/builder/cache.go | 86 ++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 52765293f..edb1b711d 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -14,7 +14,9 @@ package builder import ( + "bytes" "context" + "encoding/json" "io" "log" "os" @@ -25,14 +27,25 @@ import ( type void struct{} +type Build struct { + SHA256 string `json:"sha256"` + MD5 string `json:"md5"` +} + // LocalCache implements the structure used for local caching of // manifests and layer uploads. type LocalCache struct { + // Manifest cache mmtx sync.RWMutex mcache map[string]string + // Layer (tarball) cache lmtx sync.RWMutex lcache map[string]void + + // Layer (build) cache + bmtx sync.RWMutex + bcache map[string]Build } func NewCache() LocalCache { @@ -80,6 +93,22 @@ func (c *LocalCache) localCacheManifest(key, path string) { c.mmtx.Unlock() } +// Retrieve a cached build from the local cache. +func (c *LocalCache) buildFromLocalCache(key string) (*Build, bool) { + c.bmtx.RLock() + b, ok := c.bcache[key] + c.bmtx.RUnlock() + + return &b, ok +} + +// Add a build result to the local cache. +func (c *LocalCache) localCacheBuild(key string, b Build) { + c.bmtx.Lock() + c.bcache[key] = b + c.bmtx.Unlock() +} + // Retrieve a manifest from the cache(s). First the local cache is // checked, then the GCS-bucket cache. func manifestFromCache(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string) (string, bool) { @@ -118,6 +147,7 @@ func manifestFromCache(ctx *context.Context, cache *LocalCache, bucket *storage. return path, true } +// Add a manifest to the bucket & local caches func cacheManifest(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key, path string) { cache.localCacheManifest(key, path) @@ -144,3 +174,59 @@ func cacheManifest(ctx *context.Context, cache *LocalCache, bucket *storage.Buck log.Printf("Cached manifest sha1:%s (%v bytes written)\n", key, size) } + +// Retrieve a build from the cache, first checking the local cache +// followed by the bucket cache. +func buildFromCache(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string) (*Build, bool) { + build, cached := cache.buildFromLocalCache(key) + if cached { + return build, true + } + + obj := bucket.Object("builds/" + key) + _, err := obj.Attrs(*ctx) + if err != nil { + return nil, false + } + + r, err := obj.NewReader(*ctx) + if err != nil { + log.Printf("Failed to retrieve build '%s' from cache: %s\n", key, err) + return nil, false + } + defer r.Close() + + jb := bytes.NewBuffer([]byte{}) + _, err = io.Copy(jb, r) + if err != nil { + log.Printf("Failed to read build '%s' from cache: %s\n", key, err) + return nil, false + } + + var b Build + err = json.Unmarshal(jb.Bytes(), &build) + if err != nil { + log.Printf("Failed to unmarshal build '%s' from cache: %s\n", key, err) + return nil, false + } + + return &b, true +} + +func cacheBuild(ctx context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string, build Build) { + cache.localCacheBuild(key, build) + + obj := bucket.Object("builds/" + key) + + j, _ := json.Marshal(&build) + + w := obj.NewWriter(ctx) + + size, err := io.Copy(w, bytes.NewReader(j)) + if err != nil { + log.Printf("failed to cache build '%s': %s\n", key, err) + return + } + + log.Printf("cached build '%s' (%v bytes written)\n", key, size) +} From 61269175c046681711cf88370d220eb97cd621cf Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 30 Sep 2019 17:38:41 +0100 Subject: [PATCH 112/223] refactor(server): Introduce a state type to carry runtime state The state type contains things such as the bucket handle and Nixery's configuration which need to be passed around in the builder. This is only added for convenience. --- tools/nixery/server/builder/state.go | 24 ++++++++++++++++++++++++ tools/nixery/server/main.go | 18 +++++++----------- 2 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 tools/nixery/server/builder/state.go diff --git a/tools/nixery/server/builder/state.go b/tools/nixery/server/builder/state.go new file mode 100644 index 000000000..1c7f58821 --- /dev/null +++ b/tools/nixery/server/builder/state.go @@ -0,0 +1,24 @@ +package builder + +import ( + "cloud.google.com/go/storage" + "github.com/google/nixery/config" + "github.com/google/nixery/layers" +) + +// State holds the runtime state that is carried around in Nixery and +// passed to builder functions. +type State struct { + Bucket *storage.BucketHandle + Cache LocalCache + Cfg config.Config + Pop layers.Popularity +} + +func NewState(bucket *storage.BucketHandle, cfg config.Config) State { + return State{ + Bucket: bucket, + Cfg: cfg, + Cache: NewCache(), + } +} diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 9242a3731..ae8dd3ab2 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -122,10 +122,8 @@ func writeError(w http.ResponseWriter, status int, code, message string) { } type registryHandler struct { - cfg *config.Config - ctx *context.Context - bucket *storage.BucketHandle - cache *builder.LocalCache + ctx *context.Context + state *builder.State } func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -141,7 +139,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { imageTag := manifestMatches[2] log.Printf("Requesting manifest for image %q at tag %q", imageName, imageTag) image := builder.ImageFromName(imageName, imageTag) - buildResult, err := builder.BuildImage(h.ctx, h.cfg, h.cache, &image, h.bucket) + buildResult, err := builder.BuildImage(h.ctx, h.state, &image) if err != nil { writeError(w, 500, "UNKNOWN", "image build failure") @@ -172,7 +170,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) if len(layerMatches) == 3 { digest := layerMatches[2] - url, err := constructLayerUrl(h.cfg, digest) + url, err := constructLayerUrl(&h.state.Cfg, digest) if err != nil { log.Printf("Failed to sign GCS URL: %s\n", err) @@ -197,16 +195,14 @@ func main() { ctx := context.Background() bucket := prepareBucket(&ctx, cfg) - cache := builder.NewCache() + state := builder.NewState(bucket, *cfg) log.Printf("Starting Nixery on port %s\n", cfg.Port) // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ - cfg: cfg, - ctx: &ctx, - bucket: bucket, - cache: &cache, + ctx: &ctx, + state: &state, }) // All other roots are served by the static file server. From 87e196757b4e0cb22ec9c9f5ee8475573597382a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 30 Sep 2019 17:40:01 +0100 Subject: [PATCH 113/223] feat(server): Reimplement creation & uploading of layers The new build process can now call out to Nix to create layers and upload them to the bucket if necessary. The layer cache is populated, but not yet used. --- tools/nixery/server/builder/builder.go | 349 ++++++++++++++++++------- tools/nixery/server/config/config.go | 2 +- tools/nixery/server/layers/grouping.go | 2 +- 3 files changed, 258 insertions(+), 95 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index a5744a853..303d796df 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -21,20 +21,33 @@ import ( "bufio" "bytes" "context" + "crypto/md5" + "crypto/sha256" "encoding/json" "fmt" "io" "io/ioutil" "log" + "net/http" + "net/url" "os" "os/exec" "sort" "strings" "cloud.google.com/go/storage" - "github.com/google/nixery/config" + "github.com/google/nixery/layers" + "golang.org/x/oauth2/google" ) +// The maximum number of layers in an image is 125. To allow for +// extensibility, the actual number of layers Nixery is "allowed" to +// use up is set at a lower point. +const LayerBudget int = 94 + +// HTTP client to use for direct calls to APIs that are not part of the SDK +var client = &http.Client{} + // Image represents the information necessary for building a container image. // This can be either a list of package names (corresponding to keys in the // nixpkgs set) or a Nix expression that results in a *list* of derivations. @@ -47,6 +60,14 @@ type Image struct { Packages []string } +// TODO(tazjin): docstring +type BuildResult struct { + Error string + Pkgs []string + + Manifest struct{} // TODO(tazjin): OCIv1 manifest +} + // ImageFromName parses an image name into the corresponding structure which can // be used to invoke Nix. // @@ -70,24 +91,21 @@ func ImageFromName(name string, tag string) Image { } } -// BuildResult represents the output of calling the Nix derivation responsible -// for building registry images. -// -// The `layerLocations` field contains the local filesystem paths to each -// individual image layer that will need to be served, while the `manifest` -// field contains the JSON-representation of the manifest that needs to be -// served to the client. -// -// The later field is simply treated as opaque JSON and passed through. -type BuildResult struct { - Error string `json:"error"` - Pkgs []string `json:"pkgs"` - Manifest json.RawMessage `json:"manifest"` +// ImageResult represents the output of calling the Nix derivation +// responsible for preparing an image. +type ImageResult struct { + // These fields are populated in case of an error + Error string `json:"error"` + Pkgs []string `json:"pkgs"` - LayerLocations map[string]struct { - Path string `json:"path"` - Md5 []byte `json:"md5"` - } `json:"layerLocations"` + // These fields are populated in case of success + Graph layers.RuntimeGraph `json:"runtimeGraph"` + SymlinkLayer struct { + Size int `json:"size"` + SHA256 string `json:"sha256"` + MD5 string `json:"md5"` + Path string `json:"path"` + } `json:"symlinkLayer"` } // convenienceNames expands convenience package names defined by Nixery which @@ -117,107 +135,252 @@ func logNix(name string, r io.ReadCloser) { } } -// Call out to Nix and request that an image be built. Nix will, upon success, -// return a manifest for the container image. -func BuildImage(ctx *context.Context, cfg *config.Config, cache *LocalCache, image *Image, bucket *storage.BucketHandle) (*BuildResult, error) { - var resultFile string - cached := false +func callNix(program string, name string, args []string) ([]byte, error) { + cmd := exec.Command(program, args...) - key := cfg.Pkgs.CacheKey(image.Packages, image.Tag) - if key != "" { - resultFile, cached = manifestFromCache(ctx, cache, bucket, key) + outpipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err } - if !cached { - packages, err := json.Marshal(image.Packages) - if err != nil { - return nil, err - } + errpipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + go logNix(name, errpipe) - srcType, srcArgs := cfg.Pkgs.Render(image.Tag) + stdout, _ := ioutil.ReadAll(outpipe) - args := []string{ - "--timeout", cfg.Timeout, - "--argstr", "name", image.Name, - "--argstr", "packages", string(packages), - "--argstr", "srcType", srcType, - "--argstr", "srcArgs", srcArgs, - } - - if cfg.PopUrl != "" { - args = append(args, "--argstr", "popularityUrl", cfg.PopUrl) - } - - cmd := exec.Command("nixery-build-image", args...) - - outpipe, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - errpipe, err := cmd.StderrPipe() - if err != nil { - return nil, err - } - go logNix(image.Name, errpipe) - - if err = cmd.Start(); err != nil { - log.Println("Error starting nix-build:", err) - return nil, err - } - log.Printf("Started Nix image build for '%s'", image.Name) - - stdout, _ := ioutil.ReadAll(outpipe) - - if err = cmd.Wait(); err != nil { - log.Printf("nix-build execution error: %s\nstdout: %s\n", err, stdout) - return nil, err - } - - log.Println("Finished Nix image build") - - resultFile = strings.TrimSpace(string(stdout)) - - if key != "" { - cacheManifest(ctx, cache, bucket, key, resultFile) - } + if err = cmd.Wait(); err != nil { + log.Printf("%s execution error: %s\nstdout: %s\n", program, err, stdout) + return nil, err } + resultFile := strings.TrimSpace(string(stdout)) buildOutput, err := ioutil.ReadFile(resultFile) if err != nil { return nil, err } - // The build output returned by Nix is deserialised to add all - // contained layers to the bucket. Only the manifest itself is - // re-serialised to JSON and returned. - var result BuildResult + return buildOutput, nil +} - err = json.Unmarshal(buildOutput, &result) +// Call out to Nix and request metadata for the image to be built. All +// required store paths for the image will be realised, but layers +// will not yet be created from them. +// +// This function is only invoked if the manifest is not found in any +// cache. +func prepareImage(s *State, image *Image) (*ImageResult, error) { + packages, err := json.Marshal(image.Packages) if err != nil { return nil, err } - for layer, meta := range result.LayerLocations { - if !cache.hasSeenLayer(layer) { - err = uploadLayer(ctx, bucket, layer, meta.Path, meta.Md5) - if err != nil { - return nil, err - } + srcType, srcArgs := s.Cfg.Pkgs.Render(image.Tag) - cache.sawLayer(layer) - } + args := []string{ + "--timeout", s.Cfg.Timeout, + "--argstr", "packages", string(packages), + "--argstr", "srcType", srcType, + "--argstr", "srcArgs", srcArgs, + } + + output, err := callNix("nixery-build-image", image.Name, args) + if err != nil { + log.Printf("failed to call nixery-build-image: %s\n", err) + return nil, err + } + log.Printf("Finished image preparation for '%s' via Nix\n", image.Name) + + var result ImageResult + err = json.Unmarshal(output, &result) + if err != nil { + return nil, err } return &result, nil } +// Groups layers and checks whether they are present in the cache +// already, otherwise calls out to Nix to assemble layers. +// +// Returns information about all data layers that need to be included +// in the manifest, as well as information about which layers need to +// be uploaded (and from where). +func prepareLayers(ctx *context.Context, s *State, image *Image, graph *layers.RuntimeGraph) (map[string]string, error) { + grouped := layers.Group(graph, &s.Pop, LayerBudget) + + // TODO(tazjin): Introduce caching strategy, for now this will + // build all layers. + srcType, srcArgs := s.Cfg.Pkgs.Render(image.Tag) + args := []string{ + "--argstr", "srcType", srcType, + "--argstr", "srcArgs", srcArgs, + } + + var layerInput map[string][]string + for _, l := range grouped { + layerInput[l.Hash()] = l.Contents + + // The derivation responsible for building layers does not + // have the derivations that resulted in the required store + // paths in its context, which means that its sandbox will not + // contain the necessary paths if sandboxing is enabled. + // + // To work around this, all required store paths are added as + // 'extra-sandbox-paths' parameters. + for _, p := range l.Contents { + args = append(args, "--option", "extra-sandbox-paths", p) + } + } + + j, _ := json.Marshal(layerInput) + args = append(args, "--argstr", "layers", string(j)) + + output, err := callNix("nixery-build-layers", image.Name, args) + if err != nil { + log.Printf("failed to call nixery-build-layers: %s\n", err) + return nil, err + } + + result := make(map[string]string) + err = json.Unmarshal(output, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +// renameObject renames an object in the specified Cloud Storage +// bucket. +// +// The Go API for Cloud Storage does not support renaming objects, but +// the HTTP API does. The code below makes the relevant call manually. +func renameObject(ctx context.Context, s *State, old, new string) error { + bucket := s.Cfg.Bucket + + creds, err := google.FindDefaultCredentials(ctx) + if err != nil { + return err + } + + token, err := creds.TokenSource.Token() + if err != nil { + return err + } + + // as per https://cloud.google.com/storage/docs/renaming-copying-moving-objects#rename + url := fmt.Sprintf( + "https://www.googleapis.com/storage/v1/b/%s/o/%s/rewriteTo/b/%s/o/%s", + url.PathEscape(bucket), url.PathEscape(old), + url.PathEscape(bucket), url.PathEscape(new), + ) + + req, err := http.NewRequest("POST", url, nil) + req.Header.Add("Authorization", "Bearer "+token.AccessToken) + _, err = client.Do(req) + if err != nil { + return err + } + + // It seems that 'rewriteTo' copies objects instead of + // renaming/moving them, hence a deletion call afterwards is + // required. + if err = s.Bucket.Object(old).Delete(ctx); err != nil { + log.Printf("failed to delete renamed object '%s': %s\n", old, err) + // this error should not break renaming and is not returned + } + + return nil +} + +// Upload a to the storage bucket, while hashing it at the same time. +// +// The initial upload is performed in a 'staging' folder, as the +// SHA256-hash is not yet available when the upload is initiated. +// +// After a successful upload, the file is moved to its final location +// in the bucket and the build cache is populated. +// +// The return value is the layer's SHA256 hash, which is used in the +// image manifest. +func uploadHashLayer(ctx context.Context, s *State, key, path string) (string, error) { + staging := s.Bucket.Object("staging/" + key) + + // Set up a writer that simultaneously runs both hash + // algorithms and uploads to the bucket + sw := staging.NewWriter(ctx) + shasum := sha256.New() + md5sum := md5.New() + multi := io.MultiWriter(sw, shasum, md5sum) + + f, err := os.Open(path) + if err != nil { + log.Printf("failed to open layer at '%s' for reading: %s\n", path, err) + return "", err + } + defer f.Close() + + size, err := io.Copy(multi, f) + if err != nil { + log.Printf("failed to upload layer '%s' to staging: %s\n", key, err) + return "", err + } + + if err = sw.Close(); err != nil { + log.Printf("failed to upload layer '%s' to staging: %s\n", key, err) + return "", err + } + + build := Build{ + SHA256: fmt.Sprintf("%x", shasum.Sum([]byte{})), + MD5: fmt.Sprintf("%x", md5sum.Sum([]byte{})), + } + + // Hashes are now known and the object is in the bucket, what + // remains is to move it to the correct location and cache it. + err = renameObject(ctx, s, "staging/"+key, "layers/"+build.SHA256) + if err != nil { + log.Printf("failed to move layer '%s' from staging: %s\n", key, err) + return "", err + } + + cacheBuild(ctx, &s.Cache, s.Bucket, key, build) + + log.Printf("Uploaded layer sha256:%s (%v bytes written)", build.SHA256, size) + + return build.SHA256, nil +} + +func BuildImage(ctx *context.Context, s *State, image *Image) (*BuildResult, error) { + imageResult, err := prepareImage(s, image) + if err != nil { + return nil, err + } + + if imageResult.Error != "" { + return &BuildResult{ + Error: imageResult.Error, + Pkgs: imageResult.Pkgs, + }, nil + } + + _, err = prepareLayers(ctx, s, image, &imageResult.Graph) + if err != nil { + return nil, err + } + + return nil, nil +} + // uploadLayer uploads a single layer to Cloud Storage bucket. Before writing // any data the bucket is probed to see if the file already exists. // // If the file does exist, its MD5 hash is verified to ensure that the stored // file is not - for example - a fragment of a previous, incomplete upload. -func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer string, path string, md5 []byte) error { +func uploadLayer(ctx context.Context, bucket *storage.BucketHandle, layer string, path string, md5 []byte) error { layerKey := fmt.Sprintf("layers/%s", layer) obj := bucket.Object(layerKey) @@ -226,12 +389,12 @@ func uploadLayer(ctx *context.Context, bucket *storage.BucketHandle, layer strin // // If it does and the MD5 checksum matches the expected one, the layer // upload can be skipped. - attrs, err := obj.Attrs(*ctx) + attrs, err := obj.Attrs(ctx) if err == nil && bytes.Equal(attrs.MD5, md5) { log.Printf("Layer sha256:%s already exists in bucket, skipping upload", layer) } else { - writer := obj.NewWriter(*ctx) + writer := obj.NewWriter(ctx) file, err := os.Open(path) if err != nil { diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index ac8820f23..30f727db1 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -60,7 +60,7 @@ func getConfig(key, desc, def string) string { return value } -// config holds the Nixery configuration options. +// Config holds the Nixery configuration options. type Config struct { Bucket string // GCS bucket to cache & serve layers Signing *storage.SignedURLOptions // Signing options to use for GCS URLs diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index f8259ab98..07a9e0e23 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -328,7 +328,7 @@ func dominate(budget int, graph *simple.DirectedGraph) []Layer { // GroupLayers applies the algorithm described above the its input and returns a // list of layers, each consisting of a list of Nix store paths that it should // contain. -func GroupLayers(refs *RuntimeGraph, pop *Popularity, budget int) []Layer { +func Group(refs *RuntimeGraph, pop *Popularity, budget int) []Layer { graph := buildGraph(refs, pop) return dominate(budget, graph) } From 3f40c0a2d2e9ca03253da13ec53bc57e9c524f00 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 1 Oct 2019 23:24:55 +0100 Subject: [PATCH 114/223] feat(server): Implement package for creating image manifests The new manifest package creates image manifests and their configuration. This previously happened in Nix, but is now part of the server's workload. This relates to #50. --- tools/nixery/server/manifest/manifest.go | 114 +++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tools/nixery/server/manifest/manifest.go diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go new file mode 100644 index 000000000..dd447796c --- /dev/null +++ b/tools/nixery/server/manifest/manifest.go @@ -0,0 +1,114 @@ +// Package image implements logic for creating the image metadata +// (such as the image manifest and configuration). +package manifest + +import ( + "crypto/md5" + "crypto/sha256" + "encoding/json" + "fmt" +) + +const ( + // manifest constants + schemaVersion = 2 + + // media types + manifestType = "application/vnd.docker.distribution.manifest.v2+json" + layerType = "application/vnd.docker.image.rootfs.diff.tar" + configType = "application/vnd.docker.container.image.v1+json" + + // image config constants + arch = "amd64" + os = "linux" + fsType = "layers" +) + +type Entry struct { + MediaType string `json:"mediaType"` + Size int64 `json:"size"` + Digest string `json:"digest"` +} + +type manifest struct { + SchemaVersion int `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Config Entry `json:"config"` + Layers []Entry `json:"layers"` +} + +type imageConfig struct { + Architecture string `json:"architecture"` + OS string `json:"os"` + + RootFS struct { + FSType string `json:"type"` + DiffIDs []string `json:"diff_ids"` + } `json:"rootfs"` + + // sic! empty struct (rather than `null`) is required by the + // image metadata deserialiser in Kubernetes + Config struct{} `json:"config"` +} + +// ConfigLayer represents the configuration layer to be included in +// the manifest, containing its JSON-serialised content and the SHA256 +// & MD5 hashes of its input. +type ConfigLayer struct { + Config []byte + SHA256 string + MD5 string +} + +// imageConfig creates an image configuration with the values set to +// the constant defaults. +// +// Outside of this module the image configuration is treated as an +// opaque blob and it is thus returned as an already serialised byte +// array and its SHA256-hash. +func configLayer(hashes []string) ConfigLayer { + c := imageConfig{} + c.Architecture = arch + c.OS = os + c.RootFS.FSType = fsType + c.RootFS.DiffIDs = hashes + + j, _ := json.Marshal(c) + + return ConfigLayer{ + Config: j, + SHA256: fmt.Sprintf("%x", sha256.Sum256(j)), + MD5: fmt.Sprintf("%x", md5.Sum(j)), + } +} + +// Manifest creates an image manifest from the specified layer entries +// and returns its JSON-serialised form as well as the configuration +// layer. +// +// Callers do not need to set the media type for the layer entries. +func Manifest(layers []Entry) (json.RawMessage, ConfigLayer) { + hashes := make([]string, len(layers)) + for i, l := range layers { + l.MediaType = "application/vnd.docker.image.rootfs.diff.tar" + layers[i] = l + hashes[i] = l.Digest + } + + c := configLayer(hashes) + + m := manifest{ + SchemaVersion: schemaVersion, + MediaType: manifestType, + Config: Entry{ + MediaType: configType, + Size: int64(len(c.Config)), + Digest: "sha256:" + c.SHA256, + }, + Layers: layers, + } + + j, _ := json.Marshal(m) + + return json.RawMessage(j), c +} From ef2623d168fc3f91020fdb574f5287b01d7534e6 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 1 Oct 2019 23:25:51 +0100 Subject: [PATCH 115/223] fix(nix): Minor fixes to derivations for new build process --- tools/nixery/build-image/build-layers.nix | 2 +- tools/nixery/default.nix | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/nixery/build-image/build-layers.nix b/tools/nixery/build-image/build-layers.nix index 8a8bfbe9e..9a8742f13 100644 --- a/tools/nixery/build-image/build-layers.nix +++ b/tools/nixery/build-image/build-layers.nix @@ -25,7 +25,7 @@ let inherit (builtins) fromJSON mapAttrs toJSON; - inherit (pkgs) lib runCommand; + inherit (pkgs) lib runCommand writeText; pkgs = import loadPkgs { inherit srcType srcArgs importArgs; }; diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 925edbf6d..af506eea3 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -44,7 +44,7 @@ in rec { # are installing Nixery directly. nixery-bin = writeShellScriptBin "nixery" '' export WEB_DIR="${nixery-book}" - export PATH="${nixery-build-image}/bin:$PATH" + export PATH="${nixery-build-layers}/bin:${nixery-build-image}/bin:$PATH" exec ${nixery-server}/bin/nixery ''; @@ -96,6 +96,7 @@ in rec { iana-etc nix nixery-build-image + nixery-build-layers nixery-launch-script openssh zlib From 17adda03552d44b6354c3a801614559975c82144 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 1 Oct 2019 23:26:09 +0100 Subject: [PATCH 116/223] fix(server): Minor fixes to updated new builder code --- tools/nixery/server/builder/cache.go | 11 +++++------ tools/nixery/server/main.go | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index edb1b711d..254f32d83 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -52,6 +52,7 @@ func NewCache() LocalCache { return LocalCache{ mcache: make(map[string]string), lcache: make(map[string]void), + bcache: make(map[string]Build), } } @@ -111,7 +112,7 @@ func (c *LocalCache) localCacheBuild(key string, b Build) { // Retrieve a manifest from the cache(s). First the local cache is // checked, then the GCS-bucket cache. -func manifestFromCache(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string) (string, bool) { +func manifestFromCache(ctx context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string) (string, bool) { path, cached := cache.manifestFromLocalCache(key) if cached { return path, true @@ -120,12 +121,12 @@ func manifestFromCache(ctx *context.Context, cache *LocalCache, bucket *storage. obj := bucket.Object("manifests/" + key) // Probe whether the file exists before trying to fetch it. - _, err := obj.Attrs(*ctx) + _, err := obj.Attrs(ctx) if err != nil { return "", false } - r, err := obj.NewReader(*ctx) + r, err := obj.NewReader(ctx) if err != nil { log.Printf("Failed to retrieve manifest '%s' from cache: %s\n", key, err) return "", false @@ -222,11 +223,9 @@ func cacheBuild(ctx context.Context, cache *LocalCache, bucket *storage.BucketHa w := obj.NewWriter(ctx) - size, err := io.Copy(w, bytes.NewReader(j)) + _, err := io.Copy(w, bytes.NewReader(j)) if err != nil { log.Printf("failed to cache build '%s': %s\n", key, err) return } - - log.Printf("cached build '%s' (%v bytes written)\n", key, size) } diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index ae8dd3ab2..c3a0b6460 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -122,7 +122,7 @@ func writeError(w http.ResponseWriter, status int, code, message string) { } type registryHandler struct { - ctx *context.Context + ctx context.Context state *builder.State } @@ -201,7 +201,7 @@ func main() { // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ - ctx: &ctx, + ctx: ctx, state: &state, }) From aa02ae142166af23c1b6d8533b8eea5d6fa3e9a1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 1 Oct 2019 23:27:26 +0100 Subject: [PATCH 117/223] feat(server): Implement new build process core Implements the new build process to the point where it can actually construct and serve image manifests. It is worth noting that this build process works even if the Nix sandbox is enabled! It is also worth nothing that none of the caching functionality that the new build process enables (such as per-layer build caching) is actually in use yet, hence running Nixery at this commit is prone to doing more work than previously. This relates to #50. --- tools/nixery/server/builder/builder.go | 112 ++++++++++++------------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 303d796df..1bdd9212c 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -35,8 +35,8 @@ import ( "sort" "strings" - "cloud.google.com/go/storage" "github.com/google/nixery/layers" + "github.com/google/nixery/manifest" "golang.org/x/oauth2/google" ) @@ -62,10 +62,9 @@ type Image struct { // TODO(tazjin): docstring type BuildResult struct { - Error string - Pkgs []string - - Manifest struct{} // TODO(tazjin): OCIv1 manifest + Error string `json:"error"` + Pkgs []string `json:"pkgs"` + Manifest json.RawMessage `json:"manifest"` } // ImageFromName parses an image name into the corresponding structure which can @@ -149,6 +148,12 @@ func callNix(program string, name string, args []string) ([]byte, error) { } go logNix(name, errpipe) + if err = cmd.Start(); err != nil { + log.Printf("Error starting %s: %s\n", program, err) + return nil, err + } + log.Printf("Invoked Nix build (%s) for '%s'\n", program, name) + stdout, _ := ioutil.ReadAll(outpipe) if err = cmd.Wait(); err != nil { @@ -208,7 +213,7 @@ func prepareImage(s *State, image *Image) (*ImageResult, error) { // Returns information about all data layers that need to be included // in the manifest, as well as information about which layers need to // be uploaded (and from where). -func prepareLayers(ctx *context.Context, s *State, image *Image, graph *layers.RuntimeGraph) (map[string]string, error) { +func prepareLayers(ctx context.Context, s *State, image *Image, graph *layers.RuntimeGraph) (map[string]string, error) { grouped := layers.Group(graph, &s.Pop, LayerBudget) // TODO(tazjin): Introduce caching strategy, for now this will @@ -219,7 +224,8 @@ func prepareLayers(ctx *context.Context, s *State, image *Image, graph *layers.R "--argstr", "srcArgs", srcArgs, } - var layerInput map[string][]string + layerInput := make(map[string][]string) + allPaths := []string{} for _, l := range grouped { layerInput[l.Hash()] = l.Contents @@ -231,10 +237,12 @@ func prepareLayers(ctx *context.Context, s *State, image *Image, graph *layers.R // To work around this, all required store paths are added as // 'extra-sandbox-paths' parameters. for _, p := range l.Contents { - args = append(args, "--option", "extra-sandbox-paths", p) + allPaths = append(allPaths, p) } } + args = append(args, "--option", "extra-sandbox-paths", strings.Join(allPaths, " ")) + j, _ := json.Marshal(layerInput) args = append(args, "--argstr", "layers", string(j)) @@ -243,6 +251,7 @@ func prepareLayers(ctx *context.Context, s *State, image *Image, graph *layers.R log.Printf("failed to call nixery-build-layers: %s\n", err) return nil, err } + log.Printf("Finished layer preparation for '%s' via Nix\n", image.Name) result := make(map[string]string) err = json.Unmarshal(output, &result) @@ -306,32 +315,25 @@ func renameObject(ctx context.Context, s *State, old, new string) error { // // The return value is the layer's SHA256 hash, which is used in the // image manifest. -func uploadHashLayer(ctx context.Context, s *State, key, path string) (string, error) { +func uploadHashLayer(ctx context.Context, s *State, key string, data io.Reader) (*manifest.Entry, error) { staging := s.Bucket.Object("staging/" + key) - // Set up a writer that simultaneously runs both hash + // Sets up a "multiwriter" that simultaneously runs both hash // algorithms and uploads to the bucket sw := staging.NewWriter(ctx) shasum := sha256.New() md5sum := md5.New() multi := io.MultiWriter(sw, shasum, md5sum) - f, err := os.Open(path) - if err != nil { - log.Printf("failed to open layer at '%s' for reading: %s\n", path, err) - return "", err - } - defer f.Close() - - size, err := io.Copy(multi, f) + size, err := io.Copy(multi, data) if err != nil { log.Printf("failed to upload layer '%s' to staging: %s\n", key, err) - return "", err + return nil, err } if err = sw.Close(); err != nil { log.Printf("failed to upload layer '%s' to staging: %s\n", key, err) - return "", err + return nil, err } build := Build{ @@ -344,20 +346,25 @@ func uploadHashLayer(ctx context.Context, s *State, key, path string) (string, e err = renameObject(ctx, s, "staging/"+key, "layers/"+build.SHA256) if err != nil { log.Printf("failed to move layer '%s' from staging: %s\n", key, err) - return "", err + return nil, err } cacheBuild(ctx, &s.Cache, s.Bucket, key, build) log.Printf("Uploaded layer sha256:%s (%v bytes written)", build.SHA256, size) - return build.SHA256, nil + return &manifest.Entry{ + Digest: "sha256:" + build.SHA256, + Size: size, + }, nil } -func BuildImage(ctx *context.Context, s *State, image *Image) (*BuildResult, error) { +func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, error) { + // TODO(tazjin): Use the build cache + imageResult, err := prepareImage(s, image) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to prepare image '%s': %s", image.Name, err) } if imageResult.Error != "" { @@ -367,51 +374,38 @@ func BuildImage(ctx *context.Context, s *State, image *Image) (*BuildResult, err }, nil } - _, err = prepareLayers(ctx, s, image, &imageResult.Graph) + layerResult, err := prepareLayers(ctx, s, image, &imageResult.Graph) if err != nil { return nil, err } - return nil, nil -} - -// uploadLayer uploads a single layer to Cloud Storage bucket. Before writing -// any data the bucket is probed to see if the file already exists. -// -// If the file does exist, its MD5 hash is verified to ensure that the stored -// file is not - for example - a fragment of a previous, incomplete upload. -func uploadLayer(ctx context.Context, bucket *storage.BucketHandle, layer string, path string, md5 []byte) error { - layerKey := fmt.Sprintf("layers/%s", layer) - obj := bucket.Object(layerKey) - - // Before uploading a layer to the bucket, probe whether it already - // exists. - // - // If it does and the MD5 checksum matches the expected one, the layer - // upload can be skipped. - attrs, err := obj.Attrs(ctx) - - if err == nil && bytes.Equal(attrs.MD5, md5) { - log.Printf("Layer sha256:%s already exists in bucket, skipping upload", layer) - } else { - writer := obj.NewWriter(ctx) - file, err := os.Open(path) - + layers := []manifest.Entry{} + for key, path := range layerResult { + f, err := os.Open(path) if err != nil { - return fmt.Errorf("failed to open layer %s from path %s: %v", layer, path, err) + log.Printf("failed to open layer at '%s': %s\n", path, err) + return nil, err } - size, err := io.Copy(writer, file) + entry, err := uploadHashLayer(ctx, s, key, f) + f.Close() if err != nil { - return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) + return nil, err } - if err = writer.Close(); err != nil { - return fmt.Errorf("failed to write layer %s to Cloud Storage: %v", layer, err) - } - - log.Printf("Uploaded layer sha256:%s (%v bytes written)\n", layer, size) + layers = append(layers, *entry) } - return nil + m, c := manifest.Manifest(layers) + if _, err = uploadHashLayer(ctx, s, c.SHA256, bytes.NewReader(c.Config)); err != nil { + log.Printf("failed to upload config for %s: %s\n", image.Name, err) + return nil, err + } + + result := BuildResult{ + Manifest: m, + } + // TODO: cache manifest + + return &result, nil } From f4f290957305a5a81292edef717a18a7c36be4bf Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 2 Oct 2019 15:19:28 +0100 Subject: [PATCH 118/223] fix(server): Specify correct authentication scope for GCS When retrieving tokens for service service accounts, some methods of retrieval require a scope to be specified. --- tools/nixery/server/builder/builder.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 1bdd9212c..ddfd4a078 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -45,6 +45,9 @@ import ( // use up is set at a lower point. const LayerBudget int = 94 +// API scope needed for renaming objects in GCS +const gcsScope = "https://www.googleapis.com/auth/devstorage.read_write" + // HTTP client to use for direct calls to APIs that are not part of the SDK var client = &http.Client{} @@ -270,7 +273,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, graph *layers.Ru func renameObject(ctx context.Context, s *State, old, new string) error { bucket := s.Cfg.Bucket - creds, err := google.FindDefaultCredentials(ctx) + creds, err := google.FindDefaultCredentials(ctx, gcsScope) if err != nil { return err } From 64fca61ea1d898c01893f56f0e03913f36468f5d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 2 Oct 2019 15:31:57 +0100 Subject: [PATCH 119/223] fix(server): Upload symlink layer created by first Nix build This layer is needed in addition to those that are built in the second Nix build. --- tools/nixery/server/builder/builder.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index ddfd4a078..87776735f 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -63,7 +63,10 @@ type Image struct { Packages []string } -// TODO(tazjin): docstring +// BuildResult represents the data returned from the server to the +// HTTP handlers. Error information is propagated straight from Nix +// for errors inside of the build that should be fed back to the +// client (such as missing packages). type BuildResult struct { Error string `json:"error"` Pkgs []string `json:"pkgs"` @@ -382,6 +385,8 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro return nil, err } + layerResult[imageResult.SymlinkLayer.SHA256] = imageResult.SymlinkLayer.Path + layers := []manifest.Entry{} for key, path := range layerResult { f, err := os.Open(path) From 0698d7f2aafc62c1ef6fca172668310727fdaef2 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 2 Oct 2019 17:48:58 +0100 Subject: [PATCH 120/223] chore(server): Remove "layer seen" cache This cache is no longer required as it is implicit because the layer cache (mapping store path hashes to layer hashes) implies that a layer has been seen. --- tools/nixery/server/builder/cache.go | 34 ++++------------------------ 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 254f32d83..1ed87b40a 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -25,8 +25,6 @@ import ( "cloud.google.com/go/storage" ) -type void struct{} - type Build struct { SHA256 string `json:"sha256"` MD5 string `json:"md5"` @@ -39,40 +37,18 @@ type LocalCache struct { mmtx sync.RWMutex mcache map[string]string - // Layer (tarball) cache + // Layer cache lmtx sync.RWMutex - lcache map[string]void - - // Layer (build) cache - bmtx sync.RWMutex - bcache map[string]Build + lcache map[string]Build } func NewCache() LocalCache { return LocalCache{ mcache: make(map[string]string), - lcache: make(map[string]void), - bcache: make(map[string]Build), + lcache: make(map[string]Build), } } -// Has this layer hash already been seen by this Nixery instance? If -// yes, we can skip upload checking and such because it has already -// been done. -func (c *LocalCache) hasSeenLayer(hash string) bool { - c.lmtx.RLock() - defer c.lmtx.RUnlock() - _, seen := c.lcache[hash] - return seen -} - -// Layer has now been seen and should be stored. -func (c *LocalCache) sawLayer(hash string) { - c.lmtx.Lock() - defer c.lmtx.Unlock() - c.lcache[hash] = void{} -} - // Retrieve a cached manifest if the build is cacheable and it exists. func (c *LocalCache) manifestFromLocalCache(key string) (string, bool) { c.mmtx.RLock() @@ -97,7 +73,7 @@ func (c *LocalCache) localCacheManifest(key, path string) { // Retrieve a cached build from the local cache. func (c *LocalCache) buildFromLocalCache(key string) (*Build, bool) { c.bmtx.RLock() - b, ok := c.bcache[key] + b, ok := c.lcache[key] c.bmtx.RUnlock() return &b, ok @@ -106,7 +82,7 @@ func (c *LocalCache) buildFromLocalCache(key string) (*Build, bool) { // Add a build result to the local cache. func (c *LocalCache) localCacheBuild(key string, b Build) { c.bmtx.Lock() - c.bcache[key] = b + c.lcache[key] = b c.bmtx.Unlock() } From 1308a6e1fd8e5f9cbd0d6b5d872628ec234114d5 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 2 Oct 2019 18:00:35 +0100 Subject: [PATCH 121/223] refactor(server): Clean up cache implementation A couple of minor fixes and improvements to the cache implementation. --- tools/nixery/server/builder/builder.go | 2 +- tools/nixery/server/builder/cache.go | 43 +++++++++++++------------- tools/nixery/server/main.go | 8 ++--- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 87776735f..d0650648f 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -355,7 +355,7 @@ func uploadHashLayer(ctx context.Context, s *State, key string, data io.Reader) return nil, err } - cacheBuild(ctx, &s.Cache, s.Bucket, key, build) + cacheBuild(ctx, s, key, build) log.Printf("Uploaded layer sha256:%s (%v bytes written)", build.SHA256, size) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 1ed87b40a..582e28d81 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -21,8 +21,6 @@ import ( "log" "os" "sync" - - "cloud.google.com/go/storage" ) type Build struct { @@ -72,29 +70,29 @@ func (c *LocalCache) localCacheManifest(key, path string) { // Retrieve a cached build from the local cache. func (c *LocalCache) buildFromLocalCache(key string) (*Build, bool) { - c.bmtx.RLock() + c.lmtx.RLock() b, ok := c.lcache[key] - c.bmtx.RUnlock() + c.lmtx.RUnlock() return &b, ok } // Add a build result to the local cache. func (c *LocalCache) localCacheBuild(key string, b Build) { - c.bmtx.Lock() + c.lmtx.Lock() c.lcache[key] = b - c.bmtx.Unlock() + c.lmtx.Unlock() } // Retrieve a manifest from the cache(s). First the local cache is // checked, then the GCS-bucket cache. -func manifestFromCache(ctx context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string) (string, bool) { - path, cached := cache.manifestFromLocalCache(key) +func manifestFromCache(ctx context.Context, s *State, key string) (string, bool) { + path, cached := s.Cache.manifestFromLocalCache(key) if cached { return path, true } - obj := bucket.Object("manifests/" + key) + obj := s.Bucket.Object("manifests/" + key) // Probe whether the file exists before trying to fetch it. _, err := obj.Attrs(ctx) @@ -119,17 +117,17 @@ func manifestFromCache(ctx context.Context, cache *LocalCache, bucket *storage.B } log.Printf("Retrieved manifest for sha1:%s from GCS\n", key) - cache.localCacheManifest(key, path) + go s.Cache.localCacheManifest(key, path) return path, true } // Add a manifest to the bucket & local caches -func cacheManifest(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key, path string) { - cache.localCacheManifest(key, path) +func cacheManifest(ctx context.Context, s *State, key, path string) { + go s.Cache.localCacheManifest(key, path) - obj := bucket.Object("manifests/" + key) - w := obj.NewWriter(*ctx) + obj := s.Bucket.Object("manifests/" + key) + w := obj.NewWriter(ctx) f, err := os.Open(path) if err != nil { @@ -154,19 +152,19 @@ func cacheManifest(ctx *context.Context, cache *LocalCache, bucket *storage.Buck // Retrieve a build from the cache, first checking the local cache // followed by the bucket cache. -func buildFromCache(ctx *context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string) (*Build, bool) { - build, cached := cache.buildFromLocalCache(key) +func buildFromCache(ctx context.Context, s *State, key string) (*Build, bool) { + build, cached := s.Cache.buildFromLocalCache(key) if cached { return build, true } - obj := bucket.Object("builds/" + key) - _, err := obj.Attrs(*ctx) + obj := s.Bucket.Object("builds/" + key) + _, err := obj.Attrs(ctx) if err != nil { return nil, false } - r, err := obj.NewReader(*ctx) + r, err := obj.NewReader(ctx) if err != nil { log.Printf("Failed to retrieve build '%s' from cache: %s\n", key, err) return nil, false @@ -187,13 +185,14 @@ func buildFromCache(ctx *context.Context, cache *LocalCache, bucket *storage.Buc return nil, false } + go s.Cache.localCacheBuild(key, b) return &b, true } -func cacheBuild(ctx context.Context, cache *LocalCache, bucket *storage.BucketHandle, key string, build Build) { - cache.localCacheBuild(key, build) +func cacheBuild(ctx context.Context, s *State, key string, build Build) { + go s.Cache.localCacheBuild(key, build) - obj := bucket.Object("builds/" + key) + obj := s.Bucket.Object("builds/" + key) j, _ := json.Marshal(&build) diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index c3a0b6460..1ff3a471f 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -81,15 +81,15 @@ func constructLayerUrl(cfg *config.Config, digest string) (string, error) { // // The bucket is required for Nixery to function correctly, hence fatal errors // are generated in case it fails to be set up correctly. -func prepareBucket(ctx *context.Context, cfg *config.Config) *storage.BucketHandle { - client, err := storage.NewClient(*ctx) +func prepareBucket(ctx context.Context, cfg *config.Config) *storage.BucketHandle { + client, err := storage.NewClient(ctx) if err != nil { log.Fatalln("Failed to set up Cloud Storage client:", err) } bkt := client.Bucket(cfg.Bucket) - if _, err := bkt.Attrs(*ctx); err != nil { + if _, err := bkt.Attrs(ctx); err != nil { log.Fatalln("Could not access configured bucket", err) } @@ -194,7 +194,7 @@ func main() { } ctx := context.Background() - bucket := prepareBucket(&ctx, cfg) + bucket := prepareBucket(ctx, cfg) state := builder.NewState(bucket, *cfg) log.Printf("Starting Nixery on port %s\n", cfg.Port) From 355fe3f5ec05c3c698ea3ba21a5d57454daeceef Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 11:23:04 +0100 Subject: [PATCH 122/223] feat(server): Reintroduce manifest caching to GCS The new builder now caches and reads cached manifests to/from GCS. The in-memory cache is disabled, as manifests are no longer written to local file and the caching of file paths does not work (unless we reintroduce reading/writing from temp files as part of the local cache). --- tools/nixery/server/builder/builder.go | 15 +++++++-- tools/nixery/server/builder/cache.go | 43 ++++++++++---------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index d0650648f..f3342f991 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -366,7 +366,14 @@ func uploadHashLayer(ctx context.Context, s *State, key string, data io.Reader) } func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, error) { - // TODO(tazjin): Use the build cache + key := s.Cfg.Pkgs.CacheKey(image.Packages, image.Tag) + if key != "" { + if m, c := manifestFromCache(ctx, s, key); c { + return &BuildResult{ + Manifest: m, + }, nil + } + } imageResult, err := prepareImage(s, image) if err != nil { @@ -410,10 +417,12 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro return nil, err } + if key != "" { + go cacheManifest(ctx, s, key, m) + } + result := BuildResult{ Manifest: m, } - // TODO: cache manifest - return &result, nil } diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 582e28d81..a5cbbf6ce 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -18,8 +18,8 @@ import ( "context" "encoding/json" "io" + "io/ioutil" "log" - "os" "sync" ) @@ -86,57 +86,48 @@ func (c *LocalCache) localCacheBuild(key string, b Build) { // Retrieve a manifest from the cache(s). First the local cache is // checked, then the GCS-bucket cache. -func manifestFromCache(ctx context.Context, s *State, key string) (string, bool) { - path, cached := s.Cache.manifestFromLocalCache(key) - if cached { - return path, true - } +func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessage, bool) { + // path, cached := s.Cache.manifestFromLocalCache(key) + // if cached { + // return path, true + // } + // TODO: local cache? obj := s.Bucket.Object("manifests/" + key) // Probe whether the file exists before trying to fetch it. _, err := obj.Attrs(ctx) if err != nil { - return "", false + return nil, false } r, err := obj.NewReader(ctx) if err != nil { log.Printf("Failed to retrieve manifest '%s' from cache: %s\n", key, err) - return "", false + return nil, false } defer r.Close() - path = os.TempDir() + "/" + key - f, _ := os.Create(path) - defer f.Close() - - _, err = io.Copy(f, r) + m, err := ioutil.ReadAll(r) if err != nil { log.Printf("Failed to read cached manifest for '%s': %s\n", key, err) } + // TODO: locally cache manifest, but the cache needs to be changed log.Printf("Retrieved manifest for sha1:%s from GCS\n", key) - go s.Cache.localCacheManifest(key, path) - - return path, true + return json.RawMessage(m), true } // Add a manifest to the bucket & local caches -func cacheManifest(ctx context.Context, s *State, key, path string) { - go s.Cache.localCacheManifest(key, path) +func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) { + // go s.Cache.localCacheManifest(key, path) + // TODO local cache obj := s.Bucket.Object("manifests/" + key) w := obj.NewWriter(ctx) + r := bytes.NewReader([]byte(m)) - f, err := os.Open(path) - if err != nil { - log.Printf("failed to open manifest sha1:%s for cache upload: %s\n", key, err) - return - } - defer f.Close() - - size, err := io.Copy(w, f) + size, err := io.Copy(w, r) if err != nil { log.Printf("failed to cache manifest sha1:%s: %s\n", key, err) return From f6b40ed6c78a69dd417bd9e0f64a207904755af4 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 12:09:24 +0100 Subject: [PATCH 123/223] refactor(server): Cache manifest entries for layer builds MD5 hash checking is no longer performed by Nixery (it does not seem to be necessary), hence the layer cache now only keeps the SHA256 hash and size in the form of the manifest entry. This makes it possible to restructure the builder code to perform cache-fetching and cache-populating for layers in the same place. --- tools/nixery/server/builder/cache.go | 56 +++++++++++------------- tools/nixery/server/manifest/manifest.go | 2 +- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index a5cbbf6ce..ab0021f6d 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -21,12 +21,9 @@ import ( "io/ioutil" "log" "sync" -) -type Build struct { - SHA256 string `json:"sha256"` - MD5 string `json:"md5"` -} + "github.com/google/nixery/manifest" +) // LocalCache implements the structure used for local caching of // manifests and layer uploads. @@ -37,13 +34,13 @@ type LocalCache struct { // Layer cache lmtx sync.RWMutex - lcache map[string]Build + lcache map[string]manifest.Entry } func NewCache() LocalCache { return LocalCache{ mcache: make(map[string]string), - lcache: make(map[string]Build), + lcache: make(map[string]manifest.Entry), } } @@ -68,19 +65,19 @@ func (c *LocalCache) localCacheManifest(key, path string) { c.mmtx.Unlock() } -// Retrieve a cached build from the local cache. -func (c *LocalCache) buildFromLocalCache(key string) (*Build, bool) { +// Retrieve a layer build from the local cache. +func (c *LocalCache) layerFromLocalCache(key string) (*manifest.Entry, bool) { c.lmtx.RLock() - b, ok := c.lcache[key] + e, ok := c.lcache[key] c.lmtx.RUnlock() - return &b, ok + return &e, ok } -// Add a build result to the local cache. -func (c *LocalCache) localCacheBuild(key string, b Build) { +// Add a layer build result to the local cache. +func (c *LocalCache) localCacheLayer(key string, e manifest.Entry) { c.lmtx.Lock() - c.lcache[key] = b + c.lcache[key] = e c.lmtx.Unlock() } @@ -141,12 +138,11 @@ func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) log.Printf("Cached manifest sha1:%s (%v bytes written)\n", key, size) } -// Retrieve a build from the cache, first checking the local cache -// followed by the bucket cache. -func buildFromCache(ctx context.Context, s *State, key string) (*Build, bool) { - build, cached := s.Cache.buildFromLocalCache(key) - if cached { - return build, true +// Retrieve a layer build from the cache, first checking the local +// cache followed by the bucket cache. +func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, bool) { + if entry, cached := s.Cache.layerFromLocalCache(key); cached { + return entry, true } obj := s.Bucket.Object("builds/" + key) @@ -157,7 +153,7 @@ func buildFromCache(ctx context.Context, s *State, key string) (*Build, bool) { r, err := obj.NewReader(ctx) if err != nil { - log.Printf("Failed to retrieve build '%s' from cache: %s\n", key, err) + log.Printf("Failed to retrieve layer build '%s' from cache: %s\n", key, err) return nil, false } defer r.Close() @@ -165,27 +161,27 @@ func buildFromCache(ctx context.Context, s *State, key string) (*Build, bool) { jb := bytes.NewBuffer([]byte{}) _, err = io.Copy(jb, r) if err != nil { - log.Printf("Failed to read build '%s' from cache: %s\n", key, err) + log.Printf("Failed to read layer build '%s' from cache: %s\n", key, err) return nil, false } - var b Build - err = json.Unmarshal(jb.Bytes(), &build) + var entry manifest.Entry + err = json.Unmarshal(jb.Bytes(), &entry) if err != nil { - log.Printf("Failed to unmarshal build '%s' from cache: %s\n", key, err) + log.Printf("Failed to unmarshal layer build '%s' from cache: %s\n", key, err) return nil, false } - go s.Cache.localCacheBuild(key, b) - return &b, true + go s.Cache.localCacheLayer(key, entry) + return &entry, true } -func cacheBuild(ctx context.Context, s *State, key string, build Build) { - go s.Cache.localCacheBuild(key, build) +func cacheLayer(ctx context.Context, s *State, key string, entry manifest.Entry) { + s.Cache.localCacheLayer(key, entry) obj := s.Bucket.Object("builds/" + key) - j, _ := json.Marshal(&build) + j, _ := json.Marshal(&entry) w := obj.NewWriter(ctx) diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go index dd447796c..61d280a7f 100644 --- a/tools/nixery/server/manifest/manifest.go +++ b/tools/nixery/server/manifest/manifest.go @@ -25,7 +25,7 @@ const ( ) type Entry struct { - MediaType string `json:"mediaType"` + MediaType string `json:"mediaType,omitempty"` Size int64 `json:"size"` Digest string `json:"digest"` } From 53906024ff0612b6946cff4122dc28e85a414b6b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 12:11:46 +0100 Subject: [PATCH 124/223] refactor: Remove remaining MD5-hash mentions and computations --- tools/nixery/build-image/build-image.nix | 5 ++--- tools/nixery/docs/src/caching.md | 9 ++++----- tools/nixery/docs/src/under-the-hood.md | 3 +-- tools/nixery/server/builder/builder.go | 24 +++++++++--------------- tools/nixery/server/manifest/manifest.go | 7 ++----- 5 files changed, 18 insertions(+), 30 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 33500dbb9..70049885a 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -137,11 +137,10 @@ let buildInputs = with pkgs; [ coreutils jq openssl ]; }'' layerSha256=$(sha256sum ${symlinkLayer} | cut -d ' ' -f1) - layerMd5=$(openssl dgst -md5 -binary ${symlinkLayer} | openssl enc -base64) layerSize=$(stat --printf '%s' ${symlinkLayer}) - jq -n -c --arg sha256 $layerSha256 --arg md5 $layerMd5 --arg size $layerSize --arg path ${symlinkLayer} \ - '{ size: ($size | tonumber), sha256: $sha256, md5: $md5, path: $path }' >> $out + jq -n -c --arg sha256 $layerSha256 --arg size $layerSize --arg path ${symlinkLayer} \ + '{ size: ($size | tonumber), sha256: $sha256, path: $path }' >> $out '')); # Final output structure returned to Nixery if the build succeeded diff --git a/tools/nixery/docs/src/caching.md b/tools/nixery/docs/src/caching.md index 175fe04d7..b07d9e22f 100644 --- a/tools/nixery/docs/src/caching.md +++ b/tools/nixery/docs/src/caching.md @@ -46,9 +46,8 @@ They are stored content-addressably at `$BUCKET/layers/$SHA256HASH` and layer requests sent to Nixery will redirect directly to this storage location. The effect of this cache is that Nixery does not need to upload identical layers -repeatedly. When Nixery notices that a layer already exists in GCS, it will use -the object metadata to compare its MD5-hash with the locally computed one and -skip uploading. +repeatedly. When Nixery notices that a layer already exists in GCS it will skip +uploading this layer. Removing layers from the cache is *potentially problematic* if there are cached manifests or layer builds referencing those layers. @@ -61,8 +60,8 @@ reference these layers. Layer builds are cached at `$BUCKET/builds/$HASH`, where `$HASH` is a SHA1 of the Nix store paths included in the layer. -The content of the cached entries is a JSON-object that contains the MD5 and -SHA256 hashes of the built layer. +The content of the cached entries is a JSON-object that contains the SHA256 +hashes and sizes of the built layer. The effect of this cache is that different instances of Nixery will not build, hash and upload layers that have identical contents across different instances. diff --git a/tools/nixery/docs/src/under-the-hood.md b/tools/nixery/docs/src/under-the-hood.md index 6b5e5e9bb..b58a21d0d 100644 --- a/tools/nixery/docs/src/under-the-hood.md +++ b/tools/nixery/docs/src/under-the-hood.md @@ -67,8 +67,7 @@ just ... hang, for a moment. Nixery inspects the returned manifest and uploads each layer to the configured [Google Cloud Storage][gcs] bucket. To avoid unnecessary uploading, it will -first check whether layers are already present in the bucket and - just to be -safe - compare their MD5-hashes against what was built. +check whether layers are already present in the bucket. ## 4. The image manifest is sent back diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index f3342f991..64cfed143 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -21,7 +21,6 @@ import ( "bufio" "bytes" "context" - "crypto/md5" "crypto/sha256" "encoding/json" "fmt" @@ -108,7 +107,6 @@ type ImageResult struct { SymlinkLayer struct { Size int `json:"size"` SHA256 string `json:"sha256"` - MD5 string `json:"md5"` Path string `json:"path"` } `json:"symlinkLayer"` } @@ -328,8 +326,7 @@ func uploadHashLayer(ctx context.Context, s *State, key string, data io.Reader) // algorithms and uploads to the bucket sw := staging.NewWriter(ctx) shasum := sha256.New() - md5sum := md5.New() - multi := io.MultiWriter(sw, shasum, md5sum) + multi := io.MultiWriter(sw, shasum) size, err := io.Copy(multi, data) if err != nil { @@ -342,27 +339,24 @@ func uploadHashLayer(ctx context.Context, s *State, key string, data io.Reader) return nil, err } - build := Build{ - SHA256: fmt.Sprintf("%x", shasum.Sum([]byte{})), - MD5: fmt.Sprintf("%x", md5sum.Sum([]byte{})), - } + sha256sum := fmt.Sprintf("%x", shasum.Sum([]byte{})) // Hashes are now known and the object is in the bucket, what // remains is to move it to the correct location and cache it. - err = renameObject(ctx, s, "staging/"+key, "layers/"+build.SHA256) + err = renameObject(ctx, s, "staging/"+key, "layers/"+sha256sum) if err != nil { log.Printf("failed to move layer '%s' from staging: %s\n", key, err) return nil, err } - cacheBuild(ctx, s, key, build) + log.Printf("Uploaded layer sha256:%s (%v bytes written)", sha256sum, size) - log.Printf("Uploaded layer sha256:%s (%v bytes written)", build.SHA256, size) - - return &manifest.Entry{ - Digest: "sha256:" + build.SHA256, + entry := manifest.Entry{ + Digest: "sha256:" + sha256sum, Size: size, - }, nil + } + + return &entry, nil } func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, error) { diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go index 61d280a7f..f777e3f58 100644 --- a/tools/nixery/server/manifest/manifest.go +++ b/tools/nixery/server/manifest/manifest.go @@ -3,7 +3,6 @@ package manifest import ( - "crypto/md5" "crypto/sha256" "encoding/json" "fmt" @@ -52,12 +51,11 @@ type imageConfig struct { } // ConfigLayer represents the configuration layer to be included in -// the manifest, containing its JSON-serialised content and the SHA256 -// & MD5 hashes of its input. +// the manifest, containing its JSON-serialised content and SHA256 +// hash. type ConfigLayer struct { Config []byte SHA256 string - MD5 string } // imageConfig creates an image configuration with the values set to @@ -78,7 +76,6 @@ func configLayer(hashes []string) ConfigLayer { return ConfigLayer{ Config: j, SHA256: fmt.Sprintf("%x", sha256.Sum256(j)), - MD5: fmt.Sprintf("%x", md5.Sum(j)), } } From 313e5d08f12f4c8573e9347f7f04493ab99b5abf Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 12:12:06 +0100 Subject: [PATCH 125/223] refactor(builder): Streamline layer creation & reintroduce caching The functions used for layer creation are now easier to follow and have clear points at which the layer cache is checked and populated. This relates to #50. --- tools/nixery/server/builder/builder.go | 79 +++++++++++++++++--------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 64cfed143..9f529b226 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -214,14 +214,58 @@ func prepareImage(s *State, image *Image) (*ImageResult, error) { // Groups layers and checks whether they are present in the cache // already, otherwise calls out to Nix to assemble layers. // -// Returns information about all data layers that need to be included -// in the manifest, as well as information about which layers need to -// be uploaded (and from where). -func prepareLayers(ctx context.Context, s *State, image *Image, graph *layers.RuntimeGraph) (map[string]string, error) { - grouped := layers.Group(graph, &s.Pop, LayerBudget) +// Newly built layers are uploaded to the bucket. Cache entries are +// added only after successful uploads, which guarantees that entries +// retrieved from the cache are present in the bucket. +func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageResult) ([]manifest.Entry, error) { + grouped := layers.Group(&result.Graph, &s.Pop, LayerBudget) - // TODO(tazjin): Introduce caching strategy, for now this will - // build all layers. + var entries []manifest.Entry + var missing []layers.Layer + + // Splits the layers into those which are already present in + // the cache, and those that are missing (i.e. need to be + // built by Nix). + for _, l := range grouped { + if entry, cached := layerFromCache(ctx, s, l.Hash()); cached { + entries = append(entries, *entry) + } else { + missing = append(missing, l) + } + } + + built, err := buildLayers(s, image, missing) + if err != nil { + log.Printf("Failed to build missing layers: %s\n", err) + return nil, err + } + + // Symlink layer (built in the first Nix build) needs to be + // included when hashing & uploading + built[result.SymlinkLayer.SHA256] = result.SymlinkLayer.Path + + for key, path := range built { + f, err := os.Open(path) + if err != nil { + log.Printf("failed to open layer at '%s': %s\n", path, err) + return nil, err + } + + entry, err := uploadHashLayer(ctx, s, key, f) + f.Close() + if err != nil { + return nil, err + } + + entries = append(entries, *entry) + go cacheLayer(ctx, s, key, *entry) + } + + return entries, nil +} + +// Builds remaining layers (those not already cached) via Nix. +func buildLayers(s *State, image *Image, grouped []layers.Layer) (map[string]string, error) { srcType, srcArgs := s.Cfg.Pkgs.Render(image.Tag) args := []string{ "--argstr", "srcType", srcType, @@ -381,30 +425,11 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro }, nil } - layerResult, err := prepareLayers(ctx, s, image, &imageResult.Graph) + layers, err := prepareLayers(ctx, s, image, imageResult) if err != nil { return nil, err } - layerResult[imageResult.SymlinkLayer.SHA256] = imageResult.SymlinkLayer.Path - - layers := []manifest.Entry{} - for key, path := range layerResult { - f, err := os.Open(path) - if err != nil { - log.Printf("failed to open layer at '%s': %s\n", path, err) - return nil, err - } - - entry, err := uploadHashLayer(ctx, s, key, f) - f.Close() - if err != nil { - return nil, err - } - - layers = append(layers, *entry) - } - m, c := manifest.Manifest(layers) if _, err = uploadHashLayer(ctx, s, c.SHA256, bytes.NewReader(c.Config)); err != nil { log.Printf("failed to upload config for %s: %s\n", image.Name, err) From 43a642435b653d04d730de50735d310f1f1083eb Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 12:49:26 +0100 Subject: [PATCH 126/223] feat(server): Reimplement local manifest cache backed by files Implements a local manifest cache that uses the temporary directory to cache manifest builds. This is necessary due to the size of manifests: Keeping them entirely in-memory would quickly balloon the memory usage of Nixery, unless some mechanism for cache eviction is implemented. --- tools/nixery/server/builder/builder.go | 11 ++++ tools/nixery/server/builder/cache.go | 69 +++++++++++++++++--------- tools/nixery/server/builder/state.go | 24 --------- tools/nixery/server/config/config.go | 6 +-- tools/nixery/server/main.go | 13 ++++- 5 files changed, 71 insertions(+), 52 deletions(-) delete mode 100644 tools/nixery/server/builder/state.go diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 9f529b226..e622f815a 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -34,6 +34,8 @@ import ( "sort" "strings" + "cloud.google.com/go/storage" + "github.com/google/nixery/config" "github.com/google/nixery/layers" "github.com/google/nixery/manifest" "golang.org/x/oauth2/google" @@ -50,6 +52,15 @@ const gcsScope = "https://www.googleapis.com/auth/devstorage.read_write" // HTTP client to use for direct calls to APIs that are not part of the SDK var client = &http.Client{} +// State holds the runtime state that is carried around in Nixery and +// passed to builder functions. +type State struct { + Bucket *storage.BucketHandle + Cache *LocalCache + Cfg config.Config + Pop layers.Popularity +} + // Image represents the information necessary for building a container image. // This can be either a list of package names (corresponding to keys in the // nixpkgs set) or a Nix expression that results in a *list* of derivations. diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index ab0021f6d..060ed9a84 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -20,6 +20,7 @@ import ( "io" "io/ioutil" "log" + "os" "sync" "github.com/google/nixery/manifest" @@ -29,40 +30,64 @@ import ( // manifests and layer uploads. type LocalCache struct { // Manifest cache - mmtx sync.RWMutex - mcache map[string]string + mmtx sync.RWMutex + mdir string // Layer cache lmtx sync.RWMutex lcache map[string]manifest.Entry } -func NewCache() LocalCache { - return LocalCache{ - mcache: make(map[string]string), - lcache: make(map[string]manifest.Entry), +// Creates an in-memory cache and ensures that the local file path for +// manifest caching exists. +func NewCache() (LocalCache, error) { + path := os.TempDir() + "/nixery" + err := os.MkdirAll(path, 0755) + if err != nil { + return LocalCache{}, err } + + return LocalCache{ + mdir: path + "/", + lcache: make(map[string]manifest.Entry), + }, nil } // Retrieve a cached manifest if the build is cacheable and it exists. -func (c *LocalCache) manifestFromLocalCache(key string) (string, bool) { +func (c *LocalCache) manifestFromLocalCache(key string) (json.RawMessage, bool) { c.mmtx.RLock() - path, ok := c.mcache[key] - c.mmtx.RUnlock() + defer c.mmtx.RUnlock() - if !ok { - return "", false + f, err := os.Open(c.mdir + key) + if err != nil { + // TODO(tazjin): Once log levels are available, this + // might warrant a debug log. + return nil, false + } + defer f.Close() + + m, err := ioutil.ReadAll(f) + if err != nil { + log.Printf("Failed to read manifest '%s' from local cache: %s\n", key, err) + return nil, false } - return path, true + return json.RawMessage(m), true } // Adds the result of a manifest build to the local cache, if the // manifest is considered cacheable. -func (c *LocalCache) localCacheManifest(key, path string) { +// +// Manifests can be quite large and are cached on disk instead of in +// memory. +func (c *LocalCache) localCacheManifest(key string, m json.RawMessage) { c.mmtx.Lock() - c.mcache[key] = path - c.mmtx.Unlock() + defer c.mmtx.Unlock() + + err := ioutil.WriteFile(c.mdir+key, []byte(m), 0644) + if err != nil { + log.Printf("Failed to locally cache manifest for '%s': %s\n", key, err) + } } // Retrieve a layer build from the local cache. @@ -84,11 +109,9 @@ func (c *LocalCache) localCacheLayer(key string, e manifest.Entry) { // Retrieve a manifest from the cache(s). First the local cache is // checked, then the GCS-bucket cache. func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessage, bool) { - // path, cached := s.Cache.manifestFromLocalCache(key) - // if cached { - // return path, true - // } - // TODO: local cache? + if m, cached := s.Cache.manifestFromLocalCache(key); cached { + return m, true + } obj := s.Bucket.Object("manifests/" + key) @@ -110,15 +133,15 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa log.Printf("Failed to read cached manifest for '%s': %s\n", key, err) } - // TODO: locally cache manifest, but the cache needs to be changed + go s.Cache.localCacheManifest(key, m) log.Printf("Retrieved manifest for sha1:%s from GCS\n", key) + return json.RawMessage(m), true } // Add a manifest to the bucket & local caches func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) { - // go s.Cache.localCacheManifest(key, path) - // TODO local cache + go s.Cache.localCacheManifest(key, m) obj := s.Bucket.Object("manifests/" + key) w := obj.NewWriter(ctx) diff --git a/tools/nixery/server/builder/state.go b/tools/nixery/server/builder/state.go deleted file mode 100644 index 1c7f58821..000000000 --- a/tools/nixery/server/builder/state.go +++ /dev/null @@ -1,24 +0,0 @@ -package builder - -import ( - "cloud.google.com/go/storage" - "github.com/google/nixery/config" - "github.com/google/nixery/layers" -) - -// State holds the runtime state that is carried around in Nixery and -// passed to builder functions. -type State struct { - Bucket *storage.BucketHandle - Cache LocalCache - Cfg config.Config - Pop layers.Popularity -} - -func NewState(bucket *storage.BucketHandle, cfg config.Config) State { - return State{ - Bucket: bucket, - Cfg: cfg, - Cache: NewCache(), - } -} diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index 30f727db1..84c2b89d1 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -71,13 +71,13 @@ type Config struct { PopUrl string // URL to the Nix package popularity count } -func FromEnv() (*Config, error) { +func FromEnv() (Config, error) { pkgs, err := pkgSourceFromEnv() if err != nil { - return nil, err + return Config{}, err } - return &Config{ + return Config{ Bucket: getConfig("BUCKET", "GCS bucket for layer storage", ""), Port: getConfig("PORT", "HTTP port", ""), Pkgs: pkgs, diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 1ff3a471f..aeb70163d 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -194,8 +194,17 @@ func main() { } ctx := context.Background() - bucket := prepareBucket(ctx, cfg) - state := builder.NewState(bucket, *cfg) + bucket := prepareBucket(ctx, &cfg) + cache, err := builder.NewCache() + if err != nil { + log.Fatalln("Failed to instantiate build cache", err) + } + + state := builder.State{ + Bucket: bucket, + Cache: &cache, + Cfg: cfg, + } log.Printf("Starting Nixery on port %s\n", cfg.Port) From feba42e40933fe932b1ca330d2c919ae018a9a7f Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 13:02:48 +0100 Subject: [PATCH 127/223] feat(server): Fetch popularity data on launch The last missing puzzle piece for #50! --- tools/nixery/server/main.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index aeb70163d..b87af6506 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -29,6 +29,7 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" "log" "net/http" "regexp" @@ -37,6 +38,7 @@ import ( "cloud.google.com/go/storage" "github.com/google/nixery/builder" "github.com/google/nixery/config" + "github.com/google/nixery/layers" ) // ManifestMediaType is the Content-Type used for the manifest itself. This @@ -96,6 +98,32 @@ func prepareBucket(ctx context.Context, cfg *config.Config) *storage.BucketHandl return bkt } +// Downloads the popularity information for the package set from the +// URL specified in Nixery's configuration. +func downloadPopularity(url string) (layers.Popularity, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("popularity download from '%s' returned status: %s\n", url, resp.Status) + } + + j, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var pop layers.Popularity + err = json.Unmarshal(j, &pop) + if err != nil { + return nil, err + } + + return pop, nil +} + // Error format corresponding to the registry protocol V2 specification. This // allows feeding back errors to clients in a way that can be presented to // users. @@ -200,10 +228,19 @@ func main() { log.Fatalln("Failed to instantiate build cache", err) } + var pop layers.Popularity + if cfg.PopUrl != "" { + pop, err = downloadPopularity(cfg.PopUrl) + if err != nil { + log.Fatalln("Failed to fetch popularity information", err) + } + } + state := builder.State{ Bucket: bucket, Cache: &cache, Cfg: cfg, + Pop: pop, } log.Printf("Starting Nixery on port %s\n", cfg.Port) From 1124b8c236a2f01a1eec2420131627d29f678c9d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 13:08:23 +0100 Subject: [PATCH 128/223] fix(server): Do not invoke layer build if no layers are missing This previously invoked a Nix derivation that spent a few seconds on making an empty object in JSON ... --- tools/nixery/server/builder/builder.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index e622f815a..81dbd26db 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -277,6 +277,11 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes // Builds remaining layers (those not already cached) via Nix. func buildLayers(s *State, image *Image, grouped []layers.Layer) (map[string]string, error) { + result := make(map[string]string) + if len(grouped) == 0 { + return result, nil + } + srcType, srcArgs := s.Cfg.Pkgs.Render(image.Tag) args := []string{ "--argstr", "srcType", srcType, @@ -312,7 +317,6 @@ func buildLayers(s *State, image *Image, grouped []layers.Layer) (map[string]str } log.Printf("Finished layer preparation for '%s' via Nix\n", image.Name) - result := make(map[string]string) err = json.Unmarshal(output, &result) if err != nil { return nil, err From 6b06fe27be2dcf0741ff7981838fbb3a022181b7 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 18:18:52 +0100 Subject: [PATCH 129/223] feat(server): Implement creation of layer tarballs in the server This will create, upload and hash the layer tarballs in one disk read. --- tools/nixery/server/builder/archive.go | 92 +++++++++++++++ tools/nixery/server/builder/builder.go | 149 +++++++++++-------------- 2 files changed, 160 insertions(+), 81 deletions(-) create mode 100644 tools/nixery/server/builder/archive.go diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/server/builder/archive.go new file mode 100644 index 000000000..43fd19708 --- /dev/null +++ b/tools/nixery/server/builder/archive.go @@ -0,0 +1,92 @@ +package builder + +// This file implements logic for walking through a directory and creating a +// tarball of it. +// +// The tarball is written straight to the supplied reader, which makes it +// possible to create an image layer from the specified store paths, hash it and +// upload it in one reading pass. + +import ( + "archive/tar" + "io" + "log" + "os" + "path/filepath" + + "github.com/google/nixery/layers" +) + +// Create a new tarball from each of the paths in the list and write the tarball +// to the supplied writer. +func tarStorePaths(l *layers.Layer, w io.Writer) error { + t := tar.NewWriter(w) + + for _, path := range l.Contents { + err := filepath.Walk(path, tarStorePath(t)) + if err != nil { + return err + } + } + + if err := t.Close(); err != nil { + return err + } + + log.Printf("Created layer for '%s'\n", l.Hash()) + return nil +} + +func tarStorePath(w *tar.Writer) filepath.WalkFunc { + return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // If the entry is not a symlink or regular file, skip it. + if info.Mode()&os.ModeSymlink == 0 && !info.Mode().IsRegular() { + return nil + } + + // the symlink target is read if this entry is a symlink, as it + // is required when creating the file header + var link string + if info.Mode()&os.ModeSymlink != 0 { + link, err = os.Readlink(path) + if err != nil { + return err + } + } + + header, err := tar.FileInfoHeader(info, link) + if err != nil { + return err + } + + // The name retrieved from os.FileInfo only contains the file's + // basename, but the full path is required within the layer + // tarball. + header.Name = path + if err = w.WriteHeader(header); err != nil { + return err + } + + // At this point, return if no file content needs to be written + if !info.Mode().IsRegular() { + return nil + } + + f, err := os.Open(path) + if err != nil { + return err + } + + if _, err := io.Copy(w, f); err != nil { + return err + } + + f.Close() + + return nil + } +} diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 81dbd26db..d8cbbb8f2 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -232,99 +232,55 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes grouped := layers.Group(&result.Graph, &s.Pop, LayerBudget) var entries []manifest.Entry - var missing []layers.Layer // Splits the layers into those which are already present in - // the cache, and those that are missing (i.e. need to be - // built by Nix). + // the cache, and those that are missing. + // + // Missing layers are built and uploaded to the storage + // bucket. for _, l := range grouped { if entry, cached := layerFromCache(ctx, s, l.Hash()); cached { entries = append(entries, *entry) } else { - missing = append(missing, l) - } - } + lw := func(w io.Writer) error { + return tarStorePaths(&l, w) + } - built, err := buildLayers(s, image, missing) - if err != nil { - log.Printf("Failed to build missing layers: %s\n", err) - return nil, err + entry, err := uploadHashLayer(ctx, s, l.Hash(), lw) + if err != nil { + return nil, err + } + + go cacheLayer(ctx, s, l.Hash(), *entry) + entries = append(entries, *entry) + } } // Symlink layer (built in the first Nix build) needs to be - // included when hashing & uploading - built[result.SymlinkLayer.SHA256] = result.SymlinkLayer.Path - - for key, path := range built { - f, err := os.Open(path) + // included here manually: + slkey := result.SymlinkLayer.SHA256 + entry, err := uploadHashLayer(ctx, s, slkey, func(w io.Writer) error { + f, err := os.Open(result.SymlinkLayer.Path) if err != nil { - log.Printf("failed to open layer at '%s': %s\n", path, err) - return nil, err + log.Printf("failed to upload symlink layer '%s': %s\n", slkey, err) + return err } + defer f.Close() - entry, err := uploadHashLayer(ctx, s, key, f) - f.Close() - if err != nil { - return nil, err - } + _, err = io.Copy(w, f) + return err + }) - entries = append(entries, *entry) - go cacheLayer(ctx, s, key, *entry) + if err != nil { + return nil, err } + go cacheLayer(ctx, s, slkey, *entry) + entries = append(entries, *entry) + return entries, nil } -// Builds remaining layers (those not already cached) via Nix. -func buildLayers(s *State, image *Image, grouped []layers.Layer) (map[string]string, error) { - result := make(map[string]string) - if len(grouped) == 0 { - return result, nil - } - - srcType, srcArgs := s.Cfg.Pkgs.Render(image.Tag) - args := []string{ - "--argstr", "srcType", srcType, - "--argstr", "srcArgs", srcArgs, - } - - layerInput := make(map[string][]string) - allPaths := []string{} - for _, l := range grouped { - layerInput[l.Hash()] = l.Contents - - // The derivation responsible for building layers does not - // have the derivations that resulted in the required store - // paths in its context, which means that its sandbox will not - // contain the necessary paths if sandboxing is enabled. - // - // To work around this, all required store paths are added as - // 'extra-sandbox-paths' parameters. - for _, p := range l.Contents { - allPaths = append(allPaths, p) - } - } - - args = append(args, "--option", "extra-sandbox-paths", strings.Join(allPaths, " ")) - - j, _ := json.Marshal(layerInput) - args = append(args, "--argstr", "layers", string(j)) - - output, err := callNix("nixery-build-layers", image.Name, args) - if err != nil { - log.Printf("failed to call nixery-build-layers: %s\n", err) - return nil, err - } - log.Printf("Finished layer preparation for '%s' via Nix\n", image.Name) - - err = json.Unmarshal(output, &result) - if err != nil { - return nil, err - } - - return result, nil -} - // renameObject renames an object in the specified Cloud Storage // bucket. // @@ -368,7 +324,30 @@ func renameObject(ctx context.Context, s *State, old, new string) error { return nil } -// Upload a to the storage bucket, while hashing it at the same time. +// layerWriter is the type for functions that can write a layer to the +// multiwriter used for uploading & hashing. +// +// This type exists to avoid duplication between the handling of +// symlink layers and store path layers. +type layerWriter func(w io.Writer) error + +// byteCounter is a special io.Writer that counts all bytes written to +// it and does nothing else. +// +// This is required because the ad-hoc writing of tarballs leaves no +// single place to count the final tarball size otherwise. +type byteCounter struct { + count int64 +} + +func (b *byteCounter) Write(p []byte) (n int, err error) { + b.count += int64(len(p)) + return len(p), nil +} + +// Upload a layer tarball to the storage bucket, while hashing it at +// the same time. The supplied function is expected to provide the +// layer data to the writer. // // The initial upload is performed in a 'staging' folder, as the // SHA256-hash is not yet available when the upload is initiated. @@ -378,24 +357,24 @@ func renameObject(ctx context.Context, s *State, old, new string) error { // // The return value is the layer's SHA256 hash, which is used in the // image manifest. -func uploadHashLayer(ctx context.Context, s *State, key string, data io.Reader) (*manifest.Entry, error) { +func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) (*manifest.Entry, error) { staging := s.Bucket.Object("staging/" + key) // Sets up a "multiwriter" that simultaneously runs both hash // algorithms and uploads to the bucket sw := staging.NewWriter(ctx) shasum := sha256.New() - multi := io.MultiWriter(sw, shasum) + counter := &byteCounter{} + multi := io.MultiWriter(sw, shasum, counter) - size, err := io.Copy(multi, data) + err := lw(multi) if err != nil { - log.Printf("failed to upload layer '%s' to staging: %s\n", key, err) + log.Printf("failed to create and upload layer '%s': %s\n", key, err) return nil, err } if err = sw.Close(); err != nil { log.Printf("failed to upload layer '%s' to staging: %s\n", key, err) - return nil, err } sha256sum := fmt.Sprintf("%x", shasum.Sum([]byte{})) @@ -408,6 +387,7 @@ func uploadHashLayer(ctx context.Context, s *State, key string, data io.Reader) return nil, err } + size := counter.count log.Printf("Uploaded layer sha256:%s (%v bytes written)", sha256sum, size) entry := manifest.Entry{ @@ -446,7 +426,14 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro } m, c := manifest.Manifest(layers) - if _, err = uploadHashLayer(ctx, s, c.SHA256, bytes.NewReader(c.Config)); err != nil { + + lw := func(w io.Writer) error { + r := bytes.NewReader(c.Config) + _, err := io.Copy(w, r) + return err + } + + if _, err = uploadHashLayer(ctx, s, c.SHA256, lw); err != nil { log.Printf("failed to upload config for %s: %s\n", image.Name, err) return nil, err } From 0d820423e973727ddfc4b461a1063f719873743c Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 22:16:37 +0100 Subject: [PATCH 130/223] chore(build-image): Remove nixery-build-layers This functionality has been rolled into the server component and is no longer required. --- tools/nixery/build-image/build-layers.nix | 47 ----------------------- tools/nixery/build-image/default.nix | 24 ++++-------- tools/nixery/default.nix | 9 ++--- 3 files changed, 10 insertions(+), 70 deletions(-) delete mode 100644 tools/nixery/build-image/build-layers.nix diff --git a/tools/nixery/build-image/build-layers.nix b/tools/nixery/build-image/build-layers.nix deleted file mode 100644 index 9a8742f13..000000000 --- a/tools/nixery/build-image/build-layers.nix +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -{ - # Description of the package set to be used (will be loaded by load-pkgs.nix) - srcType ? "nixpkgs", - srcArgs ? "nixos-19.03", - importArgs ? { }, - # Path to load-pkgs.nix - loadPkgs ? ./load-pkgs.nix, - # Layers to assemble into tarballs - layers ? "{}" -}: - -let - inherit (builtins) fromJSON mapAttrs toJSON; - inherit (pkgs) lib runCommand writeText; - - pkgs = import loadPkgs { inherit srcType srcArgs importArgs; }; - - # Given a list of store paths, create an image layer tarball with - # their contents. - pathsToLayer = paths: runCommand "layer.tar" { - } '' - tar --no-recursion -Prf "$out" \ - --mtime="@$SOURCE_DATE_EPOCH" \ - --owner=0 --group=0 /nix /nix/store - - tar -Prpf "$out" --hard-dereference --sort=name \ - --mtime="@$SOURCE_DATE_EPOCH" \ - --owner=0 --group=0 ${lib.concatStringsSep " " paths} - ''; - - - layerTarballs = mapAttrs (_: pathsToLayer ) (fromJSON layers); -in writeText "layer-tarballs.json" (toJSON layerTarballs) diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/build-image/default.nix index 0800eb959..a61ac06bd 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/build-image/default.nix @@ -20,20 +20,10 @@ { pkgs ? import {} }: -{ - build-image = pkgs.writeShellScriptBin "nixery-build-image" '' - exec ${pkgs.nix}/bin/nix-build \ - --show-trace \ - --no-out-link "$@" \ - --argstr loadPkgs ${./load-pkgs.nix} \ - ${./build-image.nix} - ''; - - build-layers = pkgs.writeShellScriptBin "nixery-build-layers" '' - exec ${pkgs.nix}/bin/nix-build \ - --show-trace \ - --no-out-link "$@" \ - --argstr loadPkgs ${./load-pkgs.nix} \ - ${./build-layers.nix} - ''; -} +pkgs.writeShellScriptBin "nixery-build-image" '' + exec ${pkgs.nix}/bin/nix-build \ + --show-trace \ + --no-out-link "$@" \ + --argstr loadPkgs ${./load-pkgs.nix} \ + ${./build-image.nix} +'' diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index af506eea3..b194079b9 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -18,8 +18,7 @@ with pkgs; -let builders = import ./build-image { inherit pkgs; }; -in rec { +rec { # Go implementation of the Nixery server which implements the # container registry interface. # @@ -29,8 +28,7 @@ in rec { nixery-server = callPackage ./server { }; # Implementation of the Nix image building logic - nixery-build-image = builders.build-image; - nixery-build-layers = builders.build-layers; + nixery-build-image = import ./build-image { inherit pkgs; }; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at @@ -44,7 +42,7 @@ in rec { # are installing Nixery directly. nixery-bin = writeShellScriptBin "nixery" '' export WEB_DIR="${nixery-book}" - export PATH="${nixery-build-layers}/bin:${nixery-build-image}/bin:$PATH" + export PATH="${nixery-build-image}/bin:$PATH" exec ${nixery-server}/bin/nixery ''; @@ -96,7 +94,6 @@ in rec { iana-etc nix nixery-build-image - nixery-build-layers nixery-launch-script openssh zlib From 48a5ecda97e4b2ea9faa2d3031376078ccc301be Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 20:18:40 +0100 Subject: [PATCH 131/223] feat(server): Order layers in image manifest based on merge rating Image layers in manifests are now sorted in a stable (descending) order based on their merge rating, meaning that layers more likely to be shared between images come first. The reason for this change is Docker's handling of image layers on overlayfs2: Images are condensed into a single representation on disk after downloading. Due to this Docker will constantly redownload all layers that are applied in a different order in different images (layer order matters in imperatively created images), based on something it calls the 'ChainID'. Sorting the layers this way raises the likelihood of a long chain of matching layers at the beginning of an image. This relates to #39. --- tools/nixery/server/builder/builder.go | 1 + tools/nixery/server/layers/grouping.go | 8 ++++---- tools/nixery/server/manifest/manifest.go | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index d8cbbb8f2..614291e66 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -250,6 +250,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes if err != nil { return nil, err } + entry.MergeRating = l.MergeRating go cacheLayer(ctx, s, l.Hash(), *entry) entries = append(entries, *entry) diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index 07a9e0e23..9992cd3c1 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -141,7 +141,7 @@ type Popularity = map[string]int // build for the container image. type Layer struct { Contents []string `json:"contents"` - mergeRating uint64 + MergeRating uint64 } // Hash the contents of a layer to create a deterministic identifier that can be @@ -153,7 +153,7 @@ func (l *Layer) Hash() string { func (a Layer) merge(b Layer) Layer { a.Contents = append(a.Contents, b.Contents...) - a.mergeRating += b.mergeRating + a.MergeRating += b.MergeRating return a } @@ -291,7 +291,7 @@ func groupLayer(dt *flow.DominatorTree, root *closure) Layer { // both the size and the popularity when making merge // decisions, but there might be a smarter way to do // it than a plain multiplication. - mergeRating: uint64(root.Popularity) * size, + MergeRating: uint64(root.Popularity) * size, } } @@ -309,7 +309,7 @@ func dominate(budget int, graph *simple.DirectedGraph) []Layer { } sort.Slice(layers, func(i, j int) bool { - return layers[i].mergeRating < layers[j].mergeRating + return layers[i].MergeRating < layers[j].MergeRating }) if len(layers) > budget { diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go index f777e3f58..2f236178b 100644 --- a/tools/nixery/server/manifest/manifest.go +++ b/tools/nixery/server/manifest/manifest.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "sort" ) const ( @@ -27,6 +28,10 @@ type Entry struct { MediaType string `json:"mediaType,omitempty"` Size int64 `json:"size"` Digest string `json:"digest"` + + // This field is internal to Nixery and not part of the + // serialised entry. + MergeRating uint64 `json:"-"` } type manifest struct { @@ -85,6 +90,16 @@ func configLayer(hashes []string) ConfigLayer { // // Callers do not need to set the media type for the layer entries. func Manifest(layers []Entry) (json.RawMessage, ConfigLayer) { + // Sort layers by their merge rating, from highest to lowest. + // This makes it likely for a contiguous chain of shared image + // layers to appear at the beginning of a layer. + // + // Due to moby/moby#38446 Docker considers the order of layers + // when deciding which layers to download again. + sort.Slice(layers, func(i, j int) bool { + return layers[i].MergeRating > layers[j].MergeRating + }) + hashes := make([]string, len(layers)) for i, l := range layers { l.MediaType = "application/vnd.docker.image.rootfs.diff.tar" From 9bb6d0ae255c1340fe16687d740fad948e6a9335 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 22:13:13 +0100 Subject: [PATCH 132/223] fix(server): Ensure build cache objects are written to GCS Cache writes might not be flushed without this call. --- tools/nixery/server/builder/cache.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 060ed9a84..b3b9dffab 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -213,4 +213,9 @@ func cacheLayer(ctx context.Context, s *State, key string, entry manifest.Entry) log.Printf("failed to cache build '%s': %s\n", key, err) return } + + if err = w.Close(); err != nil { + log.Printf("failed to cache build '%s': %s\n", key, err) + return + } } From d9b329ef59e35ae6070eae867cf06a5230ae3d51 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 3 Oct 2019 22:13:40 +0100 Subject: [PATCH 133/223] refactor(server): Always include 'cacert' & 'iana-etc' These two packages almost always end up being required by programs, but people don't necessarily consider them. They will now always be added and their popularity is artificially inflated to ensure they end up at the top of the layer list. --- tools/nixery/server/builder/builder.go | 5 +++-- tools/nixery/server/layers/grouping.go | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 614291e66..7f391838f 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -87,7 +87,7 @@ type BuildResult struct { // be used to invoke Nix. // // It will expand convenience names under the hood (see the `convenienceNames` -// function below). +// function below) and append packages that are always included (cacert, iana-etc). // // Once assembled the image structure uses a sorted representation of // the name. This is to avoid unnecessarily cache-busting images if @@ -95,6 +95,7 @@ type BuildResult struct { func ImageFromName(name string, tag string) Image { pkgs := strings.Split(name, "/") expanded := convenienceNames(pkgs) + expanded = append(expanded, "cacert", "iana-etc") sort.Strings(pkgs) sort.Strings(expanded) @@ -131,7 +132,7 @@ type ImageResult struct { // // * `shell`: Includes bash, coreutils and other common command-line tools func convenienceNames(packages []string) []string { - shellPackages := []string{"bashInteractive", "cacert", "coreutils", "iana-etc", "moreutils", "nano"} + shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} if packages[0] == "shell" { return append(packages[1:], shellPackages...) diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index 9992cd3c1..9dbd5e88c 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -186,13 +186,11 @@ func (c *closure) bigOrPopular() bool { return true } - // The threshold value used here is currently roughly the - // minimum number of references that only 1% of packages in - // the entire package set have. - // - // TODO(tazjin): Do this more elegantly by calculating - // percentiles for each package and using those instead. - if c.Popularity >= 1000 { + // Threshold value is picked arbitrarily right now. The reason + // for this is that some packages (such as `cacert`) have very + // few direct dependencies, but are required by pretty much + // everything. + if c.Popularity >= 100 { return true } @@ -241,7 +239,17 @@ func buildGraph(refs *RuntimeGraph, pop *Popularity) *simple.DirectedGraph { Refs: c.Refs, } - if p, ok := (*pop)[node.DOTID()]; ok { + // The packages `nss-cacert` and `iana-etc` are added + // by Nixery to *every single image* and should have a + // very high popularity. + // + // Other popularity values are populated from the data + // set assembled by Nixery's popcount. + id := node.DOTID() + if strings.HasPrefix(id, "nss-cacert") || strings.HasPrefix(id, "iana-etc") { + // glibc has ~300k references, these packages need *more* + node.Popularity = 500000 + } else if p, ok := (*pop)[id]; ok { node.Popularity = p } else { node.Popularity = 1 From f4bf3518f63501ddff42592b255fd5feaf846863 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 4 Oct 2019 22:17:11 +0100 Subject: [PATCH 134/223] refactor(server): Replace log calls with logrus This introduces a structured logging library that can be used (next step) to attach additional metadata to log entries. --- tools/nixery/server/builder/archive.go | 2 +- tools/nixery/server/builder/builder.go | 2 +- tools/nixery/server/builder/cache.go | 2 +- tools/nixery/server/config/config.go | 2 +- tools/nixery/server/config/pkgsource.go | 3 ++- tools/nixery/server/go-deps.nix | 9 +++++++++ tools/nixery/server/layers/grouping.go | 2 +- tools/nixery/server/main.go | 2 +- 8 files changed, 17 insertions(+), 7 deletions(-) diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/server/builder/archive.go index 43fd19708..6d5033f13 100644 --- a/tools/nixery/server/builder/archive.go +++ b/tools/nixery/server/builder/archive.go @@ -10,11 +10,11 @@ package builder import ( "archive/tar" "io" - "log" "os" "path/filepath" "github.com/google/nixery/layers" + log "github.com/sirupsen/logrus" ) // Create a new tarball from each of the paths in the list and write the tarball diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 7f391838f..3f085c565 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -26,7 +26,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "net/url" "os" @@ -38,6 +37,7 @@ import ( "github.com/google/nixery/config" "github.com/google/nixery/layers" "github.com/google/nixery/manifest" + log "github.com/sirupsen/logrus" "golang.org/x/oauth2/google" ) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index b3b9dffab..4a060ba5e 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -19,11 +19,11 @@ import ( "encoding/json" "io" "io/ioutil" - "log" "os" "sync" "github.com/google/nixery/manifest" + log "github.com/sirupsen/logrus" ) // LocalCache implements the structure used for local caching of diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index 84c2b89d1..abc067855 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -19,10 +19,10 @@ package config import ( "io/ioutil" - "log" "os" "cloud.google.com/go/storage" + log "github.com/sirupsen/logrus" ) // Load (optional) GCS bucket signing data from the GCS_SIGNING_KEY and diff --git a/tools/nixery/server/config/pkgsource.go b/tools/nixery/server/config/pkgsource.go index 61bea33df..98719ecce 100644 --- a/tools/nixery/server/config/pkgsource.go +++ b/tools/nixery/server/config/pkgsource.go @@ -17,10 +17,11 @@ import ( "crypto/sha1" "encoding/json" "fmt" - "log" "os" "regexp" "strings" + + log "github.com/sirupsen/logrus" ) // PkgSource represents the source from which the Nix package set used diff --git a/tools/nixery/server/go-deps.nix b/tools/nixery/server/go-deps.nix index 7c40c6dde..847b44dce 100644 --- a/tools/nixery/server/go-deps.nix +++ b/tools/nixery/server/go-deps.nix @@ -117,4 +117,13 @@ sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; }; } + { + goPackagePath = "github.com/sirupsen/logrus"; + fetch = { + type = "git"; + url = "https://github.com/sirupsen/logrus"; + rev = "de736cf91b921d56253b4010270681d33fdf7cb5"; + sha256 = "1qixss8m5xy7pzbf0qz2k3shjw0asklm9sj6zyczp7mryrari0aj"; + }; + } ] diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index 9dbd5e88c..95198c90d 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -105,11 +105,11 @@ package layers import ( "crypto/sha1" "fmt" - "log" "regexp" "sort" "strings" + log "github.com/sirupsen/logrus" "gonum.org/v1/gonum/graph/flow" "gonum.org/v1/gonum/graph/simple" ) diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index b87af6506..bad4c190c 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -30,7 +30,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net/http" "regexp" "time" @@ -39,6 +38,7 @@ import ( "github.com/google/nixery/builder" "github.com/google/nixery/config" "github.com/google/nixery/layers" + log "github.com/sirupsen/logrus" ) // ManifestMediaType is the Content-Type used for the manifest itself. This From 0642f7044dea2127b1c7dab1d88d90638536183a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 5 Oct 2019 14:54:49 +0100 Subject: [PATCH 135/223] fix(server): Amend package path for Go tooling compatibility With these changes it is possible to keep Nixery in $GOPATH and build the server in there, while still having things work correctly via Nix. --- tools/nixery/default.nix | 2 +- tools/nixery/server/builder/archive.go | 2 +- tools/nixery/server/builder/builder.go | 6 +++--- tools/nixery/server/builder/cache.go | 2 +- tools/nixery/server/default.nix | 2 +- tools/nixery/server/main.go | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index b194079b9..c1a3c9f7d 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -43,7 +43,7 @@ rec { nixery-bin = writeShellScriptBin "nixery" '' export WEB_DIR="${nixery-book}" export PATH="${nixery-build-image}/bin:$PATH" - exec ${nixery-server}/bin/nixery + exec ${nixery-server}/bin/server ''; # Container image containing Nixery and Nix itself. This image can diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/server/builder/archive.go index 6d5033f13..6a2bd8e4b 100644 --- a/tools/nixery/server/builder/archive.go +++ b/tools/nixery/server/builder/archive.go @@ -13,7 +13,7 @@ import ( "os" "path/filepath" - "github.com/google/nixery/layers" + "github.com/google/nixery/server/layers" log "github.com/sirupsen/logrus" ) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 3f085c565..b675da0f7 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -34,9 +34,9 @@ import ( "strings" "cloud.google.com/go/storage" - "github.com/google/nixery/config" - "github.com/google/nixery/layers" - "github.com/google/nixery/manifest" + "github.com/google/nixery/server/config" + "github.com/google/nixery/server/layers" + "github.com/google/nixery/server/manifest" log "github.com/sirupsen/logrus" "golang.org/x/oauth2/google" ) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 4a060ba5e..5b6bf078b 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -22,7 +22,7 @@ import ( "os" "sync" - "github.com/google/nixery/manifest" + "github.com/google/nixery/server/manifest" log "github.com/sirupsen/logrus" ) diff --git a/tools/nixery/server/default.nix b/tools/nixery/server/default.nix index 9df527218..573447a6c 100644 --- a/tools/nixery/server/default.nix +++ b/tools/nixery/server/default.nix @@ -19,7 +19,7 @@ buildGoPackage { goDeps = ./go-deps.nix; src = ./.; - goPackagePath = "github.com/google/nixery"; + goPackagePath = "github.com/google/nixery/server"; # Enable checks and configure check-phase to include vet: doCheck = true; diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index bad4c190c..878e59ff6 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -35,9 +35,9 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/google/nixery/builder" - "github.com/google/nixery/config" - "github.com/google/nixery/layers" + "github.com/google/nixery/server/builder" + "github.com/google/nixery/server/config" + "github.com/google/nixery/server/layers" log "github.com/sirupsen/logrus" ) From 95abb1bcde75253aa35669eed26f734d02c6a870 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 5 Oct 2019 14:55:40 +0100 Subject: [PATCH 136/223] feat(server): Initial Stackdriver-compatible log formatter This formatter has basic support for the Stackdriver Error Reporting format, but several things are still lacking: * the service version (preferably git commit?) needs to be included in the server somehow * log streams should be split between stdout/stderr as that is how AppEngine (and several other GCP services?) seemingly differentiate between info/error logs --- tools/nixery/server/logs.go | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tools/nixery/server/logs.go diff --git a/tools/nixery/server/logs.go b/tools/nixery/server/logs.go new file mode 100644 index 000000000..55e0a13a0 --- /dev/null +++ b/tools/nixery/server/logs.go @@ -0,0 +1,68 @@ +package main + +// This file configures different log formatters via logrus. The +// standard formatter uses a structured JSON format that is compatible +// with Stackdriver Error Reporting. +// +// https://cloud.google.com/error-reporting/docs/formatting-error-messages + +import ( + "bytes" + "encoding/json" + log "github.com/sirupsen/logrus" +) + +type stackdriverFormatter struct{} + +type serviceContext struct { + Service string `json:"service"` + Version string `json:"version"` +} + +type reportLocation struct { + FilePath string `json:"filePath"` + LineNumber int `json:"lineNumber"` + FunctionName string `json:"functionName"` +} + +var nixeryContext = serviceContext{ + Service: "nixery", + Version: "TODO(tazjin)", // angry? +} + +// isError determines whether an entry should be logged as an error +// (i.e. with attached `context`). +// +// This requires the caller information to be present on the log +// entry, as stacktraces are not available currently. +func isError(e *log.Entry) bool { + l := e.Level + return (l == log.ErrorLevel || l == log.FatalLevel || l == log.PanicLevel) && + e.HasCaller() +} + +func (f stackdriverFormatter) Format(e *log.Entry) ([]byte, error) { + msg := e.Data + msg["serviceContext"] = &nixeryContext + msg["message"] = &e.Message + msg["eventTime"] = &e.Time + + if isError(e) { + loc := reportLocation{ + FilePath: e.Caller.File, + LineNumber: e.Caller.Line, + FunctionName: e.Caller.Function, + } + msg["context"] = &loc + } + + b := new(bytes.Buffer) + err := json.NewEncoder(b).Encode(&msg) + + return b.Bytes(), err +} + +func init() { + log.SetReportCaller(true) + log.SetFormatter(stackdriverFormatter{}) +} From 6912658c72291caefd7c7ea6312a35c3a686cf61 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 5 Oct 2019 22:33:41 +0100 Subject: [PATCH 137/223] feat(server): Use hash of Nixery source as version Uses a hash of Nixery's sources as the version displayed when Nixery launches or logs an error. This makes it possible to distinguish between errors logged from different versions. The source hashes should be reproducible between different checkouts of the same source tree. --- tools/nixery/default.nix | 11 ++++++++- tools/nixery/server/default.nix | 40 ++++++++++++++++++++++++++------- tools/nixery/server/logs.go | 2 +- tools/nixery/server/main.go | 6 ++++- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index c1a3c9f7d..20a5b5022 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -19,13 +19,22 @@ with pkgs; rec { + # Hash of all Nixery sources - this is used as the Nixery version in + # builds to distinguish errors between deployed versions, see + # server/logs.go for details. + nixery-src-hash = pkgs.runCommand "nixery-src-hash" {} '' + echo ${./.} | grep -Eo '[a-z0-9]{32}' > $out + ''; + # Go implementation of the Nixery server which implements the # container registry interface. # # Users will usually not want to use this directly, instead see the # 'nixery' derivation below, which automatically includes runtime # data dependencies. - nixery-server = callPackage ./server { }; + nixery-server = callPackage ./server { + srcHash = nixery-src-hash; + }; # Implementation of the Nix image building logic nixery-build-image = import ./build-image { inherit pkgs; }; diff --git a/tools/nixery/server/default.nix b/tools/nixery/server/default.nix index 573447a6c..d497f106b 100644 --- a/tools/nixery/server/default.nix +++ b/tools/nixery/server/default.nix @@ -12,21 +12,45 @@ # See the License for the specific language governing permissions and # limitations under the License. -{ buildGoPackage, lib }: +{ buildGoPackage, go, lib, srcHash }: -buildGoPackage { +buildGoPackage rec { name = "nixery-server"; goDeps = ./go-deps.nix; src = ./.; goPackagePath = "github.com/google/nixery/server"; - - # Enable checks and configure check-phase to include vet: doCheck = true; - preCheck = '' - for pkg in $(getGoDirs ""); do - buildGoDir vet "$pkg" - done + + # The following phase configurations work around the overengineered + # Nix build configuration for Go. + # + # All I want this to do is produce a binary in the standard Nix + # output path, so pretty much all the phases except for the initial + # configuration of the "dependency forest" in $GOPATH have been + # overridden. + # + # This is necessary because the upstream builder does wonky things + # with the build arguments to the compiler, but I need to set some + # complex flags myself + + outputs = [ "out" ]; + preConfigure = "bin=$out"; + buildPhase = '' + runHook preBuild + runHook renameImport + + export GOBIN="$out/bin" + go install -ldflags "-X main.version=$(cat ${srcHash})" ${goPackagePath} + ''; + + fixupPhase = '' + remove-references-to -t ${go} $out/bin/server + ''; + + checkPhase = '' + go vet ${goPackagePath} + go test ${goPackagePath} ''; meta = { diff --git a/tools/nixery/server/logs.go b/tools/nixery/server/logs.go index 55e0a13a0..9d1f17aed 100644 --- a/tools/nixery/server/logs.go +++ b/tools/nixery/server/logs.go @@ -27,7 +27,6 @@ type reportLocation struct { var nixeryContext = serviceContext{ Service: "nixery", - Version: "TODO(tazjin)", // angry? } // isError determines whether an entry should be logged as an error @@ -63,6 +62,7 @@ func (f stackdriverFormatter) Format(e *log.Entry) ([]byte, error) { } func init() { + nixeryContext.Version = version log.SetReportCaller(true) log.SetFormatter(stackdriverFormatter{}) } diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 878e59ff6..ca1f3c69f 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -47,6 +47,10 @@ import ( // https://docs.docker.com/registry/spec/manifest-v2-2/ const manifestMediaType string = "application/vnd.docker.distribution.manifest.v2+json" +// This variable will be initialised during the build process and set +// to the hash of the entire Nixery source tree. +var version string = "devel" + // Regexes matching the V2 Registry API routes. This only includes the // routes required for serving images, since pushing and other such // functionality is not available. @@ -243,7 +247,7 @@ func main() { Pop: pop, } - log.Printf("Starting Nixery on port %s\n", cfg.Port) + log.Printf("Starting Nixery (version %s) on port %s\n", version, cfg.Port) // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ From f77c93b6aeb3e847fe00099ea5c52dc98cf74b4d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 5 Oct 2019 22:48:19 +0100 Subject: [PATCH 138/223] feat(server): Add log level to severity mapping The output format now writes a `severity` field that follows that format that should be recognised by Stackdriver Logging. --- tools/nixery/server/logs.go | 34 ++++++++++++++++++++++++++++++++-- tools/nixery/server/main.go | 5 ++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/tools/nixery/server/logs.go b/tools/nixery/server/logs.go index 9d1f17aed..dec4a410f 100644 --- a/tools/nixery/server/logs.go +++ b/tools/nixery/server/logs.go @@ -40,16 +40,46 @@ func isError(e *log.Entry) bool { e.HasCaller() } +// logSeverity formats the entry's severity into a format compatible +// with Stackdriver Logging. +// +// The two formats that are being mapped do not have an equivalent set +// of severities/levels, so the mapping is somewhat arbitrary for a +// handful of them. +// +// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity +func logSeverity(l log.Level) string { + switch l { + case log.TraceLevel: + return "DEBUG" + case log.DebugLevel: + return "DEBUG" + case log.InfoLevel: + return "INFO" + case log.WarnLevel: + return "WARNING" + case log.ErrorLevel: + return "ERROR" + case log.FatalLevel: + return "CRITICAL" + case log.PanicLevel: + return "EMERGENCY" + default: + return "DEFAULT" + } +} + func (f stackdriverFormatter) Format(e *log.Entry) ([]byte, error) { msg := e.Data msg["serviceContext"] = &nixeryContext msg["message"] = &e.Message msg["eventTime"] = &e.Time + msg["severity"] = logSeverity(e.Level) if isError(e) { loc := reportLocation{ - FilePath: e.Caller.File, - LineNumber: e.Caller.Line, + FilePath: e.Caller.File, + LineNumber: e.Caller.Line, FunctionName: e.Caller.Function, } msg["context"] = &loc diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index ca1f3c69f..06de6a96a 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -247,7 +247,10 @@ func main() { Pop: pop, } - log.Printf("Starting Nixery (version %s) on port %s\n", version, cfg.Port) + log.WithFields(log.Fields{ + "version": version, + "port": cfg.Port, + }).Info("Starting Nixery") // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ From 6f148f789f43bf753b345b039d01d8a429f194e9 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 6 Oct 2019 03:18:38 +0100 Subject: [PATCH 139/223] refactor(server): Convert existing log entries to structured format This rewrites all existing log statements into the structured logrus format. For consistency, all errors are always logged separately from the primary message in a field called `error`. Only the "info", "error" and "warn" severities are used. --- tools/nixery/server/builder/archive.go | 2 - tools/nixery/server/builder/builder.go | 105 ++++++++++++++++++++---- tools/nixery/server/builder/cache.go | 85 +++++++++++++++---- tools/nixery/server/config/config.go | 17 +++- tools/nixery/server/config/pkgsource.go | 15 +++- tools/nixery/server/layers/grouping.go | 13 ++- tools/nixery/server/main.go | 60 +++++++++++--- 7 files changed, 243 insertions(+), 54 deletions(-) diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/server/builder/archive.go index 6a2bd8e4b..63ea9c738 100644 --- a/tools/nixery/server/builder/archive.go +++ b/tools/nixery/server/builder/archive.go @@ -14,7 +14,6 @@ import ( "path/filepath" "github.com/google/nixery/server/layers" - log "github.com/sirupsen/logrus" ) // Create a new tarball from each of the paths in the list and write the tarball @@ -33,7 +32,6 @@ func tarStorePaths(l *layers.Layer, w io.Writer) error { return err } - log.Printf("Created layer for '%s'\n", l.Hash()) return nil } diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index b675da0f7..14696f58a 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -143,14 +143,17 @@ func convenienceNames(packages []string) []string { // logNix logs each output line from Nix. It runs in a goroutine per // output channel that should be live-logged. -func logNix(name string, r io.ReadCloser) { +func logNix(image, cmd string, r io.ReadCloser) { scanner := bufio.NewScanner(r) for scanner.Scan() { - log.Printf("\x1b[31m[nix - %s]\x1b[39m %s\n", name, scanner.Text()) + log.WithFields(log.Fields{ + "image": image, + "cmd": cmd, + }).Info("[nix] " + scanner.Text()) } } -func callNix(program string, name string, args []string) ([]byte, error) { +func callNix(program, image string, args []string) ([]byte, error) { cmd := exec.Command(program, args...) outpipe, err := cmd.StdoutPipe() @@ -162,24 +165,45 @@ func callNix(program string, name string, args []string) ([]byte, error) { if err != nil { return nil, err } - go logNix(name, errpipe) + go logNix(program, image, errpipe) if err = cmd.Start(); err != nil { - log.Printf("Error starting %s: %s\n", program, err) + log.WithFields(log.Fields{ + "image": image, + "cmd": program, + "error": err, + }).Error("error starting command") + return nil, err } - log.Printf("Invoked Nix build (%s) for '%s'\n", program, name) + + log.WithFields(log.Fields{ + "cmd": program, + "image": image, + }).Info("invoked Nix build") stdout, _ := ioutil.ReadAll(outpipe) if err = cmd.Wait(); err != nil { - log.Printf("%s execution error: %s\nstdout: %s\n", program, err, stdout) + log.WithFields(log.Fields{ + "image": image, + "cmd": program, + "error": err, + "stdout": stdout, + }).Info("Nix execution failed") + return nil, err } resultFile := strings.TrimSpace(string(stdout)) buildOutput, err := ioutil.ReadFile(resultFile) if err != nil { + log.WithFields(log.Fields{ + "image": image, + "file": resultFile, + "error": err, + }).Info("failed to read Nix result file") + return nil, err } @@ -209,10 +233,14 @@ func prepareImage(s *State, image *Image) (*ImageResult, error) { output, err := callNix("nixery-build-image", image.Name, args) if err != nil { - log.Printf("failed to call nixery-build-image: %s\n", err) + // granular error logging is performed in callNix already return nil, err } - log.Printf("Finished image preparation for '%s' via Nix\n", image.Name) + + log.WithFields(log.Fields{ + "image": image.Name, + "tag": image.Tag, + }).Info("finished image preparation via Nix") var result ImageResult err = json.Unmarshal(output, &result) @@ -243,16 +271,27 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes if entry, cached := layerFromCache(ctx, s, l.Hash()); cached { entries = append(entries, *entry) } else { + lh := l.Hash() lw := func(w io.Writer) error { return tarStorePaths(&l, w) } - entry, err := uploadHashLayer(ctx, s, l.Hash(), lw) + entry, err := uploadHashLayer(ctx, s, lh, lw) if err != nil { return nil, err } entry.MergeRating = l.MergeRating + var pkgs []string + for _, p := range l.Contents { + pkgs = append(pkgs, layers.PackageFromPath(p)) + } + + log.WithFields(log.Fields{ + "layer": lh, + "packages": pkgs, + }).Info("created image layer") + go cacheLayer(ctx, s, l.Hash(), *entry) entries = append(entries, *entry) } @@ -264,7 +303,13 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes entry, err := uploadHashLayer(ctx, s, slkey, func(w io.Writer) error { f, err := os.Open(result.SymlinkLayer.Path) if err != nil { - log.Printf("failed to upload symlink layer '%s': %s\n", slkey, err) + log.WithFields(log.Fields{ + "image": image.Name, + "tag": image.Tag, + "error": err, + "layer": slkey, + }).Error("failed to upload symlink layer") + return err } defer f.Close() @@ -319,7 +364,12 @@ func renameObject(ctx context.Context, s *State, old, new string) error { // renaming/moving them, hence a deletion call afterwards is // required. if err = s.Bucket.Object(old).Delete(ctx); err != nil { - log.Printf("failed to delete renamed object '%s': %s\n", old, err) + log.WithFields(log.Fields{ + "new": new, + "old": old, + "error": err, + }).Warn("failed to delete renamed object") + // this error should not break renaming and is not returned } @@ -371,12 +421,19 @@ func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) err := lw(multi) if err != nil { - log.Printf("failed to create and upload layer '%s': %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to create and upload layer") + return nil, err } if err = sw.Close(); err != nil { - log.Printf("failed to upload layer '%s' to staging: %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to upload layer to staging") } sha256sum := fmt.Sprintf("%x", shasum.Sum([]byte{})) @@ -385,12 +442,21 @@ func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) // remains is to move it to the correct location and cache it. err = renameObject(ctx, s, "staging/"+key, "layers/"+sha256sum) if err != nil { - log.Printf("failed to move layer '%s' from staging: %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to move layer from staging") + return nil, err } size := counter.count - log.Printf("Uploaded layer sha256:%s (%v bytes written)", sha256sum, size) + + log.WithFields(log.Fields{ + "layer": key, + "sha256": sha256sum, + "size": size, + }).Info("uploaded layer") entry := manifest.Entry{ Digest: "sha256:" + sha256sum, @@ -436,7 +502,12 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro } if _, err = uploadHashLayer(ctx, s, c.SHA256, lw); err != nil { - log.Printf("failed to upload config for %s: %s\n", image.Name, err) + log.WithFields(log.Fields{ + "image": image.Name, + "tag": image.Tag, + "error": err, + }).Error("failed to upload config") + return nil, err } diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 5b6bf078b..916de3af1 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -60,15 +60,25 @@ func (c *LocalCache) manifestFromLocalCache(key string) (json.RawMessage, bool) f, err := os.Open(c.mdir + key) if err != nil { - // TODO(tazjin): Once log levels are available, this - // might warrant a debug log. + // This is a debug log statement because failure to + // read the manifest key is currently expected if it + // is not cached. + log.WithFields(log.Fields{ + "manifest": key, + "error": err, + }).Debug("failed to read manifest from local cache") + return nil, false } defer f.Close() m, err := ioutil.ReadAll(f) if err != nil { - log.Printf("Failed to read manifest '%s' from local cache: %s\n", key, err) + log.WithFields(log.Fields{ + "manifest": key, + "error": err, + }).Error("failed to read manifest from local cache") + return nil, false } @@ -86,7 +96,10 @@ func (c *LocalCache) localCacheManifest(key string, m json.RawMessage) { err := ioutil.WriteFile(c.mdir+key, []byte(m), 0644) if err != nil { - log.Printf("Failed to locally cache manifest for '%s': %s\n", key, err) + log.WithFields(log.Fields{ + "manifest": key, + "error": err, + }).Error("failed to locally cache manifest") } } @@ -123,18 +136,29 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa r, err := obj.NewReader(ctx) if err != nil { - log.Printf("Failed to retrieve manifest '%s' from cache: %s\n", key, err) + log.WithFields(log.Fields{ + "manifest": key, + "error": err, + }).Error("failed to retrieve manifest from bucket cache") + return nil, false } defer r.Close() m, err := ioutil.ReadAll(r) if err != nil { - log.Printf("Failed to read cached manifest for '%s': %s\n", key, err) + log.WithFields(log.Fields{ + "manifest": key, + "error": err, + }).Error("failed to read cached manifest from bucket") + + return nil, false } go s.Cache.localCacheManifest(key, m) - log.Printf("Retrieved manifest for sha1:%s from GCS\n", key) + log.WithFields(log.Fields{ + "manifest": key, + }).Info("retrieved manifest from GCS") return json.RawMessage(m), true } @@ -149,16 +173,27 @@ func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) size, err := io.Copy(w, r) if err != nil { - log.Printf("failed to cache manifest sha1:%s: %s\n", key, err) + log.WithFields(log.Fields{ + "manifest": key, + "error": err, + }).Error("failed to cache manifest to GCS") + return } if err = w.Close(); err != nil { - log.Printf("failed to cache manifest sha1:%s: %s\n", key, err) + log.WithFields(log.Fields{ + "manifest": key, + "error": err, + }).Error("failed to cache manifest to GCS") + return } - log.Printf("Cached manifest sha1:%s (%v bytes written)\n", key, size) + log.WithFields(log.Fields{ + "manifest": key, + "size": size, + }).Info("cached manifest to GCS") } // Retrieve a layer build from the cache, first checking the local @@ -176,7 +211,11 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, r, err := obj.NewReader(ctx) if err != nil { - log.Printf("Failed to retrieve layer build '%s' from cache: %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to retrieve cached layer from GCS") + return nil, false } defer r.Close() @@ -184,14 +223,22 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, jb := bytes.NewBuffer([]byte{}) _, err = io.Copy(jb, r) if err != nil { - log.Printf("Failed to read layer build '%s' from cache: %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to read cached layer from GCS") + return nil, false } var entry manifest.Entry err = json.Unmarshal(jb.Bytes(), &entry) if err != nil { - log.Printf("Failed to unmarshal layer build '%s' from cache: %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to unmarshal cached layer") + return nil, false } @@ -210,12 +257,20 @@ func cacheLayer(ctx context.Context, s *State, key string, entry manifest.Entry) _, err := io.Copy(w, bytes.NewReader(j)) if err != nil { - log.Printf("failed to cache build '%s': %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to cache layer") + return } if err = w.Close(); err != nil { - log.Printf("failed to cache build '%s': %s\n", key, err) + log.WithFields(log.Fields{ + "layer": key, + "error": err, + }).Error("failed to cache layer") + return } } diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index abc067855..9447975aa 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -32,14 +32,20 @@ func signingOptsFromEnv() *storage.SignedURLOptions { id := os.Getenv("GCS_SIGNING_ACCOUNT") if path == "" || id == "" { - log.Println("GCS URL signing disabled") + log.Info("GCS URL signing disabled") return nil } - log.Printf("GCS URL signing enabled with account %q\n", id) + log.WithFields(log.Fields{ + "account": id, + }).Info("GCS URL signing enabled") + k, err := ioutil.ReadFile(path) if err != nil { - log.Fatalf("Failed to read GCS signing key: %s\n", err) + log.WithFields(log.Fields{ + "file": path, + "error": err, + }).Fatal("failed to read GCS signing key") } return &storage.SignedURLOptions{ @@ -52,7 +58,10 @@ func signingOptsFromEnv() *storage.SignedURLOptions { func getConfig(key, desc, def string) string { value := os.Getenv(key) if value == "" && def == "" { - log.Fatalln(desc + " must be specified") + log.WithFields(log.Fields{ + "option": key, + "description": desc, + }).Fatal("missing required configuration envvar") } else if value == "" { return def } diff --git a/tools/nixery/server/config/pkgsource.go b/tools/nixery/server/config/pkgsource.go index 98719ecce..3a817f3d7 100644 --- a/tools/nixery/server/config/pkgsource.go +++ b/tools/nixery/server/config/pkgsource.go @@ -132,21 +132,30 @@ func (p *PkgsPath) CacheKey(pkgs []string, tag string) string { // specified, the Nix code will default to a recent NixOS channel. func pkgSourceFromEnv() (PkgSource, error) { if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { - log.Printf("Using Nix package set from Nix channel %q\n", channel) + log.WithFields(log.Fields{ + "channel": channel, + }).Info("using Nix package set from Nix channel or commit") + return &NixChannel{ channel: channel, }, nil } if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { - log.Printf("Using Nix package set from git repository at %q\n", git) + log.WithFields(log.Fields{ + "repo": git, + }).Info("using NIx package set from git repository") + return &GitSource{ repository: git, }, nil } if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { - log.Printf("Using Nix package set from path %q\n", path) + log.WithFields(log.Fields{ + "path": path, + }).Info("using Nix package set at local path") + return &PkgsPath{ path: path, }, nil diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index 95198c90d..1fbbf33db 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -172,8 +172,14 @@ func (c *closure) ID() int64 { var nixRegexp = regexp.MustCompile(`^/nix/store/[a-z0-9]+-`) +// PackageFromPath returns the name of a Nix package based on its +// output store path. +func PackageFromPath(path string) string { + return nixRegexp.ReplaceAllString(path, "") +} + func (c *closure) DOTID() string { - return nixRegexp.ReplaceAllString(c.Path, "") + return PackageFromPath(c.Path) } // bigOrPopular checks whether this closure should be considered for @@ -321,7 +327,10 @@ func dominate(budget int, graph *simple.DirectedGraph) []Layer { }) if len(layers) > budget { - log.Printf("Ideal image has %v layers, but budget is %v\n", len(layers), budget) + log.WithFields(log.Fields{ + "layers": len(layers), + "budget": budget, + }).Info("ideal image exceeds layer budget") } for len(layers) > budget { diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 06de6a96a..babf50790 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -68,7 +68,9 @@ var ( // The Docker client is known to follow redirects, but this might not be true // for all other registry clients. func constructLayerUrl(cfg *config.Config, digest string) (string, error) { - log.Printf("Redirecting layer '%s' request to bucket '%s'\n", digest, cfg.Bucket) + log.WithFields(log.Fields{ + "layer": digest, + }).Info("redirecting layer request to bucket") object := "layers/" + digest if cfg.Signing != nil { @@ -90,13 +92,18 @@ func constructLayerUrl(cfg *config.Config, digest string) (string, error) { func prepareBucket(ctx context.Context, cfg *config.Config) *storage.BucketHandle { client, err := storage.NewClient(ctx) if err != nil { - log.Fatalln("Failed to set up Cloud Storage client:", err) + log.WithFields(log.Fields{ + "error": err, + }).Fatal("failed to set up Cloud Storage client") } bkt := client.Bucket(cfg.Bucket) if _, err := bkt.Attrs(ctx); err != nil { - log.Fatalln("Could not access configured bucket", err) + log.WithFields(log.Fields{ + "error": err, + "bucket": cfg.Bucket, + }).Fatal("could not access configured bucket") } return bkt @@ -169,13 +176,24 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if len(manifestMatches) == 3 { imageName := manifestMatches[1] imageTag := manifestMatches[2] - log.Printf("Requesting manifest for image %q at tag %q", imageName, imageTag) + + log.WithFields(log.Fields{ + "image": imageName, + "tag": imageTag, + }).Info("requesting image manifest") + image := builder.ImageFromName(imageName, imageTag) buildResult, err := builder.BuildImage(h.ctx, h.state, &image) if err != nil { writeError(w, 500, "UNKNOWN", "image build failure") - log.Println("Failed to build image manifest", err) + + log.WithFields(log.Fields{ + "image": imageName, + "tag": imageTag, + "error": err, + }).Error("failed to build image manifest") + return } @@ -184,7 +202,13 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if buildResult.Error == "not_found" { s := fmt.Sprintf("Could not find Nix packages: %v", buildResult.Pkgs) writeError(w, 404, "MANIFEST_UNKNOWN", s) - log.Println(s) + + log.WithFields(log.Fields{ + "image": imageName, + "tag": imageTag, + "packages": buildResult.Pkgs, + }).Error("could not find Nix packages") + return } @@ -205,7 +229,11 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { url, err := constructLayerUrl(&h.state.Cfg, digest) if err != nil { - log.Printf("Failed to sign GCS URL: %s\n", err) + log.WithFields(log.Fields{ + "layer": digest, + "error": err, + }).Error("failed to sign GCS URL") + writeError(w, 500, "UNKNOWN", "could not serve layer") return } @@ -215,28 +243,38 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - log.Printf("Unsupported registry route: %s\n", r.RequestURI) + log.WithFields(log.Fields{ + "uri": r.RequestURI, + }).Info("unsupported registry route") + w.WriteHeader(404) } func main() { cfg, err := config.FromEnv() if err != nil { - log.Fatalln("Failed to load configuration", err) + log.WithFields(log.Fields{ + "error": err, + }).Fatal("failed to load configuration") } ctx := context.Background() bucket := prepareBucket(ctx, &cfg) cache, err := builder.NewCache() if err != nil { - log.Fatalln("Failed to instantiate build cache", err) + log.WithFields(log.Fields{ + "error": err, + }).Fatal("failed to instantiate build cache") } var pop layers.Popularity if cfg.PopUrl != "" { pop, err = downloadPopularity(cfg.PopUrl) if err != nil { - log.Fatalln("Failed to fetch popularity information", err) + log.WithFields(log.Fields{ + "error": err, + "popURL": cfg.PopUrl, + }).Fatal("failed to fetch popularity information") } } From c1020754a2a2a4058fd70d4b0a8276ccadd9545f Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 6 Oct 2019 03:57:35 +0100 Subject: [PATCH 140/223] fix(build-image): Import 'match' from builtins --- tools/nixery/build-image/build-image.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 70049885a..68a061290 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -39,6 +39,7 @@ let fromJSON hasAttr length + match readFile toFile toJSON; From d7ffbbdea47738acac24593f7e4448dd9c1df8ff Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 6 Oct 2019 14:48:24 +0100 Subject: [PATCH 141/223] refactor(server): Use logrus convenience functions for logs Makes use of the `.WithError` and `.WithField` convenience functions in logrus to simplify log statement construction. This has the added benefit of making it easier to correctly log errors. --- tools/nixery/server/builder/builder.go | 46 ++++++--------- tools/nixery/server/builder/cache.go | 76 ++++++++----------------- tools/nixery/server/config/config.go | 9 +-- tools/nixery/server/config/pkgsource.go | 12 +--- tools/nixery/server/main.go | 44 ++++---------- 5 files changed, 59 insertions(+), 128 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 14696f58a..78d09b55b 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -168,11 +168,10 @@ func callNix(program, image string, args []string) ([]byte, error) { go logNix(program, image, errpipe) if err = cmd.Start(); err != nil { - log.WithFields(log.Fields{ + log.WithError(err).WithFields(log.Fields{ "image": image, "cmd": program, - "error": err, - }).Error("error starting command") + }).Error("error invoking Nix") return nil, err } @@ -185,12 +184,11 @@ func callNix(program, image string, args []string) ([]byte, error) { stdout, _ := ioutil.ReadAll(outpipe) if err = cmd.Wait(); err != nil { - log.WithFields(log.Fields{ + log.WithError(err).WithFields(log.Fields{ "image": image, "cmd": program, - "error": err, "stdout": stdout, - }).Info("Nix execution failed") + }).Info("failed to invoke Nix") return nil, err } @@ -198,10 +196,9 @@ func callNix(program, image string, args []string) ([]byte, error) { resultFile := strings.TrimSpace(string(stdout)) buildOutput, err := ioutil.ReadFile(resultFile) if err != nil { - log.WithFields(log.Fields{ + log.WithError(err).WithFields(log.Fields{ "image": image, "file": resultFile, - "error": err, }).Info("failed to read Nix result file") return nil, err @@ -303,10 +300,9 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes entry, err := uploadHashLayer(ctx, s, slkey, func(w io.Writer) error { f, err := os.Open(result.SymlinkLayer.Path) if err != nil { - log.WithFields(log.Fields{ + log.WithError(err).WithFields(log.Fields{ "image": image.Name, "tag": image.Tag, - "error": err, "layer": slkey, }).Error("failed to upload symlink layer") @@ -364,10 +360,9 @@ func renameObject(ctx context.Context, s *State, old, new string) error { // renaming/moving them, hence a deletion call afterwards is // required. if err = s.Bucket.Object(old).Delete(ctx); err != nil { - log.WithFields(log.Fields{ - "new": new, - "old": old, - "error": err, + log.WithError(err).WithFields(log.Fields{ + "new": new, + "old": old, }).Warn("failed to delete renamed object") // this error should not break renaming and is not returned @@ -421,19 +416,15 @@ func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) err := lw(multi) if err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to create and upload layer") + log.WithError(err).WithField("layer", key). + Error("failed to create and upload layer") return nil, err } if err = sw.Close(); err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to upload layer to staging") + log.WithError(err).WithField("layer", key). + Error("failed to upload layer to staging") } sha256sum := fmt.Sprintf("%x", shasum.Sum([]byte{})) @@ -442,10 +433,8 @@ func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) // remains is to move it to the correct location and cache it. err = renameObject(ctx, s, "staging/"+key, "layers/"+sha256sum) if err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to move layer from staging") + log.WithError(err).WithField("layer", key). + Error("failed to move layer from staging") return nil, err } @@ -478,7 +467,7 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro imageResult, err := prepareImage(s, image) if err != nil { - return nil, fmt.Errorf("failed to prepare image '%s': %s", image.Name, err) + return nil, err } if imageResult.Error != "" { @@ -502,10 +491,9 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro } if _, err = uploadHashLayer(ctx, s, c.SHA256, lw); err != nil { - log.WithFields(log.Fields{ + log.WithError(err).WithFields(log.Fields{ "image": image.Name, "tag": image.Tag, - "error": err, }).Error("failed to upload config") return nil, err diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 916de3af1..88bf30de4 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -63,10 +63,8 @@ func (c *LocalCache) manifestFromLocalCache(key string) (json.RawMessage, bool) // This is a debug log statement because failure to // read the manifest key is currently expected if it // is not cached. - log.WithFields(log.Fields{ - "manifest": key, - "error": err, - }).Debug("failed to read manifest from local cache") + log.WithError(err).WithField("manifest", key). + Debug("failed to read manifest from local cache") return nil, false } @@ -74,10 +72,8 @@ func (c *LocalCache) manifestFromLocalCache(key string) (json.RawMessage, bool) m, err := ioutil.ReadAll(f) if err != nil { - log.WithFields(log.Fields{ - "manifest": key, - "error": err, - }).Error("failed to read manifest from local cache") + log.WithError(err).WithField("manifest", key). + Error("failed to read manifest from local cache") return nil, false } @@ -96,10 +92,8 @@ func (c *LocalCache) localCacheManifest(key string, m json.RawMessage) { err := ioutil.WriteFile(c.mdir+key, []byte(m), 0644) if err != nil { - log.WithFields(log.Fields{ - "manifest": key, - "error": err, - }).Error("failed to locally cache manifest") + log.WithError(err).WithField("manifest", key). + Error("failed to locally cache manifest") } } @@ -136,10 +130,8 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa r, err := obj.NewReader(ctx) if err != nil { - log.WithFields(log.Fields{ - "manifest": key, - "error": err, - }).Error("failed to retrieve manifest from bucket cache") + log.WithError(err).WithField("manifest", key). + Error("failed to retrieve manifest from bucket cache") return nil, false } @@ -147,18 +139,14 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa m, err := ioutil.ReadAll(r) if err != nil { - log.WithFields(log.Fields{ - "manifest": key, - "error": err, - }).Error("failed to read cached manifest from bucket") + log.WithError(err).WithField("manifest", key). + Error("failed to read cached manifest from bucket") return nil, false } go s.Cache.localCacheManifest(key, m) - log.WithFields(log.Fields{ - "manifest": key, - }).Info("retrieved manifest from GCS") + log.WithField("manifest", key).Info("retrieved manifest from GCS") return json.RawMessage(m), true } @@ -173,19 +161,15 @@ func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) size, err := io.Copy(w, r) if err != nil { - log.WithFields(log.Fields{ - "manifest": key, - "error": err, - }).Error("failed to cache manifest to GCS") + log.WithError(err).WithField("manifest", key). + Error("failed to cache manifest to GCS") return } if err = w.Close(); err != nil { - log.WithFields(log.Fields{ - "manifest": key, - "error": err, - }).Error("failed to cache manifest to GCS") + log.WithError(err).WithField("manifest", key). + Error("failed to cache manifest to GCS") return } @@ -211,10 +195,8 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, r, err := obj.NewReader(ctx) if err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to retrieve cached layer from GCS") + log.WithError(err).WithField("layer", key). + Error("failed to retrieve cached layer from GCS") return nil, false } @@ -223,10 +205,8 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, jb := bytes.NewBuffer([]byte{}) _, err = io.Copy(jb, r) if err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to read cached layer from GCS") + log.WithError(err).WithField("layer", key). + Error("failed to read cached layer from GCS") return nil, false } @@ -234,10 +214,8 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, var entry manifest.Entry err = json.Unmarshal(jb.Bytes(), &entry) if err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to unmarshal cached layer") + log.WithError(err).WithField("layer", key). + Error("failed to unmarshal cached layer") return nil, false } @@ -257,19 +235,15 @@ func cacheLayer(ctx context.Context, s *State, key string, entry manifest.Entry) _, err := io.Copy(w, bytes.NewReader(j)) if err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to cache layer") + log.WithError(err).WithField("layer", key). + Error("failed to cache layer") return } if err = w.Close(); err != nil { - log.WithFields(log.Fields{ - "layer": key, - "error": err, - }).Error("failed to cache layer") + log.WithError(err).WithField("layer", key). + Error("failed to cache layer") return } diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index 9447975aa..fe05734ee 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -36,16 +36,11 @@ func signingOptsFromEnv() *storage.SignedURLOptions { return nil } - log.WithFields(log.Fields{ - "account": id, - }).Info("GCS URL signing enabled") + log.WithField("account", id).Info("GCS URL signing enabled") k, err := ioutil.ReadFile(path) if err != nil { - log.WithFields(log.Fields{ - "file": path, - "error": err, - }).Fatal("failed to read GCS signing key") + log.WithError(err).WithField("file", path).Fatal("failed to read GCS signing key") } return &storage.SignedURLOptions{ diff --git a/tools/nixery/server/config/pkgsource.go b/tools/nixery/server/config/pkgsource.go index 3a817f3d7..95236c4b0 100644 --- a/tools/nixery/server/config/pkgsource.go +++ b/tools/nixery/server/config/pkgsource.go @@ -132,9 +132,7 @@ func (p *PkgsPath) CacheKey(pkgs []string, tag string) string { // specified, the Nix code will default to a recent NixOS channel. func pkgSourceFromEnv() (PkgSource, error) { if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { - log.WithFields(log.Fields{ - "channel": channel, - }).Info("using Nix package set from Nix channel or commit") + log.WithField("channel", channel).Info("using Nix package set from Nix channel or commit") return &NixChannel{ channel: channel, @@ -142,9 +140,7 @@ func pkgSourceFromEnv() (PkgSource, error) { } if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { - log.WithFields(log.Fields{ - "repo": git, - }).Info("using NIx package set from git repository") + log.WithField("repo", git).Info("using NIx package set from git repository") return &GitSource{ repository: git, @@ -152,9 +148,7 @@ func pkgSourceFromEnv() (PkgSource, error) { } if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { - log.WithFields(log.Fields{ - "path": path, - }).Info("using Nix package set at local path") + log.WithField("path", path).Info("using Nix package set at local path") return &PkgsPath{ path: path, diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index babf50790..f38fab2f2 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -68,9 +68,7 @@ var ( // The Docker client is known to follow redirects, but this might not be true // for all other registry clients. func constructLayerUrl(cfg *config.Config, digest string) (string, error) { - log.WithFields(log.Fields{ - "layer": digest, - }).Info("redirecting layer request to bucket") + log.WithField("layer", digest).Info("redirecting layer request to bucket") object := "layers/" + digest if cfg.Signing != nil { @@ -92,18 +90,13 @@ func constructLayerUrl(cfg *config.Config, digest string) (string, error) { func prepareBucket(ctx context.Context, cfg *config.Config) *storage.BucketHandle { client, err := storage.NewClient(ctx) if err != nil { - log.WithFields(log.Fields{ - "error": err, - }).Fatal("failed to set up Cloud Storage client") + log.WithError(err).Fatal("failed to set up Cloud Storage client") } bkt := client.Bucket(cfg.Bucket) if _, err := bkt.Attrs(ctx); err != nil { - log.WithFields(log.Fields{ - "error": err, - "bucket": cfg.Bucket, - }).Fatal("could not access configured bucket") + log.WithError(err).WithField("bucket", cfg.Bucket).Fatal("could not access configured bucket") } return bkt @@ -188,10 +181,9 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err != nil { writeError(w, 500, "UNKNOWN", "image build failure") - log.WithFields(log.Fields{ + log.WithError(err).WithFields(log.Fields{ "image": imageName, "tag": imageTag, - "error": err, }).Error("failed to build image manifest") return @@ -207,7 +199,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { "image": imageName, "tag": imageTag, "packages": buildResult.Pkgs, - }).Error("could not find Nix packages") + }).Warn("could not find Nix packages") return } @@ -229,11 +221,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { url, err := constructLayerUrl(&h.state.Cfg, digest) if err != nil { - log.WithFields(log.Fields{ - "layer": digest, - "error": err, - }).Error("failed to sign GCS URL") - + log.WithError(err).WithField("layer", digest).Error("failed to sign GCS URL") writeError(w, 500, "UNKNOWN", "could not serve layer") return } @@ -243,9 +231,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - log.WithFields(log.Fields{ - "uri": r.RequestURI, - }).Info("unsupported registry route") + log.WithField("uri", r.RequestURI).Info("unsupported registry route") w.WriteHeader(404) } @@ -253,28 +239,22 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func main() { cfg, err := config.FromEnv() if err != nil { - log.WithFields(log.Fields{ - "error": err, - }).Fatal("failed to load configuration") + log.WithError(err).Fatal("failed to load configuration") } ctx := context.Background() bucket := prepareBucket(ctx, &cfg) cache, err := builder.NewCache() if err != nil { - log.WithFields(log.Fields{ - "error": err, - }).Fatal("failed to instantiate build cache") + log.WithError(err).Fatal("failed to instantiate build cache") } var pop layers.Popularity if cfg.PopUrl != "" { pop, err = downloadPopularity(cfg.PopUrl) if err != nil { - log.WithFields(log.Fields{ - "error": err, - "popURL": cfg.PopUrl, - }).Fatal("failed to fetch popularity information") + log.WithError(err).WithField("popURL", cfg.PopUrl). + Fatal("failed to fetch popularity information") } } @@ -288,7 +268,7 @@ func main() { log.WithFields(log.Fields{ "version": version, "port": cfg.Port, - }).Info("Starting Nixery") + }).Info("starting Nixery") // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ From bf2718cebbd1c7af15c54c6da5685ed6d933cab4 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 9 Oct 2019 13:35:39 +0100 Subject: [PATCH 142/223] chore(build): Use separate GCS bucket for CI runs This has become an issue recently with changes such as GZIP compression, where CI runs no longer work because they conflict with the production bucket for the public instance. --- tools/nixery/.travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 5f0bbfd3b..31d485d72 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -26,7 +26,7 @@ script: docker run -d -p 8080:8080 --name nixery \ -v ${PWD}/test-files:/var/nixery \ -e PORT=8080 \ - -e BUCKET=nixery-layers \ + -e BUCKET=nixery-ci-tests \ -e GOOGLE_CLOUD_PROJECT=nixery \ -e GOOGLE_APPLICATION_CREDENTIALS=/var/nixery/key.json \ -e GCS_SIGNING_ACCOUNT="${GCS_SIGNING_ACCOUNT}" \ From 0693e371d66bfe3de2d97ab80e9c9684ec8abc34 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 11 Oct 2019 01:28:38 +0100 Subject: [PATCH 143/223] feat(server): Apply GZIP compression to all image layers This fixes #62 --- tools/nixery/build-image/build-image.nix | 4 ++-- tools/nixery/server/builder/archive.go | 14 ++++++++++---- tools/nixery/server/builder/builder.go | 2 +- tools/nixery/server/manifest/manifest.go | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index 68a061290..b78ee6626 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -126,9 +126,9 @@ let # Image layer that contains the symlink forest created above. This # must be included in the image to ensure that the filesystem has a # useful layout at runtime. - symlinkLayer = runCommand "symlink-layer.tar" {} '' + symlinkLayer = runCommand "symlink-layer.tar.gz" {} '' cp -r ${contentsEnv}/ ./layer - tar --transform='s|^\./||' -C layer --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 -cf $out . + tar --transform='s|^\./||' -C layer --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 -czf $out . ''; # Metadata about the symlink layer which is required for serving it. diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/server/builder/archive.go index 63ea9c738..55b3c2a8b 100644 --- a/tools/nixery/server/builder/archive.go +++ b/tools/nixery/server/builder/archive.go @@ -9,6 +9,7 @@ package builder import ( "archive/tar" + "compress/gzip" "io" "os" "path/filepath" @@ -16,10 +17,11 @@ import ( "github.com/google/nixery/server/layers" ) -// Create a new tarball from each of the paths in the list and write the tarball -// to the supplied writer. -func tarStorePaths(l *layers.Layer, w io.Writer) error { - t := tar.NewWriter(w) +// Create a new compressed tarball from each of the paths in the list +// and write it to the supplied writer. +func packStorePaths(l *layers.Layer, w io.Writer) error { + gz := gzip.NewWriter(w) + t := tar.NewWriter(gz) for _, path := range l.Contents { err := filepath.Walk(path, tarStorePath(t)) @@ -32,6 +34,10 @@ func tarStorePaths(l *layers.Layer, w io.Writer) error { return err } + if err := gz.Close(); err != nil { + return err + } + return nil } diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 78d09b55b..748ff5f67 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -270,7 +270,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes } else { lh := l.Hash() lw := func(w io.Writer) error { - return tarStorePaths(&l, w) + return packStorePaths(&l, w) } entry, err := uploadHashLayer(ctx, s, lh, lw) diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go index 2f236178b..8e65fa223 100644 --- a/tools/nixery/server/manifest/manifest.go +++ b/tools/nixery/server/manifest/manifest.go @@ -15,7 +15,7 @@ const ( // media types manifestType = "application/vnd.docker.distribution.manifest.v2+json" - layerType = "application/vnd.docker.image.rootfs.diff.tar" + layerType = "application/vnd.docker.image.rootfs.diff.tar.gzip" configType = "application/vnd.docker.container.image.v1+json" // image config constants @@ -102,7 +102,7 @@ func Manifest(layers []Entry) (json.RawMessage, ConfigLayer) { hashes := make([]string, len(layers)) for i, l := range layers { - l.MediaType = "application/vnd.docker.image.rootfs.diff.tar" + l.MediaType = layerType layers[i] = l hashes[i] = l.Digest } From e22ff5d176ba53deecf59737d402cac5d0cb9433 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 11 Oct 2019 11:57:14 +0100 Subject: [PATCH 144/223] fix(server): Use uncompressed tarball hashes in image config Docker expects hashes of compressed tarballs in the manifest (as these are used to fetch from the content-addressable layer store), but for some reason it expects hashes in the configuration layer to be of uncompressed tarballs. To achieve this an additional SHA256 hash is calculcated while creating the layer tarballs, but before passing them to the gzip writer. In the current constellation the symlink layer is first compressed and then decompressed again to calculate its hash. This can be refactored in a future change. --- tools/nixery/build-image/build-image.nix | 9 ++++++--- tools/nixery/server/builder/archive.go | 19 +++++++++++++------ tools/nixery/server/builder/builder.go | 24 +++++++++++++++++++----- tools/nixery/server/manifest/manifest.go | 6 ++++-- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index b78ee6626..ab785006a 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -137,11 +137,14 @@ let symlinkLayerMeta = fromJSON (readFile (runCommand "symlink-layer-meta.json" { buildInputs = with pkgs; [ coreutils jq openssl ]; }'' - layerSha256=$(sha256sum ${symlinkLayer} | cut -d ' ' -f1) + gzipHash=$(sha256sum ${symlinkLayer} | cut -d ' ' -f1) + tarHash=$(cat ${symlinkLayer} | gzip -d | sha256sum | cut -d ' ' -f1) layerSize=$(stat --printf '%s' ${symlinkLayer}) - jq -n -c --arg sha256 $layerSha256 --arg size $layerSize --arg path ${symlinkLayer} \ - '{ size: ($size | tonumber), sha256: $sha256, path: $path }' >> $out + jq -n -c --arg gzipHash $gzipHash --arg tarHash $tarHash --arg size $layerSize \ + --arg path ${symlinkLayer} \ + '{ size: ($size | tonumber), tarHash: $tarHash, gzipHash: $gzipHash, path: $path }' \ + >> $out '')); # Final output structure returned to Nixery if the build succeeded diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/server/builder/archive.go index 55b3c2a8b..a3fb99882 100644 --- a/tools/nixery/server/builder/archive.go +++ b/tools/nixery/server/builder/archive.go @@ -10,6 +10,8 @@ package builder import ( "archive/tar" "compress/gzip" + "crypto/sha256" + "fmt" "io" "os" "path/filepath" @@ -19,26 +21,31 @@ import ( // Create a new compressed tarball from each of the paths in the list // and write it to the supplied writer. -func packStorePaths(l *layers.Layer, w io.Writer) error { +// +// The uncompressed tarball is hashed because image manifests must +// contain both the hashes of compressed and uncompressed layers. +func packStorePaths(l *layers.Layer, w io.Writer) (string, error) { + shasum := sha256.New() gz := gzip.NewWriter(w) - t := tar.NewWriter(gz) + multi := io.MultiWriter(shasum, gz) + t := tar.NewWriter(multi) for _, path := range l.Contents { err := filepath.Walk(path, tarStorePath(t)) if err != nil { - return err + return "", err } } if err := t.Close(); err != nil { - return err + return "", err } if err := gz.Close(); err != nil { - return err + return "", err } - return nil + return fmt.Sprintf("sha256:%x", shasum.Sum([]byte{})), nil } func tarStorePath(w *tar.Writer) filepath.WalkFunc { diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 748ff5f67..39befd0fb 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -117,9 +117,10 @@ type ImageResult struct { // These fields are populated in case of success Graph layers.RuntimeGraph `json:"runtimeGraph"` SymlinkLayer struct { - Size int `json:"size"` - SHA256 string `json:"sha256"` - Path string `json:"path"` + Size int `json:"size"` + TarHash string `json:"tarHash"` + GzipHash string `json:"gzipHash"` + Path string `json:"path"` } `json:"symlinkLayer"` } @@ -269,8 +270,18 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes entries = append(entries, *entry) } else { lh := l.Hash() + + // While packing store paths, the SHA sum of + // the uncompressed layer is computed and + // written to `tarhash`. + // + // TODO(tazjin): Refactor this to make the + // flow of data cleaner. + var tarhash string lw := func(w io.Writer) error { - return packStorePaths(&l, w) + var err error + tarhash, err = packStorePaths(&l, w) + return err } entry, err := uploadHashLayer(ctx, s, lh, lw) @@ -278,6 +289,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes return nil, err } entry.MergeRating = l.MergeRating + entry.TarHash = tarhash var pkgs []string for _, p := range l.Contents { @@ -287,6 +299,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes log.WithFields(log.Fields{ "layer": lh, "packages": pkgs, + "tarhash": tarhash, }).Info("created image layer") go cacheLayer(ctx, s, l.Hash(), *entry) @@ -296,7 +309,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes // Symlink layer (built in the first Nix build) needs to be // included here manually: - slkey := result.SymlinkLayer.SHA256 + slkey := result.SymlinkLayer.GzipHash entry, err := uploadHashLayer(ctx, s, slkey, func(w io.Writer) error { f, err := os.Open(result.SymlinkLayer.Path) if err != nil { @@ -318,6 +331,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes return nil, err } + entry.TarHash = "sha256:" + result.SymlinkLayer.TarHash go cacheLayer(ctx, s, slkey, *entry) entries = append(entries, *entry) diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go index 8e65fa223..8ad828239 100644 --- a/tools/nixery/server/manifest/manifest.go +++ b/tools/nixery/server/manifest/manifest.go @@ -29,9 +29,10 @@ type Entry struct { Size int64 `json:"size"` Digest string `json:"digest"` - // This field is internal to Nixery and not part of the + // These fields are internal to Nixery and not part of the // serialised entry. MergeRating uint64 `json:"-"` + TarHash string `json:",omitempty"` } type manifest struct { @@ -102,9 +103,10 @@ func Manifest(layers []Entry) (json.RawMessage, ConfigLayer) { hashes := make([]string, len(layers)) for i, l := range layers { + hashes[i] = l.TarHash l.MediaType = layerType + l.TarHash = "" layers[i] = l - hashes[i] = l.Digest } c := configLayer(hashes) From 1853c74998953048478b8526f408f8c57b129958 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 11 Oct 2019 12:26:18 +0100 Subject: [PATCH 145/223] refactor(server): Only compress symlink forest layer once Instead of compressing & decompressing again to get the underlying tar hash, use a similar mechanism as for store path layers for the symlink layer and only compress it once while uploading. --- tools/nixery/build-image/build-image.nix | 13 +++++------- tools/nixery/server/builder/builder.go | 27 +++++++++++++++++------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index ab785006a..d4df707a8 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -126,9 +126,9 @@ let # Image layer that contains the symlink forest created above. This # must be included in the image to ensure that the filesystem has a # useful layout at runtime. - symlinkLayer = runCommand "symlink-layer.tar.gz" {} '' + symlinkLayer = runCommand "symlink-layer.tar" {} '' cp -r ${contentsEnv}/ ./layer - tar --transform='s|^\./||' -C layer --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 -czf $out . + tar --transform='s|^\./||' -C layer --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 -cf $out . ''; # Metadata about the symlink layer which is required for serving it. @@ -137,14 +137,11 @@ let symlinkLayerMeta = fromJSON (readFile (runCommand "symlink-layer-meta.json" { buildInputs = with pkgs; [ coreutils jq openssl ]; }'' - gzipHash=$(sha256sum ${symlinkLayer} | cut -d ' ' -f1) - tarHash=$(cat ${symlinkLayer} | gzip -d | sha256sum | cut -d ' ' -f1) + tarHash=$(sha256sum ${symlinkLayer} | cut -d ' ' -f1) layerSize=$(stat --printf '%s' ${symlinkLayer}) - jq -n -c --arg gzipHash $gzipHash --arg tarHash $tarHash --arg size $layerSize \ - --arg path ${symlinkLayer} \ - '{ size: ($size | tonumber), tarHash: $tarHash, gzipHash: $gzipHash, path: $path }' \ - >> $out + jq -n -c --arg tarHash $tarHash --arg size $layerSize --arg path ${symlinkLayer} \ + '{ size: ($size | tonumber), tarHash: $tarHash, path: $path }' >> $out '')); # Final output structure returned to Nixery if the build succeeded diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 39befd0fb..2a3aa182a 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -20,6 +20,7 @@ package builder import ( "bufio" "bytes" + "compress/gzip" "context" "crypto/sha256" "encoding/json" @@ -117,10 +118,9 @@ type ImageResult struct { // These fields are populated in case of success Graph layers.RuntimeGraph `json:"runtimeGraph"` SymlinkLayer struct { - Size int `json:"size"` - TarHash string `json:"tarHash"` - GzipHash string `json:"gzipHash"` - Path string `json:"path"` + Size int `json:"size"` + TarHash string `json:"tarHash"` + Path string `json:"path"` } `json:"symlinkLayer"` } @@ -309,9 +309,22 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes // Symlink layer (built in the first Nix build) needs to be // included here manually: - slkey := result.SymlinkLayer.GzipHash + slkey := result.SymlinkLayer.TarHash entry, err := uploadHashLayer(ctx, s, slkey, func(w io.Writer) error { f, err := os.Open(result.SymlinkLayer.Path) + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "image": image.Name, + "tag": image.Tag, + "layer": slkey, + }).Error("failed to open symlink layer") + + return err + } + defer f.Close() + + gz := gzip.NewWriter(w) + _, err = io.Copy(gz, f) if err != nil { log.WithError(err).WithFields(log.Fields{ "image": image.Name, @@ -321,10 +334,8 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes return err } - defer f.Close() - _, err = io.Copy(w, f) - return err + return gz.Close() }) if err != nil { From cca835ae37cc35f3cae80afe5af8049009a6aa89 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 11 Oct 2019 13:55:10 +0100 Subject: [PATCH 146/223] fix(build): Only take the first matching hash for source hashing Some Nix download mechanisms will add a second hash in the store path, which had been added to the source hash output (breaking argument interpolation). --- tools/nixery/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 20a5b5022..422148d62 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -23,7 +23,7 @@ rec { # builds to distinguish errors between deployed versions, see # server/logs.go for details. nixery-src-hash = pkgs.runCommand "nixery-src-hash" {} '' - echo ${./.} | grep -Eo '[a-z0-9]{32}' > $out + echo ${./.} | grep -Eo '[a-z0-9]{32}' | head -c 32 > $out ''; # Go implementation of the Nixery server which implements the From 3a5db4f9f184d38799cda1ca83039d11ff457c04 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 27 Oct 2019 13:36:53 +0100 Subject: [PATCH 147/223] refactor(server): Load GCS signing key from service account key The JSON file generated for service account keys already contains the required information for signing URLs in GCS, thus the environment variables for toggling signing behaviour have been removed. Signing is now enabled automatically in the presence of service account credentials (i.e. `GOOGLE_APPLICATION_CREDENTIALS`). --- tools/nixery/server/config/config.go | 30 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index fe05734ee..6c1baafce 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -23,29 +23,33 @@ import ( "cloud.google.com/go/storage" log "github.com/sirupsen/logrus" + "golang.org/x/oauth2/google" ) -// Load (optional) GCS bucket signing data from the GCS_SIGNING_KEY and -// GCS_SIGNING_ACCOUNT envvars. +// Configure GCS URL signing in the presence of a service account key +// (toggled if the user has set GOOGLE_APPLICATION_CREDENTIALS). func signingOptsFromEnv() *storage.SignedURLOptions { - path := os.Getenv("GCS_SIGNING_KEY") - id := os.Getenv("GCS_SIGNING_ACCOUNT") - - if path == "" || id == "" { - log.Info("GCS URL signing disabled") + path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") + if path == "" { return nil } - log.WithField("account", id).Info("GCS URL signing enabled") - - k, err := ioutil.ReadFile(path) + key, err := ioutil.ReadFile(path) if err != nil { - log.WithError(err).WithField("file", path).Fatal("failed to read GCS signing key") + log.WithError(err).WithField("file", path).Fatal("failed to read service account key") } + conf, err := google.JWTConfigFromJSON(key) + if err != nil { + log.WithError(err).WithField("file", path).Fatal("failed to parse service account key") + } + + log.WithField("account", conf.Email).Info("GCS URL signing enabled") + return &storage.SignedURLOptions{ - GoogleAccessID: id, - PrivateKey: k, + Scheme: storage.SigningSchemeV4, + GoogleAccessID: conf.Email, + PrivateKey: conf.PrivateKey, Method: "GET", } } From 7b7d21205fb5288f1772d6ea4baff080565ebd9e Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 27 Oct 2019 13:42:24 +0100 Subject: [PATCH 148/223] docs: Update GCS signing key documentation This key is now taken straight from the configured service account key. --- tools/nixery/README.md | 18 ++++++++++-------- tools/nixery/docs/src/run-your-own.md | 8 ++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 3026451c7..1574d5950 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -79,15 +79,17 @@ variables: * `NIXERY_CHANNEL`: The name of a Nix/NixOS channel to use for building * `NIXERY_PKGS_REPO`: URL of a git repository containing a package set (uses locally configured SSH/git credentials) -* `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use - for building +* `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to + use for building * `NIX_TIMEOUT`: Number of seconds that any Nix builder is allowed to run - (defaults to 60 -* `NIX_POPULARITY_URL`: URL to a file containing popularity data for the package set (see `popcount/`) -* `GCS_SIGNING_KEY`: A Google service account key (in PEM format) that can be - used to sign Cloud Storage URLs -* `GCS_SIGNING_ACCOUNT`: Google service account ID that the signing key belongs - to + (defaults to 60) +* `NIX_POPULARITY_URL`: URL to a file containing popularity data for + the package set (see `popcount/`) + +If the `GOOGLE_APPLICATION_CREDENTIALS` environment variable is set to a service +account key, Nixery will also use this key to create [signed URLs][] for layers +in the storage bucket. This makes it possible to serve layers from a bucket +without having to make them publicly available. ## Roadmap diff --git a/tools/nixery/docs/src/run-your-own.md b/tools/nixery/docs/src/run-your-own.md index 7a294f560..ffddec32d 100644 --- a/tools/nixery/docs/src/run-your-own.md +++ b/tools/nixery/docs/src/run-your-own.md @@ -85,15 +85,15 @@ You may set *all* of these: * `NIX_TIMEOUT`: Number of seconds that any Nix builder is allowed to run (defaults to 60) -* `GCS_SIGNING_KEY`: A Google service account key (in PEM format) that can be - used to [sign Cloud Storage URLs][signed-urls] -* `GCS_SIGNING_ACCOUNT`: Google service account ID that the signing key belongs - to To authenticate to the configured GCS bucket, Nixery uses Google's [Application Default Credentials][ADC]. Depending on your environment this may require additional configuration. +If the `GOOGLE_APPLICATION_CREDENTIALS` environment is configured, the service +account's private key will be used to create [signed URLs for +layers][signed-urls]. + ## 4. Deploy Nixery With the above environment variables configured, you can run the image that was From ffe58d6cb510d0274ea1afed3e0e2b44c69e32c2 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 27 Oct 2019 15:33:14 +0100 Subject: [PATCH 149/223] refactor(build): Do not expose nixery-server attribute In most cases this is not useful for users without the wrapper script, so users should always build nixery-bin anyways. --- tools/nixery/default.nix | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 422148d62..354103796 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -18,7 +18,7 @@ with pkgs; -rec { +let # Hash of all Nixery sources - this is used as the Nixery version in # builds to distinguish errors between deployed versions, see # server/logs.go for details. @@ -29,13 +29,11 @@ rec { # Go implementation of the Nixery server which implements the # container registry interface. # - # Users will usually not want to use this directly, instead see the - # 'nixery' derivation below, which automatically includes runtime - # data dependencies. + # Users should use the nixery-bin derivation below instead. nixery-server = callPackage ./server { srcHash = nixery-src-hash; }; - +in rec { # Implementation of the Nix image building logic nixery-build-image = import ./build-image { inherit pkgs; }; From f7d16c5d454ea2aee65e7180e19a9bb891178bbb Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 27 Oct 2019 16:49:54 +0100 Subject: [PATCH 150/223] refactor(server): Introduce pluggable interface for storage backends This abstracts over the functionality of Google Cloud Storage and other potential underlying storage backends to make it possible to replace these in Nixery. The GCS backend is not yet reimplemented. --- tools/nixery/server/builder/builder.go | 99 ++++++-------------------- tools/nixery/server/builder/cache.go | 94 +++++++++++------------- tools/nixery/server/config/config.go | 45 ++---------- tools/nixery/server/main.go | 66 +++-------------- tools/nixery/server/storage/storage.go | 34 +++++++++ 5 files changed, 110 insertions(+), 228 deletions(-) create mode 100644 tools/nixery/server/storage/storage.go diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 2a3aa182a..e2982b993 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -28,18 +28,16 @@ import ( "io" "io/ioutil" "net/http" - "net/url" "os" "os/exec" "sort" "strings" - "cloud.google.com/go/storage" "github.com/google/nixery/server/config" "github.com/google/nixery/server/layers" "github.com/google/nixery/server/manifest" + "github.com/google/nixery/server/storage" log "github.com/sirupsen/logrus" - "golang.org/x/oauth2/google" ) // The maximum number of layers in an image is 125. To allow for @@ -47,19 +45,16 @@ import ( // use up is set at a lower point. const LayerBudget int = 94 -// API scope needed for renaming objects in GCS -const gcsScope = "https://www.googleapis.com/auth/devstorage.read_write" - // HTTP client to use for direct calls to APIs that are not part of the SDK var client = &http.Client{} // State holds the runtime state that is carried around in Nixery and // passed to builder functions. type State struct { - Bucket *storage.BucketHandle - Cache *LocalCache - Cfg config.Config - Pop layers.Popularity + Storage storage.Backend + Cache *LocalCache + Cfg config.Config + Pop layers.Popularity } // Image represents the information necessary for building a container image. @@ -349,53 +344,6 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes return entries, nil } -// renameObject renames an object in the specified Cloud Storage -// bucket. -// -// The Go API for Cloud Storage does not support renaming objects, but -// the HTTP API does. The code below makes the relevant call manually. -func renameObject(ctx context.Context, s *State, old, new string) error { - bucket := s.Cfg.Bucket - - creds, err := google.FindDefaultCredentials(ctx, gcsScope) - if err != nil { - return err - } - - token, err := creds.TokenSource.Token() - if err != nil { - return err - } - - // as per https://cloud.google.com/storage/docs/renaming-copying-moving-objects#rename - url := fmt.Sprintf( - "https://www.googleapis.com/storage/v1/b/%s/o/%s/rewriteTo/b/%s/o/%s", - url.PathEscape(bucket), url.PathEscape(old), - url.PathEscape(bucket), url.PathEscape(new), - ) - - req, err := http.NewRequest("POST", url, nil) - req.Header.Add("Authorization", "Bearer "+token.AccessToken) - _, err = client.Do(req) - if err != nil { - return err - } - - // It seems that 'rewriteTo' copies objects instead of - // renaming/moving them, hence a deletion call afterwards is - // required. - if err = s.Bucket.Object(old).Delete(ctx); err != nil { - log.WithError(err).WithFields(log.Fields{ - "new": new, - "old": old, - }).Warn("failed to delete renamed object") - - // this error should not break renaming and is not returned - } - - return nil -} - // layerWriter is the type for functions that can write a layer to the // multiwriter used for uploading & hashing. // @@ -430,33 +378,32 @@ func (b *byteCounter) Write(p []byte) (n int, err error) { // The return value is the layer's SHA256 hash, which is used in the // image manifest. func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) (*manifest.Entry, error) { - staging := s.Bucket.Object("staging/" + key) + path := "staging/" + key + sha256sum, size, err := s.Storage.Persist(path, func(sw io.Writer) (string, int64, error) { + // Sets up a "multiwriter" that simultaneously runs both hash + // algorithms and uploads to the storage backend. + shasum := sha256.New() + counter := &byteCounter{} + multi := io.MultiWriter(sw, shasum, counter) - // Sets up a "multiwriter" that simultaneously runs both hash - // algorithms and uploads to the bucket - sw := staging.NewWriter(ctx) - shasum := sha256.New() - counter := &byteCounter{} - multi := io.MultiWriter(sw, shasum, counter) + err := lw(multi) + sha256sum := fmt.Sprintf("%x", shasum.Sum([]byte{})) + + return sha256sum, counter.count, err + }) - err := lw(multi) if err != nil { - log.WithError(err).WithField("layer", key). - Error("failed to create and upload layer") + log.WithError(err).WithFields(log.Fields{ + "layer": key, + "backend": s.Storage.Name(), + }).Error("failed to create and store layer") return nil, err } - if err = sw.Close(); err != nil { - log.WithError(err).WithField("layer", key). - Error("failed to upload layer to staging") - } - - sha256sum := fmt.Sprintf("%x", shasum.Sum([]byte{})) - // Hashes are now known and the object is in the bucket, what // remains is to move it to the correct location and cache it. - err = renameObject(ctx, s, "staging/"+key, "layers/"+sha256sum) + err = s.Storage.Move("staging/"+key, "layers/"+sha256sum) if err != nil { log.WithError(err).WithField("layer", key). Error("failed to move layer from staging") @@ -464,8 +411,6 @@ func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) return nil, err } - size := counter.count - log.WithFields(log.Fields{ "layer": key, "sha256": sha256sum, diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 88bf30de4..2af214cd9 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -114,24 +114,18 @@ func (c *LocalCache) localCacheLayer(key string, e manifest.Entry) { } // Retrieve a manifest from the cache(s). First the local cache is -// checked, then the GCS-bucket cache. +// checked, then the storage backend. func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessage, bool) { if m, cached := s.Cache.manifestFromLocalCache(key); cached { return m, true } - obj := s.Bucket.Object("manifests/" + key) - - // Probe whether the file exists before trying to fetch it. - _, err := obj.Attrs(ctx) + r, err := s.Storage.Fetch("manifests/" + key) if err != nil { - return nil, false - } - - r, err := obj.NewReader(ctx) - if err != nil { - log.WithError(err).WithField("manifest", key). - Error("failed to retrieve manifest from bucket cache") + log.WithError(err).WithFields(log.Fields{ + "manifest": key, + "backend": s.Storage.Name(), + }) return nil, false } @@ -139,8 +133,10 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa m, err := ioutil.ReadAll(r) if err != nil { - log.WithError(err).WithField("manifest", key). - Error("failed to read cached manifest from bucket") + log.WithError(err).WithFields(log.Fields{ + "manifest": key, + "backend": s.Storage.Name(), + }).Error("failed to read cached manifest from storage backend") return nil, false } @@ -155,21 +151,17 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) { go s.Cache.localCacheManifest(key, m) - obj := s.Bucket.Object("manifests/" + key) - w := obj.NewWriter(ctx) - r := bytes.NewReader([]byte(m)) + path := "manifests/" + key + _, size, err := s.Storage.Persist(path, func(w io.Writer) (string, int64, error) { + size, err := io.Copy(w, bytes.NewReader([]byte(m))) + return "", size, err + }) - size, err := io.Copy(w, r) if err != nil { - log.WithError(err).WithField("manifest", key). - Error("failed to cache manifest to GCS") - - return - } - - if err = w.Close(); err != nil { - log.WithError(err).WithField("manifest", key). - Error("failed to cache manifest to GCS") + log.WithError(err).WithFields(log.Fields{ + "manifest": key, + "backend": s.Storage.Name(), + }).Error("failed to cache manifest to storage backend") return } @@ -177,7 +169,8 @@ func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) log.WithFields(log.Fields{ "manifest": key, "size": size, - }).Info("cached manifest to GCS") + "backend": s.Storage.Name(), + }).Info("cached manifest to storage backend") } // Retrieve a layer build from the cache, first checking the local @@ -187,16 +180,12 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, return entry, true } - obj := s.Bucket.Object("builds/" + key) - _, err := obj.Attrs(ctx) + r, err := s.Storage.Fetch("builds/" + key) if err != nil { - return nil, false - } - - r, err := obj.NewReader(ctx) - if err != nil { - log.WithError(err).WithField("layer", key). - Error("failed to retrieve cached layer from GCS") + log.WithError(err).WithFields(log.Fields{ + "layer": key, + "backend": s.Storage.Name(), + }).Warn("failed to retrieve cached layer from storage backend") return nil, false } @@ -205,8 +194,10 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, jb := bytes.NewBuffer([]byte{}) _, err = io.Copy(jb, r) if err != nil { - log.WithError(err).WithField("layer", key). - Error("failed to read cached layer from GCS") + log.WithError(err).WithFields(log.Fields{ + "layer": key, + "backend": s.Storage.Name(), + }).Error("failed to read cached layer from storage backend") return nil, false } @@ -227,24 +218,19 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, func cacheLayer(ctx context.Context, s *State, key string, entry manifest.Entry) { s.Cache.localCacheLayer(key, entry) - obj := s.Bucket.Object("builds/" + key) - j, _ := json.Marshal(&entry) + path := "builds/" + key + _, _, err := s.Storage.Persist(path, func(w io.Writer) (string, int64, error) { + size, err := io.Copy(w, bytes.NewReader(j)) + return "", size, err + }) - w := obj.NewWriter(ctx) - - _, err := io.Copy(w, bytes.NewReader(j)) if err != nil { - log.WithError(err).WithField("layer", key). - Error("failed to cache layer") - - return + log.WithError(err).WithFields(log.Fields{ + "layer": key, + "backend": s.Storage.Name(), + }).Error("failed to cache layer") } - if err = w.Close(); err != nil { - log.WithError(err).WithField("layer", key). - Error("failed to cache layer") - - return - } + return } diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index 6c1baafce..ad6dff404 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -18,42 +18,11 @@ package config import ( - "io/ioutil" "os" - "cloud.google.com/go/storage" log "github.com/sirupsen/logrus" - "golang.org/x/oauth2/google" ) -// Configure GCS URL signing in the presence of a service account key -// (toggled if the user has set GOOGLE_APPLICATION_CREDENTIALS). -func signingOptsFromEnv() *storage.SignedURLOptions { - path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") - if path == "" { - return nil - } - - key, err := ioutil.ReadFile(path) - if err != nil { - log.WithError(err).WithField("file", path).Fatal("failed to read service account key") - } - - conf, err := google.JWTConfigFromJSON(key) - if err != nil { - log.WithError(err).WithField("file", path).Fatal("failed to parse service account key") - } - - log.WithField("account", conf.Email).Info("GCS URL signing enabled") - - return &storage.SignedURLOptions{ - Scheme: storage.SigningSchemeV4, - GoogleAccessID: conf.Email, - PrivateKey: conf.PrivateKey, - Method: "GET", - } -} - func getConfig(key, desc, def string) string { value := os.Getenv(key) if value == "" && def == "" { @@ -70,13 +39,11 @@ func getConfig(key, desc, def string) string { // Config holds the Nixery configuration options. type Config struct { - Bucket string // GCS bucket to cache & serve layers - Signing *storage.SignedURLOptions // Signing options to use for GCS URLs - Port string // Port on which to launch HTTP server - Pkgs PkgSource // Source for Nix package set - Timeout string // Timeout for a single Nix builder (seconds) - WebDir string // Directory with static web assets - PopUrl string // URL to the Nix package popularity count + Port string // Port on which to launch HTTP server + Pkgs PkgSource // Source for Nix package set + Timeout string // Timeout for a single Nix builder (seconds) + WebDir string // Directory with static web assets + PopUrl string // URL to the Nix package popularity count } func FromEnv() (Config, error) { @@ -86,10 +53,8 @@ func FromEnv() (Config, error) { } return Config{ - Bucket: getConfig("BUCKET", "GCS bucket for layer storage", ""), Port: getConfig("PORT", "HTTP port", ""), Pkgs: pkgs, - Signing: signingOptsFromEnv(), Timeout: getConfig("NIX_TIMEOUT", "Nix builder timeout", "60"), WebDir: getConfig("WEB_DIR", "Static web file dir", ""), PopUrl: os.Getenv("NIX_POPULARITY_URL"), diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index f38fab2f2..22ed6f1a5 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -32,9 +32,7 @@ import ( "io/ioutil" "net/http" "regexp" - "time" - "cloud.google.com/go/storage" "github.com/google/nixery/server/builder" "github.com/google/nixery/server/config" "github.com/google/nixery/server/layers" @@ -59,49 +57,6 @@ var ( layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) ) -// layerRedirect constructs the public URL of the layer object in the Cloud -// Storage bucket, signs it and redirects the user there. -// -// Signing the URL allows unauthenticated clients to retrieve objects from the -// bucket. -// -// The Docker client is known to follow redirects, but this might not be true -// for all other registry clients. -func constructLayerUrl(cfg *config.Config, digest string) (string, error) { - log.WithField("layer", digest).Info("redirecting layer request to bucket") - object := "layers/" + digest - - if cfg.Signing != nil { - opts := *cfg.Signing - opts.Expires = time.Now().Add(5 * time.Minute) - return storage.SignedURL(cfg.Bucket, object, &opts) - } else { - return ("https://storage.googleapis.com/" + cfg.Bucket + "/" + object), nil - } -} - -// prepareBucket configures the handle to a Cloud Storage bucket in which -// individual layers will be stored after Nix builds. Nixery does not directly -// serve layers to registry clients, instead it redirects them to the public -// URLs of the Cloud Storage bucket. -// -// The bucket is required for Nixery to function correctly, hence fatal errors -// are generated in case it fails to be set up correctly. -func prepareBucket(ctx context.Context, cfg *config.Config) *storage.BucketHandle { - client, err := storage.NewClient(ctx) - if err != nil { - log.WithError(err).Fatal("failed to set up Cloud Storage client") - } - - bkt := client.Bucket(cfg.Bucket) - - if _, err := bkt.Attrs(ctx); err != nil { - log.WithError(err).WithField("bucket", cfg.Bucket).Fatal("could not access configured bucket") - } - - return bkt -} - // Downloads the popularity information for the package set from the // URL specified in Nixery's configuration. func downloadPopularity(url string) (layers.Popularity, error) { @@ -218,16 +173,15 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) if len(layerMatches) == 3 { digest := layerMatches[2] - url, err := constructLayerUrl(&h.state.Cfg, digest) - + storage := h.state.Storage + err := storage.ServeLayer(digest, w) if err != nil { - log.WithError(err).WithField("layer", digest).Error("failed to sign GCS URL") - writeError(w, 500, "UNKNOWN", "could not serve layer") - return + log.WithError(err).WithFields(log.Fields{ + "layer": digest, + "backend": storage.Name(), + }).Error("failed to serve layer from storage backend") } - w.Header().Set("Location", url) - w.WriteHeader(303) return } @@ -243,7 +197,6 @@ func main() { } ctx := context.Background() - bucket := prepareBucket(ctx, &cfg) cache, err := builder.NewCache() if err != nil { log.WithError(err).Fatal("failed to instantiate build cache") @@ -259,10 +212,9 @@ func main() { } state := builder.State{ - Bucket: bucket, - Cache: &cache, - Cfg: cfg, - Pop: pop, + Cache: &cache, + Cfg: cfg, + Pop: pop, } log.WithFields(log.Fields{ diff --git a/tools/nixery/server/storage/storage.go b/tools/nixery/server/storage/storage.go new file mode 100644 index 000000000..15b8355e6 --- /dev/null +++ b/tools/nixery/server/storage/storage.go @@ -0,0 +1,34 @@ +// Package storage implements an interface that can be implemented by +// storage backends, such as Google Cloud Storage or the local +// filesystem. +package storage + +import ( + "io" + "net/http" +) + +type Backend interface { + // Name returns the name of the storage backend, for use in + // log messages and such. + Name() string + + // Persist provides a user-supplied function with a writer + // that stores data in the storage backend. + // + // It needs to return the SHA256 hash of the data written as + // well as the total number of bytes, as those are required + // for the image manifest. + Persist(string, func(io.Writer) (string, int64, error)) (string, int64, error) + + // Fetch retrieves data from the storage backend. + Fetch(path string) (io.ReadCloser, error) + + // Move renames a path inside the storage backend. This is + // used for staging uploads while calculating their hashes. + Move(old, new string) error + + // Serve provides a handler function to serve HTTP requests + // for layers in the storage backend. + ServeLayer(digest string, w http.ResponseWriter) error +} From 20e0ca53cba796a67311360d7a21c0f7c8baf78a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 27 Oct 2019 17:59:30 +0100 Subject: [PATCH 151/223] feat(server): Implement GCS storage backend with new interface Logical implementation is mostly identical to the previous one, but adhering to the new storage.Backend interface. --- tools/nixery/server/storage/gcs.go | 206 +++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 tools/nixery/server/storage/gcs.go diff --git a/tools/nixery/server/storage/gcs.go b/tools/nixery/server/storage/gcs.go new file mode 100644 index 000000000..1b75722bb --- /dev/null +++ b/tools/nixery/server/storage/gcs.go @@ -0,0 +1,206 @@ +// Google Cloud Storage backend for Nixery. +package storage + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "time" + + "cloud.google.com/go/storage" + log "github.com/sirupsen/logrus" + "golang.org/x/oauth2/google" +) + +// HTTP client to use for direct calls to APIs that are not part of the SDK +var client = &http.Client{} + +// API scope needed for renaming objects in GCS +const gcsScope = "https://www.googleapis.com/auth/devstorage" + +type GCSBackend struct { + bucket string + handle *storage.BucketHandle + signing *storage.SignedURLOptions +} + +// Constructs a new GCS bucket backend based on the configured +// environment variables. +func New() (GCSBackend, error) { + bucket := os.Getenv("GCS_BUCKET") + if bucket == "" { + return GCSBackend{}, fmt.Errorf("GCS_BUCKET must be configured for GCS usage") + } + + ctx := context.Background() + client, err := storage.NewClient(ctx) + if err != nil { + log.WithError(err).Fatal("failed to set up Cloud Storage client") + } + + handle := client.Bucket(bucket) + + if _, err := handle.Attrs(ctx); err != nil { + log.WithError(err).WithField("bucket", bucket).Error("could not access configured bucket") + return GCSBackend{}, err + } + + signing, err := signingOptsFromEnv() + if err != nil { + log.WithError(err).Error("failed to configure GCS bucket signing") + return GCSBackend{}, err + } + + return GCSBackend{ + bucket: bucket, + handle: handle, + signing: signing, + }, nil +} + +func (b *GCSBackend) Name() string { + return "Google Cloud Storage (" + b.bucket + ")" +} + +func (b *GCSBackend) Persist(path string, f func(io.Writer) (string, int, error)) (string, int, error) { + ctx := context.Background() + obj := b.handle.Object(path) + w := obj.NewWriter(ctx) + + hash, size, err := f(w) + if err != nil { + log.WithError(err).WithField("path", path).Error("failed to upload to GCS") + return hash, size, err + } + + return hash, size, w.Close() +} + +func (b *GCSBackend) Fetch(path string) (io.ReadCloser, error) { + ctx := context.Background() + obj := b.handle.Object(path) + + // Probe whether the file exists before trying to fetch it + _, err := obj.Attrs(ctx) + if err != nil { + return nil, err + } + + return obj.NewReader(ctx) +} + +// renameObject renames an object in the specified Cloud Storage +// bucket. +// +// The Go API for Cloud Storage does not support renaming objects, but +// the HTTP API does. The code below makes the relevant call manually. +func (b *GCSBackend) Move(old, new string) error { + ctx := context.Background() + creds, err := google.FindDefaultCredentials(ctx, gcsScope) + if err != nil { + return err + } + + token, err := creds.TokenSource.Token() + if err != nil { + return err + } + + // as per https://cloud.google.com/storage/docs/renaming-copying-moving-objects#rename + url := fmt.Sprintf( + "https://www.googleapis.com/storage/v1/b/%s/o/%s/rewriteTo/b/%s/o/%s", + url.PathEscape(b.bucket), url.PathEscape(old), + url.PathEscape(b.bucket), url.PathEscape(new), + ) + + req, err := http.NewRequest("POST", url, nil) + req.Header.Add("Authorization", "Bearer "+token.AccessToken) + _, err = client.Do(req) + if err != nil { + return err + } + + // It seems that 'rewriteTo' copies objects instead of + // renaming/moving them, hence a deletion call afterwards is + // required. + if err = b.handle.Object(old).Delete(ctx); err != nil { + log.WithError(err).WithFields(log.Fields{ + "new": new, + "old": old, + }).Warn("failed to delete renamed object") + + // this error should not break renaming and is not returned + } + + return nil +} + +func (b *GCSBackend) Serve(digest string, w http.ResponseWriter) error { + url, err := b.constructLayerUrl(digest) + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "layer": digest, + "bucket": b.bucket, + }).Error("failed to sign GCS URL") + + return err + } + + w.Header().Set("Location", url) + w.WriteHeader(303) + return nil +} + +// Configure GCS URL signing in the presence of a service account key +// (toggled if the user has set GOOGLE_APPLICATION_CREDENTIALS). +func signingOptsFromEnv() (*storage.SignedURLOptions, error) { + path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") + if path == "" { + // No credentials configured -> no URL signing + return nil, nil + } + + key, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read service account key: %s", err) + } + + conf, err := google.JWTConfigFromJSON(key) + if err != nil { + return nil, fmt.Errorf("failed to parse service account key: %s", err) + } + + log.WithField("account", conf.Email).Info("GCS URL signing enabled") + + return &storage.SignedURLOptions{ + Scheme: storage.SigningSchemeV4, + GoogleAccessID: conf.Email, + PrivateKey: conf.PrivateKey, + Method: "GET", + }, nil +} + +// layerRedirect constructs the public URL of the layer object in the Cloud +// Storage bucket, signs it and redirects the user there. +// +// Signing the URL allows unauthenticated clients to retrieve objects from the +// bucket. +// +// The Docker client is known to follow redirects, but this might not be true +// for all other registry clients. +func (b *GCSBackend) constructLayerUrl(digest string) (string, error) { + log.WithField("layer", digest).Info("redirecting layer request to bucket") + object := "layers/" + digest + + if b.signing != nil { + opts := *b.signing + opts.Expires = time.Now().Add(5 * time.Minute) + return storage.SignedURL(b.bucket, object, &opts) + } else { + return ("https://storage.googleapis.com/" + b.bucket + "/" + object), nil + } +} From e8fd6b67348610ff7bbd4585036567b9e56945b7 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 27 Oct 2019 18:33:57 +0100 Subject: [PATCH 152/223] refactor(server): Change setup to create new storage backends --- tools/nixery/server/builder/builder.go | 4 ---- tools/nixery/server/builder/cache.go | 2 +- tools/nixery/server/config/config.go | 19 +++++++++++++++++++ tools/nixery/server/main.go | 20 +++++++++++++++++--- tools/nixery/server/storage/gcs.go | 14 +++++++------- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index e2982b993..17ea1b8e6 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -27,7 +27,6 @@ import ( "fmt" "io" "io/ioutil" - "net/http" "os" "os/exec" "sort" @@ -45,9 +44,6 @@ import ( // use up is set at a lower point. const LayerBudget int = 94 -// HTTP client to use for direct calls to APIs that are not part of the SDK -var client = &http.Client{} - // State holds the runtime state that is carried around in Nixery and // passed to builder functions. type State struct { diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 2af214cd9..4aed4b534 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -125,7 +125,7 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa log.WithError(err).WithFields(log.Fields{ "manifest": key, "backend": s.Storage.Name(), - }) + }).Error("failed to fetch manifest from cache") return nil, false } diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index ad6dff404..6cc69fa1f 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -37,6 +37,13 @@ func getConfig(key, desc, def string) string { return value } +// Backend represents the possible storage backend types +type Backend int + +const ( + GCS = iota +) + // Config holds the Nixery configuration options. type Config struct { Port string // Port on which to launch HTTP server @@ -44,6 +51,7 @@ type Config struct { Timeout string // Timeout for a single Nix builder (seconds) WebDir string // Directory with static web assets PopUrl string // URL to the Nix package popularity count + Backend Backend // Storage backend to use for Nixery } func FromEnv() (Config, error) { @@ -52,11 +60,22 @@ func FromEnv() (Config, error) { return Config{}, err } + var b Backend + switch os.Getenv("NIXERY_STORAGE_BACKEND") { + case "gcs": + b = GCS + default: + log.WithField("values", []string{ + "gcs", + }).Fatal("NIXERY_STORAGE_BUCKET must be set to a supported value") + } + return Config{ Port: getConfig("PORT", "HTTP port", ""), Pkgs: pkgs, Timeout: getConfig("NIX_TIMEOUT", "Nix builder timeout", "60"), WebDir: getConfig("WEB_DIR", "Static web file dir", ""), PopUrl: os.Getenv("NIX_POPULARITY_URL"), + Backend: b, }, nil } diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 22ed6f1a5..b5d7091ed 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -36,6 +36,7 @@ import ( "github.com/google/nixery/server/builder" "github.com/google/nixery/server/config" "github.com/google/nixery/server/layers" + "github.com/google/nixery/server/storage" log "github.com/sirupsen/logrus" ) @@ -196,6 +197,18 @@ func main() { log.WithError(err).Fatal("failed to load configuration") } + var s storage.Backend + + switch cfg.Backend { + case config.GCS: + s, err = storage.NewGCSBackend() + } + if err != nil { + log.WithError(err).Fatal("failed to initialise storage backend") + } + + log.WithField("backend", s.Name()).Info("initialised storage backend") + ctx := context.Background() cache, err := builder.NewCache() if err != nil { @@ -212,9 +225,10 @@ func main() { } state := builder.State{ - Cache: &cache, - Cfg: cfg, - Pop: pop, + Cache: &cache, + Cfg: cfg, + Pop: pop, + Storage: s, } log.WithFields(log.Fields{ diff --git a/tools/nixery/server/storage/gcs.go b/tools/nixery/server/storage/gcs.go index 1b75722bb..feb6d30d6 100644 --- a/tools/nixery/server/storage/gcs.go +++ b/tools/nixery/server/storage/gcs.go @@ -30,10 +30,10 @@ type GCSBackend struct { // Constructs a new GCS bucket backend based on the configured // environment variables. -func New() (GCSBackend, error) { +func NewGCSBackend() (*GCSBackend, error) { bucket := os.Getenv("GCS_BUCKET") if bucket == "" { - return GCSBackend{}, fmt.Errorf("GCS_BUCKET must be configured for GCS usage") + return nil, fmt.Errorf("GCS_BUCKET must be configured for GCS usage") } ctx := context.Background() @@ -46,16 +46,16 @@ func New() (GCSBackend, error) { if _, err := handle.Attrs(ctx); err != nil { log.WithError(err).WithField("bucket", bucket).Error("could not access configured bucket") - return GCSBackend{}, err + return nil, err } signing, err := signingOptsFromEnv() if err != nil { log.WithError(err).Error("failed to configure GCS bucket signing") - return GCSBackend{}, err + return nil, err } - return GCSBackend{ + return &GCSBackend{ bucket: bucket, handle: handle, signing: signing, @@ -66,7 +66,7 @@ func (b *GCSBackend) Name() string { return "Google Cloud Storage (" + b.bucket + ")" } -func (b *GCSBackend) Persist(path string, f func(io.Writer) (string, int, error)) (string, int, error) { +func (b *GCSBackend) Persist(path string, f func(io.Writer) (string, int64, error)) (string, int64, error) { ctx := context.Background() obj := b.handle.Object(path) w := obj.NewWriter(ctx) @@ -139,7 +139,7 @@ func (b *GCSBackend) Move(old, new string) error { return nil } -func (b *GCSBackend) Serve(digest string, w http.ResponseWriter) error { +func (b *GCSBackend) ServeLayer(digest string, w http.ResponseWriter) error { url, err := b.constructLayerUrl(digest) if err != nil { log.WithError(err).WithFields(log.Fields{ From e5bb2fc887f8d4e7216a1dfcbfa81baac2b09cfd Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 17:52:41 +0100 Subject: [PATCH 153/223] feat(server): Implement initial filesystem storage backend This allows users to store and serve layers from a local filesystem path. --- tools/nixery/server/storage/filesystem.go | 68 +++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tools/nixery/server/storage/filesystem.go diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/server/storage/filesystem.go new file mode 100644 index 000000000..ef763da67 --- /dev/null +++ b/tools/nixery/server/storage/filesystem.go @@ -0,0 +1,68 @@ +// Filesystem storage backend for Nixery. +package storage + +import ( + "fmt" + "io" + "net/http" + "os" + "path" + + log "github.com/sirupsen/logrus" +) + +type FSBackend struct { + path string +} + +func NewFSBackend(p string) (*FSBackend, error) { + p = path.Clean(p) + err := os.MkdirAll(p, 0755) + if err != nil { + return nil, fmt.Errorf("failed to create storage dir: %s", err) + } + + return &FSBackend{p}, nil +} + +func (b *FSBackend) Name() string { + return fmt.Sprintf("Filesystem (%s)", b.path) +} + +func (b *FSBackend) Persist(key string, f func(io.Writer) (string, int64, error)) (string, int64, error) { + full := path.Join(b.path, key) + dir := path.Dir(full) + err := os.MkdirAll(dir, 0755) + if err != nil { + log.WithError(err).WithField("path", dir).Error("failed to create storage directory") + return "", 0, err + } + + file, err := os.OpenFile(full, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + log.WithError(err).WithField("file", full).Error("failed to write file") + return "", 0, err + } + defer file.Close() + + return f(file) +} + +func (b *FSBackend) Fetch(key string) (io.ReadCloser, error) { + full := path.Join(b.path, key) + return os.Open(full) +} + +func (b *FSBackend) Move(old, new string) error { + return os.Rename(path.Join(b.path, old), path.Join(b.path, new)) +} + +func (b *FSBackend) ServeLayer(digest string, w http.ResponseWriter) error { + // http.Serve* functions attempt to be a lot more clever than + // I want, but I also would prefer to avoid implementing error + // translation myself - thus a fake request is created here. + req := http.Request{Method: "GET"} + http.ServeFile(w, &req, path.Join(b.path, "sha256:"+digest)) + + return nil +} From 167a0b32630ed86b3a053e56fa499957872d7b38 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:18:17 +0100 Subject: [PATCH 154/223] refactor(server): Pass HTTP request to storage.ServeLayer The request object is required for some serving methods (e.g. the filesystem one). --- tools/nixery/server/builder/builder.go | 2 +- tools/nixery/server/main.go | 2 +- tools/nixery/server/storage/gcs.go | 4 +++- tools/nixery/server/storage/storage.go | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 17ea1b8e6..021cc662c 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -411,7 +411,7 @@ func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) "layer": key, "sha256": sha256sum, "size": size, - }).Info("uploaded layer") + }).Info("created and persisted layer") entry := manifest.Entry{ Digest: "sha256:" + sha256sum, diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index b5d7091ed..282fe9773 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -175,7 +175,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if len(layerMatches) == 3 { digest := layerMatches[2] storage := h.state.Storage - err := storage.ServeLayer(digest, w) + err := storage.ServeLayer(digest, r, w) if err != nil { log.WithError(err).WithFields(log.Fields{ "layer": digest, diff --git a/tools/nixery/server/storage/gcs.go b/tools/nixery/server/storage/gcs.go index feb6d30d6..749c7ba15 100644 --- a/tools/nixery/server/storage/gcs.go +++ b/tools/nixery/server/storage/gcs.go @@ -139,7 +139,7 @@ func (b *GCSBackend) Move(old, new string) error { return nil } -func (b *GCSBackend) ServeLayer(digest string, w http.ResponseWriter) error { +func (b *GCSBackend) ServeLayer(digest string, r *http.Request, w http.ResponseWriter) error { url, err := b.constructLayerUrl(digest) if err != nil { log.WithError(err).WithFields(log.Fields{ @@ -150,6 +150,8 @@ func (b *GCSBackend) ServeLayer(digest string, w http.ResponseWriter) error { return err } + log.WithField("layer", digest).Info("redirecting layer request to GCS bucket") + w.Header().Set("Location", url) w.WriteHeader(303) return nil diff --git a/tools/nixery/server/storage/storage.go b/tools/nixery/server/storage/storage.go index 15b8355e6..ad10d682e 100644 --- a/tools/nixery/server/storage/storage.go +++ b/tools/nixery/server/storage/storage.go @@ -30,5 +30,5 @@ type Backend interface { // Serve provides a handler function to serve HTTP requests // for layers in the storage backend. - ServeLayer(digest string, w http.ResponseWriter) error + ServeLayer(digest string, r *http.Request, w http.ResponseWriter) error } From 790bce219cf9acf01de3257fcf137a0a2833529e Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:19:06 +0100 Subject: [PATCH 155/223] feat(server): Add filesystem storage backend config options The filesystem storage backend can be enabled by setting `NIXERY_STORAGE_BACKEND` to `filesystem` and `STORAGE_PATH` to a disk location from which Nixery can serve files. --- tools/nixery/server/config/config.go | 3 +++ tools/nixery/server/main.go | 2 ++ tools/nixery/server/storage/filesystem.go | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/nixery/server/config/config.go b/tools/nixery/server/config/config.go index 6cc69fa1f..7ec102bd6 100644 --- a/tools/nixery/server/config/config.go +++ b/tools/nixery/server/config/config.go @@ -42,6 +42,7 @@ type Backend int const ( GCS = iota + FileSystem ) // Config holds the Nixery configuration options. @@ -64,6 +65,8 @@ func FromEnv() (Config, error) { switch os.Getenv("NIXERY_STORAGE_BACKEND") { case "gcs": b = GCS + case "filesystem": + b = FileSystem default: log.WithField("values", []string{ "gcs", diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index 282fe9773..f4f707313 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -202,6 +202,8 @@ func main() { switch cfg.Backend { case config.GCS: s, err = storage.NewGCSBackend() + case config.FileSystem: + s, err = storage.NewFSBackend() } if err != nil { log.WithError(err).Fatal("failed to initialise storage backend") diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/server/storage/filesystem.go index ef763da67..f343d67b6 100644 --- a/tools/nixery/server/storage/filesystem.go +++ b/tools/nixery/server/storage/filesystem.go @@ -15,7 +15,12 @@ type FSBackend struct { path string } -func NewFSBackend(p string) (*FSBackend, error) { +func NewFSBackend() (*FSBackend, error) { + p := os.Getenv("STORAGE_PATH") + if p == "" { + return nil, fmt.Errorf("STORAGE_PATH must be set for filesystem storage") + } + p = path.Clean(p) err := os.MkdirAll(p, 0755) if err != nil { From c08aa525587add05af2ff8e7f9ddc697858c9d0c Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:20:04 +0100 Subject: [PATCH 156/223] fix(server): Ensure error messages are correctly printed in logs I assumed (incorrectly) that logrus would already take care of surfacing error messages in human-readable form. --- tools/nixery/server/logs.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/nixery/server/logs.go b/tools/nixery/server/logs.go index dec4a410f..cc218c69d 100644 --- a/tools/nixery/server/logs.go +++ b/tools/nixery/server/logs.go @@ -76,6 +76,13 @@ func (f stackdriverFormatter) Format(e *log.Entry) ([]byte, error) { msg["eventTime"] = &e.Time msg["severity"] = logSeverity(e.Level) + if err, ok := msg[log.ErrorKey]; ok { + // TODO(tazjin): Cast safely - for now there should be + // no calls to `.WithError` with a nil error, but who + // knows. + msg[log.ErrorKey] = (err.(error)).Error() + } + if isError(e) { loc := reportLocation{ FilePath: e.Caller.File, From b60a8d007b47a5570715e7a693e1aa186032f29c Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:21:58 +0100 Subject: [PATCH 157/223] fix(server): Ensure paths exist when renaming in filesystem storage The point at which files are moved happens to also (initially) be the point where the `layers` directory is created. For this reason renaming must ensure that all path components exist, which this commit takes care of. --- tools/nixery/server/storage/filesystem.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/server/storage/filesystem.go index f343d67b6..60c48e932 100644 --- a/tools/nixery/server/storage/filesystem.go +++ b/tools/nixery/server/storage/filesystem.go @@ -59,7 +59,13 @@ func (b *FSBackend) Fetch(key string) (io.ReadCloser, error) { } func (b *FSBackend) Move(old, new string) error { - return os.Rename(path.Join(b.path, old), path.Join(b.path, new)) + newpath := path.Join(b.path, new) + err := os.MkdirAll(path.Dir(newpath), 0755) + if err != nil { + return err + } + + return os.Rename(path.Join(b.path, old), newpath) } func (b *FSBackend) ServeLayer(digest string, w http.ResponseWriter) error { From 4332d38f4f1250aebc6dc3e2bf05c67559fa57e7 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:22:02 +0100 Subject: [PATCH 158/223] fix(server): Correctly construct filesystem paths for layer serving --- tools/nixery/server/storage/filesystem.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/server/storage/filesystem.go index 60c48e932..c390a4d65 100644 --- a/tools/nixery/server/storage/filesystem.go +++ b/tools/nixery/server/storage/filesystem.go @@ -68,12 +68,14 @@ func (b *FSBackend) Move(old, new string) error { return os.Rename(path.Join(b.path, old), newpath) } -func (b *FSBackend) ServeLayer(digest string, w http.ResponseWriter) error { - // http.Serve* functions attempt to be a lot more clever than - // I want, but I also would prefer to avoid implementing error - // translation myself - thus a fake request is created here. - req := http.Request{Method: "GET"} - http.ServeFile(w, &req, path.Join(b.path, "sha256:"+digest)) +func (b *FSBackend) ServeLayer(digest string, r *http.Request, w http.ResponseWriter) error { + p := path.Join(b.path, "layers", digest) + log.WithFields(log.Fields{ + "layer": digest, + "path": p, + }).Info("serving layer from filesystem") + + http.ServeFile(w, r, p) return nil } From 30e618b65bdd330ea5904b2be00cbac46d5b03e3 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:23:56 +0100 Subject: [PATCH 159/223] chore(server): Move cache miss log statement to debug level This is very annoying otherwise. --- tools/nixery/server/builder/cache.go | 2 +- tools/nixery/server/storage/filesystem.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 4aed4b534..07ac9746d 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -185,7 +185,7 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, log.WithError(err).WithFields(log.Fields{ "layer": key, "backend": s.Storage.Name(), - }).Warn("failed to retrieve cached layer from storage backend") + }).Debug("failed to retrieve cached layer from storage backend") return nil, false } diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/server/storage/filesystem.go index c390a4d65..8aca20aac 100644 --- a/tools/nixery/server/storage/filesystem.go +++ b/tools/nixery/server/storage/filesystem.go @@ -73,7 +73,7 @@ func (b *FSBackend) ServeLayer(digest string, r *http.Request, w http.ResponseWr log.WithFields(log.Fields{ "layer": digest, - "path": p, + "path": p, }).Info("serving layer from filesystem") http.ServeFile(w, r, p) From d8fba233655e127d54926394eacd4b9391ec8b8b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:32:02 +0100 Subject: [PATCH 160/223] fix(server): Thread request context to all relevant places Previously background contexts where created where necessary (e.g. in GCS interactions). Should I begin to use request timeouts or other context-dependent things in the future, it's useful to have the actual HTTP request context around. This threads the request context through the application to all places that need it. --- tools/nixery/server/builder/builder.go | 4 ++-- tools/nixery/server/builder/cache.go | 8 ++++---- tools/nixery/server/main.go | 6 +----- tools/nixery/server/storage/filesystem.go | 7 ++++--- tools/nixery/server/storage/gcs.go | 9 +++------ tools/nixery/server/storage/storage.go | 9 ++++++--- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 021cc662c..59158037e 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -375,7 +375,7 @@ func (b *byteCounter) Write(p []byte) (n int, err error) { // image manifest. func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) (*manifest.Entry, error) { path := "staging/" + key - sha256sum, size, err := s.Storage.Persist(path, func(sw io.Writer) (string, int64, error) { + sha256sum, size, err := s.Storage.Persist(ctx, path, func(sw io.Writer) (string, int64, error) { // Sets up a "multiwriter" that simultaneously runs both hash // algorithms and uploads to the storage backend. shasum := sha256.New() @@ -399,7 +399,7 @@ func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) // Hashes are now known and the object is in the bucket, what // remains is to move it to the correct location and cache it. - err = s.Storage.Move("staging/"+key, "layers/"+sha256sum) + err = s.Storage.Move(ctx, "staging/"+key, "layers/"+sha256sum) if err != nil { log.WithError(err).WithField("layer", key). Error("failed to move layer from staging") diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/server/builder/cache.go index 07ac9746d..82bd90927 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/server/builder/cache.go @@ -120,7 +120,7 @@ func manifestFromCache(ctx context.Context, s *State, key string) (json.RawMessa return m, true } - r, err := s.Storage.Fetch("manifests/" + key) + r, err := s.Storage.Fetch(ctx, "manifests/"+key) if err != nil { log.WithError(err).WithFields(log.Fields{ "manifest": key, @@ -152,7 +152,7 @@ func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) go s.Cache.localCacheManifest(key, m) path := "manifests/" + key - _, size, err := s.Storage.Persist(path, func(w io.Writer) (string, int64, error) { + _, size, err := s.Storage.Persist(ctx, path, func(w io.Writer) (string, int64, error) { size, err := io.Copy(w, bytes.NewReader([]byte(m))) return "", size, err }) @@ -180,7 +180,7 @@ func layerFromCache(ctx context.Context, s *State, key string) (*manifest.Entry, return entry, true } - r, err := s.Storage.Fetch("builds/" + key) + r, err := s.Storage.Fetch(ctx, "builds/"+key) if err != nil { log.WithError(err).WithFields(log.Fields{ "layer": key, @@ -220,7 +220,7 @@ func cacheLayer(ctx context.Context, s *State, key string, entry manifest.Entry) j, _ := json.Marshal(&entry) path := "builds/" + key - _, _, err := s.Storage.Persist(path, func(w io.Writer) (string, int64, error) { + _, _, err := s.Storage.Persist(ctx, path, func(w io.Writer) (string, int64, error) { size, err := io.Copy(w, bytes.NewReader(j)) return "", size, err }) diff --git a/tools/nixery/server/main.go b/tools/nixery/server/main.go index f4f707313..6ae073090 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/server/main.go @@ -26,7 +26,6 @@ package main import ( - "context" "encoding/json" "fmt" "io/ioutil" @@ -110,7 +109,6 @@ func writeError(w http.ResponseWriter, status int, code, message string) { } type registryHandler struct { - ctx context.Context state *builder.State } @@ -132,7 +130,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { }).Info("requesting image manifest") image := builder.ImageFromName(imageName, imageTag) - buildResult, err := builder.BuildImage(h.ctx, h.state, &image) + buildResult, err := builder.BuildImage(r.Context(), h.state, &image) if err != nil { writeError(w, 500, "UNKNOWN", "image build failure") @@ -211,7 +209,6 @@ func main() { log.WithField("backend", s.Name()).Info("initialised storage backend") - ctx := context.Background() cache, err := builder.NewCache() if err != nil { log.WithError(err).Fatal("failed to instantiate build cache") @@ -240,7 +237,6 @@ func main() { // All /v2/ requests belong to the registry handler. http.Handle("/v2/", ®istryHandler{ - ctx: ctx, state: &state, }) diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/server/storage/filesystem.go index 8aca20aac..3fb91bc5e 100644 --- a/tools/nixery/server/storage/filesystem.go +++ b/tools/nixery/server/storage/filesystem.go @@ -2,6 +2,7 @@ package storage import ( + "context" "fmt" "io" "net/http" @@ -34,7 +35,7 @@ func (b *FSBackend) Name() string { return fmt.Sprintf("Filesystem (%s)", b.path) } -func (b *FSBackend) Persist(key string, f func(io.Writer) (string, int64, error)) (string, int64, error) { +func (b *FSBackend) Persist(ctx context.Context, key string, f Persister) (string, int64, error) { full := path.Join(b.path, key) dir := path.Dir(full) err := os.MkdirAll(dir, 0755) @@ -53,12 +54,12 @@ func (b *FSBackend) Persist(key string, f func(io.Writer) (string, int64, error) return f(file) } -func (b *FSBackend) Fetch(key string) (io.ReadCloser, error) { +func (b *FSBackend) Fetch(ctx context.Context, key string) (io.ReadCloser, error) { full := path.Join(b.path, key) return os.Open(full) } -func (b *FSBackend) Move(old, new string) error { +func (b *FSBackend) Move(ctx context.Context, old, new string) error { newpath := path.Join(b.path, new) err := os.MkdirAll(path.Dir(newpath), 0755) if err != nil { diff --git a/tools/nixery/server/storage/gcs.go b/tools/nixery/server/storage/gcs.go index 749c7ba15..b9d70ef20 100644 --- a/tools/nixery/server/storage/gcs.go +++ b/tools/nixery/server/storage/gcs.go @@ -66,8 +66,7 @@ func (b *GCSBackend) Name() string { return "Google Cloud Storage (" + b.bucket + ")" } -func (b *GCSBackend) Persist(path string, f func(io.Writer) (string, int64, error)) (string, int64, error) { - ctx := context.Background() +func (b *GCSBackend) Persist(ctx context.Context, path string, f Persister) (string, int64, error) { obj := b.handle.Object(path) w := obj.NewWriter(ctx) @@ -80,8 +79,7 @@ func (b *GCSBackend) Persist(path string, f func(io.Writer) (string, int64, erro return hash, size, w.Close() } -func (b *GCSBackend) Fetch(path string) (io.ReadCloser, error) { - ctx := context.Background() +func (b *GCSBackend) Fetch(ctx context.Context, path string) (io.ReadCloser, error) { obj := b.handle.Object(path) // Probe whether the file exists before trying to fetch it @@ -98,8 +96,7 @@ func (b *GCSBackend) Fetch(path string) (io.ReadCloser, error) { // // The Go API for Cloud Storage does not support renaming objects, but // the HTTP API does. The code below makes the relevant call manually. -func (b *GCSBackend) Move(old, new string) error { - ctx := context.Background() +func (b *GCSBackend) Move(ctx context.Context, old, new string) error { creds, err := google.FindDefaultCredentials(ctx, gcsScope) if err != nil { return err diff --git a/tools/nixery/server/storage/storage.go b/tools/nixery/server/storage/storage.go index ad10d682e..70095cba4 100644 --- a/tools/nixery/server/storage/storage.go +++ b/tools/nixery/server/storage/storage.go @@ -4,10 +4,13 @@ package storage import ( + "context" "io" "net/http" ) +type Persister = func(io.Writer) (string, int64, error) + type Backend interface { // Name returns the name of the storage backend, for use in // log messages and such. @@ -19,14 +22,14 @@ type Backend interface { // It needs to return the SHA256 hash of the data written as // well as the total number of bytes, as those are required // for the image manifest. - Persist(string, func(io.Writer) (string, int64, error)) (string, int64, error) + Persist(context.Context, string, Persister) (string, int64, error) // Fetch retrieves data from the storage backend. - Fetch(path string) (io.ReadCloser, error) + Fetch(ctx context.Context, path string) (io.ReadCloser, error) // Move renames a path inside the storage backend. This is // used for staging uploads while calculating their hashes. - Move(old, new string) error + Move(ctx context.Context, old, new string) error // Serve provides a handler function to serve HTTP requests // for layers in the storage backend. From b736f5580de878a6fb095317058ea11190e53a4e Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:37:31 +0100 Subject: [PATCH 161/223] docs: Add storage configuration options to README --- tools/nixery/README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 1574d5950..32e5921fa 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -74,13 +74,17 @@ interactive images. Nixery supports the following configuration options, provided via environment variables: -* `BUCKET`: [Google Cloud Storage][gcs] bucket to store & serve image layers * `PORT`: HTTP port on which Nixery should listen * `NIXERY_CHANNEL`: The name of a Nix/NixOS channel to use for building * `NIXERY_PKGS_REPO`: URL of a git repository containing a package set (uses locally configured SSH/git credentials) * `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use for building +* `NIXERY_STORAGE_BACKEND`: The type of backend storage to use, currently + supported values are `gcs` (Google Cloud Storage) and `filesystem`. + + For each of these additional backend configuration is necessary, see the + [storage section](#storage) for details. * `NIX_TIMEOUT`: Number of seconds that any Nix builder is allowed to run (defaults to 60) * `NIX_POPULARITY_URL`: URL to a file containing popularity data for @@ -91,6 +95,26 @@ account key, Nixery will also use this key to create [signed URLs][] for layers in the storage bucket. This makes it possible to serve layers from a bucket without having to make them publicly available. +### Storage + +Nixery supports multiple different storage backends in which its build cache and +image layers are kept, and from which they are served. + +Currently the available storage backends are Google Cloud Storage and the local +file system. + +In the GCS case, images are served by redirecting clients to the storage bucket. +Layers stored on the filesystem are served straight from the local disk. + +These extra configuration variables must be set to configure storage backends: + +* `GCS_BUCKET`: Name of the Google Cloud Storage bucket to use (**required** for + `gcs`) +* `GOOGLE_APPLICATION_CREDENTIALS`: Path to a GCP service account JSON key + (**optional** for `gcs`) +* `STORAGE_PATH`: Path to a folder in which to store and from which to serve + data (**required** for `filesystem`) + ## Roadmap ### Kubernetes integration From 3611baf040f19e234b81309822ac63723690a51d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 18:49:01 +0100 Subject: [PATCH 162/223] docs(under-the-hood): Update builder & storage backend information Both of these no longer matched the reality of what was actually going on in Nixery. --- tools/nixery/docs/src/under-the-hood.md | 79 ++++++++++++++++--------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/tools/nixery/docs/src/under-the-hood.md b/tools/nixery/docs/src/under-the-hood.md index b58a21d0d..4b7983001 100644 --- a/tools/nixery/docs/src/under-the-hood.md +++ b/tools/nixery/docs/src/under-the-hood.md @@ -6,9 +6,9 @@ image is requested from Nixery. - [1. The image manifest is requested](#1-the-image-manifest-is-requested) -- [2. Nix builds the image](#2-nix-builds-the-image) -- [3. Layers are uploaded to Nixery's storage](#3-layers-are-uploaded-to-nixerys-storage) -- [4. The image manifest is sent back](#4-the-image-manifest-is-sent-back) +- [2. Nix fetches and prepares image content](#2-nix-fetches-and-prepares-image-content) +- [3. Layers are grouped, created, hashed, and persisted](#3-layers-are-grouped-created-hashed-and-persisted) +- [4. The manifest is assembled and returned to the client](#4-the-manifest-is-assembled-and-returned-to-the-client) - [5. Image layers are requested](#5-image-layers-are-requested) @@ -40,7 +40,7 @@ It then invokes Nix with three parameters: 2. image tag 3. configured package set source -## 2. Nix builds the image +## 2. Nix fetches and prepares image content Using the parameters above, Nix imports the package set and begins by mapping the image names to attributes in the package set. @@ -50,12 +50,31 @@ their name, for example anything under `haskellPackages`. The registry protocol does not allow uppercase characters, so the Nix code will translate something like `haskellpackages` (lowercased) to the correct attribute name. -After identifying all contents, Nix determines the contents of each layer while -optimising for the best possible cache efficiency (see the [layering design -doc][] for details). +After identifying all contents, Nix uses the `symlinkJoin` function to +create a special layer with the "symlink farm" required to let the +image function like a normal disk image. -Finally it builds each layer, assembles the image manifest as JSON structure, -and yields this manifest back to the web server. +Nix then returns information about the image contents as well as the +location of the special layer to Nixery. + +## 3. Layers are grouped, created, hashed, and persisted + +With the information received from Nix, Nixery determines the contents +of each layer while optimising for the best possible cache efficiency +(see the [layering design doc][] for details). + +With the grouped layers, Nixery then begins to create compressed +tarballs with all required contents for each layer. As these tarballs +are being created, they are simultaneously being hashed (as the image +manifest must contain the content-hashes of all layers) and persisted +to storage. + +Storage can be either a remote [Google Cloud Storage][gcs] bucket, or +a local filesystem path. + +During this step, Nixery checks its build cache (see [Caching][]) to +determine whether a layer needs to be built or is already cached from +a previous build. *Note:* While this step is running (which can take some time in the case of large first-time image builds), the registry client is left hanging waiting for @@ -63,39 +82,43 @@ an HTTP response. Unfortunately the registry protocol does not allow for any feedback back to the user at this point, so from the user's perspective things just ... hang, for a moment. -## 3. Layers are uploaded to Nixery's storage +## 4. The manifest is assembled and returned to the client -Nixery inspects the returned manifest and uploads each layer to the configured -[Google Cloud Storage][gcs] bucket. To avoid unnecessary uploading, it will -check whether layers are already present in the bucket. +Once armed with the hashes of all required layers, Nixery assembles +the OCI Container Image manifest which describes the structure of the +built image and names all of its layers by their content hash. -## 4. The image manifest is sent back - -If everything went well at this point, Nixery responds to the registry client -with the image manifest. - -The client now inspects the manifest and basically sees a list of SHA256-hashes, -each corresponding to one layer of the image. Most clients will now consult -their local layer storage and determine which layers they are missing. - -Each of the missing layers is then requested from Nixery. +This manifest is returned to the client. ## 5. Image layers are requested -For each image layer that it needs to retrieve, the registry client assembles a -request that looks like this: +The client now inspects the manifest and determines which of the +layers it is currently missing based on their content hashes. Note +that different container runtimes will handle this differently, and in +the case of certain engine and storage driver combinations (e.g. +Docker with OverlayFS) layers might be downloaded again even if they +are already present. + +For each of the missing layers, the client now issues a request to +Nixery that looks like this: `GET /v2/${imageName}/blob/sha256:${layerHash}` -Nixery receives these requests and *rewrites* them to Google Cloud Storage URLs, -responding with an `HTTP 303 See Other` status code and the actual download URL -of the layer. +Nixery receives these requests and handles them based on the +configured storage backend. + +If the storage backend is GCS, it *redirects* them to Google Cloud +Storage URLs, responding with an `HTTP 303 See Other` status code and +the actual download URL of the layer. Nixery supports using private buckets which are not generally world-readable, in which case [signed URLs][] are constructed using a private key. These allow the registry client to download each layer without needing to care about how the underlying authentication works. +If the storage backend is the local filesystem, Nixery will attempt to +serve the layer back to the client from disk. + --------- That's it. After these five steps the registry client has retrieved all it needs From ab190256ab3118af146a65787965e04e06ccfaa1 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 20:04:41 +0100 Subject: [PATCH 163/223] fix(server): Use correct scope for GCS tokens --- tools/nixery/server/storage/gcs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/server/storage/gcs.go b/tools/nixery/server/storage/gcs.go index b9d70ef20..5cb673c15 100644 --- a/tools/nixery/server/storage/gcs.go +++ b/tools/nixery/server/storage/gcs.go @@ -20,7 +20,7 @@ import ( var client = &http.Client{} // API scope needed for renaming objects in GCS -const gcsScope = "https://www.googleapis.com/auth/devstorage" +const gcsScope = "https://www.googleapis.com/auth/devstorage.read_write" type GCSBackend struct { bucket string From 3a7c964a22ac0d7805cbe2d0a5f42f030ee68c19 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 20:04:59 +0100 Subject: [PATCH 164/223] chore(build): Configure build to use new GCS configuration options --- tools/nixery/.travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 31d485d72..89963840c 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -26,12 +26,11 @@ script: docker run -d -p 8080:8080 --name nixery \ -v ${PWD}/test-files:/var/nixery \ -e PORT=8080 \ - -e BUCKET=nixery-ci-tests \ + -e GCS_BUCKET=nixery-ci-tests \ -e GOOGLE_CLOUD_PROJECT=nixery \ -e GOOGLE_APPLICATION_CREDENTIALS=/var/nixery/key.json \ - -e GCS_SIGNING_ACCOUNT="${GCS_SIGNING_ACCOUNT}" \ - -e GCS_SIGNING_KEY=/var/nixery/gcs.pem \ -e NIXERY_CHANNEL=nixos-unstable \ + -e NIXERY_STORAGE_BACKEND=gcs \ ${IMG} # print all of the container's logs regardless of success From 904c3dade00881c6872dc633a135e12bdfe7e76b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 22:38:27 +0100 Subject: [PATCH 165/223] refactor(server): Handle non-error errors safely This case should not be possible unless something manually constructs a logrus entry with a non-error value in the log.ErrorKey field, but it's better to be safe than sorry. --- tools/nixery/server/logs.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/nixery/server/logs.go b/tools/nixery/server/logs.go index cc218c69d..7077b2b20 100644 --- a/tools/nixery/server/logs.go +++ b/tools/nixery/server/logs.go @@ -76,11 +76,12 @@ func (f stackdriverFormatter) Format(e *log.Entry) ([]byte, error) { msg["eventTime"] = &e.Time msg["severity"] = logSeverity(e.Level) - if err, ok := msg[log.ErrorKey]; ok { - // TODO(tazjin): Cast safely - for now there should be - // no calls to `.WithError` with a nil error, but who - // knows. - msg[log.ErrorKey] = (err.(error)).Error() + if e, ok := msg[log.ErrorKey]; ok { + if err, isError := e.(error); isError { + msg[log.ErrorKey] = err.Error() + } else { + delete(msg, log.ErrorKey) + } } if isError(e) { From 2d4a3ea307350e1f7495a4fb6c6f5a37d10d3912 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 28 Oct 2019 22:39:12 +0100 Subject: [PATCH 166/223] chore(server): Remove outdated TODO Real-life experience has shown that the weighting of the metric produced here is appropriate. --- tools/nixery/server/layers/grouping.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index 1fbbf33db..74952a137 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -300,11 +300,7 @@ func groupLayer(dt *flow.DominatorTree, root *closure) Layer { sort.Strings(contents) return Layer{ - Contents: contents, - // TODO(tazjin): The point of this is to factor in - // both the size and the popularity when making merge - // decisions, but there might be a smarter way to do - // it than a plain multiplication. + Contents: contents, MergeRating: uint64(root.Popularity) * size, } } From b03f7a1b4dff4780a8eaeb5c261598d422551220 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 31 Oct 2019 17:29:59 +0000 Subject: [PATCH 167/223] feat(popcount): Add new narinfo-based popcount implementation Adds an implementation of popcount that, instead of realising derivations locally, just queries the cache's narinfo files. The downside of this is that calculating popularity for arbitrary Nix package sets is not possible with this implementation. The upside is that calculating the popularity for an entire Nix channel can now be done in ~10 seconds[0]. This fixes #65. [0]: Assuming a /fast/ internet connection. --- tools/nixery/popcount/empty.json | 1 - tools/nixery/popcount/popcount | 13 -- tools/nixery/popcount/popcount.go | 256 +++++++++++++++++++++++++++++ tools/nixery/popcount/popcount.nix | 53 ------ 4 files changed, 256 insertions(+), 67 deletions(-) delete mode 100644 tools/nixery/popcount/empty.json delete mode 100755 tools/nixery/popcount/popcount create mode 100644 tools/nixery/popcount/popcount.go delete mode 100644 tools/nixery/popcount/popcount.nix diff --git a/tools/nixery/popcount/empty.json b/tools/nixery/popcount/empty.json deleted file mode 100644 index fe51488c7..000000000 --- a/tools/nixery/popcount/empty.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tools/nixery/popcount/popcount b/tools/nixery/popcount/popcount deleted file mode 100755 index 83baf3045..000000000 --- a/tools/nixery/popcount/popcount +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -ueo pipefail - -function graphsFor() { - local pkg="${1}" - local graphs=$(nix-build --timeout 2 --argstr target "${pkg}" popcount.nix || echo -n 'empty.json') - cat $graphs | jq -r -cM '.[] | .references[]' -} - -for pkg in $(cat all-top-level.json | jq -r '.[]'); do - graphsFor "${pkg}" 2>/dev/null - echo "Printed refs for ${pkg}" >&2 -done diff --git a/tools/nixery/popcount/popcount.go b/tools/nixery/popcount/popcount.go new file mode 100644 index 000000000..a37408e37 --- /dev/null +++ b/tools/nixery/popcount/popcount.go @@ -0,0 +1,256 @@ +// Popcount fetches popularity information for each store path in a +// given Nix channel from the upstream binary cache. +// +// It does this simply by inspecting the narinfo files, rather than +// attempting to deal with instantiation of the binary cache. +// +// This is *significantly* faster than attempting to realise the whole +// channel and then calling `nix path-info` on it. +// +// TODO(tazjin): Persist intermediate results (references for each +// store path) to speed up subsequent runs. +package main + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "regexp" + "strings" +) + +var client http.Client +var pathexp = regexp.MustCompile("/nix/store/([a-z0-9]{32})-(.*)$") +var refsexp = regexp.MustCompile("(?m:^References: (.*)$)") +var refexp = regexp.MustCompile("^([a-z0-9]{32})-(.*)$") + +type meta struct { + name string + url string + commit string +} + +type item struct { + name string + hash string +} + +func failOn(err error, msg string) { + if err != nil { + log.Fatalf("%s: %s", msg, err) + } +} + +func channelMetadata(channel string) meta { + // This needs an HTTP client that does not follow redirects + // because the channel URL is used explicitly for other + // downloads. + c := http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + resp, err := c.Get(fmt.Sprintf("https://nixos.org/channels/%s", channel)) + failOn(err, "failed to retrieve channel metadata") + + loc, err := resp.Location() + failOn(err, "no redirect location given for channel") + if resp.StatusCode != 302 { + log.Fatalf("Expected redirect for channel, but received '%s'\n", resp.Status) + } + + commitResp, err := c.Get(fmt.Sprintf("%s/git-revision", loc.String())) + failOn(err, "failed to retrieve commit for channel") + + defer commitResp.Body.Close() + commit, err := ioutil.ReadAll(commitResp.Body) + failOn(err, "failed to read commit from response") + + return meta{ + name: channel, + url: loc.String(), + commit: string(commit), + } +} + +func downloadStorePaths(c *meta) []string { + resp, err := client.Get(fmt.Sprintf("%s/store-paths.xz", c.url)) + failOn(err, "failed to download store-paths.xz") + defer resp.Body.Close() + + cmd := exec.Command("xzcat") + stdin, err := cmd.StdinPipe() + failOn(err, "failed to open xzcat stdin") + stdout, err := cmd.StdoutPipe() + failOn(err, "failed to open xzcat stdout") + defer stdout.Close() + + go func() { + defer stdin.Close() + io.Copy(stdin, resp.Body) + }() + + err = cmd.Start() + failOn(err, "failed to start xzcat") + + paths, err := ioutil.ReadAll(stdout) + failOn(err, "failed to read uncompressed store paths") + + err = cmd.Wait() + failOn(err, "xzcat failed to decompress") + + return strings.Split(string(paths), "\n") +} + +func storePathToItem(path string) *item { + res := pathexp.FindStringSubmatch(path) + if len(res) != 3 { + return nil + } + + return &item{ + hash: res[1], + name: res[2], + } +} + +func narInfoToRefs(narinfo string) []string { + all := refsexp.FindAllStringSubmatch(narinfo, 1) + + if len(all) != 1 { + log.Fatalf("failed to parse narinfo:\n%s\nfound: %v\n", narinfo, all[0]) + } + + if len(all[0]) != 2 { + // no references found + return []string{} + } + + refs := strings.Split(all[0][1], " ") + for i, s := range refs { + if s == "" { + continue + } + + res := refexp.FindStringSubmatch(s) + refs[i] = res[2] + } + + return refs +} + +func fetchNarInfo(i *item) (string, error) { + resp, err := client.Get(fmt.Sprintf("https://cache.nixos.org/%s.narinfo", i.hash)) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + narinfo, err := ioutil.ReadAll(resp.Body) + return string(narinfo), err +} + +// downloader starts a worker that takes care of downloading narinfos +// for all paths received from the queue. +// +// If there is no data remaining in the queue, the downloader exits +// and informs the finaliser queue about having exited. +func downloader(queue chan *item, narinfos chan string, downloaders chan struct{}) { + for i := range queue { + ni, err := fetchNarInfo(i) + if err != nil { + log.Printf("couldn't fetch narinfo for %s: %s\n", i.name, err) + continue + + } + narinfos <- ni + } + downloaders <- struct{}{} +} + +// finaliser counts the number of downloaders that have exited and +// closes the narinfos queue to signal to the counters that no more +// elements will arrive. +func finaliser(count int, downloaders chan struct{}, narinfos chan string) { + for range downloaders { + count-- + if count == 0 { + close(downloaders) + close(narinfos) + break + } + } +} + +func main() { + if len(os.Args) == 1 { + log.Fatalf("Nix channel must be specified as first argument") + } + + count := 42 // concurrent downloader count + channel := os.Args[1] + log.Printf("Fetching metadata for channel '%s'\n", channel) + + meta := channelMetadata(channel) + log.Printf("Pinned channel '%s' to commit '%s'\n", meta.name, meta.commit) + + paths := downloadStorePaths(&meta) + log.Printf("Fetching references for %d store paths\n", len(paths)) + + // Download paths concurrently and receive their narinfos into + // a channel. Data is collated centrally into a map and + // serialised at the /very/ end. + downloadQueue := make(chan *item, len(paths)) + for _, p := range paths { + if i := storePathToItem(p); i != nil { + downloadQueue <- i + } + } + close(downloadQueue) + + // Set up a task tracking channel for parsing & counting + // narinfos, as well as a coordination channel for signaling + // that all downloads have finished + narinfos := make(chan string, 50) + downloaders := make(chan struct{}, count) + for i := 0; i < count; i++ { + go downloader(downloadQueue, narinfos, downloaders) + } + + go finaliser(count, downloaders, narinfos) + + counts := make(map[string]int) + for ni := range narinfos { + refs := narInfoToRefs(ni) + for _, ref := range refs { + if ref == "" { + continue + } + + counts[ref] += 1 + } + } + + // Remove all self-references (i.e. packages not referenced by anyone else) + for k, v := range counts { + if v == 1 { + delete(counts, k) + } + } + + bytes, _ := json.Marshal(counts) + outfile := fmt.Sprintf("popularity-%s-%s.json", meta.name, meta.commit) + err = ioutil.WriteFile(outfile, bytes, 0644) + if err != nil { + log.Fatalf("Failed to write output to '%s': %s\n", outfile, err) + } + + log.Printf("Wrote output to '%s'\n", outfile) +} diff --git a/tools/nixery/popcount/popcount.nix b/tools/nixery/popcount/popcount.nix deleted file mode 100644 index 54fd2ad58..000000000 --- a/tools/nixery/popcount/popcount.nix +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script, given a target attribute in `nixpkgs`, builds the -# target derivations' runtime closure and returns its reference graph. -# -# This is invoked by popcount.sh for each package in nixpkgs to -# collect all package references, so that package popularity can be -# tracked. -# -# Check out build-image/group-layers.go for an in-depth explanation of -# what the popularity counts are used for. - -{ pkgs ? import { config.allowUnfree = false; }, target }: - -let - inherit (pkgs) coreutils runCommand writeText; - inherit (builtins) readFile toFile fromJSON toJSON listToAttrs; - - # graphJSON abuses feature in Nix that makes structured runtime - # closure information available to builders. This data is imported - # back via IFD to process it for layering data. - graphJSON = path: - runCommand "build-graph" { - __structuredAttrs = true; - exportReferencesGraph.graph = path; - PATH = "${coreutils}/bin"; - builder = toFile "builder" '' - . .attrs.sh - cat .attrs.json > ''${outputs[out]} - ''; - } ""; - - buildClosures = paths: (fromJSON (readFile (graphJSON paths))); - - buildGraph = paths: - listToAttrs (map (c: { - name = c.path; - value = { inherit (c) closureSize references; }; - }) (buildClosures paths)); -in writeText "${target}-graph" -(toJSON (buildClosures [ pkgs."${target}" ]).graph) From 6a2fb092a72be70c173b756e5cb2276a542a09df Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 31 Oct 2019 17:35:15 +0000 Subject: [PATCH 168/223] chore: Add missing copyright headers to source files --- tools/nixery/popcount/popcount.go | 14 ++++++++++++++ tools/nixery/server/builder/archive.go | 13 +++++++++++++ tools/nixery/server/layers/grouping.go | 14 ++++++++++++++ tools/nixery/server/logs.go | 13 +++++++++++++ tools/nixery/server/manifest/manifest.go | 14 ++++++++++++++ tools/nixery/server/storage/filesystem.go | 14 ++++++++++++++ tools/nixery/server/storage/gcs.go | 14 ++++++++++++++ tools/nixery/server/storage/storage.go | 14 ++++++++++++++ 8 files changed, 110 insertions(+) diff --git a/tools/nixery/popcount/popcount.go b/tools/nixery/popcount/popcount.go index a37408e37..bc5f42af8 100644 --- a/tools/nixery/popcount/popcount.go +++ b/tools/nixery/popcount/popcount.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + // Popcount fetches popularity information for each store path in a // given Nix channel from the upstream binary cache. // diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/server/builder/archive.go index a3fb99882..e0fb76d44 100644 --- a/tools/nixery/server/builder/archive.go +++ b/tools/nixery/server/builder/archive.go @@ -1,3 +1,16 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. package builder // This file implements logic for walking through a directory and creating a diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/server/layers/grouping.go index 74952a137..3902c8a4e 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/server/layers/grouping.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + // This package reads an export reference graph (i.e. a graph representing the // runtime dependencies of a set of derivations) created by Nix and groups it in // a way that is likely to match the grouping for other derivation sets with diff --git a/tools/nixery/server/logs.go b/tools/nixery/server/logs.go index 7077b2b20..3179402e2 100644 --- a/tools/nixery/server/logs.go +++ b/tools/nixery/server/logs.go @@ -1,3 +1,16 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. package main // This file configures different log formatters via logrus. The diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go index 8ad828239..11fef3ff0 100644 --- a/tools/nixery/server/manifest/manifest.go +++ b/tools/nixery/server/manifest/manifest.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + // Package image implements logic for creating the image metadata // (such as the image manifest and configuration). package manifest diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/server/storage/filesystem.go index 3fb91bc5e..cdbc31c5e 100644 --- a/tools/nixery/server/storage/filesystem.go +++ b/tools/nixery/server/storage/filesystem.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + // Filesystem storage backend for Nixery. package storage diff --git a/tools/nixery/server/storage/gcs.go b/tools/nixery/server/storage/gcs.go index 5cb673c15..c247cca62 100644 --- a/tools/nixery/server/storage/gcs.go +++ b/tools/nixery/server/storage/gcs.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + // Google Cloud Storage backend for Nixery. package storage diff --git a/tools/nixery/server/storage/storage.go b/tools/nixery/server/storage/storage.go index 70095cba4..c97b5e4fa 100644 --- a/tools/nixery/server/storage/storage.go +++ b/tools/nixery/server/storage/storage.go @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + // Package storage implements an interface that can be implemented by // storage backends, such as Google Cloud Storage or the local // filesystem. From 05b5b1718a4b9f251d51767a189905649ad42282 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 31 Oct 2019 17:48:56 +0000 Subject: [PATCH 169/223] feat(popcount): Cache seen narinfos on disk --- tools/nixery/popcount/popcount.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/nixery/popcount/popcount.go b/tools/nixery/popcount/popcount.go index bc5f42af8..b21cee2e0 100644 --- a/tools/nixery/popcount/popcount.go +++ b/tools/nixery/popcount/popcount.go @@ -160,6 +160,11 @@ func narInfoToRefs(narinfo string) []string { } func fetchNarInfo(i *item) (string, error) { + file, err := ioutil.ReadFile("popcache/" + i.hash) + if err == nil { + return string(file), nil + } + resp, err := client.Get(fmt.Sprintf("https://cache.nixos.org/%s.narinfo", i.hash)) if err != nil { return "", err @@ -168,6 +173,10 @@ func fetchNarInfo(i *item) (string, error) { defer resp.Body.Close() narinfo, err := ioutil.ReadAll(resp.Body) + + // best-effort write the file to the cache + ioutil.WriteFile("popcache/" + i.hash, narinfo, 0644) + return string(narinfo), err } @@ -208,6 +217,11 @@ func main() { log.Fatalf("Nix channel must be specified as first argument") } + err := os.MkdirAll("popcache", 0755) + if err != nil { + log.Fatalf("Failed to create 'popcache' directory in current folder: %s\n", err) + } + count := 42 // concurrent downloader count channel := os.Args[1] log.Printf("Fetching metadata for channel '%s'\n", channel) From 7afbc912ceff01044b291388d1e0f567ac24bdef Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 31 Oct 2019 17:54:31 +0000 Subject: [PATCH 170/223] chore(build): Add nixery-popcount to top-level package set --- tools/nixery/default.nix | 2 ++ tools/nixery/popcount/default.nix | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tools/nixery/popcount/default.nix diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 354103796..af1ec904b 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -53,6 +53,8 @@ in rec { exec ${nixery-server}/bin/server ''; + nixery-popcount = callPackage ./popcount { }; + # Container image containing Nixery and Nix itself. This image can # be run on Kubernetes, published on AppEngine or whatever else is # desired. diff --git a/tools/nixery/popcount/default.nix b/tools/nixery/popcount/default.nix new file mode 100644 index 000000000..4a3c8faf9 --- /dev/null +++ b/tools/nixery/popcount/default.nix @@ -0,0 +1,26 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ go, stdenv }: + +stdenv.mkDerivation { + name = "nixery-popcount"; + + buildInputs = [ go ]; + phases = [ "buildPhase" ]; + buildPhase = '' + mkdir -p $out/bin + go build -o $out/bin/popcount ${./popcount.go} + ''; +} From 3c2de4c037b52d20476a9c47d53a0e456229ae55 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 5 Nov 2019 12:57:10 +0000 Subject: [PATCH 171/223] refactor(builder): Parameterise CPU architecture to use for images Adds the CPU architecture to the image configuration. This will make it possible to let users toggle architecture via meta-packages. Relates to #13 --- tools/nixery/build-image/build-image.nix | 8 +++++++- tools/nixery/server/builder/builder.go | 24 +++++++++++++++++++++++- tools/nixery/server/manifest/manifest.go | 7 +++---- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index d4df707a8..eb14d5242 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -25,6 +25,7 @@ # Description of the package set to be used (will be loaded by load-pkgs.nix) srcType ? "nixpkgs", srcArgs ? "nixos-19.03", + system ? "x86_64-linux", importArgs ? { }, # Path to load-pkgs.nix loadPkgs ? ./load-pkgs.nix, @@ -46,7 +47,12 @@ let inherit (pkgs) lib runCommand writeText; - pkgs = import loadPkgs { inherit srcType srcArgs importArgs; }; + pkgs = import loadPkgs { + inherit srcType srcArgs; + importArgs = importArgs // { + inherit system; + }; + }; # deepFetch traverses the top-level Nix package set to retrieve an item via a # path specified in string form. diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 59158037e..c726b137f 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -53,6 +53,22 @@ type State struct { Pop layers.Popularity } +// Architecture represents the possible CPU architectures for which +// container images can be built. +// +// The default architecture is amd64, but support for ARM platforms is +// available within nixpkgs and can be toggled via meta-packages. +type Architecture struct { + // Name of the system tuple to pass to Nix + nixSystem string + + // Name of the architecture as used in the OCI manifests + imageArch string +} + +var amd64 = Architecture{"x86_64-linux", "amd64"} +var arm = Architecture{"aarch64-linux", "arm64"} + // Image represents the information necessary for building a container image. // This can be either a list of package names (corresponding to keys in the // nixpkgs set) or a Nix expression that results in a *list* of derivations. @@ -63,6 +79,10 @@ type Image struct { // Names of packages to include in the image. These must correspond // directly to top-level names of Nix packages in the nixpkgs tree. Packages []string + + // Architecture for which to build the image. Nixery defaults + // this to amd64 if not specified via meta-packages. + Arch *Architecture } // BuildResult represents the data returned from the server to the @@ -96,6 +116,7 @@ func ImageFromName(name string, tag string) Image { Name: strings.Join(pkgs, "/"), Tag: tag, Packages: expanded, + Arch: &amd64, } } @@ -218,6 +239,7 @@ func prepareImage(s *State, image *Image) (*ImageResult, error) { "--argstr", "packages", string(packages), "--argstr", "srcType", srcType, "--argstr", "srcArgs", srcArgs, + "--argstr", "system", image.Arch.nixSystem, } output, err := callNix("nixery-build-image", image.Name, args) @@ -448,7 +470,7 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro return nil, err } - m, c := manifest.Manifest(layers) + m, c := manifest.Manifest(image.Arch.imageArch, layers) lw := func(w io.Writer) error { r := bytes.NewReader(c.Config) diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/server/manifest/manifest.go index 11fef3ff0..0d36826fb 100644 --- a/tools/nixery/server/manifest/manifest.go +++ b/tools/nixery/server/manifest/manifest.go @@ -33,7 +33,6 @@ const ( configType = "application/vnd.docker.container.image.v1+json" // image config constants - arch = "amd64" os = "linux" fsType = "layers" ) @@ -84,7 +83,7 @@ type ConfigLayer struct { // Outside of this module the image configuration is treated as an // opaque blob and it is thus returned as an already serialised byte // array and its SHA256-hash. -func configLayer(hashes []string) ConfigLayer { +func configLayer(arch string, hashes []string) ConfigLayer { c := imageConfig{} c.Architecture = arch c.OS = os @@ -104,7 +103,7 @@ func configLayer(hashes []string) ConfigLayer { // layer. // // Callers do not need to set the media type for the layer entries. -func Manifest(layers []Entry) (json.RawMessage, ConfigLayer) { +func Manifest(arch string, layers []Entry) (json.RawMessage, ConfigLayer) { // Sort layers by their merge rating, from highest to lowest. // This makes it likely for a contiguous chain of shared image // layers to appear at the beginning of a layer. @@ -123,7 +122,7 @@ func Manifest(layers []Entry) (json.RawMessage, ConfigLayer) { layers[i] = l } - c := configLayer(hashes) + c := configLayer(arch, hashes) m := manifest{ SchemaVersion: schemaVersion, From d7ccf351494873b7e45ba450019c5462ef860aeb Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 5 Nov 2019 14:03:48 +0000 Subject: [PATCH 172/223] feat(builder): Support 'arm64' meta-package Specifying this meta-package toggles support for ARM64 images, for example: # Pull a default x86_64 image docker pull nixery.dev/hello # Pull an ARM64 image docker pull nixery.dev/arm64/hello --- tools/nixery/server/builder/builder.go | 43 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index c726b137f..57b940909 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -67,7 +67,7 @@ type Architecture struct { } var amd64 = Architecture{"x86_64-linux", "amd64"} -var arm = Architecture{"aarch64-linux", "arm64"} +var arm64 = Architecture{"aarch64-linux", "arm64"} // Image represents the information necessary for building a container image. // This can be either a list of package names (corresponding to keys in the @@ -106,7 +106,7 @@ type BuildResult struct { // only the order of requested packages has changed. func ImageFromName(name string, tag string) Image { pkgs := strings.Split(name, "/") - expanded := convenienceNames(pkgs) + arch, expanded := metaPackages(pkgs) expanded = append(expanded, "cacert", "iana-etc") sort.Strings(pkgs) @@ -116,7 +116,7 @@ func ImageFromName(name string, tag string) Image { Name: strings.Join(pkgs, "/"), Tag: tag, Packages: expanded, - Arch: &amd64, + Arch: arch, } } @@ -136,22 +136,39 @@ type ImageResult struct { } `json:"symlinkLayer"` } -// convenienceNames expands convenience package names defined by Nixery which -// let users include commonly required sets of tools in a container quickly. +// metaPackages expands package names defined by Nixery which either +// include sets of packages or trigger certain image-building +// behaviour. // -// Convenience names must be specified as the first package in an image. +// Meta-packages must be specified as the first packages in an image +// name. // -// Currently defined convenience names are: +// Currently defined meta-packages are: // // * `shell`: Includes bash, coreutils and other common command-line tools -func convenienceNames(packages []string) []string { - shellPackages := []string{"bashInteractive", "coreutils", "moreutils", "nano"} - - if packages[0] == "shell" { - return append(packages[1:], shellPackages...) +// * `arm64`: Causes Nixery to build images for the ARM64 architecture +func metaPackages(packages []string) (*Architecture, []string) { + arch := &amd64 + var metapkgs []string + for idx, p := range packages { + if p == "shell" || p == "arm64" { + metapkgs = append(metapkgs, p) + } else { + packages = packages[idx:] + break + } } - return packages + for _, p := range metapkgs { + switch p { + case "shell": + packages = append(packages, "bashInteractive", "coreutils", "moreutils", "nano") + case "arm64": + arch = &arm64 + } + } + + return arch, packages } // logNix logs each output line from Nix. It runs in a goroutine per From 145b7f4289cd6c54bbbe5d1345c8d034e6f16be7 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 7 Nov 2019 17:19:06 +0000 Subject: [PATCH 173/223] fix(build-image): Allow "cross-builds" of images for different arch Imports the package set twice in the builder expression: Once configured for the target system, once configured for the native system. This makes it possible to fetch the actual image contents for the required architecture, but use local tools to assemble the symlink layer and metadata. --- tools/nixery/build-image/build-image.nix | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/build-image/build-image.nix index eb14d5242..4393f2b85 100644 --- a/tools/nixery/build-image/build-image.nix +++ b/tools/nixery/build-image/build-image.nix @@ -45,8 +45,13 @@ let toFile toJSON; - inherit (pkgs) lib runCommand writeText; + # Package set to use for sourcing utilities + nativePkgs = import loadPkgs { inherit srcType srcArgs importArgs; }; + inherit (nativePkgs) coreutils jq openssl lib runCommand writeText symlinkJoin; + # Package set to use for packages to be included in the image. This + # package set is imported with the system set to the target + # architecture. pkgs = import loadPkgs { inherit srcType srcArgs; importArgs = importArgs // { @@ -115,7 +120,7 @@ let runtimeGraph = runCommand "runtime-graph.json" { __structuredAttrs = true; exportReferencesGraph.graph = allContents.contents; - PATH = "${pkgs.coreutils}/bin"; + PATH = "${coreutils}/bin"; builder = toFile "builder" '' . .attrs.sh cp .attrs.json ''${outputs[out]} @@ -124,7 +129,7 @@ let # Create a symlink forest into all top-level store paths of the # image contents. - contentsEnv = pkgs.symlinkJoin { + contentsEnv = symlinkJoin { name = "bulk-layers"; paths = allContents.contents; }; @@ -141,7 +146,7 @@ let # Two different hashes are computed for different usages (inclusion # in manifest vs. content-checking in the layer cache). symlinkLayerMeta = fromJSON (readFile (runCommand "symlink-layer-meta.json" { - buildInputs = with pkgs; [ coreutils jq openssl ]; + buildInputs = [ coreutils jq openssl ]; }'' tarHash=$(sha256sum ${symlinkLayer} | cut -d ' ' -f1) layerSize=$(stat --printf '%s' ${symlinkLayer}) From 1d6898a7cc3c4b4d46993e0076302dffad19f46f Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 9 Nov 2019 14:19:21 +0000 Subject: [PATCH 174/223] feat(build): Include arm64 in build matrix --- tools/nixery/.travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 89963840c..02700c319 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -1,9 +1,13 @@ language: nix +arch: + - amd64 + - arm64 services: - docker env: - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/5271f8dddc0f2e54f55bd2fc1868c09ff72ac980.tar.gz before_script: + - echo "Running Nixery CI build on $(uname -m)" - mkdir test-files - echo ${GOOGLE_KEY} | base64 -d > test-files/key.json - echo ${GCS_SIGNING_PEM} | base64 -d > test-files/gcs.pem From 9a8abeff977cfb488073c1f338df0821021d41ab Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 9 Nov 2019 14:30:01 +0000 Subject: [PATCH 175/223] feat(build): Integration test on both CPU architectures --- tools/nixery/.travis.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 02700c319..72b2a657b 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -55,4 +55,25 @@ script: echo -n "." sleep 1 done - - docker run --rm localhost:8080/hello hello + + # Pull and run an image of the current CPU architecture + - | + case $(uname -m) in + x86_64) + docker run --rm localhost:8080/hello hello + ;; + aarch64) + docker run --rm localhost:8080/arm64/hello hello + ;; + esac + + # Pull an image of the opposite CPU architecture (but without running it) + - | + case $(uname -m) in + x86_64) + docker pull localhost:8080/arm64/hello + ;; + aarch64) + docker pull localhost:8080/hello + ;; + esac From 104c930040ab081f6e24aad95a7df71339d8e6d4 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 9 Nov 2019 14:30:48 +0000 Subject: [PATCH 176/223] chore(build): Use significantly fewer layers for Nixery itself Nixery itself is built with the buildLayeredImage system, which takes some time to create large numbers of layers. This adjusts the default number of image layers from 96 to 20. Additionally Nixery's image is often loaded with `docker load -i`, which ignores layer cache hits anyways. Additionaly the CI build is configured to use only 1, which speeds up CI runs. --- tools/nixery/.travis.yml | 2 +- tools/nixery/default.nix | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 72b2a657b..b0a2b3f99 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -15,7 +15,7 @@ before_script: - cachix use nixery script: - test -z $(gofmt -l server/ build-image/) - - nix-build | cachix push nixery + - nix-build --arg maxLayers 1 | cachix push nixery # This integration test makes sure that the container image built # for Nixery itself runs fine in Docker, and that images pulled diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index af1ec904b..44ac7313a 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -14,7 +14,8 @@ { pkgs ? import { } , preLaunch ? "" -, extraPackages ? [] }: +, extraPackages ? [] +, maxLayers ? 20 }: with pkgs; @@ -92,7 +93,8 @@ in rec { in dockerTools.buildLayeredImage { name = "nixery"; config.Cmd = [ "${nixery-launch-script}/bin/nixery" ]; - maxLayers = 96; + + inherit maxLayers; contents = [ bashInteractive cacert From a924093d0932b01e03a9cc6979926dddadb67323 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 27 Nov 2019 11:43:51 +0000 Subject: [PATCH 177/223] test(builder): Add test coverage for name->image conversion Adds tests to cover that packages & metapackages are parsed into image names correctly. --- tools/nixery/server/builder/builder_test.go | 123 ++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tools/nixery/server/builder/builder_test.go diff --git a/tools/nixery/server/builder/builder_test.go b/tools/nixery/server/builder/builder_test.go new file mode 100644 index 000000000..3fbe2ab40 --- /dev/null +++ b/tools/nixery/server/builder/builder_test.go @@ -0,0 +1,123 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +package builder + +import ( + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" +) + +var ignoreArch = cmpopts.IgnoreFields(Image{}, "Arch") + +func TestImageFromNameSimple(t *testing.T) { + image := ImageFromName("hello", "latest") + expected := Image{ + Name: "hello", + Tag: "latest", + Packages: []string{ + "cacert", + "hello", + "iana-etc", + }, + } + + if diff := cmp.Diff(expected, image, ignoreArch); diff != "" { + t.Fatalf("Image(\"hello\", \"latest\") mismatch:\n%s", diff) + } +} + +func TestImageFromNameMultiple(t *testing.T) { + image := ImageFromName("hello/git/htop", "latest") + expected := Image{ + Name: "git/hello/htop", + Tag: "latest", + Packages: []string{ + "cacert", + "git", + "hello", + "htop", + "iana-etc", + }, + } + + if diff := cmp.Diff(expected, image, ignoreArch); diff != "" { + t.Fatalf("Image(\"hello/git/htop\", \"latest\") mismatch:\n%s", diff) + } +} + +func TestImageFromNameShell(t *testing.T) { + image := ImageFromName("shell", "latest") + expected := Image{ + Name: "shell", + Tag: "latest", + Packages: []string{ + "bashInteractive", + "cacert", + "coreutils", + "iana-etc", + "moreutils", + "nano", + }, + } + + if diff := cmp.Diff(expected, image, ignoreArch); diff != "" { + t.Fatalf("Image(\"shell\", \"latest\") mismatch:\n%s", diff) + } +} + +func TestImageFromNameShellMultiple(t *testing.T) { + image := ImageFromName("shell/htop", "latest") + expected := Image{ + Name: "htop/shell", + Tag: "latest", + Packages: []string{ + "bashInteractive", + "cacert", + "coreutils", + "htop", + "iana-etc", + "moreutils", + "nano", + }, + } + + if diff := cmp.Diff(expected, image, ignoreArch); diff != "" { + t.Fatalf("Image(\"shell/htop\", \"latest\") mismatch:\n%s", diff) + } +} + +func TestImageFromNameShellArm64(t *testing.T) { + image := ImageFromName("shell/arm64", "latest") + expected := Image{ + Name: "arm64/shell", + Tag: "latest", + Packages: []string{ + "bashInteractive", + "cacert", + "coreutils", + "iana-etc", + "moreutils", + "nano", + }, + } + + if diff := cmp.Diff(expected, image, ignoreArch); diff != "" { + t.Fatalf("Image(\"shell/arm64\", \"latest\") mismatch:\n%s", diff) + } + + if image.Arch.imageArch != "arm64" { + t.Fatal("Image(\"shell/arm64\"): Expected arch arm64") + } +} From df88da126a5c0dc97aa0fadaf1baf069b80ce251 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 27 Nov 2019 11:44:26 +0000 Subject: [PATCH 178/223] fix(builder): Ensure "solo-metapackages" do not break builds The previous logic failed because single meta-packages such as "nixery.dev/shell" would not end up removing the meta-package itself from the list of packages passed to Nix, causing a build failure. This was a regression introduced in 827468a. --- tools/nixery/server/builder/builder.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/server/builder/builder.go index 57b940909..da9dede1a 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/server/builder/builder.go @@ -149,16 +149,22 @@ type ImageResult struct { // * `arm64`: Causes Nixery to build images for the ARM64 architecture func metaPackages(packages []string) (*Architecture, []string) { arch := &amd64 + var metapkgs []string + lastMeta := 0 for idx, p := range packages { if p == "shell" || p == "arm64" { metapkgs = append(metapkgs, p) + lastMeta = idx + 1 } else { - packages = packages[idx:] break } } + // Chop off the meta-packages from the front of the package + // list + packages = packages[lastMeta:] + for _, p := range metapkgs { switch p { case "shell": From 2b82f1b71a50b8b1473421cce0eec1a0d7ddc360 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 11 Nov 2019 21:07:16 +0000 Subject: [PATCH 179/223] refactor: Reshuffle file structure for better code layout This gets rid of the package called "server" and instead moves everything into the project root, such that Go actually builds us a binary called `nixery`. This is the first step towards factoring out CLI-based functionality for Nixery. --- tools/nixery/{server => }/builder/archive.go | 5 +- tools/nixery/{server => }/builder/builder.go | 24 +++---- .../{server => }/builder/builder_test.go | 0 tools/nixery/{server => }/builder/cache.go | 2 +- .../layers/grouping.go => builder/layers.go} | 33 +++++----- tools/nixery/{server => }/config/config.go | 0 tools/nixery/{server => }/config/pkgsource.go | 0 tools/nixery/default.nix | 44 ++++++++++--- tools/nixery/{server => }/go-deps.nix | 0 tools/nixery/{server => logs}/logs.go | 4 +- tools/nixery/{server => }/main.go | 15 ++--- .../nixery/{server => }/manifest/manifest.go | 0 tools/nixery/popcount/popcount.go | 2 +- .../default.nix | 4 +- .../load-pkgs.nix | 0 .../prepare-image.nix} | 0 tools/nixery/server/default.nix | 62 ------------------- tools/nixery/shell.nix | 2 +- .../nixery/{server => }/storage/filesystem.go | 0 tools/nixery/{server => }/storage/gcs.go | 0 tools/nixery/{server => }/storage/storage.go | 0 21 files changed, 83 insertions(+), 114 deletions(-) rename tools/nixery/{server => }/builder/archive.go (96%) rename tools/nixery/{server => }/builder/builder.go (95%) rename tools/nixery/{server => }/builder/builder_test.go (100%) rename tools/nixery/{server => }/builder/cache.go (99%) rename tools/nixery/{server/layers/grouping.go => builder/layers.go} (92%) rename tools/nixery/{server => }/config/config.go (100%) rename tools/nixery/{server => }/config/pkgsource.go (100%) rename tools/nixery/{server => }/go-deps.nix (100%) rename tools/nixery/{server => logs}/logs.go (98%) rename tools/nixery/{server => }/main.go (95%) rename tools/nixery/{server => }/manifest/manifest.go (100%) rename tools/nixery/{build-image => prepare-image}/default.nix (92%) rename tools/nixery/{build-image => prepare-image}/load-pkgs.nix (100%) rename tools/nixery/{build-image/build-image.nix => prepare-image/prepare-image.nix} (100%) delete mode 100644 tools/nixery/server/default.nix rename tools/nixery/{server => }/storage/filesystem.go (100%) rename tools/nixery/{server => }/storage/gcs.go (100%) rename tools/nixery/{server => }/storage/storage.go (100%) diff --git a/tools/nixery/server/builder/archive.go b/tools/nixery/builder/archive.go similarity index 96% rename from tools/nixery/server/builder/archive.go rename to tools/nixery/builder/archive.go index e0fb76d44..ff822e389 100644 --- a/tools/nixery/server/builder/archive.go +++ b/tools/nixery/builder/archive.go @@ -19,7 +19,6 @@ package builder // The tarball is written straight to the supplied reader, which makes it // possible to create an image layer from the specified store paths, hash it and // upload it in one reading pass. - import ( "archive/tar" "compress/gzip" @@ -28,8 +27,6 @@ import ( "io" "os" "path/filepath" - - "github.com/google/nixery/server/layers" ) // Create a new compressed tarball from each of the paths in the list @@ -37,7 +34,7 @@ import ( // // The uncompressed tarball is hashed because image manifests must // contain both the hashes of compressed and uncompressed layers. -func packStorePaths(l *layers.Layer, w io.Writer) (string, error) { +func packStorePaths(l *layer, w io.Writer) (string, error) { shasum := sha256.New() gz := gzip.NewWriter(w) multi := io.MultiWriter(shasum, gz) diff --git a/tools/nixery/server/builder/builder.go b/tools/nixery/builder/builder.go similarity index 95% rename from tools/nixery/server/builder/builder.go rename to tools/nixery/builder/builder.go index da9dede1a..ceb112df9 100644 --- a/tools/nixery/server/builder/builder.go +++ b/tools/nixery/builder/builder.go @@ -12,9 +12,10 @@ // License for the specific language governing permissions and limitations under // the License. -// Package builder implements the code required to build images via Nix. Image -// build data is cached for up to 24 hours to avoid duplicated calls to Nix -// (which are costly even if no building is performed). +// Package builder implements the logic for assembling container +// images. It shells out to Nix to retrieve all required Nix-packages +// and assemble the symlink layer and then creates the required +// tarballs in-process. package builder import ( @@ -32,10 +33,9 @@ import ( "sort" "strings" - "github.com/google/nixery/server/config" - "github.com/google/nixery/server/layers" - "github.com/google/nixery/server/manifest" - "github.com/google/nixery/server/storage" + "github.com/google/nixery/config" + "github.com/google/nixery/manifest" + "github.com/google/nixery/storage" log "github.com/sirupsen/logrus" ) @@ -50,7 +50,7 @@ type State struct { Storage storage.Backend Cache *LocalCache Cfg config.Config - Pop layers.Popularity + Pop Popularity } // Architecture represents the possible CPU architectures for which @@ -128,7 +128,7 @@ type ImageResult struct { Pkgs []string `json:"pkgs"` // These fields are populated in case of success - Graph layers.RuntimeGraph `json:"runtimeGraph"` + Graph runtimeGraph `json:"runtimeGraph"` SymlinkLayer struct { Size int `json:"size"` TarHash string `json:"tarHash"` @@ -265,7 +265,7 @@ func prepareImage(s *State, image *Image) (*ImageResult, error) { "--argstr", "system", image.Arch.nixSystem, } - output, err := callNix("nixery-build-image", image.Name, args) + output, err := callNix("nixery-prepare-image", image.Name, args) if err != nil { // granular error logging is performed in callNix already return nil, err @@ -292,7 +292,7 @@ func prepareImage(s *State, image *Image) (*ImageResult, error) { // added only after successful uploads, which guarantees that entries // retrieved from the cache are present in the bucket. func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageResult) ([]manifest.Entry, error) { - grouped := layers.Group(&result.Graph, &s.Pop, LayerBudget) + grouped := groupLayers(&result.Graph, &s.Pop, LayerBudget) var entries []manifest.Entry @@ -329,7 +329,7 @@ func prepareLayers(ctx context.Context, s *State, image *Image, result *ImageRes var pkgs []string for _, p := range l.Contents { - pkgs = append(pkgs, layers.PackageFromPath(p)) + pkgs = append(pkgs, packageFromPath(p)) } log.WithFields(log.Fields{ diff --git a/tools/nixery/server/builder/builder_test.go b/tools/nixery/builder/builder_test.go similarity index 100% rename from tools/nixery/server/builder/builder_test.go rename to tools/nixery/builder/builder_test.go diff --git a/tools/nixery/server/builder/cache.go b/tools/nixery/builder/cache.go similarity index 99% rename from tools/nixery/server/builder/cache.go rename to tools/nixery/builder/cache.go index 82bd90927..a4ebe03e1 100644 --- a/tools/nixery/server/builder/cache.go +++ b/tools/nixery/builder/cache.go @@ -22,7 +22,7 @@ import ( "os" "sync" - "github.com/google/nixery/server/manifest" + "github.com/google/nixery/manifest" log "github.com/sirupsen/logrus" ) diff --git a/tools/nixery/server/layers/grouping.go b/tools/nixery/builder/layers.go similarity index 92% rename from tools/nixery/server/layers/grouping.go rename to tools/nixery/builder/layers.go index 3902c8a4e..f769e43c5 100644 --- a/tools/nixery/server/layers/grouping.go +++ b/tools/nixery/builder/layers.go @@ -114,7 +114,7 @@ // // Layer budget: 10 // Layers: { E }, { D, F }, { A }, { B }, { C } -package layers +package builder import ( "crypto/sha1" @@ -128,11 +128,11 @@ import ( "gonum.org/v1/gonum/graph/simple" ) -// RuntimeGraph represents structured information from Nix about the runtime +// runtimeGraph represents structured information from Nix about the runtime // dependencies of a derivation. // // This is generated in Nix by using the exportReferencesGraph feature. -type RuntimeGraph struct { +type runtimeGraph struct { References struct { Graph []string `json:"graph"` } `json:"exportReferencesGraph"` @@ -153,19 +153,19 @@ type Popularity = map[string]int // Layer represents the data returned for each layer that Nix should // build for the container image. -type Layer struct { +type layer struct { Contents []string `json:"contents"` MergeRating uint64 } // Hash the contents of a layer to create a deterministic identifier that can be // used for caching. -func (l *Layer) Hash() string { +func (l *layer) Hash() string { sum := sha1.Sum([]byte(strings.Join(l.Contents, ":"))) return fmt.Sprintf("%x", sum) } -func (a Layer) merge(b Layer) Layer { +func (a layer) merge(b layer) layer { a.Contents = append(a.Contents, b.Contents...) a.MergeRating += b.MergeRating return a @@ -188,12 +188,15 @@ var nixRegexp = regexp.MustCompile(`^/nix/store/[a-z0-9]+-`) // PackageFromPath returns the name of a Nix package based on its // output store path. -func PackageFromPath(path string) string { +func packageFromPath(path string) string { return nixRegexp.ReplaceAllString(path, "") } +// DOTID provides a human-readable package name. The name stems from +// the dot format used by GraphViz, into which the dependency graph +// can be rendered. func (c *closure) DOTID() string { - return PackageFromPath(c.Path) + return packageFromPath(c.Path) } // bigOrPopular checks whether this closure should be considered for @@ -236,7 +239,7 @@ func insertEdges(graph *simple.DirectedGraph, cmap *map[string]*closure, node *c } // Create a graph structure from the references supplied by Nix. -func buildGraph(refs *RuntimeGraph, pop *Popularity) *simple.DirectedGraph { +func buildGraph(refs *runtimeGraph, pop *Popularity) *simple.DirectedGraph { cmap := make(map[string]*closure) graph := simple.NewDirectedGraph() @@ -296,7 +299,7 @@ func buildGraph(refs *RuntimeGraph, pop *Popularity) *simple.DirectedGraph { // Extracts a subgraph starting at the specified root from the // dominator tree. The subgraph is converted into a flat list of // layers, each containing the store paths and merge rating. -func groupLayer(dt *flow.DominatorTree, root *closure) Layer { +func groupLayer(dt *flow.DominatorTree, root *closure) layer { size := root.Size contents := []string{root.Path} children := dt.DominatedBy(root.ID()) @@ -313,7 +316,7 @@ func groupLayer(dt *flow.DominatorTree, root *closure) Layer { // Contents are sorted to ensure that hashing is consistent sort.Strings(contents) - return Layer{ + return layer{ Contents: contents, MergeRating: uint64(root.Popularity) * size, } @@ -324,10 +327,10 @@ func groupLayer(dt *flow.DominatorTree, root *closure) Layer { // // Layers are merged together until they fit into the layer budget, // based on their merge rating. -func dominate(budget int, graph *simple.DirectedGraph) []Layer { +func dominate(budget int, graph *simple.DirectedGraph) []layer { dt := flow.Dominators(graph.Node(0), graph) - var layers []Layer + var layers []layer for _, n := range dt.DominatedBy(dt.Root().ID()) { layers = append(layers, groupLayer(&dt, n.(*closure))) } @@ -352,10 +355,10 @@ func dominate(budget int, graph *simple.DirectedGraph) []Layer { return layers } -// GroupLayers applies the algorithm described above the its input and returns a +// groupLayers applies the algorithm described above the its input and returns a // list of layers, each consisting of a list of Nix store paths that it should // contain. -func Group(refs *RuntimeGraph, pop *Popularity, budget int) []Layer { +func groupLayers(refs *runtimeGraph, pop *Popularity, budget int) []layer { graph := buildGraph(refs, pop) return dominate(budget, graph) } diff --git a/tools/nixery/server/config/config.go b/tools/nixery/config/config.go similarity index 100% rename from tools/nixery/server/config/config.go rename to tools/nixery/config/config.go diff --git a/tools/nixery/server/config/pkgsource.go b/tools/nixery/config/pkgsource.go similarity index 100% rename from tools/nixery/server/config/pkgsource.go rename to tools/nixery/config/pkgsource.go diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 44ac7313a..7454c14a8 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -20,6 +20,8 @@ with pkgs; let + inherit (pkgs) buildGoPackage; + # Hash of all Nixery sources - this is used as the Nixery version in # builds to distinguish errors between deployed versions, see # server/logs.go for details. @@ -30,13 +32,41 @@ let # Go implementation of the Nixery server which implements the # container registry interface. # - # Users should use the nixery-bin derivation below instead. - nixery-server = callPackage ./server { - srcHash = nixery-src-hash; + # Users should use the nixery-bin derivation below instead as it + # provides the paths of files needed at runtime. + nixery-server = buildGoPackage rec { + name = "nixery-server"; + goDeps = ./go-deps.nix; + src = ./.; + + goPackagePath = "github.com/google/nixery"; + doCheck = true; + + # Simplify the Nix build instructions for Go to just the basics + # required to get Nixery up and running with the additional linker + # flags required. + outputs = [ "out" ]; + preConfigure = "bin=$out"; + buildPhase = '' + runHook preBuild + runHook renameImport + + export GOBIN="$out/bin" + go install -ldflags "-X main.version=$(cat ${nixery-src-hash})" ${goPackagePath} + ''; + + fixupPhase = '' + remove-references-to -t ${go} $out/bin/nixery + ''; + + checkPhase = '' + go vet ${goPackagePath} + go test ${goPackagePath} + ''; }; in rec { # Implementation of the Nix image building logic - nixery-build-image = import ./build-image { inherit pkgs; }; + nixery-prepare-image = import ./prepare-image { inherit pkgs; }; # Use mdBook to build a static asset page which Nixery can then # serve. This is primarily used for the public instance at @@ -50,8 +80,8 @@ in rec { # are installing Nixery directly. nixery-bin = writeShellScriptBin "nixery" '' export WEB_DIR="${nixery-book}" - export PATH="${nixery-build-image}/bin:$PATH" - exec ${nixery-server}/bin/server + export PATH="${nixery-prepare-image}/bin:$PATH" + exec ${nixery-server}/bin/nixery ''; nixery-popcount = callPackage ./popcount { }; @@ -104,7 +134,7 @@ in rec { gzip iana-etc nix - nixery-build-image + nixery-prepare-image nixery-launch-script openssh zlib diff --git a/tools/nixery/server/go-deps.nix b/tools/nixery/go-deps.nix similarity index 100% rename from tools/nixery/server/go-deps.nix rename to tools/nixery/go-deps.nix diff --git a/tools/nixery/server/logs.go b/tools/nixery/logs/logs.go similarity index 98% rename from tools/nixery/server/logs.go rename to tools/nixery/logs/logs.go index 3179402e2..4c755bc8a 100644 --- a/tools/nixery/server/logs.go +++ b/tools/nixery/logs/logs.go @@ -11,7 +11,7 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. -package main +package logs // This file configures different log formatters via logrus. The // standard formatter uses a structured JSON format that is compatible @@ -112,7 +112,7 @@ func (f stackdriverFormatter) Format(e *log.Entry) ([]byte, error) { return b.Bytes(), err } -func init() { +func Init(version string) { nixeryContext.Version = version log.SetReportCaller(true) log.SetFormatter(stackdriverFormatter{}) diff --git a/tools/nixery/server/main.go b/tools/nixery/main.go similarity index 95% rename from tools/nixery/server/main.go rename to tools/nixery/main.go index 6ae073090..6cad93740 100644 --- a/tools/nixery/server/main.go +++ b/tools/nixery/main.go @@ -32,10 +32,10 @@ import ( "net/http" "regexp" - "github.com/google/nixery/server/builder" - "github.com/google/nixery/server/config" - "github.com/google/nixery/server/layers" - "github.com/google/nixery/server/storage" + "github.com/google/nixery/builder" + "github.com/google/nixery/config" + "github.com/google/nixery/logs" + "github.com/google/nixery/storage" log "github.com/sirupsen/logrus" ) @@ -59,7 +59,7 @@ var ( // Downloads the popularity information for the package set from the // URL specified in Nixery's configuration. -func downloadPopularity(url string) (layers.Popularity, error) { +func downloadPopularity(url string) (builder.Popularity, error) { resp, err := http.Get(url) if err != nil { return nil, err @@ -74,7 +74,7 @@ func downloadPopularity(url string) (layers.Popularity, error) { return nil, err } - var pop layers.Popularity + var pop builder.Popularity err = json.Unmarshal(j, &pop) if err != nil { return nil, err @@ -190,6 +190,7 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func main() { + logs.Init(version) cfg, err := config.FromEnv() if err != nil { log.WithError(err).Fatal("failed to load configuration") @@ -214,7 +215,7 @@ func main() { log.WithError(err).Fatal("failed to instantiate build cache") } - var pop layers.Popularity + var pop builder.Popularity if cfg.PopUrl != "" { pop, err = downloadPopularity(cfg.PopUrl) if err != nil { diff --git a/tools/nixery/server/manifest/manifest.go b/tools/nixery/manifest/manifest.go similarity index 100% rename from tools/nixery/server/manifest/manifest.go rename to tools/nixery/manifest/manifest.go diff --git a/tools/nixery/popcount/popcount.go b/tools/nixery/popcount/popcount.go index b21cee2e0..992a88e87 100644 --- a/tools/nixery/popcount/popcount.go +++ b/tools/nixery/popcount/popcount.go @@ -175,7 +175,7 @@ func fetchNarInfo(i *item) (string, error) { narinfo, err := ioutil.ReadAll(resp.Body) // best-effort write the file to the cache - ioutil.WriteFile("popcache/" + i.hash, narinfo, 0644) + ioutil.WriteFile("popcache/"+i.hash, narinfo, 0644) return string(narinfo), err } diff --git a/tools/nixery/build-image/default.nix b/tools/nixery/prepare-image/default.nix similarity index 92% rename from tools/nixery/build-image/default.nix rename to tools/nixery/prepare-image/default.nix index a61ac06bd..60b208f52 100644 --- a/tools/nixery/build-image/default.nix +++ b/tools/nixery/prepare-image/default.nix @@ -20,10 +20,10 @@ { pkgs ? import {} }: -pkgs.writeShellScriptBin "nixery-build-image" '' +pkgs.writeShellScriptBin "nixery-prepare-image" '' exec ${pkgs.nix}/bin/nix-build \ --show-trace \ --no-out-link "$@" \ --argstr loadPkgs ${./load-pkgs.nix} \ - ${./build-image.nix} + ${./prepare-image.nix} '' diff --git a/tools/nixery/build-image/load-pkgs.nix b/tools/nixery/prepare-image/load-pkgs.nix similarity index 100% rename from tools/nixery/build-image/load-pkgs.nix rename to tools/nixery/prepare-image/load-pkgs.nix diff --git a/tools/nixery/build-image/build-image.nix b/tools/nixery/prepare-image/prepare-image.nix similarity index 100% rename from tools/nixery/build-image/build-image.nix rename to tools/nixery/prepare-image/prepare-image.nix diff --git a/tools/nixery/server/default.nix b/tools/nixery/server/default.nix deleted file mode 100644 index d497f106b..000000000 --- a/tools/nixery/server/default.nix +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -{ buildGoPackage, go, lib, srcHash }: - -buildGoPackage rec { - name = "nixery-server"; - goDeps = ./go-deps.nix; - src = ./.; - - goPackagePath = "github.com/google/nixery/server"; - doCheck = true; - - # The following phase configurations work around the overengineered - # Nix build configuration for Go. - # - # All I want this to do is produce a binary in the standard Nix - # output path, so pretty much all the phases except for the initial - # configuration of the "dependency forest" in $GOPATH have been - # overridden. - # - # This is necessary because the upstream builder does wonky things - # with the build arguments to the compiler, but I need to set some - # complex flags myself - - outputs = [ "out" ]; - preConfigure = "bin=$out"; - buildPhase = '' - runHook preBuild - runHook renameImport - - export GOBIN="$out/bin" - go install -ldflags "-X main.version=$(cat ${srcHash})" ${goPackagePath} - ''; - - fixupPhase = '' - remove-references-to -t ${go} $out/bin/server - ''; - - checkPhase = '' - go vet ${goPackagePath} - go test ${goPackagePath} - ''; - - meta = { - description = "Container image builder serving Nix-backed images"; - homepage = "https://github.com/google/nixery"; - license = lib.licenses.asl20; - maintainers = [ lib.maintainers.tazjin ]; - }; -} diff --git a/tools/nixery/shell.nix b/tools/nixery/shell.nix index 93cd1f4ce..b37caa83a 100644 --- a/tools/nixery/shell.nix +++ b/tools/nixery/shell.nix @@ -20,5 +20,5 @@ let nixery = import ./default.nix { inherit pkgs; }; in pkgs.stdenv.mkDerivation { name = "nixery-dev-shell"; - buildInputs = with pkgs; [ jq nixery.nixery-build-image ]; + buildInputs = with pkgs; [ jq nixery.nixery-prepare-image ]; } diff --git a/tools/nixery/server/storage/filesystem.go b/tools/nixery/storage/filesystem.go similarity index 100% rename from tools/nixery/server/storage/filesystem.go rename to tools/nixery/storage/filesystem.go diff --git a/tools/nixery/server/storage/gcs.go b/tools/nixery/storage/gcs.go similarity index 100% rename from tools/nixery/server/storage/gcs.go rename to tools/nixery/storage/gcs.go diff --git a/tools/nixery/server/storage/storage.go b/tools/nixery/storage/storage.go similarity index 100% rename from tools/nixery/server/storage/storage.go rename to tools/nixery/storage/storage.go From 1031d890ec2b9a0a4d965eddc304bc5bf580442d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 19 Jan 2020 02:21:08 +0000 Subject: [PATCH 180/223] fix(builder): Fix minor logging switcharoo --- tools/nixery/builder/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/builder/builder.go b/tools/nixery/builder/builder.go index ceb112df9..028bcc576 100644 --- a/tools/nixery/builder/builder.go +++ b/tools/nixery/builder/builder.go @@ -201,7 +201,7 @@ func callNix(program, image string, args []string) ([]byte, error) { if err != nil { return nil, err } - go logNix(program, image, errpipe) + go logNix(image, program, errpipe) if err = cmd.Start(); err != nil { log.WithError(err).WithFields(log.Fields{ From 215df37187501523bb3bf348b7bbd76a01692f19 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Tue, 25 Feb 2020 10:40:25 -0800 Subject: [PATCH 181/223] fix(popcount): Fix nix-build -A nixery-popcount Previously, this was failing as follows: ``` these derivations will be built: /nix/store/7rbrf06phkiyz31dwpq88x920zjhnw0c-nixery-popcount.drv building '/nix/store/7rbrf06phkiyz31dwpq88x920zjhnw0c-nixery-popcount.drv'... building warning: GOPATH set to GOROOT (/nix/store/4859cp1v7zqcqh43jkqsayl4wrz3g6hp-go-1.13.4/share/go) has no effect failed to initialize build cache at /homeless-shelter/.cache/go-build: mkdir /homeless-shelter: permission denied builder for '/nix/store/7rbrf06phkiyz31dwpq88x920zjhnw0c-nixery-popcount.drv' failed with exit code 1 error: build of '/nix/store/7rbrf06phkiyz31dwpq88x920zjhnw0c-nixery-popcount.drv' failed ``` --- tools/nixery/popcount/default.nix | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tools/nixery/popcount/default.nix b/tools/nixery/popcount/default.nix index 4a3c8faf9..bd695380c 100644 --- a/tools/nixery/popcount/default.nix +++ b/tools/nixery/popcount/default.nix @@ -12,15 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -{ go, stdenv }: +{ buildGoPackage }: -stdenv.mkDerivation { +buildGoPackage { name = "nixery-popcount"; - buildInputs = [ go ]; - phases = [ "buildPhase" ]; - buildPhase = '' - mkdir -p $out/bin - go build -o $out/bin/popcount ${./popcount.go} - ''; + src = ./.; + + goPackagePath = "github.com/google/nixery/popcount"; + doCheck = true; } From bdda24a77287e09cb855eee7019148ee8bbd1cd9 Mon Sep 17 00:00:00 2001 From: Raphael Borun Das Gupta Date: Fri, 1 May 2020 01:38:25 +0200 Subject: [PATCH 182/223] chore(nix): update channel 19.03 -> 20.03 Use a NixOS / NixPkgs release that's actually being supported and regularly updated. --- tools/nixery/docs/src/caching.md | 2 +- tools/nixery/docs/src/nixery.md | 2 +- tools/nixery/docs/src/run-your-own.md | 4 ++-- tools/nixery/prepare-image/prepare-image.nix | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/nixery/docs/src/caching.md b/tools/nixery/docs/src/caching.md index b07d9e22f..05ea6d89b 100644 --- a/tools/nixery/docs/src/caching.md +++ b/tools/nixery/docs/src/caching.md @@ -29,7 +29,7 @@ Manifest caching *only* applies in the following cases: Manifest caching *never* applies in the following cases: * package source specification is a local file path (i.e. `NIXERY_PKGS_PATH`) -* package source specification is a NixOS channel (e.g. `NIXERY_CHANNEL=nixos-19.03`) +* package source specification is a NixOS channel (e.g. `NIXERY_CHANNEL=nixos-20.03`) * package source specification is a git branch or tag (e.g. `staging`, `master` or `latest`) It is thus always preferable to request images from a fully-pinned package diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index 6cc16431c..185c84630 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -54,7 +54,7 @@ own instance of Nixery. ### Which revision of `nixpkgs` is used for the builds? The instance at `nixery.dev` tracks a recent NixOS channel, currently NixOS -19.03. The channel is updated several times a day. +20.03. The channel is updated several times a day. Private registries might be configured to track a different channel (such as `nixos-unstable`) or even track a git repository with custom packages. diff --git a/tools/nixery/docs/src/run-your-own.md b/tools/nixery/docs/src/run-your-own.md index ffddec32d..9c20e3f2c 100644 --- a/tools/nixery/docs/src/run-your-own.md +++ b/tools/nixery/docs/src/run-your-own.md @@ -44,7 +44,7 @@ be performed for trivial things. However if you are running a private Nixery, chances are high that you intend to use it with your own packages. There are three options available: -1. Specify an upstream Nix/NixOS channel[^1], such as `nixos-19.03` or +1. Specify an upstream Nix/NixOS channel[^1], such as `nixos-20.03` or `nixos-unstable`. 2. Specify your own git-repository with a custom package set[^2]. This makes it possible to pull different tags, branches or commits by modifying the image @@ -73,7 +73,7 @@ You must set *all* of these: * `BUCKET`: [Google Cloud Storage][gcs] bucket to store & serve image layers * `PORT`: HTTP port on which Nixery should listen -You may set *one* of these, if unset Nixery defaults to `nixos-19.03`: +You may set *one* of these, if unset Nixery defaults to `nixos-20.03`: * `NIXERY_CHANNEL`: The name of a Nix/NixOS channel to use for building * `NIXERY_PKGS_REPO`: URL of a git repository containing a package set (uses diff --git a/tools/nixery/prepare-image/prepare-image.nix b/tools/nixery/prepare-image/prepare-image.nix index 4393f2b85..7b73b92bf 100644 --- a/tools/nixery/prepare-image/prepare-image.nix +++ b/tools/nixery/prepare-image/prepare-image.nix @@ -24,7 +24,7 @@ { # Description of the package set to be used (will be loaded by load-pkgs.nix) srcType ? "nixpkgs", - srcArgs ? "nixos-19.03", + srcArgs ? "nixos-20.03", system ? "x86_64-linux", importArgs ? { }, # Path to load-pkgs.nix From b4e0b55e5608ae2b394880e69912d9352fea2d9d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 1 May 2020 12:46:38 +0100 Subject: [PATCH 183/223] chore(build): Change pin for default nixpkgs used to build Nixery This moves the pin from just being in the Travis configuration to also being set in a nixpkgs-pin.nix file, which makes it trivial to build at the right commit when performing local builds. --- tools/nixery/.travis.yml | 2 +- tools/nixery/default.nix | 2 +- tools/nixery/nixpkgs-pin.nix | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 tools/nixery/nixpkgs-pin.nix diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index b0a2b3f99..72bbb90b6 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -5,7 +5,7 @@ arch: services: - docker env: - - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/5271f8dddc0f2e54f55bd2fc1868c09ff72ac980.tar.gz + - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/0a40a3999eb4d577418515da842a2622a64880c5.tar.gz before_script: - echo "Running Nixery CI build on $(uname -m)" - mkdir test-files diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 7454c14a8..411865a8a 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -{ pkgs ? import { } +{ pkgs ? import ./nixpkgs-pin.nix , preLaunch ? "" , extraPackages ? [] , maxLayers ? 20 }: diff --git a/tools/nixery/nixpkgs-pin.nix b/tools/nixery/nixpkgs-pin.nix new file mode 100644 index 000000000..ea1b37bfe --- /dev/null +++ b/tools/nixery/nixpkgs-pin.nix @@ -0,0 +1,4 @@ +import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs-channels/archive/0a40a3999eb4d577418515da842a2622a64880c5.tar.gz"; + sha256 = "1j8gy2d61lmrp5gzi1a2jmb2v2pbk4b9666y8pf1pjg3jiqkzf7m"; +}) {} From 987a90510aca61e429ac659e510cbc51de9ae0bb Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 1 May 2020 12:47:31 +0100 Subject: [PATCH 184/223] fix(popcount): Accommodate upstream changes on nixos.org Channel serving has moved to a new subdomain, and the redirect semantics have changed. Instead of serving temporary redirects, permanent redirects are now issued. I've reported this upstream as a bug, but this workaround will fix it in the meantime. --- tools/nixery/popcount/popcount.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/nixery/popcount/popcount.go b/tools/nixery/popcount/popcount.go index 992a88e87..d632090c0 100644 --- a/tools/nixery/popcount/popcount.go +++ b/tools/nixery/popcount/popcount.go @@ -70,12 +70,16 @@ func channelMetadata(channel string) meta { }, } - resp, err := c.Get(fmt.Sprintf("https://nixos.org/channels/%s", channel)) + resp, err := c.Get(fmt.Sprintf("https://channels.nixos.org/%s", channel)) failOn(err, "failed to retrieve channel metadata") loc, err := resp.Location() failOn(err, "no redirect location given for channel") - if resp.StatusCode != 302 { + + // TODO(tazjin): These redirects are currently served as 301s, but + // should (and used to) be 302s. Check if/when this is fixed and + // update accordingly. + if !(resp.StatusCode == 301 || resp.StatusCode == 302) { log.Fatalf("Expected redirect for channel, but received '%s'\n", resp.Status) } @@ -85,6 +89,9 @@ func channelMetadata(channel string) meta { defer commitResp.Body.Close() commit, err := ioutil.ReadAll(commitResp.Body) failOn(err, "failed to read commit from response") + if commitResp.StatusCode != 200 { + log.Fatalf("non-success status code when fetching commit: %s", string(commit), commitResp.StatusCode) + } return meta{ name: channel, From bc9742f927dcd7d68abb3aadb0d578d2c6088ff3 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 1 May 2020 13:19:55 +0100 Subject: [PATCH 185/223] chore(build): Update pinned Go dependencies --- tools/nixery/go-deps.nix | 207 ++++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 99 deletions(-) diff --git a/tools/nixery/go-deps.nix b/tools/nixery/go-deps.nix index 847b44dce..7d57ef204 100644 --- a/tools/nixery/go-deps.nix +++ b/tools/nixery/go-deps.nix @@ -4,9 +4,18 @@ goPackagePath = "cloud.google.com/go"; fetch = { type = "git"; - url = "https://code.googlesource.com/gocloud"; - rev = "77f6a3a292a7dbf66a5329de0d06326f1906b450"; - sha256 = "1c9pkx782nbcp8jnl5lprcbzf97van789ky5qsncjgywjyymhigi"; + url = "https://github.com/googleapis/google-cloud-go"; + rev = "22b9552106761e34e39c0cf48b783f092c660767"; + sha256 = "17sb3753h1m5pbjlv4r5ydbd35kfh086g2qxv2zjlqd90kcsdj7x"; + }; + } + { + goPackagePath = "github.com/golang/groupcache"; + fetch = { + type = "git"; + url = "https://github.com/golang/groupcache"; + rev = "8c9f03a8e57eb486e42badaed3fb287da51807ba"; + sha256 = "0vjjr79r32icjzlb05wn02k59av7jx0rn1jijml8r4whlg7dnkfh"; }; } { @@ -14,8 +23,8 @@ fetch = { type = "git"; url = "https://github.com/golang/protobuf"; - rev = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"; - sha256 = "1k1wb4zr0qbwgpvz9q5ws9zhlal8hq7dmq62pwxxriksayl6hzym"; + rev = "d04d7b157bb510b1e0c10132224b616ac0e26b17"; + sha256 = "0m5z81im4nsyfgarjhppayk4hqnrwswr3nix9mj8pff8x9jvcjqw"; }; } { @@ -23,98 +32,8 @@ fetch = { type = "git"; url = "https://github.com/googleapis/gax-go"; - rev = "bd5b16380fd03dc758d11cef74ba2e3bc8b0e8c2"; - sha256 = "1lxawwngv6miaqd25s3ba0didfzylbwisd2nz7r4gmbmin6jsjrx"; - }; - } - { - goPackagePath = "github.com/hashicorp/golang-lru"; - fetch = { - type = "git"; - url = "https://github.com/hashicorp/golang-lru"; - rev = "59383c442f7d7b190497e9bb8fc17a48d06cd03f"; - sha256 = "0yzwl592aa32vfy73pl7wdc21855w17zssrp85ckw2nisky8rg9c"; - }; - } - { - goPackagePath = "go.opencensus.io"; - fetch = { - type = "git"; - url = "https://github.com/census-instrumentation/opencensus-go"; - rev = "b4a14686f0a98096416fe1b4cb848e384fb2b22b"; - sha256 = "1aidyp301v5ngwsnnc8v1s09vvbsnch1jc4vd615f7qv77r9s7dn"; - }; - } - { - goPackagePath = "golang.org/x/net"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/net"; - rev = "da137c7871d730100384dbcf36e6f8fa493aef5b"; - sha256 = "1qsiyr3irmb6ii06hivm9p2c7wqyxczms1a9v1ss5698yjr3fg47"; - }; - } - { - goPackagePath = "golang.org/x/oauth2"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/oauth2"; - rev = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33"; - sha256 = "06jwpvx0x2gjn2y959drbcir5kd7vg87k0r1216abk6rrdzzrzi2"; - }; - } - { - goPackagePath = "golang.org/x/sys"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/sys"; - rev = "51ab0e2deafac1f46c46ad59cf0921be2f180c3d"; - sha256 = "0xdhpckbql3bsqkpc2k5b1cpnq3q1qjqjjq2j3p707rfwb8nm91a"; - }; - } - { - goPackagePath = "golang.org/x/text"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/text"; - rev = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"; - sha256 = "0flv9idw0jm5nm8lx25xqanbkqgfiym6619w575p7nrdh0riqwqh"; - }; - } - { - goPackagePath = "google.golang.org/api"; - fetch = { - type = "git"; - url = "https://code.googlesource.com/google-api-go-client"; - rev = "069bea57b1be6ad0671a49ea7a1128025a22b73f"; - sha256 = "19q2b610lkf3z3y9hn6rf11dd78xr9q4340mdyri7kbijlj2r44q"; - }; - } - { - goPackagePath = "google.golang.org/genproto"; - fetch = { - type = "git"; - url = "https://github.com/google/go-genproto"; - rev = "c506a9f9061087022822e8da603a52fc387115a8"; - sha256 = "03hh80aqi58dqi5ykj4shk3chwkzrgq2f3k6qs5qhgvmcy79y2py"; - }; - } - { - goPackagePath = "google.golang.org/grpc"; - fetch = { - type = "git"; - url = "https://github.com/grpc/grpc-go"; - rev = "977142214c45640483838b8672a43c46f89f90cb"; - sha256 = "05wig23l2sil3bfdv19gq62sya7hsabqj9l8pzr1sm57qsvj218d"; - }; - } - { - goPackagePath = "gonum.org/v1/gonum"; - fetch = { - type = "git"; - url = "https://github.com/gonum/gonum"; - rev = "ced62fe5104b907b6c16cb7e575c17b2e62ceddd"; - sha256 = "1b7q6haabnp53igpmvr6a2414yralhbrldixx4kbxxg1apy8jdjg"; + rev = "be11bb253a768098254dc71e95d1a81ced778de3"; + sha256 = "072iv8llzr99za4pndfskgndq9rzms38r0sqy4d127ijnzmgl5nd"; }; } { @@ -122,8 +41,98 @@ fetch = { type = "git"; url = "https://github.com/sirupsen/logrus"; - rev = "de736cf91b921d56253b4010270681d33fdf7cb5"; - sha256 = "1qixss8m5xy7pzbf0qz2k3shjw0asklm9sj6zyczp7mryrari0aj"; + rev = "6699a89a232f3db797f2e280639854bbc4b89725"; + sha256 = "1a59pw7zimvm8k423iq9l4f4qjj1ia1xc6pkmhwl2mxc46y2n442"; + }; + } + { + goPackagePath = "go.opencensus.io"; + fetch = { + type = "git"; + url = "https://github.com/census-instrumentation/opencensus-go"; + rev = "d7677d6af5953e0506ac4c08f349c62b917a443a"; + sha256 = "1nphs1qjz4a99b2n4izpqw13shiy26jy0i7qgm95giyhwwdqspk0"; + }; + } + { + goPackagePath = "golang.org/x/net"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/net"; + rev = "627f9648deb96c27737b83199d44bb5c1010cbcf"; + sha256 = "0ziz7i9mhz6dy2f58dsa83flkk165w1cnazm7yksql5i9m7x099z"; + }; + } + { + goPackagePath = "golang.org/x/oauth2"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/oauth2"; + rev = "bf48bf16ab8d622ce64ec6ce98d2c98f916b6303"; + sha256 = "1sirdib60zwmh93kf9qrx51r8544k1p9rs5mk0797wibz3m4mrdg"; + }; + } + { + goPackagePath = "golang.org/x/sys"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/sys"; + rev = "226ff32320da7b90d0b5bc2365f4e359c466fb78"; + sha256 = "137cdvmmrmx8qf72r94pn6zxp0wg9rfl0ii2fa9jk5hdkhifiqa6"; + }; + } + { + goPackagePath = "golang.org/x/text"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/text"; + rev = "3a82255431918bb7c2e1c09c964a18991756910b"; + sha256 = "1vrps79ap8dy7car64pf0j4hnb1j2hwp9wf67skv6izmi8r9425w"; + }; + } + { + goPackagePath = "gonum.org/v1/gonum"; + fetch = { + type = "git"; + url = "https://github.com/gonum/gonum"; + rev = "f69c0ac28e32bbfe5ccf3f821d85533b5f646b04"; + sha256 = "1qpws9899qyr0m2v4v7asxgy1z0wh9f284ampc2ha5c0hp0x6r4m"; + }; + } + { + goPackagePath = "google.golang.org/api"; + fetch = { + type = "git"; + url = "https://github.com/googleapis/google-api-go-client"; + rev = "4ec5466f5645b0f7f76ecb2246e7c5f3568cf8bb"; + sha256 = "1clrw2syb40a51zqpaw0mri8jk54cqp3scjwxq44hqr7cmqp0967"; + }; + } + { + goPackagePath = "google.golang.org/genproto"; + fetch = { + type = "git"; + url = "https://github.com/googleapis/go-genproto"; + rev = "43cab4749ae7254af90e92cb2cd767dfc641f6dd"; + sha256 = "0svbzzf9drd4ndxwj5rlggly4jnds9c76fkcq5f8czalbf5mlvb6"; + }; + } + { + goPackagePath = "google.golang.org/grpc"; + fetch = { + type = "git"; + url = "https://github.com/grpc/grpc-go"; + rev = "9106c3fff5236fd664a8de183f1c27682c66b823"; + sha256 = "02m1k06p6a5fc6ahj9qgd81wdzspa9xc29gd7awygwiyk22rd3md"; + }; + } + { + goPackagePath = "google.golang.org/protobuf"; + fetch = { + type = "git"; + url = "https://go.googlesource.com/protobuf"; + rev = "a0f95d5b14735d688bd94ca511d396115e5b86be"; + sha256 = "0l4xsfz337vmlij7pg8s2bjwlmrk4g83p1b9n8vnfavrmnrcw6j0"; }; } ] From c194c5662b7a64047e469c27f6a5ac45b68e8d03 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 25 Jul 2020 14:08:24 +0100 Subject: [PATCH 186/223] fix(build): Don't use Cachix as the binary cache during builds Permission changes in the Travis CI Nix builders have caused this to start failing, as the build user now has insufficient permissions to use caches. There may be a way to change the permissions instead, but in the meantime we will just cause things to rebuild. --- tools/nixery/.travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 72bbb90b6..674231217 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -12,7 +12,6 @@ before_script: - echo ${GOOGLE_KEY} | base64 -d > test-files/key.json - echo ${GCS_SIGNING_PEM} | base64 -d > test-files/gcs.pem - nix-env -f '' -iA cachix -A go - - cachix use nixery script: - test -z $(gofmt -l server/ build-image/) - nix-build --arg maxLayers 1 | cachix push nixery From ad0541940fe61181e2653c9e63f9d7cb0911a130 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Oct 2020 12:42:03 +0100 Subject: [PATCH 187/223] fix(build): Completely remove Cachix from build setup Installing Cachix started failing on ARM64. --- tools/nixery/.travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index 674231217..ed715da14 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -11,10 +11,10 @@ before_script: - mkdir test-files - echo ${GOOGLE_KEY} | base64 -d > test-files/key.json - echo ${GCS_SIGNING_PEM} | base64 -d > test-files/gcs.pem - - nix-env -f '' -iA cachix -A go + - nix-env -f '' -iA -A go script: - test -z $(gofmt -l server/ build-image/) - - nix-build --arg maxLayers 1 | cachix push nixery + - nix-build --arg maxLayers 1 # This integration test makes sure that the container image built # for Nixery itself runs fine in Docker, and that images pulled From 4ce32adfe8aff1189e42a8a375782c9ea86a0b79 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Oct 2020 12:52:05 +0100 Subject: [PATCH 188/223] fix(build): Work around arbitrary new maxLayers restriction --- tools/nixery/.travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml index ed715da14..fcf2034d4 100644 --- a/tools/nixery/.travis.yml +++ b/tools/nixery/.travis.yml @@ -14,7 +14,7 @@ before_script: - nix-env -f '' -iA -A go script: - test -z $(gofmt -l server/ build-image/) - - nix-build --arg maxLayers 1 + - nix-build --arg maxLayers 2 # This integration test makes sure that the container image built # for Nixery itself runs fine in Docker, and that images pulled From 5ce745d104e82d836967e3bd9fd7af9602e76114 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Oct 2020 13:30:12 +0100 Subject: [PATCH 189/223] refactor(main): Split HTTP handlers into separate functions There is a new handler coming up to fix #102 and I want to avoid falling into the classic Go trap of creating thousand-line functions. --- tools/nixery/config/pkgsource.go | 2 +- tools/nixery/main.go | 123 ++++++++++++++++--------------- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/tools/nixery/config/pkgsource.go b/tools/nixery/config/pkgsource.go index 95236c4b0..380e66436 100644 --- a/tools/nixery/config/pkgsource.go +++ b/tools/nixery/config/pkgsource.go @@ -140,7 +140,7 @@ func pkgSourceFromEnv() (PkgSource, error) { } if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { - log.WithField("repo", git).Info("using NIx package set from git repository") + log.WithField("repo", git).Info("using Nix package set from git repository") return &GitSource{ repository: git, diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 6cad93740..92c602b1f 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -53,8 +53,8 @@ var version string = "devel" // routes required for serving images, since pushing and other such // functionality is not available. var ( - manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) - layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) + manifestTagRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) + layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) ) // Downloads the popularity information for the package set from the @@ -112,75 +112,80 @@ type registryHandler struct { state *builder.State } +// Serve a manifest by tag, building it via Nix and populating caches +// if necessary. +func (h *registryHandler) serveManifestTag(w http.ResponseWriter, r *http.Request, name string, tag string) { + log.WithFields(log.Fields{ + "image": name, + "tag": tag, + }).Info("requesting image manifest") + + image := builder.ImageFromName(name, tag) + buildResult, err := builder.BuildImage(r.Context(), h.state, &image) + + if err != nil { + writeError(w, 500, "UNKNOWN", "image build failure") + + log.WithError(err).WithFields(log.Fields{ + "image": name, + "tag": tag, + }).Error("failed to build image manifest") + + return + } + + // Some error types have special handling, which is applied + // here. + if buildResult.Error == "not_found" { + s := fmt.Sprintf("Could not find Nix packages: %v", buildResult.Pkgs) + writeError(w, 404, "MANIFEST_UNKNOWN", s) + + log.WithFields(log.Fields{ + "image": name, + "tag": tag, + "packages": buildResult.Pkgs, + }).Warn("could not find Nix packages") + + return + } + + // This marshaling error is ignored because we know that this + // field represents valid JSON data. + manifest, _ := json.Marshal(buildResult.Manifest) + w.Header().Add("Content-Type", manifestMediaType) + w.Write(manifest) +} + +// serveLayer serves an image layer from storage (if it exists). +func (h *registryHandler) serveLayer(w http.ResponseWriter, r *http.Request, digest string) { + storage := h.state.Storage + err := storage.ServeLayer(digest, r, w) + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "layer": digest, + "backend": storage.Name(), + }).Error("failed to serve layer from storage backend") + } +} + +// ServeHTTP dispatches HTTP requests to the matching handlers. func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Acknowledge that we speak V2 with an empty response if r.RequestURI == "/v2/" { return } - // Serve the manifest (straight from Nix) - manifestMatches := manifestRegex.FindStringSubmatch(r.RequestURI) + // Build & serve a manifest by tag + manifestMatches := manifestTagRegex.FindStringSubmatch(r.RequestURI) if len(manifestMatches) == 3 { - imageName := manifestMatches[1] - imageTag := manifestMatches[2] - - log.WithFields(log.Fields{ - "image": imageName, - "tag": imageTag, - }).Info("requesting image manifest") - - image := builder.ImageFromName(imageName, imageTag) - buildResult, err := builder.BuildImage(r.Context(), h.state, &image) - - if err != nil { - writeError(w, 500, "UNKNOWN", "image build failure") - - log.WithError(err).WithFields(log.Fields{ - "image": imageName, - "tag": imageTag, - }).Error("failed to build image manifest") - - return - } - - // Some error types have special handling, which is applied - // here. - if buildResult.Error == "not_found" { - s := fmt.Sprintf("Could not find Nix packages: %v", buildResult.Pkgs) - writeError(w, 404, "MANIFEST_UNKNOWN", s) - - log.WithFields(log.Fields{ - "image": imageName, - "tag": imageTag, - "packages": buildResult.Pkgs, - }).Warn("could not find Nix packages") - - return - } - - // This marshaling error is ignored because we know that this - // field represents valid JSON data. - manifest, _ := json.Marshal(buildResult.Manifest) - w.Header().Add("Content-Type", manifestMediaType) - w.Write(manifest) + h.serveManifestTag(w, r, manifestMatches[1], manifestMatches[2]) return } - // Serve an image layer. For this we need to first ask Nix for - // the manifest, then proceed to extract the correct layer from - // it. + // Serve an image layer layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) if len(layerMatches) == 3 { - digest := layerMatches[2] - storage := h.state.Storage - err := storage.ServeLayer(digest, r, w) - if err != nil { - log.WithError(err).WithFields(log.Fields{ - "layer": digest, - "backend": storage.Name(), - }).Error("failed to serve layer from storage backend") - } - + h.serveLayer(w, r, layerMatches[2]) return } From cbbf45b5cb268a605134022d91308e6c57c9d273 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Oct 2020 13:47:08 +0100 Subject: [PATCH 190/223] refactor(storage): Rename ServeLayer -> Serve This is going to be used for general content-addressed objects, and is not layer specific anymore. --- tools/nixery/main.go | 4 ++-- tools/nixery/storage/filesystem.go | 8 ++++---- tools/nixery/storage/gcs.go | 6 +++--- tools/nixery/storage/storage.go | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 92c602b1f..573f42767 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC +// Copyright 2019-2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of @@ -159,7 +159,7 @@ func (h *registryHandler) serveManifestTag(w http.ResponseWriter, r *http.Reques // serveLayer serves an image layer from storage (if it exists). func (h *registryHandler) serveLayer(w http.ResponseWriter, r *http.Request, digest string) { storage := h.state.Storage - err := storage.ServeLayer(digest, r, w) + err := storage.Serve(digest, r, w) if err != nil { log.WithError(err).WithFields(log.Fields{ "layer": digest, diff --git a/tools/nixery/storage/filesystem.go b/tools/nixery/storage/filesystem.go index cdbc31c5e..926e9257a 100644 --- a/tools/nixery/storage/filesystem.go +++ b/tools/nixery/storage/filesystem.go @@ -83,13 +83,13 @@ func (b *FSBackend) Move(ctx context.Context, old, new string) error { return os.Rename(path.Join(b.path, old), newpath) } -func (b *FSBackend) ServeLayer(digest string, r *http.Request, w http.ResponseWriter) error { +func (b *FSBackend) Serve(digest string, r *http.Request, w http.ResponseWriter) error { p := path.Join(b.path, "layers", digest) log.WithFields(log.Fields{ - "layer": digest, - "path": p, - }).Info("serving layer from filesystem") + "digest": digest, + "path": p, + }).Info("serving blob from filesystem") http.ServeFile(w, r, p) return nil diff --git a/tools/nixery/storage/gcs.go b/tools/nixery/storage/gcs.go index c247cca62..61b5dea52 100644 --- a/tools/nixery/storage/gcs.go +++ b/tools/nixery/storage/gcs.go @@ -150,18 +150,18 @@ func (b *GCSBackend) Move(ctx context.Context, old, new string) error { return nil } -func (b *GCSBackend) ServeLayer(digest string, r *http.Request, w http.ResponseWriter) error { +func (b *GCSBackend) Serve(digest string, r *http.Request, w http.ResponseWriter) error { url, err := b.constructLayerUrl(digest) if err != nil { log.WithError(err).WithFields(log.Fields{ - "layer": digest, + "digest": digest, "bucket": b.bucket, }).Error("failed to sign GCS URL") return err } - log.WithField("layer", digest).Info("redirecting layer request to GCS bucket") + log.WithField("digest", digest).Info("redirecting blob request to GCS bucket") w.Header().Set("Location", url) w.WriteHeader(303) diff --git a/tools/nixery/storage/storage.go b/tools/nixery/storage/storage.go index c97b5e4fa..4040ef08d 100644 --- a/tools/nixery/storage/storage.go +++ b/tools/nixery/storage/storage.go @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC +// Copyright 2019-2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of @@ -46,6 +46,6 @@ type Backend interface { Move(ctx context.Context, old, new string) error // Serve provides a handler function to serve HTTP requests - // for layers in the storage backend. - ServeLayer(digest string, r *http.Request, w http.ResponseWriter) error + // for objects in the storage backend. + Serve(digest string, r *http.Request, w http.ResponseWriter) error } From 94570aa83f0ebb9a192ff9d62e14b811457a6284 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Oct 2020 14:24:14 +0100 Subject: [PATCH 191/223] feat(main): Implement serving of manifests by digest Modifies the layer serving endpoint to be a generic blob-serving endpoint that can handle both manifest and layer object "types". Note that this commit does not yet populate the CAS with any manifests. --- tools/nixery/main.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 573f42767..8a6d3c3ae 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -53,8 +53,8 @@ var version string = "devel" // routes required for serving images, since pushing and other such // functionality is not available. var ( - manifestTagRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) - layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) + manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) + blobRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/(blobs|manifests)/sha256:(\w+)$`) ) // Downloads the popularity information for the package set from the @@ -156,15 +156,16 @@ func (h *registryHandler) serveManifestTag(w http.ResponseWriter, r *http.Reques w.Write(manifest) } -// serveLayer serves an image layer from storage (if it exists). -func (h *registryHandler) serveLayer(w http.ResponseWriter, r *http.Request, digest string) { +// serveBlob serves a blob from storage by digest +func (h *registryHandler) serveBlob(w http.ResponseWriter, r *http.Request, blobType, digest string) { storage := h.state.Storage err := storage.Serve(digest, r, w) if err != nil { log.WithError(err).WithFields(log.Fields{ - "layer": digest, + "type": blobType, + "digest": digest, "backend": storage.Name(), - }).Error("failed to serve layer from storage backend") + }).Error("failed to serve blob from storage backend") } } @@ -176,16 +177,16 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Build & serve a manifest by tag - manifestMatches := manifestTagRegex.FindStringSubmatch(r.RequestURI) + manifestMatches := manifestRegex.FindStringSubmatch(r.RequestURI) if len(manifestMatches) == 3 { h.serveManifestTag(w, r, manifestMatches[1], manifestMatches[2]) return } - // Serve an image layer - layerMatches := layerRegex.FindStringSubmatch(r.RequestURI) - if len(layerMatches) == 3 { - h.serveLayer(w, r, layerMatches[2]) + // Serve a blob by digest + layerMatches := blobRegex.FindStringSubmatch(r.RequestURI) + if len(layerMatches) == 4 { + h.serveBlob(w, r, layerMatches[2], layerMatches[3]) return } From 9e5ebb2f4f57c310977e979166e9ab287ef65865 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Oct 2020 14:50:02 +0100 Subject: [PATCH 192/223] feat(main): Implement caching of manifests in CAS To ensure that registry clients which attempt to pull manifests by their content hash can interact with Nixery, this change implements persisting image manifests in the CAS in the same way as image layers. In combination with the previous refactorings this means that Nixery's serving flow is now compatible with containerd. I have verified this locally, but CI currently only runs against Docker and not containerd, which is something I plan to address in a subsequent PR. This fixes #102 --- tools/nixery/main.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 8a6d3c3ae..39cfbc525 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -26,8 +26,11 @@ package main import ( + "context" + "crypto/sha256" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "regexp" @@ -153,6 +156,38 @@ func (h *registryHandler) serveManifestTag(w http.ResponseWriter, r *http.Reques // field represents valid JSON data. manifest, _ := json.Marshal(buildResult.Manifest) w.Header().Add("Content-Type", manifestMediaType) + + // The manifest needs to be persisted to the blob storage (to become + // available for clients that fetch manifests by their hash, e.g. + // containerd) and served to the client. + // + // Since we have no stable key to address this manifest (it may be + // uncacheable, yet still addressable by blob) we need to separate + // out the hashing, uploading and serving phases. The latter is + // especially important as clients may start to fetch it by digest + // as soon as they see a response. + sha256sum := fmt.Sprintf("%x", sha256.Sum256(manifest)) + path := "layers/" + sha256sum + ctx := context.TODO() + + _, _, err = h.state.Storage.Persist(ctx, path, func(sw io.Writer) (string, int64, error) { + // We already know the hash, so no additional hash needs to be + // constructed here. + written, err := sw.Write(manifest) + return sha256sum, int64(written), err + }) + + if err != nil { + writeError(w, 500, "MANIFEST_UPLOAD", "could not upload manifest to blob store") + + log.WithError(err).WithFields(log.Fields{ + "image": name, + "tag": tag, + }).Error("could not upload manifest") + + return + } + w.Write(manifest) } From 8a5c446babbac860d6eaee6f7e0c5a5a8a6f4183 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Oct 2020 22:55:20 +0100 Subject: [PATCH 193/223] docs: Add a note about a Nix-native builder to the roadmap ... if I don't mention this somewhere I'll probably never do it! --- tools/nixery/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 32e5921fa..6731d8786 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -124,6 +124,12 @@ correct caching behaviour, addressing and so on. See [issue #4](https://github.com/google/nixery/issues/4). +### Nix-native builder + +The image building and layering functionality of Nixery will be extracted into a +separate Nix function, which will make it possible to build images directly in +Nix builds. + [Nix]: https://nixos.org/ [layering strategy]: https://storage.googleapis.com/nixdoc/nixery-layers.html [gist]: https://gist.github.com/tazjin/08f3d37073b3590aacac424303e6f745 From cc35bf0fc3a900dccf4f9edcc581cadb5956c439 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 29 Oct 2020 16:13:53 +0100 Subject: [PATCH 194/223] feat(storage): Add support for content-types (GCS only) Extends storage.Persist to accept a Content-Type argument, which in the GCS backend is persisted with the object to ensure that the object is served back with this content-type. This is not yet implemented for the filesystem backend, where the parameter is simply ignored. This should help in the case of clients which expect the returned objects to have content-types set when, for example, fetching layers by digest. --- tools/nixery/builder/builder.go | 2 +- tools/nixery/builder/cache.go | 4 ++-- tools/nixery/main.go | 3 ++- tools/nixery/manifest/manifest.go | 8 ++++---- tools/nixery/storage/filesystem.go | 3 ++- tools/nixery/storage/gcs.go | 25 ++++++++++++++++++++++--- tools/nixery/storage/storage.go | 2 +- 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/tools/nixery/builder/builder.go b/tools/nixery/builder/builder.go index 028bcc576..115f1e37e 100644 --- a/tools/nixery/builder/builder.go +++ b/tools/nixery/builder/builder.go @@ -420,7 +420,7 @@ func (b *byteCounter) Write(p []byte) (n int, err error) { // image manifest. func uploadHashLayer(ctx context.Context, s *State, key string, lw layerWriter) (*manifest.Entry, error) { path := "staging/" + key - sha256sum, size, err := s.Storage.Persist(ctx, path, func(sw io.Writer) (string, int64, error) { + sha256sum, size, err := s.Storage.Persist(ctx, path, manifest.LayerType, func(sw io.Writer) (string, int64, error) { // Sets up a "multiwriter" that simultaneously runs both hash // algorithms and uploads to the storage backend. shasum := sha256.New() diff --git a/tools/nixery/builder/cache.go b/tools/nixery/builder/cache.go index a4ebe03e1..35b563e52 100644 --- a/tools/nixery/builder/cache.go +++ b/tools/nixery/builder/cache.go @@ -152,7 +152,7 @@ func cacheManifest(ctx context.Context, s *State, key string, m json.RawMessage) go s.Cache.localCacheManifest(key, m) path := "manifests/" + key - _, size, err := s.Storage.Persist(ctx, path, func(w io.Writer) (string, int64, error) { + _, size, err := s.Storage.Persist(ctx, path, manifest.ManifestType, func(w io.Writer) (string, int64, error) { size, err := io.Copy(w, bytes.NewReader([]byte(m))) return "", size, err }) @@ -220,7 +220,7 @@ func cacheLayer(ctx context.Context, s *State, key string, entry manifest.Entry) j, _ := json.Marshal(&entry) path := "builds/" + key - _, _, err := s.Storage.Persist(ctx, path, func(w io.Writer) (string, int64, error) { + _, _, err := s.Storage.Persist(ctx, path, "", func(w io.Writer) (string, int64, error) { size, err := io.Copy(w, bytes.NewReader(j)) return "", size, err }) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 39cfbc525..d94d51b46 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -38,6 +38,7 @@ import ( "github.com/google/nixery/builder" "github.com/google/nixery/config" "github.com/google/nixery/logs" + mf "github.com/google/nixery/manifest" "github.com/google/nixery/storage" log "github.com/sirupsen/logrus" ) @@ -170,7 +171,7 @@ func (h *registryHandler) serveManifestTag(w http.ResponseWriter, r *http.Reques path := "layers/" + sha256sum ctx := context.TODO() - _, _, err = h.state.Storage.Persist(ctx, path, func(sw io.Writer) (string, int64, error) { + _, _, err = h.state.Storage.Persist(ctx, path, mf.ManifestType, func(sw io.Writer) (string, int64, error) { // We already know the hash, so no additional hash needs to be // constructed here. written, err := sw.Write(manifest) diff --git a/tools/nixery/manifest/manifest.go b/tools/nixery/manifest/manifest.go index 0d36826fb..e49992007 100644 --- a/tools/nixery/manifest/manifest.go +++ b/tools/nixery/manifest/manifest.go @@ -28,8 +28,8 @@ const ( schemaVersion = 2 // media types - manifestType = "application/vnd.docker.distribution.manifest.v2+json" - layerType = "application/vnd.docker.image.rootfs.diff.tar.gzip" + ManifestType = "application/vnd.docker.distribution.manifest.v2+json" + LayerType = "application/vnd.docker.image.rootfs.diff.tar.gzip" configType = "application/vnd.docker.container.image.v1+json" // image config constants @@ -117,7 +117,7 @@ func Manifest(arch string, layers []Entry) (json.RawMessage, ConfigLayer) { hashes := make([]string, len(layers)) for i, l := range layers { hashes[i] = l.TarHash - l.MediaType = layerType + l.MediaType = LayerType l.TarHash = "" layers[i] = l } @@ -126,7 +126,7 @@ func Manifest(arch string, layers []Entry) (json.RawMessage, ConfigLayer) { m := manifest{ SchemaVersion: schemaVersion, - MediaType: manifestType, + MediaType: ManifestType, Config: Entry{ MediaType: configType, Size: int64(len(c.Config)), diff --git a/tools/nixery/storage/filesystem.go b/tools/nixery/storage/filesystem.go index 926e9257a..bd757587b 100644 --- a/tools/nixery/storage/filesystem.go +++ b/tools/nixery/storage/filesystem.go @@ -49,7 +49,8 @@ func (b *FSBackend) Name() string { return fmt.Sprintf("Filesystem (%s)", b.path) } -func (b *FSBackend) Persist(ctx context.Context, key string, f Persister) (string, int64, error) { +// TODO(tazjin): Implement support for persisting content-types for the filesystem backend. +func (b *FSBackend) Persist(ctx context.Context, key, _type string, f Persister) (string, int64, error) { full := path.Join(b.path, key) dir := path.Dir(full) err := os.MkdirAll(dir, 0755) diff --git a/tools/nixery/storage/gcs.go b/tools/nixery/storage/gcs.go index 61b5dea52..eac34461a 100644 --- a/tools/nixery/storage/gcs.go +++ b/tools/nixery/storage/gcs.go @@ -80,17 +80,36 @@ func (b *GCSBackend) Name() string { return "Google Cloud Storage (" + b.bucket + ")" } -func (b *GCSBackend) Persist(ctx context.Context, path string, f Persister) (string, int64, error) { +func (b *GCSBackend) Persist(ctx context.Context, path, contentType string, f Persister) (string, int64, error) { obj := b.handle.Object(path) w := obj.NewWriter(ctx) hash, size, err := f(w) if err != nil { - log.WithError(err).WithField("path", path).Error("failed to upload to GCS") + log.WithError(err).WithField("path", path).Error("failed to write to GCS") return hash, size, err } - return hash, size, w.Close() + err = w.Close() + if err != nil { + log.WithError(err).WithField("path", path).Error("failed to complete GCS upload") + return hash, size, err + } + + // GCS natively supports content types for objects, which will be + // used when serving them back. + if contentType != "" { + _, err = obj.Update(ctx, storage.ObjectAttrsToUpdate{ + ContentType: contentType, + }) + + if err != nil { + log.WithError(err).WithField("path", path).Error("failed to update object attrs") + return hash, size, err + } + } + + return hash, size, nil } func (b *GCSBackend) Fetch(ctx context.Context, path string) (io.ReadCloser, error) { diff --git a/tools/nixery/storage/storage.go b/tools/nixery/storage/storage.go index 4040ef08d..fd496f440 100644 --- a/tools/nixery/storage/storage.go +++ b/tools/nixery/storage/storage.go @@ -36,7 +36,7 @@ type Backend interface { // It needs to return the SHA256 hash of the data written as // well as the total number of bytes, as those are required // for the image manifest. - Persist(context.Context, string, Persister) (string, int64, error) + Persist(ctx context.Context, path, contentType string, f Persister) (string, int64, error) // Fetch retrieves data from the storage backend. Fetch(ctx context.Context, path string) (io.ReadCloser, error) From 8ad5c55ad281902e91e2c4d9a7f8e33d9f73c24e Mon Sep 17 00:00:00 2001 From: Dave Nicponski Date: Thu, 3 Dec 2020 13:53:31 -0500 Subject: [PATCH 195/223] docs(config): Fix comment typo --- tools/nixery/config/pkgsource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/config/pkgsource.go b/tools/nixery/config/pkgsource.go index 380e66436..55007bc80 100644 --- a/tools/nixery/config/pkgsource.go +++ b/tools/nixery/config/pkgsource.go @@ -32,7 +32,7 @@ type PkgSource interface { // for calling Nix. Render(tag string) (string, string) - // Create a key by which builds for this source and iamge + // Create a key by which builds for this source and image // combination can be cached. // // The empty string means that this value is not cacheable due From 3e1d63ccb309f5a7d677adeb630472e598783628 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 5 Dec 2020 13:23:26 +0100 Subject: [PATCH 196/223] docs: Update README with a link to the NixCon talk --- tools/nixery/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 6731d8786..c701a0e62 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -20,10 +20,9 @@ and/or large dependencies. A public instance as well as additional documentation is available at [nixery.dev][public]. -The project started out inspired by the [buildLayeredImage][] blog post with the -intention of becoming a Kubernetes controller that can serve declarative image -specifications specified in CRDs as container images. The design for this was -outlined in [a public gist][gist]. +You can watch the NixCon 2019 [talk about +Nixery](https://www.youtube.com/watch?v=pOI9H4oeXqA) for more information about +the project and its use-cases. This is not an officially supported Google project. @@ -115,6 +114,13 @@ These extra configuration variables must be set to configure storage backends: * `STORAGE_PATH`: Path to a folder in which to store and from which to serve data (**required** for `filesystem`) +### Background + +The project started out inspired by the [buildLayeredImage][] blog post with the +intention of becoming a Kubernetes controller that can serve declarative image +specifications specified in CRDs as container images. The design for this was +outlined in [a public gist][gist]. + ## Roadmap ### Kubernetes integration From 954953d8bad1978b529df146d8ea50d91d4e257a Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Tue, 13 Apr 2021 16:20:06 +0200 Subject: [PATCH 197/223] chore(nix): update channel URL It looks like NixPkgs channels have moved. Fixing this URL allows using nixos-20.09, for instance. --- tools/nixery/prepare-image/load-pkgs.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/prepare-image/load-pkgs.nix b/tools/nixery/prepare-image/load-pkgs.nix index cceebfc14..4a89dcde3 100644 --- a/tools/nixery/prepare-image/load-pkgs.nix +++ b/tools/nixery/prepare-image/load-pkgs.nix @@ -23,7 +23,7 @@ let fetchImportChannel = channel: let url = - "https://github.com/NixOS/nixpkgs-channels/archive/${channel}.tar.gz"; + "https://github.com/NixOS/nixpkgs/archive/${channel}.tar.gz"; in import (fetchTarball url) importArgs; # If a git repository is requested, it is retrieved via From f172107ef1b2568bd3a1b1eafa4b9e2546e14c1d Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Tue, 13 Apr 2021 16:26:04 +0200 Subject: [PATCH 198/223] feat(storage): Add generic support for content-types When serving a manifest, it is important to set the content-type correctly (otherwise pulling an image is likely to give a cryptic error message, "Error response from daemon: missing signature key"). This makes sure that we set the content-type properly for both manifests and layers. --- tools/nixery/main.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index d94d51b46..6af4636e5 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -195,6 +195,16 @@ func (h *registryHandler) serveManifestTag(w http.ResponseWriter, r *http.Reques // serveBlob serves a blob from storage by digest func (h *registryHandler) serveBlob(w http.ResponseWriter, r *http.Request, blobType, digest string) { storage := h.state.Storage + switch blobType { + case "manifests": + // It is necessary to set the correct content-type when serving manifests. + // Otherwise, you may get the following mysterious error message when pulling: + // "Error response from daemon: missing signature key" + w.Header().Add("Content-Type", mf.ManifestType) + case "blobs": + // It is not strictly necessary to set this content-type, but since we're here... + w.Header().Add("Content-Type", mf.LayerType) + } err := storage.Serve(digest, r, w) if err != nil { log.WithError(err).WithFields(log.Fields{ From d2767bbe8a7af403e4e252f3fbfe9ec8fa0c5a90 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 27 Apr 2021 16:28:49 +0200 Subject: [PATCH 199/223] feat(ci): Configure initial GitHub Actions setup Travis is being deprecated, and this might be the best option for now. --- .../.github/workflows/build-and-test.yaml | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tools/nixery/.github/workflows/build-and-test.yaml diff --git a/tools/nixery/.github/workflows/build-and-test.yaml b/tools/nixery/.github/workflows/build-and-test.yaml new file mode 100644 index 000000000..79dd54a71 --- /dev/null +++ b/tools/nixery/.github/workflows/build-and-test.yaml @@ -0,0 +1,29 @@ +# Build Nixery, spin up an instance and pull an image from it. +name: "Build and test Nixery" +on: + push: + branches: + - master + pull_request: {} +permissions: "read-all" +env: + NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/0a40a3999eb4d577418515da842a2622a64880c5.tar.gz" +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: "Do we have Docker?" + run: | + docker ps -a + - name: Install Nix + uses: cachix/install-nix-action@v13 + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Prepare environment + run: | + mkdir test-storage + nix-env -f '' -iA go + - name: Check formatting + run: "test -z $(gofmt -l .)" + - name: Build Nixery + run: "nix-build --arg maxLayers 2" From ee48bd891cbe0c5f7e819787f6ddd39053507cf1 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Thu, 29 Apr 2021 15:22:00 +0200 Subject: [PATCH 200/223] feat(ci): remove unneeded permissions: read-all We don't intend to label, authenticate or whatever with the GITHUB_TOKEN, so there's not really a reason to give any broader permissions than the defaults. --- tools/nixery/.github/workflows/build-and-test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/nixery/.github/workflows/build-and-test.yaml b/tools/nixery/.github/workflows/build-and-test.yaml index 79dd54a71..1036220aa 100644 --- a/tools/nixery/.github/workflows/build-and-test.yaml +++ b/tools/nixery/.github/workflows/build-and-test.yaml @@ -5,7 +5,6 @@ on: branches: - master pull_request: {} -permissions: "read-all" env: NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/0a40a3999eb4d577418515da842a2622a64880c5.tar.gz" jobs: From 970f49223599ec124809ead7be0b61e3e30431f9 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Thu, 29 Apr 2021 15:10:54 +0200 Subject: [PATCH 201/223] feat(ci): add integration tests to GitHub Actions, remove .travis.yaml This copies the integration tests from `.travis.yaml` into a script, documents the assumptions it makes, and wires it into GitHub Actions. Contrary to the travis version, we don't use Nixery's GCS backend, as handing out access to the bucket used, especially for PRs, needs to be done carefully. Adding back GCS to the integration test can be done at a later point, either by using a mock server, or by only exposing the credentials for master builds (and have the test script decide on whether GOOGLE_APPLICATION_CREDENTIALS is set or not). The previous travis version had some complicated post-mortem log gathering - instead of doing this, we can just `docker run` nixery, but fork it into the background with the shell - causing it to still be able to log its output as it's running. An additional `--rm` is appended, so the container gets cleaned up on termination - this allows subsequent runs on non-CI infrastructure (like developer laptops), without having to manually clean up containers. Fixes #119. --- .../.github/workflows/build-and-test.yaml | 2 + tools/nixery/.travis.yml | 78 ------------------- tools/nixery/scripts/integration-test.sh | 51 ++++++++++++ 3 files changed, 53 insertions(+), 78 deletions(-) delete mode 100644 tools/nixery/.travis.yml create mode 100755 tools/nixery/scripts/integration-test.sh diff --git a/tools/nixery/.github/workflows/build-and-test.yaml b/tools/nixery/.github/workflows/build-and-test.yaml index 1036220aa..3ae4e639e 100644 --- a/tools/nixery/.github/workflows/build-and-test.yaml +++ b/tools/nixery/.github/workflows/build-and-test.yaml @@ -26,3 +26,5 @@ jobs: run: "test -z $(gofmt -l .)" - name: Build Nixery run: "nix-build --arg maxLayers 2" + - name: Run integration test + run: scripts/integration-test.sh diff --git a/tools/nixery/.travis.yml b/tools/nixery/.travis.yml deleted file mode 100644 index fcf2034d4..000000000 --- a/tools/nixery/.travis.yml +++ /dev/null @@ -1,78 +0,0 @@ -language: nix -arch: - - amd64 - - arm64 -services: - - docker -env: - - NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/0a40a3999eb4d577418515da842a2622a64880c5.tar.gz -before_script: - - echo "Running Nixery CI build on $(uname -m)" - - mkdir test-files - - echo ${GOOGLE_KEY} | base64 -d > test-files/key.json - - echo ${GCS_SIGNING_PEM} | base64 -d > test-files/gcs.pem - - nix-env -f '' -iA -A go -script: - - test -z $(gofmt -l server/ build-image/) - - nix-build --arg maxLayers 2 - - # This integration test makes sure that the container image built - # for Nixery itself runs fine in Docker, and that images pulled - # from it work in Docker. - # - # Output from the Nixery container is printed at the end of the - # test regardless of test status. - - IMG=$(docker load -q -i $(nix-build -A nixery-image) | awk '{ print $3 }') - - echo "Loaded Nixery image as ${IMG}" - - - | - docker run -d -p 8080:8080 --name nixery \ - -v ${PWD}/test-files:/var/nixery \ - -e PORT=8080 \ - -e GCS_BUCKET=nixery-ci-tests \ - -e GOOGLE_CLOUD_PROJECT=nixery \ - -e GOOGLE_APPLICATION_CREDENTIALS=/var/nixery/key.json \ - -e NIXERY_CHANNEL=nixos-unstable \ - -e NIXERY_STORAGE_BACKEND=gcs \ - ${IMG} - - # print all of the container's logs regardless of success - - | - function print_logs { - echo "Nixery container logs:" - docker logs nixery - } - trap print_logs EXIT - - # Give the container ~20 seconds to come up - - | - attempts=0 - echo -n "Waiting for Nixery to start ..." - until $(curl --fail --silent "http://localhost:8080/v2/"); do - [[ attempts -eq 30 ]] && echo "Nixery container failed to start!" && exit 1 - ((attempts++)) - echo -n "." - sleep 1 - done - - # Pull and run an image of the current CPU architecture - - | - case $(uname -m) in - x86_64) - docker run --rm localhost:8080/hello hello - ;; - aarch64) - docker run --rm localhost:8080/arm64/hello hello - ;; - esac - - # Pull an image of the opposite CPU architecture (but without running it) - - | - case $(uname -m) in - x86_64) - docker pull localhost:8080/arm64/hello - ;; - aarch64) - docker pull localhost:8080/hello - ;; - esac diff --git a/tools/nixery/scripts/integration-test.sh b/tools/nixery/scripts/integration-test.sh new file mode 100755 index 000000000..9595f37d9 --- /dev/null +++ b/tools/nixery/scripts/integration-test.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -eou pipefail + +# This integration test makes sure that the container image built +# for Nixery itself runs fine in Docker, and that images pulled +# from it work in Docker. + +IMG=$(docker load -q -i "$(nix-build -A nixery-image)" | awk '{ print $3 }') +echo "Loaded Nixery image as ${IMG}" + +# Run the built nixery docker image in the background, but keep printing its +# output as it occurs. +docker run --rm -p 8080:8080 --name nixery \ + -e PORT=8080 \ + --mount type=tmpfs,destination=/var/cache/nixery \ + -e NIXERY_CHANNEL=nixos-unstable \ + -e NIXERY_STORAGE_BACKEND=filesystem \ + -e STORAGE_PATH=/var/cache/nixery \ + "${IMG}" & + +# Give the container ~20 seconds to come up +set +e +attempts=0 +echo -n "Waiting for Nixery to start ..." +until curl --fail --silent "http://localhost:8080/v2/"; do + [[ attempts -eq 30 ]] && echo "Nixery container failed to start!" && exit 1 + ((attempts++)) + echo -n "." + sleep 1 +done +set -e + +# Pull and run an image of the current CPU architecture +case $(uname -m) in + x86_64) + docker run --rm localhost:8080/hello hello + ;; + aarch64) + docker run --rm localhost:8080/arm64/hello hello + ;; +esac + +# Pull an image of the opposite CPU architecture (but without running it) +case $(uname -m) in +x86_64) + docker pull localhost:8080/arm64/hello + ;; +aarch64) + docker pull localhost:8080/hello + ;; +esac From 7e8295189bbcd4a30ea684c65c0a3c343d4842a9 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Thu, 29 Apr 2021 16:02:26 +0200 Subject: [PATCH 202/223] docs: document unset GOOGLE_APPLICATION_CREDENTIALS In case the `GOOGLE_APPLICATION_CREDENTIALS` environment variable is not set, a redirect to storage.googleapis.com is issued, which means the underlying bucket objects need to be publicly accessible. This wasn't really obvious until now, so further clarify it. --- tools/nixery/README.md | 4 ++++ tools/nixery/storage/gcs.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index c701a0e62..cebf28b58 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -94,6 +94,10 @@ account key, Nixery will also use this key to create [signed URLs][] for layers in the storage bucket. This makes it possible to serve layers from a bucket without having to make them publicly available. +In case the `GOOGLE_APPLICATION_CREDENTIALS` environment variable is not set, a +redirect to storage.googleapis.com is issued, which means the underlying bucket +objects need to be publicly accessible. + ### Storage Nixery supports multiple different storage backends in which its build cache and diff --git a/tools/nixery/storage/gcs.go b/tools/nixery/storage/gcs.go index eac34461a..a4bb4ba31 100644 --- a/tools/nixery/storage/gcs.go +++ b/tools/nixery/storage/gcs.go @@ -222,6 +222,10 @@ func signingOptsFromEnv() (*storage.SignedURLOptions, error) { // Signing the URL allows unauthenticated clients to retrieve objects from the // bucket. // +// In case signing is not configured, a redirect to storage.googleapis.com is +// issued, which means the underlying bucket objects need to be publicly +// accessible. +// // The Docker client is known to follow redirects, but this might not be true // for all other registry clients. func (b *GCSBackend) constructLayerUrl(digest string) (string, error) { From 8a1add9ef8f6899b8f110bd789d2422dbe688642 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 29 Apr 2021 23:52:42 +0200 Subject: [PATCH 203/223] chore(ci): Remove unnecessary commands from new CI setup * remove a step that was not supposed to be committed ("Do we have Docker?") * remove setup of old temporary storage directory (now done in integration script test instead) * skip creation of out-link for initial Nixery build (to avoid cache-busting on the second build) --- tools/nixery/.github/workflows/build-and-test.yaml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tools/nixery/.github/workflows/build-and-test.yaml b/tools/nixery/.github/workflows/build-and-test.yaml index 3ae4e639e..86a2c4363 100644 --- a/tools/nixery/.github/workflows/build-and-test.yaml +++ b/tools/nixery/.github/workflows/build-and-test.yaml @@ -11,20 +11,15 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - name: "Do we have Docker?" - run: | - docker ps -a - name: Install Nix uses: cachix/install-nix-action@v13 - name: Checkout uses: actions/checkout@v2.3.4 - name: Prepare environment - run: | - mkdir test-storage - nix-env -f '' -iA go + run: nix-env -f '' -iA go - name: Check formatting run: "test -z $(gofmt -l .)" - name: Build Nixery - run: "nix-build --arg maxLayers 2" + run: "nix-build --arg maxLayers 2 --no-out-link" - name: Run integration test run: scripts/integration-test.sh From 7520f2cb96159ae3cbee80b4822900f9a92f7f53 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 23 Apr 2021 11:00:06 +0200 Subject: [PATCH 204/223] chore: Update default NixOS channel to nixos-20.09 --- tools/nixery/.github/workflows/build-and-test.yaml | 2 +- tools/nixery/docs/src/caching.md | 2 +- tools/nixery/docs/src/nixery.md | 2 +- tools/nixery/docs/src/run-your-own.md | 4 ++-- tools/nixery/nixpkgs-pin.nix | 4 ++-- tools/nixery/prepare-image/prepare-image.nix | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/nixery/.github/workflows/build-and-test.yaml b/tools/nixery/.github/workflows/build-and-test.yaml index 86a2c4363..4563239a8 100644 --- a/tools/nixery/.github/workflows/build-and-test.yaml +++ b/tools/nixery/.github/workflows/build-and-test.yaml @@ -6,7 +6,7 @@ on: - master pull_request: {} env: - NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/0a40a3999eb4d577418515da842a2622a64880c5.tar.gz" + NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/4263ba5e133cc3fc699c1152ab5ee46ef668e675.tar.gz" jobs: build-and-test: runs-on: ubuntu-latest diff --git a/tools/nixery/docs/src/caching.md b/tools/nixery/docs/src/caching.md index 05ea6d89b..05ea68ef6 100644 --- a/tools/nixery/docs/src/caching.md +++ b/tools/nixery/docs/src/caching.md @@ -29,7 +29,7 @@ Manifest caching *only* applies in the following cases: Manifest caching *never* applies in the following cases: * package source specification is a local file path (i.e. `NIXERY_PKGS_PATH`) -* package source specification is a NixOS channel (e.g. `NIXERY_CHANNEL=nixos-20.03`) +* package source specification is a NixOS channel (e.g. `NIXERY_CHANNEL=nixos-20.09`) * package source specification is a git branch or tag (e.g. `staging`, `master` or `latest`) It is thus always preferable to request images from a fully-pinned package diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index 185c84630..5f6dcb7e3 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -54,7 +54,7 @@ own instance of Nixery. ### Which revision of `nixpkgs` is used for the builds? The instance at `nixery.dev` tracks a recent NixOS channel, currently NixOS -20.03. The channel is updated several times a day. +20.09. The channel is updated several times a day. Private registries might be configured to track a different channel (such as `nixos-unstable`) or even track a git repository with custom packages. diff --git a/tools/nixery/docs/src/run-your-own.md b/tools/nixery/docs/src/run-your-own.md index 9c20e3f2c..4ffb8c4d4 100644 --- a/tools/nixery/docs/src/run-your-own.md +++ b/tools/nixery/docs/src/run-your-own.md @@ -44,7 +44,7 @@ be performed for trivial things. However if you are running a private Nixery, chances are high that you intend to use it with your own packages. There are three options available: -1. Specify an upstream Nix/NixOS channel[^1], such as `nixos-20.03` or +1. Specify an upstream Nix/NixOS channel[^1], such as `nixos-20.09` or `nixos-unstable`. 2. Specify your own git-repository with a custom package set[^2]. This makes it possible to pull different tags, branches or commits by modifying the image @@ -73,7 +73,7 @@ You must set *all* of these: * `BUCKET`: [Google Cloud Storage][gcs] bucket to store & serve image layers * `PORT`: HTTP port on which Nixery should listen -You may set *one* of these, if unset Nixery defaults to `nixos-20.03`: +You may set *one* of these, if unset Nixery defaults to `nixos-20.09`: * `NIXERY_CHANNEL`: The name of a Nix/NixOS channel to use for building * `NIXERY_PKGS_REPO`: URL of a git repository containing a package set (uses diff --git a/tools/nixery/nixpkgs-pin.nix b/tools/nixery/nixpkgs-pin.nix index ea1b37bfe..ffedc92ed 100644 --- a/tools/nixery/nixpkgs-pin.nix +++ b/tools/nixery/nixpkgs-pin.nix @@ -1,4 +1,4 @@ import (builtins.fetchTarball { - url = "https://github.com/NixOS/nixpkgs-channels/archive/0a40a3999eb4d577418515da842a2622a64880c5.tar.gz"; - sha256 = "1j8gy2d61lmrp5gzi1a2jmb2v2pbk4b9666y8pf1pjg3jiqkzf7m"; + url = "https://github.com/NixOS/nixpkgs/archive/4263ba5e133cc3fc699c1152ab5ee46ef668e675.tar.gz"; + sha256 = "1nzqrdw0lhbldbs9r651zmgqpwhjhh9sssykhcl2155kgsfsrk7i"; }) {} diff --git a/tools/nixery/prepare-image/prepare-image.nix b/tools/nixery/prepare-image/prepare-image.nix index 7b73b92bf..316bfbbf2 100644 --- a/tools/nixery/prepare-image/prepare-image.nix +++ b/tools/nixery/prepare-image/prepare-image.nix @@ -24,7 +24,7 @@ { # Description of the package set to be used (will be loaded by load-pkgs.nix) srcType ? "nixpkgs", - srcArgs ? "nixos-20.03", + srcArgs ? "nixos-20.09", system ? "x86_64-linux", importArgs ? { }, # Path to load-pkgs.nix From 5c2db7b8ce4386bff4596eb0dfcc5d1f61dbf744 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 30 Apr 2021 12:44:16 +0200 Subject: [PATCH 205/223] chore(build): Use current git commit hash as build version --- tools/nixery/default.nix | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 411865a8a..00ecad583 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC +# Copyright 2019-2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,12 +22,10 @@ with pkgs; let inherit (pkgs) buildGoPackage; - # Hash of all Nixery sources - this is used as the Nixery version in + # Current Nixery commit - this is used as the Nixery version in # builds to distinguish errors between deployed versions, see # server/logs.go for details. - nixery-src-hash = pkgs.runCommand "nixery-src-hash" {} '' - echo ${./.} | grep -Eo '[a-z0-9]{32}' | head -c 32 > $out - ''; + nixery-commit-hash = pkgs.lib.commitIdFromGitRepo ./.git; # Go implementation of the Nixery server which implements the # container registry interface. @@ -52,7 +50,7 @@ let runHook renameImport export GOBIN="$out/bin" - go install -ldflags "-X main.version=$(cat ${nixery-src-hash})" ${goPackagePath} + go install -ldflags "-X main.version=$(cat ${nixery-commit-hash})" ${goPackagePath} ''; fixupPhase = '' From 13d97c9e51a3c6cc2abcb354bcd0519a49aeed68 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 30 Apr 2021 12:56:36 +0200 Subject: [PATCH 206/223] refactor(build): Pin dependencies using Go modules Drops the go2nix configuration in favour of pkgs.buildGoModule. Note that the go.sum file is bloated by issues with cyclic dependencies in some Google projects, but this large number of dependencies is not actually built. --- .../.github/workflows/build-and-test.yaml | 2 +- tools/nixery/default.nix | 32 +- tools/nixery/go-deps.nix | 138 ----- tools/nixery/go.mod | 11 + tools/nixery/go.sum | 534 ++++++++++++++++++ 5 files changed, 553 insertions(+), 164 deletions(-) delete mode 100644 tools/nixery/go-deps.nix create mode 100644 tools/nixery/go.mod create mode 100644 tools/nixery/go.sum diff --git a/tools/nixery/.github/workflows/build-and-test.yaml b/tools/nixery/.github/workflows/build-and-test.yaml index 4563239a8..2c0aff49d 100644 --- a/tools/nixery/.github/workflows/build-and-test.yaml +++ b/tools/nixery/.github/workflows/build-and-test.yaml @@ -20,6 +20,6 @@ jobs: - name: Check formatting run: "test -z $(gofmt -l .)" - name: Build Nixery - run: "nix-build --arg maxLayers 2 --no-out-link" + run: "nix-build --no-out-link" - name: Run integration test run: scripts/integration-test.sh diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 00ecad583..a5cc61e7e 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -20,7 +20,7 @@ with pkgs; let - inherit (pkgs) buildGoPackage; + inherit (pkgs) buildGoModule; # Current Nixery commit - this is used as the Nixery version in # builds to distinguish errors between deployed versions, see @@ -32,35 +32,17 @@ let # # Users should use the nixery-bin derivation below instead as it # provides the paths of files needed at runtime. - nixery-server = buildGoPackage rec { + nixery-server = buildGoModule rec { name = "nixery-server"; - goDeps = ./go-deps.nix; src = ./.; - - goPackagePath = "github.com/google/nixery"; doCheck = true; - # Simplify the Nix build instructions for Go to just the basics - # required to get Nixery up and running with the additional linker - # flags required. - outputs = [ "out" ]; - preConfigure = "bin=$out"; - buildPhase = '' - runHook preBuild - runHook renameImport + # Needs to be updated after every modification of go.mod/go.sum + vendorSha256 = "1ff0kfww6fy6pnvyva7x8cc6l1d12aafps48wrkwawk2qjy9a8b9"; - export GOBIN="$out/bin" - go install -ldflags "-X main.version=$(cat ${nixery-commit-hash})" ${goPackagePath} - ''; - - fixupPhase = '' - remove-references-to -t ${go} $out/bin/nixery - ''; - - checkPhase = '' - go vet ${goPackagePath} - go test ${goPackagePath} - ''; + buildFlagsArray = [ + "-ldflags=-s -w -X main.version=${nixery-commit-hash}" + ]; }; in rec { # Implementation of the Nix image building logic diff --git a/tools/nixery/go-deps.nix b/tools/nixery/go-deps.nix deleted file mode 100644 index 7d57ef204..000000000 --- a/tools/nixery/go-deps.nix +++ /dev/null @@ -1,138 +0,0 @@ -# This file was generated by https://github.com/kamilchm/go2nix v1.3.0 -[ - { - goPackagePath = "cloud.google.com/go"; - fetch = { - type = "git"; - url = "https://github.com/googleapis/google-cloud-go"; - rev = "22b9552106761e34e39c0cf48b783f092c660767"; - sha256 = "17sb3753h1m5pbjlv4r5ydbd35kfh086g2qxv2zjlqd90kcsdj7x"; - }; - } - { - goPackagePath = "github.com/golang/groupcache"; - fetch = { - type = "git"; - url = "https://github.com/golang/groupcache"; - rev = "8c9f03a8e57eb486e42badaed3fb287da51807ba"; - sha256 = "0vjjr79r32icjzlb05wn02k59av7jx0rn1jijml8r4whlg7dnkfh"; - }; - } - { - goPackagePath = "github.com/golang/protobuf"; - fetch = { - type = "git"; - url = "https://github.com/golang/protobuf"; - rev = "d04d7b157bb510b1e0c10132224b616ac0e26b17"; - sha256 = "0m5z81im4nsyfgarjhppayk4hqnrwswr3nix9mj8pff8x9jvcjqw"; - }; - } - { - goPackagePath = "github.com/googleapis/gax-go"; - fetch = { - type = "git"; - url = "https://github.com/googleapis/gax-go"; - rev = "be11bb253a768098254dc71e95d1a81ced778de3"; - sha256 = "072iv8llzr99za4pndfskgndq9rzms38r0sqy4d127ijnzmgl5nd"; - }; - } - { - goPackagePath = "github.com/sirupsen/logrus"; - fetch = { - type = "git"; - url = "https://github.com/sirupsen/logrus"; - rev = "6699a89a232f3db797f2e280639854bbc4b89725"; - sha256 = "1a59pw7zimvm8k423iq9l4f4qjj1ia1xc6pkmhwl2mxc46y2n442"; - }; - } - { - goPackagePath = "go.opencensus.io"; - fetch = { - type = "git"; - url = "https://github.com/census-instrumentation/opencensus-go"; - rev = "d7677d6af5953e0506ac4c08f349c62b917a443a"; - sha256 = "1nphs1qjz4a99b2n4izpqw13shiy26jy0i7qgm95giyhwwdqspk0"; - }; - } - { - goPackagePath = "golang.org/x/net"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/net"; - rev = "627f9648deb96c27737b83199d44bb5c1010cbcf"; - sha256 = "0ziz7i9mhz6dy2f58dsa83flkk165w1cnazm7yksql5i9m7x099z"; - }; - } - { - goPackagePath = "golang.org/x/oauth2"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/oauth2"; - rev = "bf48bf16ab8d622ce64ec6ce98d2c98f916b6303"; - sha256 = "1sirdib60zwmh93kf9qrx51r8544k1p9rs5mk0797wibz3m4mrdg"; - }; - } - { - goPackagePath = "golang.org/x/sys"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/sys"; - rev = "226ff32320da7b90d0b5bc2365f4e359c466fb78"; - sha256 = "137cdvmmrmx8qf72r94pn6zxp0wg9rfl0ii2fa9jk5hdkhifiqa6"; - }; - } - { - goPackagePath = "golang.org/x/text"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/text"; - rev = "3a82255431918bb7c2e1c09c964a18991756910b"; - sha256 = "1vrps79ap8dy7car64pf0j4hnb1j2hwp9wf67skv6izmi8r9425w"; - }; - } - { - goPackagePath = "gonum.org/v1/gonum"; - fetch = { - type = "git"; - url = "https://github.com/gonum/gonum"; - rev = "f69c0ac28e32bbfe5ccf3f821d85533b5f646b04"; - sha256 = "1qpws9899qyr0m2v4v7asxgy1z0wh9f284ampc2ha5c0hp0x6r4m"; - }; - } - { - goPackagePath = "google.golang.org/api"; - fetch = { - type = "git"; - url = "https://github.com/googleapis/google-api-go-client"; - rev = "4ec5466f5645b0f7f76ecb2246e7c5f3568cf8bb"; - sha256 = "1clrw2syb40a51zqpaw0mri8jk54cqp3scjwxq44hqr7cmqp0967"; - }; - } - { - goPackagePath = "google.golang.org/genproto"; - fetch = { - type = "git"; - url = "https://github.com/googleapis/go-genproto"; - rev = "43cab4749ae7254af90e92cb2cd767dfc641f6dd"; - sha256 = "0svbzzf9drd4ndxwj5rlggly4jnds9c76fkcq5f8czalbf5mlvb6"; - }; - } - { - goPackagePath = "google.golang.org/grpc"; - fetch = { - type = "git"; - url = "https://github.com/grpc/grpc-go"; - rev = "9106c3fff5236fd664a8de183f1c27682c66b823"; - sha256 = "02m1k06p6a5fc6ahj9qgd81wdzspa9xc29gd7awygwiyk22rd3md"; - }; - } - { - goPackagePath = "google.golang.org/protobuf"; - fetch = { - type = "git"; - url = "https://go.googlesource.com/protobuf"; - rev = "a0f95d5b14735d688bd94ca511d396115e5b86be"; - sha256 = "0l4xsfz337vmlij7pg8s2bjwlmrk4g83p1b9n8vnfavrmnrcw6j0"; - }; - } -] diff --git a/tools/nixery/go.mod b/tools/nixery/go.mod new file mode 100644 index 000000000..e24b2b6ff --- /dev/null +++ b/tools/nixery/go.mod @@ -0,0 +1,11 @@ +module github.com/google/nixery + +go 1.15 + +require ( + cloud.google.com/go/storage v1.15.0 + github.com/google/go-cmp v0.5.5 + github.com/sirupsen/logrus v1.8.1 + golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c + gonum.org/v1/gonum v0.9.1 +) diff --git a/tools/nixery/go.sum b/tools/nixery/go.sum new file mode 100644 index 000000000..493079683 --- /dev/null +++ b/tools/nixery/go.sum @@ -0,0 +1,534 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0= +cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY= +golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog= +golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.1 h1:HCWmqqNoELL0RAQeKBXWtkp04mGk8koafcB4He6+uhc= +gonum.org/v1/gonum v0.9.1/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww= +google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2 h1:g2sJMUGCpeHZqTx8p3wsAWRS64nFq20i4dvJWcKGqvY= +google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 768f3986a9399c82fc61ddcd4865d10f3bb93351 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 30 Apr 2021 13:01:16 +0200 Subject: [PATCH 207/223] feat(build): Run `go vet` as a step in the GitHub Actions workflow --- tools/nixery/.github/workflows/build-and-test.yaml | 2 ++ tools/nixery/popcount/popcount.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/nixery/.github/workflows/build-and-test.yaml b/tools/nixery/.github/workflows/build-and-test.yaml index 2c0aff49d..d3f258ffa 100644 --- a/tools/nixery/.github/workflows/build-and-test.yaml +++ b/tools/nixery/.github/workflows/build-and-test.yaml @@ -19,6 +19,8 @@ jobs: run: nix-env -f '' -iA go - name: Check formatting run: "test -z $(gofmt -l .)" + - name: Run `go vet` + run: "go vet ./..." - name: Build Nixery run: "nix-build --no-out-link" - name: Run integration test diff --git a/tools/nixery/popcount/popcount.go b/tools/nixery/popcount/popcount.go index d632090c0..dab10aae6 100644 --- a/tools/nixery/popcount/popcount.go +++ b/tools/nixery/popcount/popcount.go @@ -90,7 +90,7 @@ func channelMetadata(channel string) meta { commit, err := ioutil.ReadAll(commitResp.Body) failOn(err, "failed to read commit from response") if commitResp.StatusCode != 200 { - log.Fatalf("non-success status code when fetching commit: %s", string(commit), commitResp.StatusCode) + log.Fatalf("non-success status code when fetching commit: %s (%v)", string(commit), commitResp.StatusCode) } return meta{ From 3efbbfcd4e22811dd549c6ed46374736308b0b82 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Fri, 18 Jun 2021 21:08:05 +0200 Subject: [PATCH 208/223] feat(ci): don't mount /var/cache/nixery from tmpfs into docker container With https://github.com/google/nixery/pull/127, nixery will use extended attributes to store metadata (when using local storage). Right now, our integration test mounts a tmpfs to /var/cache/nixery. However, *user* xattrs aren't supported with tmpfs [1], so setting xattrs would fail. To workaround this, use a folder in the current working directory and hope it's backed by something supporting user xattrs (which is the case for GitHub Actions). [1]: https://man7.org/linux/man-pages/man5/tmpfs.5.html#NOTES --- tools/nixery/.gitignore | 3 +++ tools/nixery/scripts/integration-test.sh | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tools/nixery/.gitignore b/tools/nixery/.gitignore index 4203fee19..578eea392 100644 --- a/tools/nixery/.gitignore +++ b/tools/nixery/.gitignore @@ -7,3 +7,6 @@ debug/ *.pem *.p12 *.json + +# Created by the integration test +var-cache-nixery diff --git a/tools/nixery/scripts/integration-test.sh b/tools/nixery/scripts/integration-test.sh index 9595f37d9..9d06e96ba 100755 --- a/tools/nixery/scripts/integration-test.sh +++ b/tools/nixery/scripts/integration-test.sh @@ -10,9 +10,17 @@ echo "Loaded Nixery image as ${IMG}" # Run the built nixery docker image in the background, but keep printing its # output as it occurs. -docker run --rm -p 8080:8080 --name nixery \ +# We can't just mount a tmpfs to /var/cache/nixery, as tmpfs doesn't support +# user xattrs. +# So create a temporary directory in the current working directory, and hope +# it's backed by something supporting user xattrs. +# We'll notice it isn't if nixery starts complaining about not able to set +# xattrs anyway. +if [ -d var-cache-nixery ]; then rm -Rf var-cache-nixery; fi +mkdir var-cache-nixery +docker run --privileged --rm -p 8080:8080 --name nixery \ -e PORT=8080 \ - --mount type=tmpfs,destination=/var/cache/nixery \ + --mount "type=bind,source=${PWD}/var-cache-nixery,target=/var/cache/nixery" \ -e NIXERY_CHANNEL=nixos-unstable \ -e NIXERY_STORAGE_BACKEND=filesystem \ -e STORAGE_PATH=/var/cache/nixery \ From 94e04a76b6d2baf0cc5060c3168a428fae6c28ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Thu, 22 Apr 2021 16:52:12 +0200 Subject: [PATCH 209/223] feat(storage): Store blob content-type in extended attributes After the discussion in #116, this stores the blob content types in extended attributes when using the filesystem backend. If the underlying filesystem doesn't support extended attributes, storing blobs won't work; also, if extended attributes get removed, blobs won't be served anymore. We can relax this behavior if needed (i.e. log errors but still accept to store or serve blobs). However, since the Docker Engine (and possibly other container engines) won't accept to pull images from a registry that doesn't use correct content types for manifest files, it could be argued that it's better to give a hard fail. (Otherwise, the container engine gives cryptic error messages like "missing signature key".) I can change that behavior (and log errors but still store/serve blobs to the filesystem) if you think it's better. --- tools/nixery/default.nix | 2 +- tools/nixery/go.mod | 1 + tools/nixery/go.sum | 3 +++ tools/nixery/storage/filesystem.go | 17 +++++++++++++++-- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index a5cc61e7e..390035375 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -38,7 +38,7 @@ let doCheck = true; # Needs to be updated after every modification of go.mod/go.sum - vendorSha256 = "1ff0kfww6fy6pnvyva7x8cc6l1d12aafps48wrkwawk2qjy9a8b9"; + vendorSha256 = "1adjav0dxb97ws0w2k50rhk6r46wvfry6aj4sik3ninl525kd15s"; buildFlagsArray = [ "-ldflags=-s -w -X main.version=${nixery-commit-hash}" diff --git a/tools/nixery/go.mod b/tools/nixery/go.mod index e24b2b6ff..3b819a796 100644 --- a/tools/nixery/go.mod +++ b/tools/nixery/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( cloud.google.com/go/storage v1.15.0 github.com/google/go-cmp v0.5.5 + github.com/pkg/xattr v0.4.3 github.com/sirupsen/logrus v1.8.1 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c gonum.org/v1/gonum v0.9.1 diff --git a/tools/nixery/go.sum b/tools/nixery/go.sum index 493079683..812babcf3 100644 --- a/tools/nixery/go.sum +++ b/tools/nixery/go.sum @@ -158,6 +158,8 @@ github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2 github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/xattr v0.4.3 h1:5Jx4GCg5ABtqWZH8WLzeI4fOtM1HyX4RBawuCoua1es= +github.com/pkg/xattr v0.4.3/go.mod h1:sBD3RAqlr8Q+RC3FutZcikpT8nyDrIEEBw2J744gVWs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -322,6 +324,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/tools/nixery/storage/filesystem.go b/tools/nixery/storage/filesystem.go index bd757587b..2be3457f3 100644 --- a/tools/nixery/storage/filesystem.go +++ b/tools/nixery/storage/filesystem.go @@ -23,6 +23,7 @@ import ( "os" "path" + "github.com/pkg/xattr" log "github.com/sirupsen/logrus" ) @@ -49,8 +50,7 @@ func (b *FSBackend) Name() string { return fmt.Sprintf("Filesystem (%s)", b.path) } -// TODO(tazjin): Implement support for persisting content-types for the filesystem backend. -func (b *FSBackend) Persist(ctx context.Context, key, _type string, f Persister) (string, int64, error) { +func (b *FSBackend) Persist(ctx context.Context, key, contentType string, f Persister) (string, int64, error) { full := path.Join(b.path, key) dir := path.Dir(full) err := os.MkdirAll(dir, 0755) @@ -66,6 +66,12 @@ func (b *FSBackend) Persist(ctx context.Context, key, _type string, f Persister) } defer file.Close() + err = xattr.Set(full, "user.mime_type", []byte(contentType)) + if err != nil { + log.WithError(err).WithField("file", full).Error("failed to store file type in xattrs") + return "", 0, err + } + return f(file) } @@ -92,6 +98,13 @@ func (b *FSBackend) Serve(digest string, r *http.Request, w http.ResponseWriter) "path": p, }).Info("serving blob from filesystem") + contentType, err := xattr.Get(p, "user.mime_type") + if err != nil { + log.WithError(err).WithField("file", p).Error("failed to read file type from xattrs") + return err + } + w.Header().Add("Content-Type", string(contentType)) + http.ServeFile(w, r, p) return nil } From 84fb380f574aa4dac4e4cc4c9d0cded8a4c54fa3 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 26 Jun 2021 01:32:07 +0200 Subject: [PATCH 210/223] docs: Update build badge in README Moves the build badge to point at Github Actions, instead of the old (failing) Travis build --- tools/nixery/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index cebf28b58..745e73cb4 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -4,7 +4,7 @@ ----------------- -[![Build Status](https://travis-ci.org/google/nixery.svg?branch=master)](https://travis-ci.org/google/nixery) +[![Build Status](https://github.com/google/nixery/actions/workflows/build-and-test.yaml/badge.svg)](https://github.com/google/nixery/actions/workflows/build-and-test.yaml) **Nixery** is a Docker-compatible container registry that is capable of transparently building and serving container images using [Nix][]. From 02455bd0fdf42c698524320c8d43b2bd7ef11c3b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 6 Aug 2021 14:21:47 +0300 Subject: [PATCH 211/223] chore(build): Allow passing in a specific commit hash when building Required for builds where the full repository isn't available (e.g. from a tarball). --- tools/nixery/default.nix | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 390035375..696420d0f 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -15,7 +15,8 @@ { pkgs ? import ./nixpkgs-pin.nix , preLaunch ? "" , extraPackages ? [] -, maxLayers ? 20 }: +, maxLayers ? 20 +, commitHash ? null }@args: with pkgs; @@ -25,7 +26,7 @@ let # Current Nixery commit - this is used as the Nixery version in # builds to distinguish errors between deployed versions, see # server/logs.go for details. - nixery-commit-hash = pkgs.lib.commitIdFromGitRepo ./.git; + nixery-commit-hash = args.commitHash or pkgs.lib.commitIdFromGitRepo ./.git; # Go implementation of the Nixery server which implements the # container registry interface. From af337010e964671e2f8568a0167a4f8f9eb08216 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 25 Aug 2021 13:53:31 +0300 Subject: [PATCH 212/223] feat(prepare-image): Ensure /usr/bin/env is always present This is required by common patterns in shell scripts. There are some caveats around this. Adding logic to filter whether coreutils is included in an image would slow down the Nix evaluation, so the link is currently created even in cases where it doesn't point to anything. Fixes #109 --- tools/nixery/prepare-image/prepare-image.nix | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/nixery/prepare-image/prepare-image.nix b/tools/nixery/prepare-image/prepare-image.nix index 316bfbbf2..56f9e7a3b 100644 --- a/tools/nixery/prepare-image/prepare-image.nix +++ b/tools/nixery/prepare-image/prepare-image.nix @@ -132,6 +132,18 @@ let contentsEnv = symlinkJoin { name = "bulk-layers"; paths = allContents.contents; + + # Ensure that there is always a /usr/bin/env for shell scripts + # that require it. + # + # Note that images which do not actually contain `coreutils` will + # still have this symlink, but it will be dangling. + # + # TODO(tazjin): Don't link this if coreutils is not included. + postBuild = '' + mkdir -p $out/usr/bin + ln -s ${coreutils}/bin/env $out/usr/bin/env + ''; }; # Image layer that contains the symlink forest created above. This From dd778e77665f41f9aa9ad75d34bdf0b0d0dc6953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Mon, 4 Oct 2021 19:08:25 +0200 Subject: [PATCH 213/223] revert: "feat(storage): Add generic support for content-types" This reverts commit 7db252f36a68d875429a25e06d88fbfc804d84fd. Superseded by the implementation in #127. --- tools/nixery/main.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tools/nixery/main.go b/tools/nixery/main.go index 6af4636e5..d94d51b46 100644 --- a/tools/nixery/main.go +++ b/tools/nixery/main.go @@ -195,16 +195,6 @@ func (h *registryHandler) serveManifestTag(w http.ResponseWriter, r *http.Reques // serveBlob serves a blob from storage by digest func (h *registryHandler) serveBlob(w http.ResponseWriter, r *http.Request, blobType, digest string) { storage := h.state.Storage - switch blobType { - case "manifests": - // It is necessary to set the correct content-type when serving manifests. - // Otherwise, you may get the following mysterious error message when pulling: - // "Error response from daemon: missing signature key" - w.Header().Add("Content-Type", mf.ManifestType) - case "blobs": - // It is not strictly necessary to set this content-type, but since we're here... - w.Header().Add("Content-Type", mf.LayerType) - } err := storage.Serve(digest, r, w) if err != nil { log.WithError(err).WithFields(log.Fields{ From 9929891f563cf5d8c004d89a64867ae1533746fe Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 29 Oct 2021 17:25:58 +0200 Subject: [PATCH 214/223] docs: Remove note about unsupported Google projects I no longer work at Google and the repo has moved, so this is no longer relevant. --- tools/nixery/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 745e73cb4..90799fac3 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -24,8 +24,6 @@ You can watch the NixCon 2019 [talk about Nixery](https://www.youtube.com/watch?v=pOI9H4oeXqA) for more information about the project and its use-cases. -This is not an officially supported Google project. - ## Demo Click the image to see an example in which an image containing an interactive From f4daffbb50f947a49ec17e51d846f1ebb15fd41b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 29 Oct 2021 17:29:33 +0200 Subject: [PATCH 215/223] chore(docs): Bump included nix-1p version ... basically never updated this, oops. --- tools/nixery/docs/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/docs/default.nix b/tools/nixery/docs/default.nix index fdf0e6ff9..d27cbe5b3 100644 --- a/tools/nixery/docs/default.nix +++ b/tools/nixery/docs/default.nix @@ -24,8 +24,8 @@ let nix-1p = fetchFromGitHub { owner = "tazjin"; repo = "nix-1p"; - rev = "e0a051a016b9118bea90ec293d6cd346b9707e77"; - sha256 = "0d1lfkxg03lki8dc3229g1cgqiq3nfrqgrknw99p6w0zk1pjd4dj"; + rev = "9f0baf5e270128d9101ba4446cf6844889e399a2"; + sha256 = "1pf9i90gn98vz67h296w5lnwhssk62dc6pij983dff42dbci7lhj"; }; in runCommand "nixery-book" { } '' mkdir -p $out From 485b8aa929f7ae07ea917814018f6c416556e44a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 29 Oct 2021 17:36:05 +0200 Subject: [PATCH 216/223] chore: Bump nixpkgs pin to nixos-unstable 2021-10-29 --- tools/nixery/nixpkgs-pin.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nixery/nixpkgs-pin.nix b/tools/nixery/nixpkgs-pin.nix index ffedc92ed..d868855b5 100644 --- a/tools/nixery/nixpkgs-pin.nix +++ b/tools/nixery/nixpkgs-pin.nix @@ -1,4 +1,4 @@ import (builtins.fetchTarball { - url = "https://github.com/NixOS/nixpkgs/archive/4263ba5e133cc3fc699c1152ab5ee46ef668e675.tar.gz"; - sha256 = "1nzqrdw0lhbldbs9r651zmgqpwhjhh9sssykhcl2155kgsfsrk7i"; + url = "https://github.com/NixOS/nixpkgs/archive/2deb07f3ac4eeb5de1c12c4ba2911a2eb1f6ed61.tar.gz"; + sha256 = "0036sv1sc4ddf8mv8f8j9ifqzl3fhvsbri4z1kppn0f1zk6jv9yi"; }) {} From fc62b905142a2e43b0c94d291e820b4b0426f258 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 29 Oct 2021 19:13:19 +0200 Subject: [PATCH 217/223] chore: Bump all Go dependencies Result of 'go get -u && go mod tidy' --- tools/nixery/default.nix | 2 +- tools/nixery/go.mod | 22 +++-- tools/nixery/go.sum | 181 ++++++++++++++++++++++++++++++++------- 3 files changed, 169 insertions(+), 36 deletions(-) diff --git a/tools/nixery/default.nix b/tools/nixery/default.nix index 696420d0f..19143fccf 100644 --- a/tools/nixery/default.nix +++ b/tools/nixery/default.nix @@ -39,7 +39,7 @@ let doCheck = true; # Needs to be updated after every modification of go.mod/go.sum - vendorSha256 = "1adjav0dxb97ws0w2k50rhk6r46wvfry6aj4sik3ninl525kd15s"; + vendorSha256 = "1xnmyz2a5s5sck0fzhcz51nds4s80p0jw82dhkf4v2c4yzga83yk"; buildFlagsArray = [ "-ldflags=-s -w -X main.version=${nixery-commit-hash}" diff --git a/tools/nixery/go.mod b/tools/nixery/go.mod index 3b819a796..dfaeb7206 100644 --- a/tools/nixery/go.mod +++ b/tools/nixery/go.mod @@ -3,10 +3,22 @@ module github.com/google/nixery go 1.15 require ( - cloud.google.com/go/storage v1.15.0 - github.com/google/go-cmp v0.5.5 - github.com/pkg/xattr v0.4.3 + cloud.google.com/go/storage v1.18.2 + github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect + github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect + github.com/envoyproxy/go-control-plane v0.10.0 // indirect + github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-cmp v0.5.6 + github.com/pkg/xattr v0.4.4 github.com/sirupsen/logrus v1.8.1 - golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c - gonum.org/v1/gonum v0.9.1 + golang.org/x/net v0.0.0-20211029160332-540bb53d3b2e // indirect + golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 + golang.org/x/sys v0.0.0-20211029162942-c1bf0bb051ef // indirect + gonum.org/v1/gonum v0.9.3 + google.golang.org/api v0.60.0 // indirect + google.golang.org/genproto v0.0.0-20211029142109-e255c875f7c7 // indirect + google.golang.org/grpc v1.41.0 // indirect ) diff --git a/tools/nixery/go.sum b/tools/nixery/go.sum index 812babcf3..312cbfaa2 100644 --- a/tools/nixery/go.sum +++ b/tools/nixery/go.sum @@ -17,8 +17,15 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -36,15 +43,24 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.15.0 h1:Ljj+ZXVEhCr/1+4ZhvtteN1ND7UUsNTlduGclLh8GO0= -cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI= +cloud.google.com/go/storage v1.18.2 h1:5NQw6tOn3eMm0oE8vTkfjau18kjL79FlMjy/CHTpmoY= +cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -52,6 +68,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -61,9 +85,16 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.0 h1:WVt4HEPbdRbRD/PKKPbPnIVavO6gk/h673jWyIJ016k= +github.com/envoyproxy/go-control-plane v0.10.0/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2 h1:JiO+kJTpmYGjEodY7O1Zk8oZcNz1+f30UtwtXoFUPzE= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= @@ -76,8 +107,9 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -86,6 +118,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -104,6 +137,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -116,13 +151,15 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -134,49 +171,65 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/xattr v0.4.3 h1:5Jx4GCg5ABtqWZH8WLzeI4fOtM1HyX4RBawuCoua1es= -github.com/pkg/xattr v0.4.3/go.mod h1:sBD3RAqlr8Q+RC3FutZcikpT8nyDrIEEBw2J744gVWs= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/xattr v0.4.4 h1:FSoblPdYobYoKCItkqASqcrKCxRn9Bgurz0sCBwzO5g= +github.com/pkg/xattr v0.4.4/go.mod h1:sBD3RAqlr8Q+RC3FutZcikpT8nyDrIEEBw2J744gVWs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -185,9 +238,11 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -224,8 +279,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -235,8 +290,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -269,8 +325,12 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029160332-540bb53d3b2e h1:2lVrcCMRP9p7tfk4KUpV1ESqtf49jpihlUtYnSj67k4= +golang.org/x/net v0.0.0-20211029160332-540bb53d3b2e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -282,9 +342,13 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY= -golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 h1:v79phzBz03tsVCUTbvTBmmC3CUXF5mKYt7DA4ZVldpM= +golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -334,8 +398,21 @@ golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog= -golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211029162942-c1bf0bb051ef h1:1ZMK6QI8sz0q1ficx9/snrJ8E/PeRW7Oagamf+0xp10= +golang.org/x/sys v0.0.0-20211029162942-c1bf0bb051ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -343,8 +420,10 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -396,8 +475,12 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -405,8 +488,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.1 h1:HCWmqqNoELL0RAQeKBXWtkp04mGk8koafcB4He6+uhc= -gonum.org/v1/gonum v0.9.1/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.9.3 h1:DnoIG+QAMaF5NvxnGe/oKsgKcAc6PcUyl8q0VetfQ8s= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= @@ -432,8 +515,17 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww= -google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= +google.golang.org/api v0.60.0 h1:eq/zs5WPH4J9undYM9IP1O7dSr7Yh8Y0GtSCpzGzIUk= +google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -465,6 +557,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -481,9 +574,27 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2 h1:g2sJMUGCpeHZqTx8p3wsAWRS64nFq20i4dvJWcKGqvY= -google.golang.org/genproto v0.0.0-20210420162539-3c870d7478d2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211029142109-e255c875f7c7 h1:aaSaYY/DIDJy3f/JLXWv6xJ1mBQSRnQ1s5JhAFTnzO4= +google.golang.org/genproto v0.0.0-20211029142109-e255c875f7c7/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -497,13 +608,21 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -515,13 +634,15 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 1dd342161550adfc58f3a2ea5e6c5843dc2c5ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Thu, 23 Dec 2021 12:10:39 +0100 Subject: [PATCH 218/223] docs: update installation instructions These instructions were not up-to-date (they didn't mention the different storage backends, and some variables were tagged as optional while they were mandatory). With this update, they should (hopefully) be more accurate! :) I also added instructions if someone wants to run Nixery outside of the container image (I found it convenient when working on Nixery's code). --- tools/nixery/docs/src/run-your-own.md | 72 ++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/tools/nixery/docs/src/run-your-own.md b/tools/nixery/docs/src/run-your-own.md index 4ffb8c4d4..eb7d494ee 100644 --- a/tools/nixery/docs/src/run-your-own.md +++ b/tools/nixery/docs/src/run-your-own.md @@ -32,7 +32,16 @@ To run Nixery, you must have: * [Nix][] (to build Nixery itself) * Somewhere to run it (your own server, Google AppEngine, a Kubernetes cluster, whatever!) -* A [Google Cloud Storage][gcs] bucket in which to store & serve layers +* *Either* a [Google Cloud Storage][gcs] bucket in which to store & serve layers, + *or* a comfortable amount of disk space + +Note that while the main Nixery process is a server written in Go, +it invokes a script that itself relies on Nix to be available. +You can compile the main Nixery daemon without Nix, but it won't +work without Nix. + +(If you are completely new to Nix and don't know how to get +started, check the [Nix installation documentation][nixinstall].) ## 1. Choose a package set @@ -54,8 +63,11 @@ use it with your own packages. There are three options available: ## 2. Build Nixery itself -Building Nixery creates a container image. This section assumes that the -container runtime used is Docker, please modify instructions correspondingly if +### 2.1. With a container image + +The easiest way to run Nixery is to build a container image. +This section assumes that the container runtime used is Docker, +please modify instructions accordingly if you are using something else. With a working Nix installation, building Nixery is done by invoking `nix-build @@ -64,23 +76,46 @@ With a working Nix installation, building Nixery is done by invoking `nix-build This will create a `result`-symlink which points to a tarball containing the image. In Docker, this tarball can be loaded by using `docker load -i result`. +### 2.2. Without a container image + +*This method might be more convenient if you intend to work on +the code of the Nixery server itself, because you won't have to +rebuild (and reload) an image each time to test your changes.* + +You will need to run the two following commands at the root of the repo: + +* `go build` to build the `nixery` binary; +* `nix-env --install --file prepare-image/default.nix` to build + the required helpers. + ## 3. Prepare configuration Nixery is configured via environment variables. You must set *all* of these: -* `BUCKET`: [Google Cloud Storage][gcs] bucket to store & serve image layers +* `NIXERY_STORAGE_BACKEND` (must be set to `gcs` or `filesystem`) * `PORT`: HTTP port on which Nixery should listen +* `WEB_DIR`: directory containing static files (see below) -You may set *one* of these, if unset Nixery defaults to `nixos-20.09`: +You must set *one* of these: -* `NIXERY_CHANNEL`: The name of a Nix/NixOS channel to use for building +* `NIXERY_CHANNEL`: The name of a [Nix/NixOS channel][nixchannel] to use for building, + for instance `nixos-21.05` * `NIXERY_PKGS_REPO`: URL of a git repository containing a package set (uses locally configured SSH/git credentials) * `NIXERY_PKGS_PATH`: A local filesystem path containing a Nix package set to use for building +If `NIXERY_STORAGE_BACKEND` is set to `filesystem`, then `STORAGE_PATH` +must be set to the directory that will hold the registry blobs. +That directory must be located on a filesystem that supports extended +attributes (which means that on most systems, `/tmp` won't work). + +If `NIXERY_STORAGE_BACKEND` is set to `gcs`, then `GCS_BUCKET` +must be set to the [Google Cloud Storage][gcs] bucket that will be +used to store & serve image layers. + You may set *all* of these: * `NIX_TIMEOUT`: Number of seconds that any Nix builder is allowed to run @@ -94,13 +129,11 @@ If the `GOOGLE_APPLICATION_CREDENTIALS` environment is configured, the service account's private key will be used to create [signed URLs for layers][signed-urls]. -## 4. Deploy Nixery +## 4. Start Nixery -With the above environment variables configured, you can run the image that was -built in step 2. - -How this works depends on the environment you are using and is, for now, outside -of the scope of this tutorial. +Run the image that was built in step 2.1 with all the environment variables +mentioned above. Alternatively, set all the environment variables and run +the Nixery server that was built in step 2.2. Once Nixery is running you can immediately start requesting images from it. @@ -125,6 +158,19 @@ following: * Configure request timeouts for Nixery if you have your own web server in front of it. This will be natively supported by Nixery in the future. +## 6. `WEB_DIR` + +All the URLs accessed by Docker registry clients start with `/v2/`. +This means that it is possible to serve a static website from Nixery +itself (as long as you don't want to serve anything starting with `/v2`). +This is how, for instance, https://nixery.dev shows the website for Nixery, +while it is also possible to e.g. `docker pull nixery.dev/shell`. + +When running Nixery, you must set the `WEB_DIR` environment variable. +When Nixery receives requests that don't look like registry requests, +it tries to serve them using files in the directory indicated by `WEB_DIR`. +If the directory doesn't exist, Nixery will run fine but serve 404. + ------- [^1]: Nixery will not work with Nix channels older than `nixos-19.03`. @@ -141,3 +187,5 @@ following: [repo]: https://github.com/google/nixery [signed-urls]: under-the-hood.html#5-image-layers-are-requested [ADC]: https://cloud.google.com/docs/authentication/production#finding_credentials_automatically +[nixinstall]: https://nixos.org/manual/nix/stable/installation/installing-binary.html +[nixchannel]: https://nixos.wiki/wiki/Nix_channels From aaf53703443075dc7c54127d390d8bfb6cb206ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Thu, 23 Dec 2021 12:14:49 +0100 Subject: [PATCH 219/223] chore: fix env var name in error message The error message shows the wrong variable name, which might be confusing for new users. --- tools/nixery/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nixery/config/config.go b/tools/nixery/config/config.go index 7ec102bd6..8ea2edc28 100644 --- a/tools/nixery/config/config.go +++ b/tools/nixery/config/config.go @@ -70,7 +70,7 @@ func FromEnv() (Config, error) { default: log.WithField("values", []string{ "gcs", - }).Fatal("NIXERY_STORAGE_BUCKET must be set to a supported value") + }).Fatal("NIXERY_STORAGE_BACKEND must be set to a supported value (gcs or filesystem)") } return Config{ From 15f79e1364410bc78d1d734f4af7617f2ab8e432 Mon Sep 17 00:00:00 2001 From: Ethan Davidson Date: Thu, 9 Dec 2021 13:42:35 -0500 Subject: [PATCH 220/223] docs: mention arm64 metapackage --- tools/nixery/docs/src/nixery.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index 5f6dcb7e3..3f68311da 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -33,8 +33,10 @@ Each path segment corresponds either to a key in the Nix package set, or a meta-package that automatically expands to several other packages. Meta-packages **must** be the first path component if they are used. Currently -the only meta-package is `shell`, which provides a `bash`-shell with interactive -configuration and standard tools like `coreutils`. +there are only two meta-packages: +- `shell`, which provides a `bash`-shell with interactive configuration and + standard tools like `coreutils`. +- `arm64`, which provides ARM64 binaries. **Tip:** When pulling from a private Nixery instance, replace `nixery.dev` in the above examples with your registry address. From 7433d620bbe82fb2642097226a580b96487f32c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Fri, 24 Dec 2021 16:11:49 +0100 Subject: [PATCH 221/223] feat: add /tmp Examples of programs that fail when /tmp doesn't exist: - terraform - anything using mktemp and similar helpers --- tools/nixery/prepare-image/prepare-image.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/nixery/prepare-image/prepare-image.nix b/tools/nixery/prepare-image/prepare-image.nix index 56f9e7a3b..acd143054 100644 --- a/tools/nixery/prepare-image/prepare-image.nix +++ b/tools/nixery/prepare-image/prepare-image.nix @@ -133,14 +133,16 @@ let name = "bulk-layers"; paths = allContents.contents; - # Ensure that there is always a /usr/bin/env for shell scripts - # that require it. + # Provide a few essentials that many programs expect: + # - a /tmp directory, + # - a /usr/bin/env for shell scripts that require it. # - # Note that images which do not actually contain `coreutils` will - # still have this symlink, but it will be dangling. + # Note that in images that do not actually contain `coreutils`, + # /usr/bin/env will be a dangling symlink. # - # TODO(tazjin): Don't link this if coreutils is not included. + # TODO(tazjin): Don't link /usr/bin/env if coreutils is not included. postBuild = '' + mkdir -p $out/tmp mkdir -p $out/usr/bin ln -s ${coreutils}/bin/env $out/usr/bin/env ''; From dd7de32c36845ea40b73b85084c7d5e80027d96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Thu, 23 Dec 2021 12:19:39 +0100 Subject: [PATCH 222/223] feat: set SSL_CERT_FILE and provide a Cmd Two minor "quality of life" improvements: - automatically set SSL_CERT_FILE environment variable, so that programs relying on OpenSSL for certificate validation can actually validate certificates (the certificates are included no matter what since we add the "cacert" package to all iamges) - if the requested image includes an interactive shell (e.g. if it includes the "shell" metapackage), set the image Cmd to "bash", which allows to execute "docker run nixery.dev/shell" and get a shell) I'm happy to split this PR in two if you'd like, but since both features touch the Config structure and are rather small, I thought it would make sense to bundle them together. --- tools/nixery/builder/builder.go | 10 +++++++++- tools/nixery/manifest/manifest.go | 17 +++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/tools/nixery/builder/builder.go b/tools/nixery/builder/builder.go index 115f1e37e..4279cb0a1 100644 --- a/tools/nixery/builder/builder.go +++ b/tools/nixery/builder/builder.go @@ -493,7 +493,15 @@ func BuildImage(ctx context.Context, s *State, image *Image) (*BuildResult, erro return nil, err } - m, c := manifest.Manifest(image.Arch.imageArch, layers) + // If the requested packages include a shell, + // set cmd accordingly. + cmd := "" + for _, pkg := range image.Packages { + if pkg == "bashInteractive" { + cmd = "bash" + } + } + m, c := manifest.Manifest(image.Arch.imageArch, layers, cmd) lw := func(w io.Writer) error { r := bytes.NewReader(c.Config) diff --git a/tools/nixery/manifest/manifest.go b/tools/nixery/manifest/manifest.go index e49992007..afe84072e 100644 --- a/tools/nixery/manifest/manifest.go +++ b/tools/nixery/manifest/manifest.go @@ -64,9 +64,10 @@ type imageConfig struct { DiffIDs []string `json:"diff_ids"` } `json:"rootfs"` - // sic! empty struct (rather than `null`) is required by the - // image metadata deserialiser in Kubernetes - Config struct{} `json:"config"` + Config struct { + Cmd []string `json:"cmd,omitempty"` + Env []string `json:"env,omitempty"` + } `json:"config"` } // ConfigLayer represents the configuration layer to be included in @@ -83,12 +84,16 @@ type ConfigLayer struct { // Outside of this module the image configuration is treated as an // opaque blob and it is thus returned as an already serialised byte // array and its SHA256-hash. -func configLayer(arch string, hashes []string) ConfigLayer { +func configLayer(arch string, hashes []string, cmd string) ConfigLayer { c := imageConfig{} c.Architecture = arch c.OS = os c.RootFS.FSType = fsType c.RootFS.DiffIDs = hashes + if cmd != "" { + c.Config.Cmd = []string{cmd} + } + c.Config.Env = []string{"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"} j, _ := json.Marshal(c) @@ -103,7 +108,7 @@ func configLayer(arch string, hashes []string) ConfigLayer { // layer. // // Callers do not need to set the media type for the layer entries. -func Manifest(arch string, layers []Entry) (json.RawMessage, ConfigLayer) { +func Manifest(arch string, layers []Entry, cmd string) (json.RawMessage, ConfigLayer) { // Sort layers by their merge rating, from highest to lowest. // This makes it likely for a contiguous chain of shared image // layers to appear at the beginning of a layer. @@ -122,7 +127,7 @@ func Manifest(arch string, layers []Entry) (json.RawMessage, ConfigLayer) { layers[i] = l } - c := configLayer(arch, hashes) + c := configLayer(arch, hashes, cmd) m := manifest{ SchemaVersion: schemaVersion, From 3d26ea9e636e9cd137d9430dd36f672e83239e7b Mon Sep 17 00:00:00 2001 From: Raphael Borun Das Gupta Date: Tue, 19 Apr 2022 21:32:46 +0200 Subject: [PATCH 223/223] docs: change references to repo URL The Nixery main Git repo has moved from https://github.com/google/nixery to https://github.com/tazjin/nixery . So change it in README and on the https://nixery.dev/ website. --- tools/nixery/README.md | 4 ++-- tools/nixery/docs/src/nixery.md | 2 +- tools/nixery/docs/src/run-your-own.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/nixery/README.md b/tools/nixery/README.md index 90799fac3..cba8ce6b1 100644 --- a/tools/nixery/README.md +++ b/tools/nixery/README.md @@ -4,7 +4,7 @@ ----------------- -[![Build Status](https://github.com/google/nixery/actions/workflows/build-and-test.yaml/badge.svg)](https://github.com/google/nixery/actions/workflows/build-and-test.yaml) +[![Build Status](https://github.com/tazjin/nixery/actions/workflows/build-and-test.yaml/badge.svg)](https://github.com/tazjin/nixery/actions/workflows/build-and-test.yaml) **Nixery** is a Docker-compatible container registry that is capable of transparently building and serving container images using [Nix][]. @@ -130,7 +130,7 @@ outlined in [a public gist][gist]. It should be trivial to deploy Nixery inside of a Kubernetes cluster with correct caching behaviour, addressing and so on. -See [issue #4](https://github.com/google/nixery/issues/4). +See [issue #4](https://github.com/tazjin/nixery/issues/4). ### Nix-native builder diff --git a/tools/nixery/docs/src/nixery.md b/tools/nixery/docs/src/nixery.md index 3f68311da..7b78ddf5a 100644 --- a/tools/nixery/docs/src/nixery.md +++ b/tools/nixery/docs/src/nixery.md @@ -77,7 +77,7 @@ availability. Nixery was written by [tazjin][], but many people have contributed to Nix over time, maybe you could become one of them? -[Nixery]: https://github.com/google/nixery +[Nixery]: https://github.com/tazjin/nixery [Nix]: https://nixos.org/nix [layering strategy]: https://storage.googleapis.com/nixdoc/nixery-layers.html [layers]: https://grahamc.com/blog/nix-and-layered-docker-images diff --git a/tools/nixery/docs/src/run-your-own.md b/tools/nixery/docs/src/run-your-own.md index eb7d494ee..cf4dc2ce6 100644 --- a/tools/nixery/docs/src/run-your-own.md +++ b/tools/nixery/docs/src/run-your-own.md @@ -181,10 +181,10 @@ If the directory doesn't exist, Nixery will run fine but serve 404. extensively. [GKE]: https://cloud.google.com/kubernetes-engine/ -[nixery#4]: https://github.com/google/nixery/issues/4 +[nixery#4]: https://github.com/tazjin/nixery/issues/4 [Nix]: https://nixos.org/nix [gcs]: https://cloud.google.com/storage/ -[repo]: https://github.com/google/nixery +[repo]: https://github.com/tazjin/nixery [signed-urls]: under-the-hood.html#5-image-layers-are-requested [ADC]: https://cloud.google.com/docs/authentication/production#finding_credentials_automatically [nixinstall]: https://nixos.org/manual/nix/stable/installation/installing-binary.html