about summary refs log tree commit diff
path: root/tools/nixery/main.go
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@google.com>2019-07-23T21·48+0100
committerVincent Ambo <tazjin@google.com>2019-07-23T22·23+0100
commitdb1086a5bbb273d50c7a8645daa9c79801040e58 (patch)
tree1f75bea74e713a649417c3476d6824973d0e933a /tools/nixery/main.go
parent5d9b32977ddd332f47f89aa30202b80906d3e719 (diff)
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.
Diffstat (limited to 'tools/nixery/main.go')
-rw-r--r--tools/nixery/main.go81
1 files changed, 49 insertions, 32 deletions
diff --git a/tools/nixery/main.go b/tools/nixery/main.go
index 29b22f3018..5db200fd32 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
 		}