about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--const.go12
-rw-r--r--image.go158
-rw-r--r--main.go152
-rw-r--r--types.go79
4 files changed, 242 insertions, 159 deletions
diff --git a/const.go b/const.go
new file mode 100644
index 000000000000..173fa9efc390
--- /dev/null
+++ b/const.go
@@ -0,0 +1,12 @@
+package main
+
+// HTTP content types
+
+const ImageConfigMediaType string = "application/vnd.docker.container.image.v1+json"
+const ManifestMediaType string = "application/vnd.docker.distribution.manifest.v2+json"
+const LayerMediaType string = "application/vnd.docker.image.rootfs.diff.tar.gzip"
+
+// HTTP header names
+
+const ContentType string = "Content-Type"
+const DigestHeader string = "Docker-Content-Digest"
diff --git a/image.go b/image.go
index e627544462e5..3daeac34d6bb 100644
--- a/image.go
+++ b/image.go
@@ -1,28 +1,150 @@
+// The code in this file creates a Docker image layer containing the binary of the
+// application itself.
+
 package main
 
-import "time"
+import (
+	"archive/tar"
+	"bytes"
+	"compress/gzip"
+	"crypto/sha256"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"time"
+)
+
+// This function creates a Docker-image digest (i.e. SHA256 hash with
+// algorithm-specification prefix)
+func Digest(b []byte) string {
+	hash := sha256.New()
+	hash.Write(b)
+
+	return fmt.Sprintf("sha256:%x", hash.Sum(nil))
+}
+
+func GetImageOfCurrentExecutable() Image {
+	binary := getCurrentBinary()
+	tarArchive := createTarArchive(&map[string][]byte{
+		"/main": binary,
+	})
+
+	configJson, configElem := createConfig([]string{Digest(tarArchive)})
+	compressed := gzipArchive("Quinistry image", tarArchive)
+	manifest := createManifest(&configElem, &compressed)
+	manifestJson, _ := json.Marshal(manifest)
+
+	return Image{
+		Layer:          compressed,
+		LayerDigest:    Digest(compressed),
+		Manifest:       manifestJson,
+		ManifestDigest: Digest(manifestJson),
+		Config:         configJson,
+		ConfigDigest:   Digest(configJson),
+	}
+
+}
+
+func getCurrentBinary() []byte {
+	path, _ := os.Executable()
+	file, _ := ioutil.ReadFile(path)
+	return file
+}
+
+func createTarArchive(files *map[string][]byte) []byte {
+	buf := new(bytes.Buffer)
+	w := tar.NewWriter(buf)
 
-type RootFs struct {
-	DiffIds []string `json:"diff_ids"`
-	Type    string   `json:"type"`
+	for name, file := range *files {
+		hdr := &tar.Header{
+			Name: name,
+			// Everything is executable \o/
+			Mode: 0755,
+			Size: int64(len(file)),
+		}
+		w.WriteHeader(hdr)
+		w.Write(file)
+	}
+
+	if err := w.Close(); err != nil {
+		log.Fatalln(err)
+		os.Exit(1)
+	}
+
+	return buf.Bytes()
 }
 
-type History struct {
-	Created   time.Time `json:"created"`
-	CreatedBy string    `json:"created_by"`
+func gzipArchive(name string, archive []byte) []byte {
+	buf := new(bytes.Buffer)
+	w := gzip.NewWriter(buf)
+	w.Name = name
+	w.Write(archive)
+
+	if err := w.Close(); err != nil {
+		log.Fatalln(err)
+		os.Exit(1)
+	}
+
+	return buf.Bytes()
 }
 
-type ImageConfig struct {
-	Cmd []string
-	Env []string
+func createConfig(layerDigests []string) (configJson []byte, elem Element) {
+	now := time.Now()
+
+	imageConfig := &ImageConfig{
+		Cmd: []string{"/main"},
+		Env: []string{"PATH=/"},
+	}
+
+	rootFs := RootFs{
+		DiffIds: layerDigests,
+		Type:    "layers",
+	}
+
+	history := []History{
+		{
+			Created:   now,
+			CreatedBy: "Quinistry magic",
+		},
+	}
+
+	config := Config{
+		Created:      now,
+		Author:       "tazjin",
+		Architecture: "amd64",
+		Os:           "linux",
+		Config:       imageConfig,
+		RootFs:       rootFs,
+		History:      history,
+	}
+
+	configJson, _ = json.Marshal(config)
+
+	elem = Element{
+		MediaType: ImageConfigMediaType,
+		Size:      len(configJson),
+		Digest:    Digest(configJson),
+	}
+
+	return
 }
 
-type Config struct {
-	Created      time.Time    `json:"created"`
-	Author       string       `json:"author"`
-	Architecture string       `json:"architecture"`
-	Os           string       `json:"os"`
-	Config       *ImageConfig `json:"config"`
-	RootFs       RootFs       `json:"rootfs"`
-	History      []History    `json:"history"`
+func createManifest(config *Element, layer *[]byte) Manifest {
+	layers := []Element{
+		{
+			MediaType: LayerMediaType,
+			Size:      len(*layer),
+			// Layers must contain the digest of the *gzipped* layer.
+			Digest: Digest(*layer),
+		},
+	}
+
+	return Manifest{
+		SchemaVersion: 2,
+		MediaType:     ManifestMediaType,
+		Config:        *config,
+		Layers:        layers,
+	}
 }
diff --git a/main.go b/main.go
index ad1470e9eb5f..50b47418d1a8 100644
--- a/main.go
+++ b/main.go
@@ -1,92 +1,18 @@
 package main
 
 import (
-	"archive/tar"
-	"bytes"
-	"compress/gzip"
-	"crypto/sha256"
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net/http"
-	"os"
-	"time"
 )
 
-const ImageContentType string = "application/vnd.docker.container.image.v1+json"
-const ManifestContentType string = "application/vnd.docker.distribution.manifest.v2+json"
-const LayerContentType string = "application/vnd.docker.image.rootfs.diff.tar.gzip"
-const DigestHeader string = "Docker-Content-Digest"
-
-type Element struct {
-	MediaType string `json:"mediaType"`
-	Size      int    `json:"size"`
-	Digest    string `json:"digest"`
-}
-
-type Manifest struct {
-	SchemaVersion int       `json:"schemaVersion"`
-	MediaType     string    `json:"mediaType"`
-	Config        Element   `json:"config"`
-	Layers        []Element `json:"layers"`
-}
-
-// A really "dumb" representation of an image, with a data blob (tar.gz image) and its hash as the type expected
-// in the manifest.
-type Image struct {
-	Data       []byte
-	TarDigest  string
-	GzipDigest string
-}
-
 func main() {
 	log.Println("Starting quinistry")
 
-	img := getImage()
-	now := time.Now()
-
-	config := Config{
-		Created:      now,
-		Author:       "tazjin",
-		Architecture: "amd64",
-		Os:           "linux",
-		Config: &ImageConfig{
-			Cmd: []string{"main"},
-			Env: []string{"PATH=/"},
-		},
-		RootFs: RootFs{
-			DiffIds: []string{
-				img.TarDigest,
-			},
-			Type: "layers",
-		},
-		History: []History{
-			{
-				Created:   now,
-				CreatedBy: "quinistry magic",
-			},
-		},
-	}
-
-	configJson, _ := json.Marshal(config)
+	image := GetImageOfCurrentExecutable()
 
-	manifest := Manifest{
-		SchemaVersion: 2,
-		MediaType:     ManifestContentType,
-		Config: Element{
-			MediaType: ImageContentType,
-			Size:      len(configJson),
-			Digest:    digest(configJson),
-		},
-		Layers: []Element{
-			{
-				MediaType: LayerContentType,
-				Size:      len(img.Data),
-				Digest:    img.GzipDigest,
-			},
-		},
-	}
+	layerUri := fmt.Sprintf("/v2/quinistry/blobs/%s", image.LayerDigest)
+	configUri := fmt.Sprintf("/v2/quinistry/blobs/%s", image.ConfigDigest)
 
 	log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		// Acknowledge that we speak V2
@@ -99,29 +25,26 @@ func main() {
 		// Serve manifest
 		if r.RequestURI == "/v2/quinistry/manifests/latest" {
 			logRequest("Serving manifest", r)
-			w.Header().Add("Content-Type", ManifestContentType)
-			resp, _ := json.Marshal(manifest)
-			w.Header().Add(DigestHeader, digest(resp))
-			w.Write(resp)
+			w.Header().Set(ContentType, ManifestMediaType)
+			w.Header().Add(DigestHeader, image.ManifestDigest)
+			w.Write(image.Manifest)
 			return
 		}
 
 		// Serve actual image layer
-		layerUri := fmt.Sprintf("/v2/quinistry/blobs/%s", img.GzipDigest)
 		if r.RequestURI == layerUri {
 			logRequest("Serving image layer blob", r)
-			w.Header().Add(DigestHeader, img.GzipDigest)
-			w.Write(img.Data)
+			w.Header().Add(DigestHeader, image.LayerDigest)
+			w.Write(image.Layer)
 			return
 		}
 
 		// Serve image config
-		configUri := fmt.Sprintf("/v2/quinistry/blobs/%s", digest(configJson))
 		if r.RequestURI == configUri {
 			logRequest("Serving config", r)
-			w.Header().Set("Content-Type", ImageContentType)
-			w.Header().Set(DigestHeader, digest(configJson))
-			w.Write(configJson)
+			w.Header().Set("Content-Type", ImageConfigMediaType)
+			w.Header().Set(DigestHeader, image.ConfigDigest)
+			w.Write(image.Config)
 			return
 		}
 
@@ -132,56 +55,3 @@ func main() {
 func logRequest(msg string, r *http.Request) {
 	log.Printf("%s: %s %s\n", msg, r.Method, r.RequestURI)
 }
-
-func digest(b []byte) string {
-	hash := sha256.New()
-	hash.Write(b)
-
-	return fmt.Sprintf("sha256:%x", hash.Sum(nil))
-}
-
-// Creates an image of the currently running binary (spooky!)
-func getImage() *Image {
-	// Current binary, imagine this is some other output or whatever
-	path, _ := os.Executable()
-
-	// don't care about error! :O
-	file, _ := ioutil.ReadFile(path)
-
-	// First create tar archive
-	tarBuf := new(bytes.Buffer)
-	tarW := tar.NewWriter(tarBuf)
-	hdr := &tar.Header{
-		Name: "/main",
-		Mode: 0755,
-		Size: int64(len(file)),
-	}
-	tarW.WriteHeader(hdr)
-	tarW.Write(file)
-
-	if err := tarW.Close(); err != nil {
-		log.Fatalln(err)
-		os.Exit(1)
-	}
-
-	tarBytes := tarBuf.Bytes()
-
-	// Then GZIP it
-	zBuf := new(bytes.Buffer)
-	zw := gzip.NewWriter(zBuf)
-	zw.Name = "Docker registry fake test"
-
-	zw.Write(tarBytes)
-	if err := zw.Close(); err != nil {
-		log.Fatal(err)
-		os.Exit(1)
-	}
-
-	gzipData := zBuf.Bytes()
-
-	return &Image{
-		TarDigest:  digest(tarBytes),
-		GzipDigest: digest(gzipData),
-		Data:       gzipData,
-	}
-}
diff --git a/types.go b/types.go
new file mode 100644
index 000000000000..498cbac2f2ab
--- /dev/null
+++ b/types.go
@@ -0,0 +1,79 @@
+package main
+
+import "time"
+
+// This type represents the rootfs-key of the Docker image config.
+// It specifies the digest (i.e. usually SHA256 hash) of the tar'ed, but NOT
+// compressed image layers.
+type RootFs struct {
+	// The digests of the non-compressed FS layers.
+	DiffIds []string `json:"diff_ids"`
+
+	// Type should always be set to "layers"
+	Type string `json:"type"`
+}
+
+// This type represents an entry in the Docker image config's history key.
+// Every history element "belongs" to a filesystem layer.
+type History struct {
+	Created   time.Time `json:"created"`
+	CreatedBy string    `json:"created_by"`
+}
+
+// This type represents runtime-configuration for the Docker image.
+// A lot of possible keys are omitted here, see:
+// https://github.com/docker/docker/blob/master/image/spec/v1.2.md#image-json-description
+type ImageConfig struct {
+	Cmd []string
+	Env []string
+}
+
+// This type represents the Docker image configuration
+type Config struct {
+	Created time.Time `json:"created"`
+	Author  string    `json:"author"`
+
+	// Architecture should be "amd64"
+	Architecture string `json:"architecture"`
+
+	// OS should be "linux"
+	Os string `json:"os"`
+
+	// Configuration can be set to 'nil', in which case all options have to be
+	// supplied at container launch time.
+	Config *ImageConfig `json:"config"`
+
+	// Filesystem layers and history elements have to be in the same order.
+	RootFs  RootFs    `json:"rootfs"`
+	History []History `json:"history"`
+}
+
+// This type represents any manifest
+type Element struct {
+	MediaType string `json:"mediaType"`
+	Size      int    `json:"size"`
+	Digest    string `json:"digest"`
+}
+
+// This type represents a Docker image manifest as used by the registry
+// protocol V2.
+type Manifest struct {
+	SchemaVersion int       `json:"schemaVersion"` // Must be 2
+	MediaType     string    `json:"mediaType"`     // Use ManifestMediaType const
+	Config        Element   `json:"config"`
+	Layers        []Element `json:"layers"`
+}
+
+// A really "dumb" representation of an image, with its data blob and related
+// metadata.
+// Note: This is not a registry API type.
+type Image struct {
+	Layer       []byte
+	LayerDigest string
+
+	Manifest       []byte
+	ManifestDigest string
+
+	Config       []byte
+	ConfigDigest string
+}