2017-03-07 04:39:22 +08:00
|
|
|
package buildah
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2017-03-08 02:26:17 +08:00
|
|
|
"net/url"
|
2017-03-07 04:39:22 +08:00
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2017-07-17 22:42:58 +08:00
|
|
|
"syscall"
|
2017-03-28 15:01:59 +08:00
|
|
|
"time"
|
2017-03-07 04:39:22 +08:00
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/containers/storage/pkg/archive"
|
2017-03-22 02:26:00 +08:00
|
|
|
"github.com/containers/storage/pkg/chrootarchive"
|
2017-06-02 03:23:02 +08:00
|
|
|
"github.com/pkg/errors"
|
2017-03-07 04:39:22 +08:00
|
|
|
)
|
|
|
|
|
2017-04-04 01:43:34 +08:00
|
|
|
// addURL copies the contents of the source URL to the destination. This is
|
2017-03-07 04:39:22 +08:00
|
|
|
// its own function so that deferred closes happen after we're done pulling
|
|
|
|
// down each item of potentially many.
|
2017-04-04 01:43:34 +08:00
|
|
|
func addURL(destination, srcurl string) error {
|
2017-03-07 04:39:22 +08:00
|
|
|
logrus.Debugf("saving %q to %q", srcurl, destination)
|
|
|
|
resp, err := http.Get(srcurl)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error getting %q", srcurl)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
f, err := os.Create(destination)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error creating %q", destination)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-28 15:01:59 +08:00
|
|
|
if last := resp.Header.Get("Last-Modified"); last != "" {
|
2017-04-04 05:44:23 +08:00
|
|
|
if mtime, err2 := time.Parse(time.RFC1123, last); err2 != nil {
|
|
|
|
logrus.Debugf("error parsing Last-Modified time %q: %v", last, err2)
|
2017-03-28 15:01:59 +08:00
|
|
|
} else {
|
|
|
|
defer func() {
|
2017-04-04 05:44:23 +08:00
|
|
|
if err3 := os.Chtimes(destination, time.Now(), mtime); err3 != nil {
|
|
|
|
logrus.Debugf("error setting mtime to Last-Modified time %q: %v", last, err3)
|
2017-03-28 15:01:59 +08:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
defer f.Close()
|
|
|
|
n, err := io.Copy(f, resp.Body)
|
2017-04-04 05:44:23 +08:00
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error reading contents for %q", destination)
|
2017-04-04 05:44:23 +08:00
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
if resp.ContentLength >= 0 && n != resp.ContentLength {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("error reading contents for %q: wrong length (%d != %d)", destination, n, resp.ContentLength)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-28 15:01:59 +08:00
|
|
|
if err := f.Chmod(0600); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error setting permissions on %q", destination)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-24 01:48:23 +08:00
|
|
|
// Add copies the contents of the specified sources into the container's root
|
|
|
|
// filesystem, optionally extracting contents of local files that look like
|
|
|
|
// non-empty archives.
|
2017-03-07 04:39:22 +08:00
|
|
|
func (b *Builder) Add(destination string, extract bool, source ...string) error {
|
2017-03-24 01:48:23 +08:00
|
|
|
mountPoint, err := b.Mount("")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-24 01:48:23 +08:00
|
|
|
defer func() {
|
|
|
|
if err2 := b.Unmount(); err2 != nil {
|
|
|
|
logrus.Errorf("error unmounting container: %v", err2)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
dest := mountPoint
|
2017-03-07 04:39:22 +08:00
|
|
|
if destination != "" && filepath.IsAbs(destination) {
|
|
|
|
dest = filepath.Join(dest, destination)
|
|
|
|
} else {
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
if err = os.MkdirAll(filepath.Join(dest, b.WorkDir()), 0755); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error ensuring directory %q exists)", filepath.Join(dest, b.WorkDir()))
|
2017-03-24 01:47:07 +08:00
|
|
|
}
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
dest = filepath.Join(dest, b.WorkDir(), destination)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-28 03:35:09 +08:00
|
|
|
// If the destination was explicitly marked as a directory by ending it
|
|
|
|
// with a '/', create it so that we can be sure that it's a directory,
|
|
|
|
// and any files we're copying will be placed in the directory.
|
|
|
|
if len(destination) > 0 && destination[len(destination)-1] == os.PathSeparator {
|
2017-04-04 05:44:23 +08:00
|
|
|
if err = os.MkdirAll(dest, 0755); err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error ensuring directory %q exists", dest)
|
2017-03-28 03:35:09 +08:00
|
|
|
}
|
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
// Make sure the destination's parent directory is usable.
|
2017-05-24 03:00:57 +08:00
|
|
|
if destpfi, err2 := os.Stat(filepath.Dir(dest)); err2 == nil && !destpfi.IsDir() {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("%q already exists, but is not a subdirectory)", filepath.Dir(dest))
|
2017-03-08 02:26:17 +08:00
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
// Now look at the destination itself.
|
|
|
|
destfi, err := os.Stat(dest)
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsNotExist(err) {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "couldn't determine what %q is", dest)
|
2017-03-24 01:49:38 +08:00
|
|
|
}
|
|
|
|
destfi = nil
|
|
|
|
}
|
2017-05-24 03:00:57 +08:00
|
|
|
if len(source) > 1 && (destfi == nil || !destfi.IsDir()) {
|
2017-06-03 00:17:27 +08:00
|
|
|
return errors.Errorf("destination %q is not a directory", dest)
|
2017-03-08 02:26:17 +08:00
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
for _, src := range source {
|
|
|
|
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
|
|
|
|
// We assume that source is a file, and we're copying
|
2017-03-28 15:02:41 +08:00
|
|
|
// it to the destination. If the destination is
|
|
|
|
// already a directory, create a file inside of it.
|
|
|
|
// Otherwise, the destination is the file to which
|
|
|
|
// we'll save the contents.
|
2017-03-08 02:26:17 +08:00
|
|
|
url, err := url.Parse(src)
|
|
|
|
if err != nil {
|
2017-06-02 03:23:02 +08:00
|
|
|
return errors.Wrapf(err, "error parsing URL %q", src)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
d := dest
|
2017-05-24 03:00:57 +08:00
|
|
|
if destfi != nil && destfi.IsDir() {
|
2017-03-24 01:49:38 +08:00
|
|
|
d = filepath.Join(dest, path.Base(url.Path))
|
|
|
|
}
|
2017-04-04 01:43:34 +08:00
|
|
|
if err := addURL(d, src); err != nil {
|
2017-03-07 04:39:22 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
|
|
|
|
glob, err := filepath.Glob(src)
|
2017-03-07 04:39:22 +08:00
|
|
|
if err != nil {
|
2017-07-17 22:42:58 +08:00
|
|
|
return errors.Wrapf(err, "invalid glob %q", src)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
if len(glob) == 0 {
|
|
|
|
return errors.Wrapf(syscall.ENOENT, "no files found matching %q", src)
|
|
|
|
}
|
|
|
|
for _, gsrc := range glob {
|
|
|
|
srcfi, err := os.Stat(gsrc)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error reading %q", gsrc)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
if srcfi.IsDir() {
|
|
|
|
// The source is a directory, so copy the contents of
|
|
|
|
// the source directory into the target directory. Try
|
|
|
|
// to create it first, so that if there's a problem,
|
|
|
|
// we'll discover why that won't work.
|
|
|
|
d := dest
|
|
|
|
if err := os.MkdirAll(d, 0755); err != nil {
|
|
|
|
return errors.Wrapf(err, "error ensuring directory %q exists", d)
|
|
|
|
}
|
|
|
|
logrus.Debugf("copying %q to %q", gsrc+string(os.PathSeparator)+"*", d+string(os.PathSeparator)+"*")
|
|
|
|
if err := chrootarchive.CopyWithTar(gsrc, d); err != nil {
|
|
|
|
return errors.Wrapf(err, "error copying %q to %q", gsrc, d)
|
|
|
|
}
|
|
|
|
continue
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
if !extract || !archive.IsArchivePath(gsrc) {
|
|
|
|
// This source is a file, and either it's not an
|
|
|
|
// archive, or we don't care whether or not it's an
|
|
|
|
// archive.
|
|
|
|
d := dest
|
|
|
|
if destfi != nil && destfi.IsDir() {
|
|
|
|
d = filepath.Join(dest, filepath.Base(gsrc))
|
|
|
|
}
|
|
|
|
// Copy the file, preserving attributes.
|
|
|
|
logrus.Debugf("copying %q to %q", gsrc, d)
|
|
|
|
if err := chrootarchive.CopyFileWithTar(gsrc, d); err != nil {
|
|
|
|
return errors.Wrapf(err, "error copying %q to %q", gsrc, d)
|
|
|
|
}
|
|
|
|
continue
|
2017-03-24 01:49:38 +08:00
|
|
|
}
|
2017-07-17 22:42:58 +08:00
|
|
|
// We're extracting an archive into the destination directory.
|
|
|
|
logrus.Debugf("extracting contents of %q into %q", gsrc, dest)
|
|
|
|
if err := chrootarchive.UntarPath(gsrc, dest); err != nil {
|
|
|
|
return errors.Wrapf(err, "error extracting %q into %q", gsrc, dest)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|