migrate Podman to containers/common/libimage

Migrate the Podman code base over to `common/libimage` which replaces
`libpod/image` and a lot of glue code entirely.

Note that I tried to leave bread crumbs for changed tests.

Miscellaneous changes:

 * Some errors yield different messages which required to alter some
   tests.

 * I fixed some pre-existing issues in the code.  Others were marked as
   `//TODO`s to prevent the PR from exploding.

 * The `NamesHistory` of an image is returned as is from the storage.
   Previously, we did some filtering which I think is undesirable.
   Instead we should return the data as stored in the storage.

 * Touched handlers use the ABI interfaces where possible.

 * Local image resolution: previously Podman would match "foo" on
   "myfoo".  This behaviour has been changed and Podman will now
   only match on repository boundaries such that "foo" would match
   "my/foo" but not "myfoo".  I consider the old behaviour to be a
   bug, at the very least an exotic corner case.

 * Futhermore, "foo:none" does *not* resolve to a local image "foo"
   without tag anymore.  It's a hill I am (almost) willing to die on.

 * `image prune` prints the IDs of pruned images.  Previously, in some
   cases, the names were printed instead.  The API clearly states ID,
   so we should stick to it.

 * Compat endpoint image removal with _force_ deletes the entire not
   only the specified tag.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2021-04-22 08:01:12 +02:00
parent 8eefca5a25
commit 0f7d54b026
190 changed files with 8669 additions and 7743 deletions

View File

