From 86ff65a36ca9e9cc9aaa6b845eecb9c6c7fa488a Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 21 Dec 2019 00:48:37 +0000 Subject: chore(quinistry): Prepare for depot merge --- .gitignore | 2 - README.md | 63 ------------------ const.go | 12 ---- fun/quinistry/.gitignore | 2 + fun/quinistry/README.md | 63 ++++++++++++++++++ fun/quinistry/const.go | 12 ++++ fun/quinistry/image.go | 150 ++++++++++++++++++++++++++++++++++++++++++ fun/quinistry/k8s/child.yaml | 27 ++++++++ fun/quinistry/k8s/parent.yaml | 27 ++++++++ fun/quinistry/main.go | 57 ++++++++++++++++ fun/quinistry/types.go | 79 ++++++++++++++++++++++ image.go | 150 ------------------------------------------ k8s/child.yaml | 27 -------- k8s/parent.yaml | 27 -------- main.go | 57 ---------------- types.go | 79 ---------------------- 16 files changed, 417 insertions(+), 417 deletions(-) delete mode 100644 .gitignore delete mode 100644 README.md delete mode 100644 const.go create mode 100644 fun/quinistry/.gitignore create mode 100644 fun/quinistry/README.md create mode 100644 fun/quinistry/const.go create mode 100644 fun/quinistry/image.go create mode 100644 fun/quinistry/k8s/child.yaml create mode 100644 fun/quinistry/k8s/parent.yaml create mode 100644 fun/quinistry/main.go create mode 100644 fun/quinistry/types.go delete mode 100644 image.go delete mode 100644 k8s/child.yaml delete mode 100644 k8s/parent.yaml delete mode 100644 main.go delete mode 100644 types.go diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 622119552e..0000000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.idea/ -quinistry \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index de197a219e..0000000000 --- a/README.md +++ /dev/null @@ -1,63 +0,0 @@ -Quinistry -========= - -*A simple Docker registry quine.* - -## What? - -This is an example project for a from-scratch implementation of an HTTP server compatible with the [Docker Registry V2][] -protocol. - -It serves a single image called `quinistry:latest` which is a Docker image that runs quinistry itself, therefore it is a -sort of Docker registry [quine][]. - -The official documentation does not contain enough information to actually implement this protocol (which I assume is -intentional), but a bit of trial&error lead there anyways. I've added comments to parts of the code to clear up things -that may be helpful to other developers in the future. - -## Example - -``` -# Run quinistry: -vincent@urdhva ~/go/src/github.com/tazjin/quinistry (git)-[master] % ./quinistry -2017/03/16 14:11:56 Starting quinistry - -# Pull the quinistry image from itself: -vincent@urdhva ~ % docker pull localhost:8080/quinistry -Using default tag: latest -latest: Pulling from quinistry -7bf1a8b18466: Already exists -Digest: sha256:d5cd4490901ef04b4e28e4ccc03a1d25fe3461200cf4d7166aab86fcd495e22e -Status: Downloaded newer image for localhost:8080/quinistry:latest - -# Quinistry will log: -2017/03/16 14:14:03 Acknowleding V2 API: GET /v2/ -2017/03/16 14:14:03 Serving manifest: GET /v2/quinistry/manifests/latest -2017/03/16 14:14:03 Serving config: GET /v2/quinistry/blobs/sha256:fbb165c48849de16017aa398aa9bb08fd1c00eaa7c150b6c2af37312913db279 - -# Run the downloaded image: -vincent@urdhva ~ % docker run -p 8090:8080 localhost:8080/quinistry -2017/03/16 13:15:18 Starting quinistry - -# And download it again from itself: -vincent@urdhva ~ % docker pull localhost:8090/quinistry -Using default tag: latest -latest: Pulling from quinistry -7bf1a8b18466: Already exists -Digest: sha256:11141d95ddce0bac9ffa32ab1e8bc94748ed923e87762c68858dc41d11a46d3f -Status: Downloaded newer image for localhost:8090/quinistry:latest -``` - -## Building - -Quinistry creates a Docker image that only contains a statically linked `main` binary. As this package makes use of -`net/http`, Go will (by default) link against `libc` for DNS resolution and create a dynamic binary instead. - -To disable this, `build` the project with `-tags netgo`: - -``` -go build -tags netgo -``` - -[Docker Registry V2]: https://docs.docker.com/registry/spec/api/ -[quine]: https://en.wikipedia.org/wiki/Quine_(computing) \ No newline at end of file diff --git a/const.go b/const.go deleted file mode 100644 index 173fa9efc3..0000000000 --- a/const.go +++ /dev/null @@ -1,12 +0,0 @@ -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/fun/quinistry/.gitignore b/fun/quinistry/.gitignore new file mode 100644 index 0000000000..622119552e --- /dev/null +++ b/fun/quinistry/.gitignore @@ -0,0 +1,2 @@ +.idea/ +quinistry \ No newline at end of file diff --git a/fun/quinistry/README.md b/fun/quinistry/README.md new file mode 100644 index 0000000000..de197a219e --- /dev/null +++ b/fun/quinistry/README.md @@ -0,0 +1,63 @@ +Quinistry +========= + +*A simple Docker registry quine.* + +## What? + +This is an example project for a from-scratch implementation of an HTTP server compatible with the [Docker Registry V2][] +protocol. + +It serves a single image called `quinistry:latest` which is a Docker image that runs quinistry itself, therefore it is a +sort of Docker registry [quine][]. + +The official documentation does not contain enough information to actually implement this protocol (which I assume is +intentional), but a bit of trial&error lead there anyways. I've added comments to parts of the code to clear up things +that may be helpful to other developers in the future. + +## Example + +``` +# Run quinistry: +vincent@urdhva ~/go/src/github.com/tazjin/quinistry (git)-[master] % ./quinistry +2017/03/16 14:11:56 Starting quinistry + +# Pull the quinistry image from itself: +vincent@urdhva ~ % docker pull localhost:8080/quinistry +Using default tag: latest +latest: Pulling from quinistry +7bf1a8b18466: Already exists +Digest: sha256:d5cd4490901ef04b4e28e4ccc03a1d25fe3461200cf4d7166aab86fcd495e22e +Status: Downloaded newer image for localhost:8080/quinistry:latest + +# Quinistry will log: +2017/03/16 14:14:03 Acknowleding V2 API: GET /v2/ +2017/03/16 14:14:03 Serving manifest: GET /v2/quinistry/manifests/latest +2017/03/16 14:14:03 Serving config: GET /v2/quinistry/blobs/sha256:fbb165c48849de16017aa398aa9bb08fd1c00eaa7c150b6c2af37312913db279 + +# Run the downloaded image: +vincent@urdhva ~ % docker run -p 8090:8080 localhost:8080/quinistry +2017/03/16 13:15:18 Starting quinistry + +# And download it again from itself: +vincent@urdhva ~ % docker pull localhost:8090/quinistry +Using default tag: latest +latest: Pulling from quinistry +7bf1a8b18466: Already exists +Digest: sha256:11141d95ddce0bac9ffa32ab1e8bc94748ed923e87762c68858dc41d11a46d3f +Status: Downloaded newer image for localhost:8090/quinistry:latest +``` + +## Building + +Quinistry creates a Docker image that only contains a statically linked `main` binary. As this package makes use of +`net/http`, Go will (by default) link against `libc` for DNS resolution and create a dynamic binary instead. + +To disable this, `build` the project with `-tags netgo`: + +``` +go build -tags netgo +``` + +[Docker Registry V2]: https://docs.docker.com/registry/spec/api/ +[quine]: https://en.wikipedia.org/wiki/Quine_(computing) \ No newline at end of file diff --git a/fun/quinistry/const.go b/fun/quinistry/const.go new file mode 100644 index 0000000000..173fa9efc3 --- /dev/null +++ b/fun/quinistry/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/fun/quinistry/image.go b/fun/quinistry/image.go new file mode 100644 index 0000000000..3daeac34d6 --- /dev/null +++ b/fun/quinistry/image.go @@ -0,0 +1,150 @@ +// The code in this file creates a Docker image layer containing the binary of the +// application itself. + +package main + +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) + + 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() +} + +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() +} + +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 +} + +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/fun/quinistry/k8s/child.yaml b/fun/quinistry/k8s/child.yaml new file mode 100644 index 0000000000..aa2e318262 --- /dev/null +++ b/fun/quinistry/k8s/child.yaml @@ -0,0 +1,27 @@ +# This is a child quinistry, running via an image served off the parent. +--- +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + name: quinistry-gen2 + labels: + k8s-app: quinistry + quinistry/role: child + quinistry/generation: '2' +spec: + template: + metadata: + labels: + k8s-app: quinistry + quinistry/role: child + quinistry/generation: '2' + spec: + containers: + - name: quinistry + # Bootstrap via Docker Hub (or any other registry) + image: localhost:5000/quinistry + ports: + - name: registry + containerPort: 8080 + # Incremented hostPort, + hostPort: 5001 diff --git a/fun/quinistry/k8s/parent.yaml b/fun/quinistry/k8s/parent.yaml new file mode 100644 index 0000000000..0db2fe300e --- /dev/null +++ b/fun/quinistry/k8s/parent.yaml @@ -0,0 +1,27 @@ +# This is a bootstrapped Quinistry DaemonSet. The initial image +# comes from Docker Hub +--- +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + name: quinistry + labels: + k8s-app: quinistry + quinistry/role: parent + quinistry/generation: '1' +spec: + template: + metadata: + labels: + k8s-app: quinistry + quinistry/role: parent + quinistry/generation: '1' + spec: + containers: + - name: quinistry + # Bootstrap via Docker Hub (or any other registry) + image: tazjin/quinistry + ports: + - name: registry + containerPort: 8080 + hostPort: 5000 diff --git a/fun/quinistry/main.go b/fun/quinistry/main.go new file mode 100644 index 0000000000..50b47418d1 --- /dev/null +++ b/fun/quinistry/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "log" + "net/http" +) + +func main() { + log.Println("Starting quinistry") + + image := GetImageOfCurrentExecutable() + + 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 + 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().Set(ContentType, ManifestMediaType) + w.Header().Add(DigestHeader, image.ManifestDigest) + w.Write(image.Manifest) + return + } + + // Serve actual image layer + if r.RequestURI == layerUri { + logRequest("Serving image layer blob", r) + w.Header().Add(DigestHeader, image.LayerDigest) + w.Write(image.Layer) + return + } + + // Serve image config + if r.RequestURI == configUri { + logRequest("Serving config", r) + w.Header().Set("Content-Type", ImageConfigMediaType) + w.Header().Set(DigestHeader, image.ConfigDigest) + w.Write(image.Config) + 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) +} diff --git a/fun/quinistry/types.go b/fun/quinistry/types.go new file mode 100644 index 0000000000..498cbac2f2 --- /dev/null +++ b/fun/quinistry/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 +} diff --git a/image.go b/image.go deleted file mode 100644 index 3daeac34d6..0000000000 --- a/image.go +++ /dev/null @@ -1,150 +0,0 @@ -// The code in this file creates a Docker image layer containing the binary of the -// application itself. - -package main - -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) - - 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() -} - -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() -} - -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 -} - -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/k8s/child.yaml b/k8s/child.yaml deleted file mode 100644 index aa2e318262..0000000000 --- a/k8s/child.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# This is a child quinistry, running via an image served off the parent. ---- -apiVersion: extensions/v1beta1 -kind: DaemonSet -metadata: - name: quinistry-gen2 - labels: - k8s-app: quinistry - quinistry/role: child - quinistry/generation: '2' -spec: - template: - metadata: - labels: - k8s-app: quinistry - quinistry/role: child - quinistry/generation: '2' - spec: - containers: - - name: quinistry - # Bootstrap via Docker Hub (or any other registry) - image: localhost:5000/quinistry - ports: - - name: registry - containerPort: 8080 - # Incremented hostPort, - hostPort: 5001 diff --git a/k8s/parent.yaml b/k8s/parent.yaml deleted file mode 100644 index 0db2fe300e..0000000000 --- a/k8s/parent.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# This is a bootstrapped Quinistry DaemonSet. The initial image -# comes from Docker Hub ---- -apiVersion: extensions/v1beta1 -kind: DaemonSet -metadata: - name: quinistry - labels: - k8s-app: quinistry - quinistry/role: parent - quinistry/generation: '1' -spec: - template: - metadata: - labels: - k8s-app: quinistry - quinistry/role: parent - quinistry/generation: '1' - spec: - containers: - - name: quinistry - # Bootstrap via Docker Hub (or any other registry) - image: tazjin/quinistry - ports: - - name: registry - containerPort: 8080 - hostPort: 5000 diff --git a/main.go b/main.go deleted file mode 100644 index 50b47418d1..0000000000 --- a/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" -) - -func main() { - log.Println("Starting quinistry") - - image := GetImageOfCurrentExecutable() - - 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 - 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().Set(ContentType, ManifestMediaType) - w.Header().Add(DigestHeader, image.ManifestDigest) - w.Write(image.Manifest) - return - } - - // Serve actual image layer - if r.RequestURI == layerUri { - logRequest("Serving image layer blob", r) - w.Header().Add(DigestHeader, image.LayerDigest) - w.Write(image.Layer) - return - } - - // Serve image config - if r.RequestURI == configUri { - logRequest("Serving config", r) - w.Header().Set("Content-Type", ImageConfigMediaType) - w.Header().Set(DigestHeader, image.ConfigDigest) - w.Write(image.Config) - 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) -} diff --git a/types.go b/types.go deleted file mode 100644 index 498cbac2f2..0000000000 --- a/types.go +++ /dev/null @@ -1,79 +0,0 @@ -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 -} -- cgit 1.4.1