Skip to content

Commit

Permalink
Access v1.Layer via our index impls (#935)
Browse files Browse the repository at this point in the history
* Access v1.Layer via our index impls

* Add test coverage

* More registry coverage
  • Loading branch information
jonjohnsonjr committed Feb 6, 2021
1 parent 9c81ed0 commit 4d068fb
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 65 deletions.
25 changes: 15 additions & 10 deletions pkg/registry/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,29 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
// list's constituent manifests are already uploaded.
// This isn't strictly required by the registry API, but some
// registries require this.
if mf.contentType == string(types.OCIImageIndex) ||
mf.contentType == string(types.DockerManifestList) {

if types.MediaType(mf.contentType).IsIndex() {
im, err := v1.ParseIndexManifest(b)
if err != nil {
return &regError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Status: http.StatusBadRequest,
Code: "MANIFEST_INVALID",
Message: err.Error(),
}
}
for _, desc := range im.Manifests {
if _, found := m.manifests[repo][desc.Digest.String()]; !found {
return &regError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Message: fmt.Sprintf("Sub-manifest %q not found", desc.Digest),
if !desc.MediaType.IsDistributable() {
continue
}
if desc.MediaType.IsIndex() || desc.MediaType.IsImage() {
if _, found := m.manifests[repo][desc.Digest.String()]; !found {
return &regError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Message: fmt.Sprintf("Sub-manifest %q not found", desc.Digest),
}
}
} else {
// TODO: Probably want to do an existence check for blobs.
}
}
}
Expand Down
48 changes: 48 additions & 0 deletions pkg/registry/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ import (
"github.com/google/go-containerregistry/pkg/registry"
)

const (
weirdIndex = `{
"manifests": [
{
"digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"mediaType":"application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
},{
"digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"mediaType":"application/xml"
},{
"digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"mediaType":"application/vnd.oci.image.manifest.v1+json"
}
]
}`
)

func sha256String(s string) string {
h := sha256.Sum256([]byte(s))
return hex.EncodeToString(h[:])
Expand Down Expand Up @@ -254,6 +271,37 @@ func TestCalls(t *testing.T) {
Code: http.StatusCreated,
Body: "foo",
},
{
Description: "create index",
Method: "PUT",
URL: "/v2/foo/manifests/latest",
Code: http.StatusCreated,
Body: weirdIndex,
RequestHeader: map[string]string{
"Content-Type": "application/vnd.oci.image.index.v1+json",
},
Manifests: map[string]string{"foo/manifests/image": "foo"},
},
{
Description: "create index missing child",
Method: "PUT",
URL: "/v2/foo/manifests/latest",
Code: http.StatusNotFound,
Body: weirdIndex,
RequestHeader: map[string]string{
"Content-Type": "application/vnd.oci.image.index.v1+json",
},
},
{
Description: "bad index body",
Method: "PUT",
URL: "/v2/foo/manifests/latest",
Code: http.StatusBadRequest,
Body: "foo",
RequestHeader: map[string]string{
"Content-Type": "application/vnd.oci.image.index.v1+json",
},
},
{
Description: "bad manifest method",
Method: "BAR",
Expand Down
2 changes: 1 addition & 1 deletion pkg/v1/layout/testdata/test_index/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"mediaType": "application/vnd.oci.descriptor.v1+json",
"size": 423,
"digest": "sha256:32589985702551b6c56033bb3334432a0a513bf9d6aceda0f67c42b003850720",
"annotations": {
Expand Down
26 changes: 26 additions & 0 deletions pkg/v1/layout/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ func (l Path) WriteImage(img v1.Image) error {
return l.WriteBlob(d, ioutil.NopCloser(bytes.NewReader(manifest)))
}

type withLayer interface {
Layer(v1.Hash) (v1.Layer, error)
}

type withBlob interface {
Blob(v1.Hash) (io.ReadCloser, error)
}

func (l Path) writeIndexToFile(indexFile string, ii v1.ImageIndex) error {
index, err := ii.IndexManifest()
if err != nil {
Expand Down Expand Up @@ -343,6 +351,24 @@ func (l Path) writeIndexToFile(indexFile string, ii v1.ImageIndex) error {
default:
// TODO: The layout could reference arbitrary things, which we should
// probably just pass through.

var blob io.ReadCloser
// Workaround for #819.
if wl, ok := ii.(withLayer); ok {
layer, err := wl.Layer(desc.Digest)
if err != nil {
return err
}
blob, err = layer.Compressed()
} else if wb, ok := ii.(withBlob); ok {
blob, err = wb.Blob(desc.Digest)
}
if err != nil {
return err
}
if err := l.WriteBlob(desc.Digest, blob); err != nil {
return err
}
}
}

Expand Down
33 changes: 3 additions & 30 deletions pkg/v1/layout/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"io/ioutil"
"log"
"os"
"strconv"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -98,7 +97,7 @@ func TestAppendDescriptorInitializesIndex(t *testing.T) {
}
}

func TestAppendArtifacts(t *testing.T) {
func TestRoundtrip(t *testing.T) {
tmp, err := ioutil.TempDir("", "write-index-test")
if err != nil {
t.Fatal(err)
Expand All @@ -116,36 +115,10 @@ func TestAppendArtifacts(t *testing.T) {
t.Fatal(err)
}

// Let's reconstruct the original.
temp, err := Write(tmp, empty.Index)
if err != nil {
// Write it back.
if _, err := Write(tmp, original); err != nil {
t.Fatal(err)
}
for i, desc := range originalManifest.Manifests {
// Each descriptor is annotated with its position.
annotations := map[string]string{
"org.opencontainers.image.ref.name": strconv.Itoa(i + 1),
}
switch desc.MediaType {
case types.OCIImageIndex, types.DockerManifestList:
ii, err := original.ImageIndex(desc.Digest)
if err != nil {
t.Fatal(err)
}
if err := temp.AppendIndex(ii, WithAnnotations(annotations)); err != nil {
t.Fatal(err)
}
case types.OCIManifestSchema1, types.DockerManifestSchema2:
img, err := original.Image(desc.Digest)
if err != nil {
t.Fatal(err)
}
if err := temp.AppendImage(img, WithAnnotations(annotations)); err != nil {
t.Fatal(err)
}
}
}

reconstructed, err := ImageIndexFromPath(tmp)
if err != nil {
t.Fatalf("ImageIndexFromPath() = %v", err)
Expand Down
20 changes: 20 additions & 0 deletions pkg/v1/mutate/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package mutate

import (
"encoding/json"
"fmt"
"strings"

"github.com/google/go-containerregistry/pkg/logs"
Expand Down Expand Up @@ -65,6 +66,7 @@ type index struct {
mediaType *types.MediaType
imageMap map[v1.Hash]v1.Image
indexMap map[v1.Hash]v1.ImageIndex
layerMap map[v1.Hash]v1.Layer
}

var _ v1.ImageIndex = (*index)(nil)
Expand All @@ -86,6 +88,7 @@ func (i *index) compute() error {

i.imageMap = make(map[v1.Hash]v1.Image)
i.indexMap = make(map[v1.Hash]v1.ImageIndex)
i.layerMap = make(map[v1.Hash]v1.Layer)

m, err := i.base.IndexManifest()
if err != nil {
Expand Down Expand Up @@ -115,6 +118,8 @@ func (i *index) compute() error {
i.indexMap[desc.Digest] = idx
} else if img, ok := add.Add.(v1.Image); ok {
i.imageMap[desc.Digest] = img
} else if l, ok := add.Add.(v1.Layer); ok {
i.layerMap[desc.Digest] = l
} else {
logs.Warn.Printf("Unexpected index addendum: %T", add.Add)
}
Expand Down Expand Up @@ -151,6 +156,21 @@ func (i *index) ImageIndex(h v1.Hash) (v1.ImageIndex, error) {
return i.base.ImageIndex(h)
}

type withLayer interface {
Layer(v1.Hash) (v1.Layer, error)
}

// Workaround for #819.
func (i *index) Layer(h v1.Hash) (v1.Layer, error) {
if layer, ok := i.layerMap[h]; ok {
return layer, nil
}
if wl, ok := i.base.(withLayer); ok {
return wl.Layer(h)
}
return nil, fmt.Errorf("layer not found: %s", h)
}

// Digest returns the sha256 of this image's manifest.
func (i *index) Digest() (v1.Hash, error) {
if err := i.compute(); err != nil {
Expand Down
14 changes: 10 additions & 4 deletions pkg/v1/mutate/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@ func TestAppendIndex(t *testing.T) {
},
}, mutate.IndexAddendum{
Add: img,
Descriptor: v1.Descriptor{
URLs: []string{"image.example.com"},
},
}, mutate.IndexAddendum{
Add: l,
Descriptor: v1.Descriptor{
MediaType: types.MediaType("application/xml"),
URLs: []string{"image.example.com"},
URLs: []string{"blob.example.com"},
},
}, mutate.IndexAddendum{
Add: l,
Expand Down Expand Up @@ -100,18 +105,19 @@ func TestAppendIndex(t *testing.T) {
for i, want := range map[int]string{
3: "index.example.com",
4: "image.example.com",
5: "layer.example.com",
5: "blob.example.com",
6: "layer.example.com",
} {
if got := m.Manifests[i].URLs[0]; got != want {
t.Errorf("wrong URLs[0] for Manifests[%d]: %s != %s", i, got, want)
}
}

if got, want := m.Manifests[4].MediaType, types.MediaType("application/xml"); got != want {
if got, want := m.Manifests[5].MediaType, types.MediaType("application/xml"); got != want {
t.Errorf("wrong MediaType for layer: %s != %s", got, want)
}

if got, want := m.Manifests[5].MediaType, types.OCIUncompressedRestrictedLayer; got != want {
if got, want := m.Manifests[6].MediaType, types.OCIUncompressedRestrictedLayer; got != want {
t.Errorf("wrong MediaType for layer: %s != %s", got, want)
}

Expand Down
24 changes: 24 additions & 0 deletions pkg/v1/remote/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,30 @@ func (r *remoteIndex) ImageIndex(h v1.Hash) (v1.ImageIndex, error) {
return desc.ImageIndex()
}

// Workaround for #819.
func (r *remoteIndex) Layer(h v1.Hash) (v1.Layer, error) {
index, err := r.IndexManifest()
if err != nil {
return nil, err
}
for _, childDesc := range index.Manifests {
if h == childDesc.Digest {
l, err := partial.CompressedToLayer(&remoteLayer{
fetcher: r.fetcher,
digest: h,
})
if err != nil {
return nil, err
}
return &MountableLayer{
Layer: l,
Reference: r.Ref.Context().Digest(h.String()),
}, nil
}
}
return nil, fmt.Errorf("layer not found: %s", h)
}

func (r *remoteIndex) imageByPlatform(platform v1.Platform) (v1.Image, error) {
desc, err := r.childByPlatform(platform)
if err != nil {
Expand Down
Loading

0 comments on commit 4d068fb

Please sign in to comment.