Skip to content

Commit

Permalink
Issue containers#155 - Making sure existing OCI index gets not overwr…
Browse files Browse the repository at this point in the history
…itten
  • Loading branch information
hferentschik committed Aug 12, 2017
1 parent 74e3593 commit 3e7e1f4
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 41 deletions.
59 changes: 46 additions & 13 deletions oci/layout/oci_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@ type ociImageDestination struct {
}

// newImageDestination returns an ImageDestination for writing to an existing directory.
func newImageDestination(ref ociReference) types.ImageDestination {
func newImageDestination(ref ociReference) (types.ImageDestination, error) {
index := imgspecv1.Index{
Versioned: imgspec.Versioned{
SchemaVersion: 2,
},
}
return &ociImageDestination{ref: ref, index: index}

if exists(ref.dir) {
var err error
index, err = ref.getIndex()
if err != nil {
return nil, err
}
}

return &ociImageDestination{ref: ref, index: index}, nil
}

// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
Expand Down Expand Up @@ -184,23 +193,20 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
Architecture: runtime.GOARCH,
OS: runtime.GOOS,
}
d.index.Manifests = append(d.index.Manifests, desc)
d.addManifest(&desc)

return nil
}

func ensureDirectoryExists(path string) error {
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(path, 0755); err != nil {
return err
func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor){
for i, manifest := range d.index.Manifests {
if manifest.Digest.String() == desc.Digest.String() {
// TODO Should there first be a cleanup based on the descriptor we are going to replace?
d.index.Manifests[i] = *desc
return
}
}
return nil
}

// ensureParentDirectoryExists ensures the parent of the supplied path exists.
func ensureParentDirectoryExists(path string) error {
return ensureDirectoryExists(filepath.Dir(path))
d.index.Manifests = append(d.index.Manifests, *desc)
}

func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
Expand All @@ -224,3 +230,30 @@ func (d *ociImageDestination) Commit() error {
}
return ioutil.WriteFile(d.ref.indexPath(), indexJSON, 0644)
}

func ensureDirectoryExists(path string) error {
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
}
return nil
}

// ensureParentDirectoryExists ensures the parent of the supplied path exists.
func ensureParentDirectoryExists(path string) error {
return ensureDirectoryExists(filepath.Dir(path))
}

// Exists checks whether the specified path exists.
// The implementation is opinionated, since in case of unexpected errors false is returned
func exists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return true
}
59 changes: 58 additions & 1 deletion oci/layout/oci_dest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package layout
import (
"os"
"testing"

"github.com/containers/image/types"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"path/filepath"
)

// readerFromFunc allows implementing Reader by any function, e.g. a closure.
Expand Down Expand Up @@ -59,3 +60,59 @@ func TestPutBlobDigestFailure(t *testing.T) {
require.Error(t, err)
require.True(t, os.IsNotExist(err))
}

// TestPutManifestAppendsToExistingManifest tests that new manifests are getting added to existing index.
func TestPutManifestAppendsToExistingManifest(t *testing.T) {
ref, tmpDir := refToTempOCI(t)
defer os.RemoveAll(tmpDir)

ociRef, ok := ref.(ociReference)
require.True(t, ok)

index, err := ociRef.getIndex()
assert.NoError(t, err)
assert.Equal(t, 1, len(index.Manifests), "Unexpected number of manifests")

putTestManifest(t, ociRef, tmpDir)

index, err = ociRef.getIndex()
assert.NoError(t, err)
assert.Equal(t, 2, len(index.Manifests), "Unexpected number of manifests")
}

// TestPutManifestTwice tests that existing manifest gets updated and not appended.
func TestPutManifestTwice(t *testing.T) {
ref, tmpDir := refToTempOCI(t)
defer os.RemoveAll(tmpDir)

ociRef, ok := ref.(ociReference)
require.True(t, ok)

putTestManifest(t, ociRef, tmpDir)
putTestManifest(t, ociRef, tmpDir)

index, err := ociRef.getIndex()
assert.NoError(t, err)
assert.Equal(t, 2, len(index.Manifests), "Unexpected number of manifests")
}

func putTestManifest(t *testing.T, ociRef ociReference, tmpDir string) {
imageDest, err := newImageDestination(ociRef)
assert.NoError(t, err)

data := []byte("abc")
err = imageDest.PutManifest(data)
assert.NoError(t, err)

err = imageDest.Commit()
assert.NoError(t, err)

paths := []string{}
filepath.Walk(tmpDir, func(path string, info os.FileInfo, err error) error {
paths = append(paths, path)
return nil
})

digest := "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
assert.Contains(t, paths, filepath.Join(tmpDir, "blobs", "sha256", digest), "The OCI directory does not contain the new manifest data")
}
26 changes: 21 additions & 5 deletions oci/layout/oci_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/opencontainers/go-digest"
imgspec "github.com/opencontainers/image-spec/specs-go"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/pkg/errors"
)

Expand Down Expand Up @@ -186,14 +188,28 @@ func (ref ociReference) NewImage(ctx *types.SystemContext) (types.Image, error)
return image.FromSource(src)
}

func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
func (ref ociReference) getIndex() (imgspecv1.Index, error) {
index := imgspecv1.Index{
Versioned: imgspec.Versioned{
SchemaVersion: 2,
},
}

indexJSON, err := os.Open(ref.indexPath())
defer indexJSON.Close()
if err != nil {
return imgspecv1.Descriptor{}, err
return index, err
}
defer indexJSON.Close()
index := imgspecv1.Index{}

if err := json.NewDecoder(indexJSON).Decode(&index); err != nil {
return index, err
}
return index, nil
}

