feat: Fully working quine registry
This commit is contained in:
parent
9690defd2b
commit
1c2d087ec4
2 changed files with 115 additions and 31 deletions
28
image.go
Normal file
28
image.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type RootFs struct {
|
||||||
|
DiffIds []string `json:"diff_ids"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type History struct {
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageConfig struct {
|
||||||
|
Cmd []string
|
||||||
|
Env []string
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
112
main.go
112
main.go
|
|
@ -11,9 +11,15 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Layer struct {
|
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"`
|
MediaType string `json:"mediaType"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Digest string `json:"digest"`
|
Digest string `json:"digest"`
|
||||||
|
|
@ -22,62 +28,111 @@ type Layer struct {
|
||||||
type Manifest struct {
|
type Manifest struct {
|
||||||
SchemaVersion int `json:"schemaVersion"`
|
SchemaVersion int `json:"schemaVersion"`
|
||||||
MediaType string `json:"mediaType"`
|
MediaType string `json:"mediaType"`
|
||||||
Config map[string]string `json:"config"`
|
Config Element `json:"config"`
|
||||||
Layers []Layer `json:"layers"`
|
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
|
// A really "dumb" representation of an image, with a data blob (tar.gz image) and its hash as the type expected
|
||||||
// in the manifest.
|
// in the manifest.
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Layer Layer
|
|
||||||
Data []byte
|
Data []byte
|
||||||
|
TarDigest string
|
||||||
|
GzipDigest string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ManifestContentType string = "application/vnd.docker.distribution.manifest.v2+json"
|
|
||||||
const LayerContentType string = "application/vnd.docker.image.rootfs.diff.tar.gzip"
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.Println("Starting quinistry")
|
||||||
|
|
||||||
img := getImage()
|
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{
|
manifest := Manifest{
|
||||||
SchemaVersion: 2,
|
SchemaVersion: 2,
|
||||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
MediaType: ManifestContentType,
|
||||||
Config: map[string]string{},
|
Config: Element{
|
||||||
Layers: []Layer{
|
MediaType: ImageContentType,
|
||||||
img.Layer,
|
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) {
|
log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Acknowledge that we speak V2
|
// Acknowledge that we speak V2
|
||||||
if r.RequestURI == "/v2/" {
|
if r.RequestURI == "/v2/" {
|
||||||
log.Println("Acknowleding V2 API")
|
logRequest("Acknowleding V2 API", r)
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve manifest
|
// Serve manifest
|
||||||
if r.RequestURI == "/v2/quinistry/manifests/latest" {
|
if r.RequestURI == "/v2/quinistry/manifests/latest" {
|
||||||
log.Printf("Serving manifest for %v\n", *r)
|
logRequest("Serving manifest", r)
|
||||||
w.Header().Add("Content-Type", ManifestContentType)
|
w.Header().Add("Content-Type", ManifestContentType)
|
||||||
resp, _ := json.Marshal(manifest)
|
resp, _ := json.Marshal(manifest)
|
||||||
w.Header().Add("Docker-Content-Digest", digest(resp))
|
w.Header().Add(DigestHeader, digest(resp))
|
||||||
log.Println(digest(resp))
|
w.Write(resp)
|
||||||
fmt.Fprintln(w, string(resp))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve actual image layer
|
// Serve actual image layer
|
||||||
if r.RequestURI == fmt.Sprintf("/v2/quinistry/blob/%s", img.Layer.Digest) {
|
layerUri := fmt.Sprintf("/v2/quinistry/blobs/%s", img.GzipDigest)
|
||||||
fmt.Printf("Serving layer for %v\n", *r)
|
if r.RequestURI == layerUri {
|
||||||
fmt.Fprint(w, img.Data)
|
logRequest("Serving image layer blob", r)
|
||||||
|
w.Header().Add(DigestHeader, img.GzipDigest)
|
||||||
|
w.Write(img.Data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Unhandled: %v\n", *r)
|
// 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 {
|
func digest(b []byte) string {
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
hash.Write(b)
|
hash.Write(b)
|
||||||
|
|
@ -97,7 +152,7 @@ func getImage() *Image {
|
||||||
tarBuf := new(bytes.Buffer)
|
tarBuf := new(bytes.Buffer)
|
||||||
tarW := tar.NewWriter(tarBuf)
|
tarW := tar.NewWriter(tarBuf)
|
||||||
hdr := &tar.Header{
|
hdr := &tar.Header{
|
||||||
Name: "main",
|
Name: "/main",
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
Size: int64(len(file)),
|
Size: int64(len(file)),
|
||||||
}
|
}
|
||||||
|
|
@ -109,23 +164,24 @@ func getImage() *Image {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tarBytes := tarBuf.Bytes()
|
||||||
|
|
||||||
// Then GZIP it
|
// Then GZIP it
|
||||||
zBuf := new(bytes.Buffer)
|
zBuf := new(bytes.Buffer)
|
||||||
zw := gzip.NewWriter(zBuf)
|
zw := gzip.NewWriter(zBuf)
|
||||||
zw.Name = "Docker registry fake test"
|
zw.Name = "Docker registry fake test"
|
||||||
|
|
||||||
zw.Write(tarBuf.Bytes())
|
zw.Write(tarBytes)
|
||||||
if err := zw.Close(); err != nil {
|
if err := zw.Close(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gzipData := zBuf.Bytes()
|
||||||
|
|
||||||
return &Image{
|
return &Image{
|
||||||
Layer: Layer{
|
TarDigest: digest(tarBytes),
|
||||||
MediaType: LayerContentType,
|
GzipDigest: digest(gzipData),
|
||||||
Size: zBuf.Len(),
|
Data: gzipData,
|
||||||
Digest: digest(zBuf.Bytes()),
|
|
||||||
},
|
|
||||||
Data: zBuf.Bytes(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue