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

directory: simplify newImageDestination #2520

Open
wants to merge 2 commits into
base: main
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
145 changes: 60 additions & 85 deletions directory/directory_dest.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package directory

import (
"bytes"
"context"
"errors"
"fmt"
Expand All @@ -15,13 +16,10 @@ import (
"github.com/containers/image/v5/internal/putblobdigest"
"github.com/containers/image/v5/internal/signature"
"github.com/containers/image/v5/types"
"github.com/containers/storage/pkg/fileutils"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)

const version = "Directory Transport Version: 1.1\n"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I think about it, this is really a constant of the file format, not a private implementation detail of initDestDir (we can’t just change the contents to make initDestDir individually nicer, for example); so it should be a global constant in the module. Also, eventually, #1876 will need this to live outside of initDestDir.

[Well, we should eventually actually parse the numbers and do comparisons, but that’s not yet necessary.]


// ErrNotContainerImageDir indicates that the directory doesn't match the expected contents of a directory created
// using the 'dir' transport
var ErrNotContainerImageDir = errors.New("not a containers image directory, don't want to overwrite important data")
Expand Down Expand Up @@ -51,52 +49,8 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im
}
}

// If directory exists check if it is empty
// if not empty, check whether the contents match that of a container image directory and overwrite the contents
// if the contents don't match throw an error
dirExists, err := pathExists(ref.resolvedPath)
if err != nil {
return nil, fmt.Errorf("checking for path %q: %w", ref.resolvedPath, err)
}
if dirExists {
isEmpty, err := isDirEmpty(ref.resolvedPath)
if err != nil {
return nil, err
}

if !isEmpty {
versionExists, err := pathExists(ref.versionPath())
if err != nil {
return nil, fmt.Errorf("checking if path exists %q: %w", ref.versionPath(), err)
}
if versionExists {
contents, err := os.ReadFile(ref.versionPath())
if err != nil {
return nil, err
}
// check if contents of version file is what we expect it to be
if string(contents) != version {
return nil, ErrNotContainerImageDir
}
} else {
return nil, ErrNotContainerImageDir
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
}
// delete directory contents so that only one image is in the directory at a time
if err = removeDirContents(ref.resolvedPath); err != nil {
return nil, fmt.Errorf("erasing contents in %q: %w", ref.resolvedPath, err)
}
logrus.Debugf("overwriting existing container image directory %q", ref.resolvedPath)
}
} else {
// create directory if it doesn't exist
if err := os.MkdirAll(ref.resolvedPath, 0755); err != nil {
return nil, fmt.Errorf("unable to create directory %q: %w", ref.resolvedPath, err)
}
}
// create version file
err = os.WriteFile(ref.versionPath(), []byte(version), 0644)
if err != nil {
return nil, fmt.Errorf("creating version file %q: %w", ref.versionPath(), err)
if err := initDestDir(ref.resolvedPath); err != nil {
return nil, err
}

d := &dirImageDestination{
Expand All @@ -116,6 +70,63 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im
return d, nil
}

func initDestDir(dir string) error {
const versionContents = "Directory Transport Version: 1.1\n"
versionFile := filepath.Join(dir, "version")

// If dir exists, checks if it is empty.
// If not empty, check whether the contents match that of a container
// image directory and overwrite the contents.
// If the contents don't match, throw ErrNotContainerImageDir.
fd, err := os.Open(dir)
switch {
case err == nil: // Directory exists.
contents, err := fd.Readdirnames(-1)
_ = fd.Close()
if err != nil { // Unexpected error.
return fmt.Errorf("%q: %w", dir, err)
}
if len(contents) == 0 {
break
}
// Check if contents of version file is what we expect it to be.
ver, err := os.ReadFile(versionFile)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return ErrNotContainerImageDir
}
return fmt.Errorf("%q: %w", versionFile, err)
}
if !bytes.Equal(ver, []byte(versionContents)) {
return ErrNotContainerImageDir
}
// Indeed an image directory. Reuse by removing all the old contents
// (including the versionFile, which will be recreated below).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems worth it to preserve the rationale “so that only one image is in the directory at a time”.

(OTOH containers/skopeo#1237 exists, but that’s clearly not this PR.)

logrus.Debugf("overwriting existing container image directory %q", dir)
for _, name := range contents {
rm := filepath.Join(dir, name)
err := os.RemoveAll(rm)
if err != nil {
return fmt.Errorf("%q: %w", rm, err)
}
}
case errors.Is(err, os.ErrNotExist): // Directory does not exist; create it.
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("%q: %w", dir, err)
}
default:
// Unexpected error.
return fmt.Errorf("%q: %w", dir, err)
}

err = os.WriteFile(versionFile, []byte(versionContents), 0o644)
if err != nil {
return fmt.Errorf("%q: %w", versionFile, err)
}

return nil
}

// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
func (d *dirImageDestination) Reference() types.ImageReference {
Expand Down Expand Up @@ -261,39 +272,3 @@ func (d *dirImageDestination) PutSignaturesWithFormat(ctx context.Context, signa
func (d *dirImageDestination) Commit(context.Context, types.UnparsedImage) error {
return nil
}

// returns true if path exists
func pathExists(path string) (bool, error) {
err := fileutils.Exists(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

// returns true if directory is empty
func isDirEmpty(path string) (bool, error) {
files, err := os.ReadDir(path)
if err != nil {
return false, err
}
return len(files) == 0, nil
}

// deletes the contents of a directory
func removeDirContents(path string) error {
files, err := os.ReadDir(path)
if err != nil {
return err
}

for _, file := range files {
if err := os.RemoveAll(filepath.Join(path, file.Name())); err != nil {
return err
}
}
return nil
}
5 changes: 0 additions & 5 deletions directory/directory_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,3 @@ func (ref dirReference) signaturePath(index int, instanceDigest *digest.Digest)
}
return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1)), nil
}

// versionPath returns a path for the version file within a directory using our conventions.
func (ref dirReference) versionPath() string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the "version" path logic in this place, beside the other …Path locations, might be a good way to sort-of document the file format? Also, eventually, #1876 will need this to live outside of initDestDir.

return filepath.Join(ref.path, "version")
}
9 changes: 1 addition & 8 deletions directory/directory_transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func TestReferenceNewImageSource(t *testing.T) {
func TestReferenceNewImageDestination(t *testing.T) {
ref, _ := refToTempDir(t)
dest, err := ref.NewImageDestination(context.Background(), nil)
assert.NoError(t, err)
require.NoError(t, err)
defer dest.Close()
}

Expand Down Expand Up @@ -243,10 +243,3 @@ func TestReferenceSignaturePath(t *testing.T) {
_, err = dirRef.signaturePath(0, &invalidDigest)
assert.Error(t, err)
}

func TestReferenceVersionPath(t *testing.T) {
ref, tmpDir := refToTempDir(t)
dirRef, ok := ref.(dirReference)
require.True(t, ok)
assert.Equal(t, tmpDir+"/version", dirRef.versionPath())
}