-
Notifications
You must be signed in to change notification settings - Fork 378
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package directory | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
@@ -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" | ||
|
||
// 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") | ||
|
@@ -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{ | ||
|
@@ -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). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
@@ -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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keeping the |
||
return filepath.Join(ref.path, "version") | ||
} |
There was a problem hiding this comment.
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 makeinitDestDir
individually nicer, for example); so it should be a global constant in the module. Also, eventually, #1876 will need this to live outside ofinitDestDir
.[Well, we should eventually actually parse the numbers and do comparisons, but that’s not yet necessary.]