2017-03-07 04:39:22 +08:00
|
|
|
package buildah
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"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-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-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 {
|
|
|
|
return fmt.Errorf("error getting %q: %v", srcurl, err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
f, err := os.Create(destination)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating %q: %v", destination, err)
|
|
|
|
}
|
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 {
|
|
|
|
return fmt.Errorf("error reading contents for %q: %v", destination, err)
|
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
if resp.ContentLength >= 0 && n != resp.ContentLength {
|
|
|
|
return fmt.Errorf("error reading contents for %q: wrong length (%d != %d)", destination, n, resp.ContentLength)
|
|
|
|
}
|
2017-03-28 15:01:59 +08:00
|
|
|
if err := f.Chmod(0600); err != nil {
|
2017-03-07 04:39:22 +08:00
|
|
|
return fmt.Errorf("error setting permissions on %q: %v", destination, err)
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
return fmt.Errorf("error ensuring directory %q exists: %v)", filepath.Join(dest, b.WorkDir()), err)
|
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-03-28 03:35:09 +08:00
|
|
|
return fmt.Errorf("error ensuring directory %q exists: %v)", dest, err)
|
|
|
|
}
|
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
// Make sure the destination's parent directory is usable.
|
2017-04-04 05:44:23 +08:00
|
|
|
if destpfi, err2 := os.Stat(filepath.Dir(dest)); err2 == nil && !destpfi.Mode().IsDir() {
|
2017-03-24 01:49:38 +08:00
|
|
|
return fmt.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) {
|
|
|
|
return fmt.Errorf("couldn't determine what %q is: %v)", dest, err)
|
|
|
|
}
|
|
|
|
destfi = nil
|
|
|
|
}
|
|
|
|
if len(source) > 1 && (destfi == nil || !destfi.Mode().IsDir()) {
|
|
|
|
return fmt.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 {
|
|
|
|
return fmt.Errorf("error parsing URL %q: %v", src, err)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
d := dest
|
|
|
|
if destfi != nil && destfi.Mode().IsDir() {
|
|
|
|
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-03-24 01:49:38 +08:00
|
|
|
srcfi, err := os.Stat(src)
|
2017-03-07 04:39:22 +08:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error reading %q: %v", src, err)
|
|
|
|
}
|
2017-03-24 01:49:38 +08:00
|
|
|
if srcfi.Mode().IsDir() {
|
2017-03-28 15:02:41 +08:00
|
|
|
// 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.
|
2017-03-24 01:49:38 +08:00
|
|
|
d := dest
|
2017-03-08 02:26:17 +08:00
|
|
|
if err := os.MkdirAll(d, 0755); err != nil {
|
2017-03-28 15:02:41 +08:00
|
|
|
return fmt.Errorf("error ensuring directory %q exists: %v)", d, err)
|
2017-03-07 04:39:22 +08:00
|
|
|
}
|
|
|
|
logrus.Debugf("copying %q to %q", src+string(os.PathSeparator)+"*", d+string(os.PathSeparator)+"*")
|
2017-03-22 02:26:00 +08:00
|
|
|
if err := chrootarchive.CopyWithTar(src, d); err != nil {
|
2017-03-07 04:39:22 +08:00
|
|
|
return fmt.Errorf("error copying %q to %q: %v", src, d, err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !extract || !archive.IsArchivePath(src) {
|
|
|
|
// This source is a file, and either it's not an
|
|
|
|
// archive, or we don't care whether or not it's an
|
2017-03-08 02:26:17 +08:00
|
|
|
// archive.
|
2017-03-24 01:49:38 +08:00
|
|
|
d := dest
|
|
|
|
if destfi != nil && destfi.Mode().IsDir() {
|
|
|
|
d = filepath.Join(dest, filepath.Base(src))
|
|
|
|
}
|
2017-03-07 04:39:22 +08:00
|
|
|
// Copy the file, preserving attributes.
|
|
|
|
logrus.Debugf("copying %q to %q", src, d)
|
2017-03-22 02:26:00 +08:00
|
|
|
if err := chrootarchive.CopyFileWithTar(src, d); err != nil {
|
2017-03-07 04:39:22 +08:00
|
|
|
return fmt.Errorf("error copying %q to %q: %v", src, d, err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2017-03-08 02:26:17 +08:00
|
|
|
// We're extracting an archive into the destination directory.
|
2017-03-07 04:39:22 +08:00
|
|
|
logrus.Debugf("extracting contents of %q into %q", src, dest)
|
2017-03-22 02:26:00 +08:00
|
|
|
if err := chrootarchive.UntarPath(src, dest); err != nil {
|
2017-03-07 04:39:22 +08:00
|
|
|
return fmt.Errorf("error extracting %q into %q: %v", src, dest, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|