func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
index, err := ref.getIndex()
if err != nil {
return imgspecv1.Descriptor{}, err
}
var d *imgspecv1.Descriptor
Expand Down Expand Up @@ -227,7 +243,7 @@ func (ref ociReference) NewImageSource(ctx *types.SystemContext, requestedManife
// NewImageDestination returns a types.ImageDestination for this reference.
// The caller must call .Close() on the returned ImageDestination.
func (ref ociReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
return newImageDestination(ref), nil
return newImageDestination(ref)
}

// DeleteImage deletes the named image from the registry, if supported.
Expand Down
70 changes: 48 additions & 22 deletions oci/layout/oci_transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"path/filepath"
"testing"

"fmt"
"github.com/containers/image/directory/explicitfilepath"
"github.com/containers/image/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -176,12 +178,17 @@ func TestReferencePolicyConfigurationIdentity(t *testing.T) {
ref, tmpDir := refToTempOCI(t)
defer os.RemoveAll(tmpDir)

assert.Equal(t, tmpDir+":tagValue", ref.PolicyConfigurationIdentity())
fullyExplicitPath, err := explicitfilepath.ResolvePathToFullyExplicit(tmpDir)
if err != nil {
assert.Fail(t, fmt.Sprintf("Unexected error: %v", err))
}

assert.Equal(t, fullyExplicitPath+":tagValue", ref.PolicyConfigurationIdentity())
// A non-canonical path. Test just one, the various other cases are
// tested in explicitfilepath.ResolvePathToFullyExplicit.
ref, err := NewReference(tmpDir+"/.", "tag2")
ref, err = NewReference(tmpDir+"/.", "tag2")
require.NoError(t, err)
assert.Equal(t, tmpDir+":tag2", ref.PolicyConfigurationIdentity())
assert.Equal(t, fullyExplicitPath+":tag2", ref.PolicyConfigurationIdentity())

// "/" as a corner case.
ref, err = NewReference("/", "tag3")
Expand All @@ -192,31 +199,37 @@ func TestReferencePolicyConfigurationIdentity(t *testing.T) {
func TestReferencePolicyConfigurationNamespaces(t *testing.T) {
ref, tmpDir := refToTempOCI(t)
defer os.RemoveAll(tmpDir)

fullyExplicitPath, err := explicitfilepath.ResolvePathToFullyExplicit(tmpDir)
if err != nil {
assert.Fail(t, fmt.Sprintf("Unexected error: %v", err))
}

// We don't really know enough to make a full equality test here.
ns := ref.PolicyConfigurationNamespaces()
require.NotNil(t, ns)
assert.True(t, len(ns) >= 2)
assert.Equal(t, tmpDir, ns[0])
assert.Equal(t, filepath.Dir(tmpDir), ns[1])

// Test with a known path which should exist. Test just one non-canonical
// path, the various other cases are tested in explicitfilepath.ResolvePathToFullyExplicit.
//
// It would be nice to test a deeper hierarchy, but it is not obvious what
// deeper path is always available in the various distros, AND is not likely
// to contains a symbolic link.
for _, path := range []string{"/etc/skel", "/etc/skel/./."} {
_, err := os.Lstat(path)
require.NoError(t, err)
ref, err := NewReference(path, "sometag")
require.NoError(t, err)
ns := ref.PolicyConfigurationNamespaces()
require.NotNil(t, ns)
assert.Equal(t, []string{"/etc/skel", "/etc"}, ns)
}
assert.Equal(t, fullyExplicitPath, ns[0])
assert.Equal(t, filepath.Dir(fullyExplicitPath), ns[1])

//// Test with a known path which should exist. Test just one non-canonical
//// path, the various other cases are tested in explicitfilepath.ResolvePathToFullyExplicit.
////
//// It would be nice to test a deeper hierarchy, but it is not obvious what
//// deeper path is always available in the various distros, AND is not likely
//// to contains a symbolic link.
//for _, path := range []string{"/etc/ssh", "/etc/ssh/./."} {
// _, err := os.Lstat(path)
// require.NoError(t, err)
// ref, err := NewReference(path, "sometag")
// require.NoError(t, err)
// ns := ref.PolicyConfigurationNamespaces()
// require.NotNil(t, ns)
// assert.Equal(t, []string{"/etc/ssh", "/etc"}, ns)
//}

// "/" as a corner case.
ref, err := NewReference("/", "tag3")
ref, err = NewReference("/", "tag3")
require.NoError(t, err)
assert.Equal(t, []string{}, ref.PolicyConfigurationNamespaces())
}
Expand Down Expand Up @@ -289,3 +302,16 @@ func TestReferenceBlobPathInvalid(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), "unexpected digest reference "+hex)
}

func TestGetIndex(t *testing.T) {
ref, tmpDir := refToTempOCI(t)
defer os.RemoveAll(tmpDir)

ociRef, ok := ref.(ociReference)
require.True(t, ok)

index, err := ociRef.getIndex()
assert.NoError(t, err)

assert.Equal(t, len(index.Manifests), 1)
}

0 comments on commit 3e7e1f4

Please sign in to comment.