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)
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,
},
},
}
log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Acknowledge that we speak V2
if r.RequestURI == "/v2/" {
logRequest("Acknowleding V2 API", r)
fmt.Fprintln(w)
return
}
// 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)
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)
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)
return
}
log.Printf("Unhandled request: %v\n", *r)
})))
}
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,
}
}