Skip to content

Commit

Permalink
Making the registry backend able to handle V1 manifest responses
Browse files Browse the repository at this point in the history
With a unit test.

Signed-off-by: Jean Rouge <[email protected]>
  • Loading branch information
wk8 committed Oct 28, 2020
1 parent e435b83 commit 90f69bd
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 10 deletions.
12 changes: 10 additions & 2 deletions lib/backend/registrybackend/tagclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
"strconv"
"strings"

// import schema1 to have it register its unmarshalling functions with github.com/docker/distribution.RegisterManifestSchema()
_ "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/uber/kraken/core"
"github.com/uber/kraken/lib/backend"
"github.com/uber/kraken/lib/backend/backenderrors"
Expand Down Expand Up @@ -141,9 +144,14 @@ func (c *TagClient) Download(namespace, name string, dst io.Writer) error {
return backenderrors.ErrBlobNotFound
}

_, digest, err := dockerutil.ParseManifestV2(resp.Body)
contentType := resp.Header.Get("Content-Type")
if !strings.Contains(contentType, "application/vnd.docker.distribution.manifest") {
contentType = schema2.MediaTypeManifest
}

_, digest, err := dockerutil.ParseManifest(contentType, resp.Body)
if err != nil {
return fmt.Errorf("parse manifest v2: %s", err)
return fmt.Errorf("parse manifest: %s", err)

Check warning on line 154 in lib/backend/registrybackend/tagclient.go

View check run for this annotation

Codecov / codecov/patch

lib/backend/registrybackend/tagclient.go#L154

Added line #L154 was not covered by tests
}
if _, err := io.Copy(dst, strings.NewReader(digest.String())); err != nil {
return fmt.Errorf("copy: %s", err)
Expand Down
69 changes: 69 additions & 0 deletions lib/backend/registrybackend/tagclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package registrybackend

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -70,6 +71,74 @@ func TestTagDownloadSuccess(t *testing.T) {
require.Equal(digest.String(), string(b.Bytes()))
}

func TestTagDownloadV1Manifest(t *testing.T) {
require := require.New(t)

// signed V1 manifest obtained from Docker hub for busybox:1.32.0
manifest := `{
"schemaVersion": 1,
"name": "library/busybox",
"tag": "1.32.0",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:9758c28807f21c13d05c704821fdd56c0b9574912f9b916c65e1df3e6b8bc572"
}
],
"history": [
{
"v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"sh\"],\"ArgsEscaped\":true,\"Image\":\"sha256:11565868e68267a053372359046e1e70ce095538e95ff8398defd49bb66ddfce\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container\":\"6f1f5d35fed541933daae185eac73e333818ccec0b0760eb4cc8e30ce8d69de6\",\"container_config\":{\"Hostname\":\"6f1f5d35fed5\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"sh\\\"]\"],\"ArgsEscaped\":true,\"Image\":\"sha256:11565868e68267a053372359046e1e70ce095538e95ff8398defd49bb66ddfce\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2020-10-14T10:07:34.124876277Z\",\"docker_version\":\"18.09.7\",\"id\":\"6ed978c75173f577f023843ea61461568332f466c963e1b088d81fe676e8816c\",\"os\":\"linux\",\"parent\":\"bf938fec00b8d83c6d28a66dd6aa1cf76384aec8e63c7771648007b0dfce6fd8\",\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"bf938fec00b8d83c6d28a66dd6aa1cf76384aec8e63c7771648007b0dfce6fd8\",\"created\":\"2020-10-14T10:07:33.97009658Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:6098f054f12a3651c41038294c56d4a8c5c5d477259386e75ae2af763e84e683 in / \"]}}"
}
],
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "JOJV:JUGG:HTN3:ABNG:FF4D:KCTY:FESP:BHX7:45NZ:YTDE:NWEF:CK7F",
"kty": "EC",
"x": "xPD2kMN77NdX9MlaZcaIVv1MX-89ChYWgVJ_3MFAmVM",
"y": "I5Qpk8KPtmGJLd77qlpvSEtJ4cKb9MjMqbD2Dp-FR7c"
},
"alg": "ES256"
},
"signature": "dLme4aiKt0EMdtRcCDox4Q5ntnBL5310_CyROeznc16cygwj6hWWXoUREo25423mWv19d8LtxEubXZcKIQBQRQ",
"protected": "eyJmb3JtYXRMZW5ndGgiOjIxMjgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMC0xMC0yOFQxNToxMDo0OFoifQ"
}
]
}`

var deserializedManifest map[string]interface{}
require.NoError(json.Unmarshal([]byte(manifest), &deserializedManifest))
name := deserializedManifest["name"]
tag := deserializedManifest["tag"]

r := chi.NewRouter()
r.Get(fmt.Sprintf("/v2/%s/manifests/{tag}", name), func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(manifest)))
w.Header().Set("Content-Type", "application/vnd.docker.distribution.manifest.v1+prettyjws")
_, err := io.WriteString(w, manifest)
require.NoError(err)
})
addr, stop := testutil.StartServer(r)
defer stop()

config := newTestConfig(addr)
client, err := NewTagClient(config)
require.NoError(err)

var b bytes.Buffer
imageName := fmt.Sprintf("%s:%s", name, tag)
require.NoError(client.Download(imageName, imageName, &b))
require.Equal("sha256:ab2a79aa5da153ea944a10532c529579449afa693c668adcf034602b12eeb675", string(b.Bytes()))
}

func TestTagDownloadFileNotFound(t *testing.T) {
require := require.New(t)

Expand Down
27 changes: 19 additions & 8 deletions utils/dockerutil/dockerutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,18 @@ import (
"io"
"io/ioutil"

"github.com/uber/kraken/core"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema2"
"github.com/uber/kraken/core"
)

// ParseManifestV2 returns a parsed v2 manifest and its digest
func ParseManifestV2(r io.Reader) (distribution.Manifest, core.Digest, error) {
b, err := ioutil.ReadAll(r)
func ParseManifestV2(r io.Reader) (manifest distribution.Manifest, digest core.Digest, err error) {
manifest, digest, err = ParseManifest(schema2.MediaTypeManifest, r)
if err != nil {
return nil, core.Digest{}, fmt.Errorf("read: %s", err)
}
manifest, desc, err := distribution.UnmarshalManifest(schema2.MediaTypeManifest, b)
if err != nil {
return nil, core.Digest{}, fmt.Errorf("unmarshal manifest: %s", err)
return
}

deserializedManifest, ok := manifest.(*schema2.DeserializedManifest)
if !ok {
return nil, core.Digest{}, errors.New("expected schema2.DeserializedManifest")
Expand All @@ -42,6 +39,20 @@ func ParseManifestV2(r io.Reader) (distribution.Manifest, core.Digest, error) {
if version != 2 {
return nil, core.Digest{}, fmt.Errorf("unsupported manifest version: %d", version)
}

return
}

// ParseManifest returns a parsed manifest and its digest
func ParseManifest(contentType string, r io.Reader) (distribution.Manifest, core.Digest, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, core.Digest{}, fmt.Errorf("read: %s", err)
}
manifest, desc, err := distribution.UnmarshalManifest(contentType, b)
if err != nil {
return nil, core.Digest{}, fmt.Errorf("unmarshal manifest: %s", err)
}
d, err := core.ParseSHA256Digest(string(desc.Digest))
if err != nil {
return nil, core.Digest{}, fmt.Errorf("parse digest: %s", err)
Expand Down

0 comments on commit 90f69bd

Please sign in to comment.