about summary refs log blame commit diff
path: root/main.go
blob: ad1470e9eb5fd91bdbe4e9ee771cea6f6a7c7b02 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                       
              

 





                                                                                         





                                           



                                                      




                                                                                                                


                         

 
             

                                         
                         

























                                                             


                                 











                                                            





                                                                                                              
                                                            





                                                                     
                                                         

                                                                           

                                                                  



                                           














                                                                                      


                              
                                                         


            



                                                              


















                                                                        
                              










                                            

                                  




                                             
                          




                                          

                                
                      


                                             

         
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,
	}
}