@@ -70,12 +70,13 @@ func isArchivePath(path string) bool {
type requestType string
const (
requestEval requestType = "EVAL"
requestStat requestType = "STAT"
requestGet requestType = "GET"
requestPut requestType = "PUT"
requestMkdir requestType = "MKDIR"
requestQuit requestType = "QUIT"
requestEval requestType = "EVAL"
requestStat requestType = "STAT"
requestGet requestType = "GET"
requestPut requestType = "PUT"
requestMkdir requestType = "MKDIR"
requestRemove requestType = "REMOVE"
requestQuit requestType = "QUIT"
)
// Request encodes a single request.
@@ -88,10 +89,11 @@ type request struct {
preservedDirectory string
Globs []string `json:",omitempty"` // used by stat, get
preservedGlobs []string
StatOptions StatOptions `json:",omitempty"`
GetOptions GetOptions `json:",omitempty"`
PutOptions PutOptions `json:",omitempty"`
MkdirOptions MkdirOptions `json:",omitempty"`
StatOptions StatOptions `json:",omitempty"`
GetOptions GetOptions `json:",omitempty"`
PutOptions PutOptions `json:",omitempty"`
MkdirOptions MkdirOptions `json:",omitempty"`
RemoveOptions RemoveOptions `json:",omitempty"`
}
func (req *request) Excludes() []string {
@@ -106,6 +108,8 @@ func (req *request) Excludes() []string {
return nil
case requestMkdir:
return nil
case requestRemove:
return nil
case requestQuit:
return nil
default:
@@ -125,6 +129,8 @@ func (req *request) UIDMap() []idtools.IDMap {
return req.PutOptions.UIDMap
case requestMkdir:
return req.MkdirOptions.UIDMap
case requestRemove:
return nil
case requestQuit:
return nil
default:
@@ -144,6 +150,8 @@ func (req *request) GIDMap() []idtools.IDMap {
return req.PutOptions.GIDMap
case requestMkdir:
return req.MkdirOptions.GIDMap
case requestRemove:
return nil
case requestQuit:
return nil
default:
@@ -153,12 +161,13 @@ func (req *request) GIDMap() []idtools.IDMap {
// Response encodes a single response.
type response struct {
Error string `json:",omitempty"`
Stat statResponse
Eval evalResponse
Get getResponse
Put putResponse
Mkdir mkdirResponse
Error string `json:",omitempty"`
Stat statResponse `json:",omitempty"`
Eval evalResponse `json:",omitempty"`
Get getResponse `json:",omitempty"`
Put putResponse `json:",omitempty"`
Mkdir mkdirResponse `json:",omitempty"`
Remove removeResponse `json:",omitempty"`
}
// statResponse encodes a response for a single Stat request.
@@ -205,6 +214,10 @@ type putResponse struct {
type mkdirResponse struct {
}
// removeResponse encodes a response for a single Remove request.
type removeResponse struct {
}
// EvalOptions controls parts of Eval()'s behavior.
type EvalOptions struct {
}
@@ -285,6 +298,7 @@ type GetOptions struct {
Rename map[string]string // rename items with the specified names, or under the specified names
NoDerefSymlinks bool // don't follow symlinks when globs match them
IgnoreUnreadable bool // ignore errors reading items, instead of returning an error
NoCrossDevice bool // if a subdirectory is a mountpoint with a different device number, include it but skip its contents
}
// Get produces an archive containing items that match the specified glob
@@ -396,6 +410,36 @@ func Mkdir(root string, directory string, options MkdirOptions) error {
return nil
}
// RemoveOptions controls parts of Remove()'s behavior.
type RemoveOptions struct {
All bool // if Directory is a directory, remove its contents as well
}
// Remove removes the specified directory or item, traversing any intermediate
// symbolic links.
// If the root directory is not specified, the current root directory is used.
// If root is specified and the current OS supports it, and the calling process
// has the necessary privileges, the remove() is performed in a chrooted context.
// If the item to remove is specified as an absolute path, it should either be
// in the root directory or in a subdirectory of the root directory. Otherwise,
// the directory is treated as a path relative to the root directory.
func Remove(root string, item string, options RemoveOptions) error {
req := request{
Request: requestRemove,
Root: root,
Directory: item,
RemoveOptions: options,
}
resp, err := copier(nil, nil, req)
if err != nil {
return err
}
if resp.Error != "" {
return errors.New(resp.Error)
}
return nil
}
// cleanerReldirectory resolves relative path candidate lexically, attempting
// to ensure that when joined as a subdirectory of another directory, it does
// not reference anything outside of that other directory.
@@ -819,6 +863,9 @@ func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*re
return copierHandlerPut(bulkReader, req, idMappings)
case requestMkdir:
return copierHandlerMkdir(req, idMappings)
case requestRemove:
resp := copierHandlerRemove(req)
return resp, nil, nil
case requestQuit:
return nil, nil, nil
}
@@ -859,7 +906,7 @@ func pathIsExcluded(root, path string, pm *fileutils.PatternMatcher) (string, bo
// it is not expected to be.
// This helps us approximate chrooted behavior on systems and in test cases
// where chroot isn't available.
func resolvePath(root, path string, pm *fileutils.PatternMatcher) (string, error) {
func resolvePath(root, path string, evaluateFinalComponent bool, pm *fileutils.PatternMatcher) (string, error) {
rel, err := convertToRelSubdirectory(root, path)
if err != nil {
return "", errors.Errorf("error making path %q relative to %q", path, root)
@@ -876,7 +923,7 @@ func resolvePath(root, path string, pm *fileutils.PatternMatcher) (string, error
}
excluded = excluded || thisExcluded
if !excluded {
if target, err := os.Readlink(filepath.Join(workingPath, components[0])); err == nil {
if target, err := os.Readlink(filepath.Join(workingPath, components[0])); err == nil && !(len(components) == 1 && !evaluateFinalComponent) {
followed++
if followed > maxLoopsFollowed {
return "", &os.PathError{
@@ -922,7 +969,7 @@ func copierHandlerEval(req request) *response {
errorResponse := func(fmtspec string, args ...interface{}) *response {
return &response{Error: fmt.Sprintf(fmtspec, args...), Eval: evalResponse{}}
}
resolvedTarget, err := resolvePath(req.Root, req.Directory, nil)
resolvedTarget, err := resolvePath(req.Root, req.Directory, true, nil)
if err != nil {
return errorResponse("copier: eval: error resolving %q: %v", req.Directory, err)
}
@@ -941,11 +988,13 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response {
s := StatsForGlob{
Glob: req.preservedGlobs[i],
}
stats = append(stats, &s)
// glob this pattern
globMatched, err := filepath.Glob(glob)
if err != nil {
s.Error = fmt.Sprintf("copier: stat: %q while matching glob pattern %q", err.Error(), glob)
}
if len(globMatched) == 0 && strings.ContainsAny(glob, "*?[") {
continue
}
// collect the matches
@@ -1001,7 +1050,7 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response {
// could be a relative link) and in the context
// of the chroot
result.ImmediateTarget = immediateTarget
resolvedTarget, err := resolvePath(req.Root, globbed, pm)
resolvedTarget, err := resolvePath(req.Root, globbed, true, pm)
if err != nil {
return errorResponse("copier: stat: error resolving %q: %v", globbed, err)
}
@@ -1032,6 +1081,14 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response {
s.Results = nil
s.Error = fmt.Sprintf("copier: stat: %q: %v", glob, syscall.ENOENT)
}
stats = append(stats, &s)
}
// no matches -> error
if len(stats) == 0 {
s := StatsForGlob{
Error: fmt.Sprintf("copier: stat: %q: %v", req.Globs, syscall.ENOENT),
}
stats = append(stats, &s)
}
return &response{Stat: statResponse{Globs: stats}}
}
@@ -1072,6 +1129,10 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
if len(queue) == 0 {
return errorResponse("copier: get: globs %v matched nothing (%d filtered out): %v", req.Globs, globMatchedCount, syscall.ENOENT)
}
topInfo, err := os.Stat(req.Directory)
if err != nil {
return errorResponse("copier: get: error reading info about directory %q: %v", req.Directory, err)
}
cb := func() error {
tw := tar.NewWriter(bulkWriter)
defer tw.Close()
@@ -1168,14 +1229,22 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
}
symlinkTarget = target
}
// if it's a directory and we're staying on one device, and it's on a
// different device than the one we started from, skip its contents
var ok error
if info.Mode().IsDir() && req.GetOptions.NoCrossDevice {
if !sameDevice(topInfo, info) {
ok = filepath.SkipDir
}
}
// add the item to the outgoing tar stream
if err := copierHandlerGetOne(info, symlinkTarget, rel, path, options, tw, hardlinkChecker, idMappings); err != nil {
if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) {
return nil
return ok
}
return err
}
return nil
return ok
}
// walk the directory tree, checking/adding items individually
if err := filepath.Walk(item, walkfn); err != nil {
@@ -1463,7 +1532,7 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
}
return n, nil
}
targetDirectory, err := resolvePath(req.Root, req.Directory, nil)
targetDirectory, err := resolvePath(req.Root, req.Directory, true, nil)
if err != nil {
return errorResponse("copier: put: error resolving %q: %v", req.Directory, err)
}
@@ -1568,7 +1637,7 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
if req.PutOptions.Rename != nil {
hdr.Linkname = handleRename(req.PutOptions.Rename, hdr.Linkname)
}
if linkTarget, err = resolvePath(targetDirectory, filepath.Join(req.Root, filepath.FromSlash(hdr.Linkname)), nil); err != nil {
if linkTarget, err = resolvePath(targetDirectory, filepath.Join(req.Root, filepath.FromSlash(hdr.Linkname)), true, nil); err != nil {
return errors.Errorf("error resolving hardlink target path %q under root %q", hdr.Linkname, req.Root)
}
if err = os.Link(linkTarget, path); err != nil && os.IsExist(err) {
@@ -1742,7 +1811,7 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
dirUID, dirGID = hostDirPair.UID, hostDirPair.GID
}
directory, err := resolvePath(req.Root, req.Directory, nil)
directory, err := resolvePath(req.Root, req.Directory, true, nil)
if err != nil {
return errorResponse("copier: mkdir: error resolving %q: %v", req.Directory, err)
}
@@ -1772,3 +1841,22 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
return &response{Error: "", Mkdir: mkdirResponse{}}, nil, nil
}
func copierHandlerRemove(req request) *response {
errorResponse := func(fmtspec string, args ...interface{}) *response {
return &response{Error: fmt.Sprintf(fmtspec, args...), Remove: removeResponse{}}
}
resolvedTarget, err := resolvePath(req.Root, req.Directory, false, nil)
if err != nil {
return errorResponse("copier: remove: %v", err)
}
if req.RemoveOptions.All {
err = os.RemoveAll(resolvedTarget)
} else {
err = os.Remove(resolvedTarget)
}
if err != nil {
return errorResponse("copier: remove %q: %v", req.Directory, err)
}
return &response{Error: "", Remove: removeResponse{}}
}

View File

@@ -4,6 +4,7 @@ package copier
import (
"os"
"syscall"
"time"
"github.com/pkg/errors"
@@ -73,6 +74,21 @@ func lutimes(isSymlink bool, path string, atime, mtime time.Time) error {
return unix.Lutimes(path, []unix.Timeval{unix.NsecToTimeval(atime.UnixNano()), unix.NsecToTimeval(mtime.UnixNano())})
}
// sameDevice returns true unless we're sure that they're not on the same device
func sameDevice(a, b os.FileInfo) bool {
aSys := a.Sys()
bSys := b.Sys()
if aSys == nil || bSys == nil {
return true
}
au, aok := aSys.(*syscall.Stat_t)
bu, bok := bSys.(*syscall.Stat_t)
if !aok || !bok {
return true
}
return au.Dev == bu.Dev
}
const (
testModeMask = int64(os.ModePerm)
testIgnoreSymlinkDates = false

View File

@@ -77,6 +77,11 @@ func lutimes(isSymlink bool, path string, atime, mtime time.Time) error {
return windows.UtimesNano(path, []windows.Timespec{windows.NsecToTimespec(atime.UnixNano()), windows.NsecToTimespec(mtime.UnixNano())})
}
// sameDevice returns true since we can't be sure that they're not on the same device
func sameDevice(a, b os.FileInfo) bool {
return true
}
const (
testModeMask = int64(0600)
testIgnoreSymlinkDates = true