add: add a DryRun flag to AddAndCopyOptions

Add a DryRun flag to AddAndCopyOptions, so that we can "copy" content to
digest it.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #1792
Approved by: TomSweeneyRedHat
This commit is contained in:
Nalin Dahyabhai 2019-06-07 18:02:50 -04:00 committed by Atomic Bot
parent 36dcedbabd
commit db2b3e48ac
3 changed files with 101 additions and 62 deletions

46
add.go
View File

@ -35,28 +35,36 @@ type AddAndCopyOptions struct {
Hasher io.Writer Hasher io.Writer
// Excludes is the contents of the .dockerignore file // Excludes is the contents of the .dockerignore file
Excludes []string Excludes []string
// The base directory for Excludes and data to copy in // ContextDir is the base directory for Excludes for content being copied
ContextDir string ContextDir string
// ID mapping options to use when contents to be copied are part of // ID mapping options to use when contents to be copied are part of
// another container, and need ownerships to be mapped from the host to // another container, and need ownerships to be mapped from the host to
// that container's values before copying them into the container. // that container's values before copying them into the container.
IDMappingOptions *IDMappingOptions IDMappingOptions *IDMappingOptions
// DryRun indicates that the content should be digested, but not actually
// copied into the container.
DryRun bool
} }
// addURL copies the contents of the source URL to the destination. This is // addURL copies the contents of the source URL to the destination. This is
// its own function so that deferred closes happen after we're done pulling // its own function so that deferred closes happen after we're done pulling
// down each item of potentially many. // down each item of potentially many.
func addURL(destination, srcurl string, owner idtools.IDPair, hasher io.Writer) error { func addURL(destination, srcurl string, owner idtools.IDPair, hasher io.Writer, dryRun bool) error {
logrus.Debugf("saving %q to %q", srcurl, destination)
resp, err := http.Get(srcurl) resp, err := http.Get(srcurl)
if err != nil { if err != nil {
return errors.Wrapf(err, "error getting %q", srcurl) return errors.Wrapf(err, "error getting %q", srcurl)
} }
defer resp.Body.Close() defer resp.Body.Close()
thisWriter := hasher
if !dryRun {
logrus.Debugf("saving %q to %q", srcurl, destination)
f, err := os.Create(destination) f, err := os.Create(destination)
if err != nil { if err != nil {
return errors.Wrapf(err, "error creating %q", destination) return errors.Wrapf(err, "error creating %q", destination)
} }
defer f.Close()
if err = f.Chown(owner.UID, owner.GID); err != nil { if err = f.Chown(owner.UID, owner.GID); err != nil {
return errors.Wrapf(err, "error setting owner of %q to %d:%d", destination, owner.UID, owner.GID) return errors.Wrapf(err, "error setting owner of %q to %d:%d", destination, owner.UID, owner.GID)
} }
@ -71,21 +79,24 @@ func addURL(destination, srcurl string, owner idtools.IDPair, hasher io.Writer)
}() }()
} }
} }
defer f.Close() defer func() {
bodyReader := io.Reader(resp.Body) if err2 := f.Chmod(0600); err2 != nil {
if hasher != nil { logrus.Debugf("error setting permissions on %q: %v", destination, err2)
bodyReader = io.TeeReader(bodyReader, hasher)
} }
n, err := io.Copy(f, bodyReader) }()
if thisWriter != nil {
thisWriter = io.MultiWriter(f, thisWriter)
} else {
thisWriter = f
}
}
n, err := io.Copy(thisWriter, resp.Body)
if err != nil { if err != nil {
return errors.Wrapf(err, "error reading contents for %q from %q", destination, srcurl) return errors.Wrapf(err, "error reading contents for %q from %q", destination, srcurl)
} }
if resp.ContentLength >= 0 && n != resp.ContentLength { if resp.ContentLength >= 0 && n != resp.ContentLength {
return errors.Errorf("error reading contents for %q from %q: wrong length (%d != %d)", destination, srcurl, n, resp.ContentLength) return errors.Errorf("error reading contents for %q from %q: wrong length (%d != %d)", destination, srcurl, n, resp.ContentLength)
} }
if err := f.Chmod(0600); err != nil {
return errors.Wrapf(err, "error setting permissions on %q", destination)
}
return nil return nil
} }
@ -118,6 +129,8 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
} }
hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
dest := mountPoint dest := mountPoint
if !options.DryRun {
// Resolve the destination if it was specified as a relative path.
if destination != "" && filepath.IsAbs(destination) { if destination != "" && filepath.IsAbs(destination) {
dir := filepath.Dir(destination) dir := filepath.Dir(destination)
if dir != "." && dir != "/" { if dir != "." && dir != "/" {
@ -144,6 +157,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
if destpfi, err2 := os.Stat(filepath.Dir(dest)); err2 == nil && !destpfi.IsDir() { if destpfi, err2 := os.Stat(filepath.Dir(dest)); err2 == nil && !destpfi.IsDir() {
return errors.Errorf("%q already exists, but is not a subdirectory)", filepath.Dir(dest)) return errors.Errorf("%q already exists, but is not a subdirectory)", filepath.Dir(dest))
} }
}
// Now look at the destination itself. // Now look at the destination itself.
destfi, err := os.Stat(dest) destfi, err := os.Stat(dest)
if err != nil { if err != nil {
@ -155,9 +169,9 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
if len(source) > 1 && (destfi == nil || !destfi.IsDir()) { if len(source) > 1 && (destfi == nil || !destfi.IsDir()) {
return errors.Errorf("destination %q is not a directory", dest) return errors.Errorf("destination %q is not a directory", dest)
} }
copyFileWithTar := b.copyFileWithTar(options.IDMappingOptions, &containerOwner, options.Hasher) copyFileWithTar := b.copyFileWithTar(options.IDMappingOptions, &containerOwner, options.Hasher, options.DryRun)
copyWithTar := b.copyWithTar(options.IDMappingOptions, &containerOwner, options.Hasher) copyWithTar := b.copyWithTar(options.IDMappingOptions, &containerOwner, options.Hasher, options.DryRun)
untarPath := b.untarPath(nil, options.Hasher) untarPath := b.untarPath(nil, options.Hasher, options.DryRun)
err = addHelper(excludes, extract, dest, destfi, hostOwner, options, copyFileWithTar, copyWithTar, untarPath, source...) err = addHelper(excludes, extract, dest, destfi, hostOwner, options, copyFileWithTar, copyWithTar, untarPath, source...)
if err != nil { if err != nil {
return err return err
@ -245,7 +259,7 @@ func addHelper(excludes *fileutils.PatternMatcher, extract bool, dest string, de
if destfi != nil && destfi.IsDir() { if destfi != nil && destfi.IsDir() {
d = filepath.Join(dest, path.Base(url.Path)) d = filepath.Join(dest, path.Base(url.Path))
} }
if err = addURL(d, src, hostOwner, options.Hasher); err != nil { if err = addURL(d, src, hostOwner, options.Hasher, options.DryRun); err != nil {
return err return err
} }
continue continue
@ -273,9 +287,11 @@ func addHelper(excludes *fileutils.PatternMatcher, extract bool, dest string, de
// the source directory into the target directory. Try // the source directory into the target directory. Try
// to create it first, so that if there's a problem, // to create it first, so that if there's a problem,
// we'll discover why that won't work. // we'll discover why that won't work.
if !options.DryRun {
if err = idtools.MkdirAllAndChownNew(dest, 0755, hostOwner); err != nil { if err = idtools.MkdirAllAndChownNew(dest, 0755, hostOwner); err != nil {
return errors.Wrapf(err, "error creating directory %q", dest) return errors.Wrapf(err, "error creating directory %q", dest)
} }
}
logrus.Debugf("copying %q to %q", esrc+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") logrus.Debugf("copying %q to %q", esrc+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*")
if excludes == nil || !excludes.Exclusions() { if excludes == nil || !excludes.Exclusions() {
if err = copyWithTar(esrc, dest); err != nil { if err = copyWithTar(esrc, dest); err != nil {

View File

@ -431,7 +431,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st
// Add temporary copies of the contents of volume locations at the // Add temporary copies of the contents of volume locations at the
// volume locations, unless we already have something there. // volume locations, unless we already have something there.
copyWithTar := b.copyWithTar(nil, nil, nil) copyWithTar := b.copyWithTar(nil, nil, nil, false)
builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes, int(rootUID), int(rootGID)) builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes, int(rootUID), int(rootGID))
if err != nil { if err != nil {
return err return err

35
util.go
View File

@ -3,6 +3,7 @@ package buildah
import ( import (
"archive/tar" "archive/tar"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -112,7 +113,7 @@ func convertRuntimeIDMaps(UIDMap, GIDMap []rspec.LinuxIDMapping) ([]idtools.IDMa
// of any container, or another container, into our working container, mapping // of any container, or another container, into our working container, mapping
// read permissions using the passed-in ID maps, writing using the container's // read permissions using the passed-in ID maps, writing using the container's
// ID mappings, possibly overridden using the passed-in chownOpts // ID mappings, possibly overridden using the passed-in chownOpts
func (b *Builder) copyFileWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { func (b *Builder) copyFileWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(src, dest string) error {
if tarIDMappingOptions == nil { if tarIDMappingOptions == nil {
tarIDMappingOptions = &IDMappingOptions{ tarIDMappingOptions = &IDMappingOptions{
HostUIDMapping: true, HostUIDMapping: true,
@ -207,7 +208,7 @@ func (b *Builder) copyFileWithTar(tarIDMappingOptions *IDMappingOptions, chownOp
pipeWriter = nil pipeWriter = nil
}(f) }(f)
untar := b.untar(chownOpts, hasher) untar := b.untar(chownOpts, hasher, dryRun)
err = untar(pipeReader, b.MountPoint) err = untar(pipeReader, b.MountPoint)
if err == nil { if err == nil {
err = copyErr err = copyErr
@ -224,9 +225,9 @@ func (b *Builder) copyFileWithTar(tarIDMappingOptions *IDMappingOptions, chownOp
// our container or from another container, into our working container, mapping // our container or from another container, into our working container, mapping
// permissions at read-time using the container's ID maps, with ownership at // permissions at read-time using the container's ID maps, with ownership at
// write-time possibly overridden using the passed-in chownOpts // write-time possibly overridden using the passed-in chownOpts
func (b *Builder) copyWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { func (b *Builder) copyWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(src, dest string) error {
tar := b.tarPath(tarIDMappingOptions) tar := b.tarPath(tarIDMappingOptions)
untar := b.untar(chownOpts, hasher) untar := b.untar(chownOpts, hasher, dryRun)
return func(src, dest string) error { return func(src, dest string) error {
rc, err := tar(src) rc, err := tar(src)
if err != nil { if err != nil {
@ -239,8 +240,22 @@ func (b *Builder) copyWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *
// untarPath returns a function which extracts an archive in a specified // untarPath returns a function which extracts an archive in a specified
// location into our working container, mapping permissions using the // location into our working container, mapping permissions using the
// container's ID maps, possibly overridden using the passed-in chownOpts // container's ID maps, possibly overridden using the passed-in chownOpts
func (b *Builder) untarPath(chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { func (b *Builder) untarPath(chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(src, dest string) error {
convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap)
if dryRun {
return func(src, dest string) error {
if hasher == nil {
hasher = ioutil.Discard
}
f, err := os.Open(src)
if err != nil {
return errors.Wrapf(err, "error opening %q", src)
}
defer f.Close()
_, err = io.Copy(hasher, f)
return err
}
}
return chrootarchive.UntarPathAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap) return chrootarchive.UntarPathAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap)
} }
@ -272,7 +287,7 @@ func (b *Builder) tarPath(idMappingOptions *IDMappingOptions) func(path string)
// untar returns a function which extracts an archive stream to a specified // untar returns a function which extracts an archive stream to a specified
// location in the container's filesystem, mapping permissions using the // location in the container's filesystem, mapping permissions using the
// container's ID maps, possibly overridden using the passed-in chownOpts // container's ID maps, possibly overridden using the passed-in chownOpts
func (b *Builder) untar(chownOpts *idtools.IDPair, hasher io.Writer) func(tarArchive io.ReadCloser, dest string) error { func (b *Builder) untar(chownOpts *idtools.IDPair, hasher io.Writer, dryRun bool) func(tarArchive io.ReadCloser, dest string) error {
convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap)
untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap)
options := &archive.TarOptions{ options := &archive.TarOptions{
@ -281,6 +296,14 @@ func (b *Builder) untar(chownOpts *idtools.IDPair, hasher io.Writer) func(tarArc
ChownOpts: chownOpts, ChownOpts: chownOpts,
} }
untar := chrootarchive.Untar untar := chrootarchive.Untar
if dryRun {
untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
if _, err := io.Copy(ioutil.Discard, tarArchive); err != nil {
return errors.Wrapf(err, "error digesting tar stream")
}
return nil
}
}
if hasher != nil { if hasher != nil {
originalUntar := untar originalUntar := untar
untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {