Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making the registry backend able to handle V1 manifest responses #289

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"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 @@
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