131 lines
2.9 KiB
Go
131 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
)
|
|
|
|
type Layer 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 map[string]string `json:"config"`
|
|
Layers []Layer `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 {
|
|
Layer Layer
|
|
Data []byte
|
|
}
|
|
|
|
const ManifestContentType string = "application/vnd.docker.distribution.manifest.v2+json"
|
|
const LayerContentType string = "application/vnd.docker.image.rootfs.diff.tar.gzip"
|
|
|
|
func main() {
|
|
img := getImage()
|
|
|
|
manifest := Manifest{
|
|
SchemaVersion: 2,
|
|
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
|
Config: map[string]string{},
|
|
Layers: []Layer{
|
|
img.Layer,
|
|
},
|
|
}
|
|
|
|
log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Acknowledge that we speak V2
|
|
if r.RequestURI == "/v2/" {
|
|
log.Println("Acknowleding V2 API")
|
|
fmt.Fprintln(w)
|
|
return
|
|
}
|
|
|
|
// Serve manifest
|
|
if r.RequestURI == "/v2/quinistry/manifests/latest" {
|
|
log.Printf("Serving manifest for %v\n", *r)
|
|
w.Header().Add("Content-Type", ManifestContentType)
|
|
resp, _ := json.Marshal(manifest)
|
|
w.Header().Add("Docker-Content-Digest", digest(resp))
|
|
log.Println(digest(resp))
|
|
fmt.Fprintln(w, string(resp))
|
|
return
|
|
}
|
|
|
|
// Serve actual image layer
|
|
if r.RequestURI == fmt.Sprintf("/v2/quinistry/blob/%s", img.Layer.Digest) {
|
|
fmt.Printf("Serving layer for %v\n", *r)
|
|
fmt.Fprint(w, img.Data)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("Unhandled: %v\n", *r)
|
|
})))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// Then GZIP it
|
|
zBuf := new(bytes.Buffer)
|
|
zw := gzip.NewWriter(zBuf)
|
|
zw.Name = "Docker registry fake test"
|
|
|
|
zw.Write(tarBuf.Bytes())
|
|
if err := zw.Close(); err != nil {
|
|
log.Fatal(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
return &Image{
|
|
Layer: Layer{
|
|
MediaType: LayerContentType,
|
|
Size: zBuf.Len(),
|
|
Digest: digest(zBuf.Bytes()),
|
|
},
|
|
Data: zBuf.Bytes(),
|
|
}
|
|
}
|