mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #934 from dotcloud/fix-add-behavior
* Build: Stabilize ADD behavior
This commit is contained in:
commit
a660cc0d01
3 changed files with 116 additions and 10 deletions
82
archive.go
82
archive.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Archive io.Reader
|
type Archive io.Reader
|
||||||
|
@ -79,10 +80,29 @@ func (compression *Compression) Extension() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tar creates an archive from the directory at `path`, and returns it as a
|
||||||
|
// stream of bytes.
|
||||||
func Tar(path string, compression Compression) (io.Reader, error) {
|
func Tar(path string, compression Compression) (io.Reader, error) {
|
||||||
return CmdStream(exec.Command("tar", "-f", "-", "-C", path, "-c"+compression.Flag(), "."))
|
return TarFilter(path, compression, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tar creates an archive from the directory at `path`, only including files whose relative
|
||||||
|
// paths are included in `filter`. If `filter` is nil, then all files are included.
|
||||||
|
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
|
||||||
|
args := []string{"tar", "-f", "-", "-C", path}
|
||||||
|
if filter == nil {
|
||||||
|
filter = []string{"."}
|
||||||
|
}
|
||||||
|
for _, f := range filter {
|
||||||
|
args = append(args, "-c"+compression.Flag(), f)
|
||||||
|
}
|
||||||
|
return CmdStream(exec.Command(args[0], args[1:]...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||||
|
// and unpacks it into the directory at `path`.
|
||||||
|
// The archive may be compressed with one of the following algorithgms:
|
||||||
|
// identity (uncompressed), gzip, bzip2, xz.
|
||||||
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
||||||
func Untar(archive io.Reader, path string) error {
|
func Untar(archive io.Reader, path string) error {
|
||||||
|
|
||||||
|
@ -107,6 +127,18 @@ func Untar(archive io.Reader, path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TarUntar is a convenience function which calls Tar and Untar, with
|
||||||
|
// the output of one piped into the other. If either Tar or Untar fails,
|
||||||
|
// TarUntar aborts and returns the error.
|
||||||
|
func TarUntar(src string, filter []string, dst string) error {
|
||||||
|
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
|
||||||
|
archive, err := TarFilter(src, Uncompressed, filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Untar(archive, dst)
|
||||||
|
}
|
||||||
|
|
||||||
// UntarPath is a convenience function which looks for an archive
|
// UntarPath is a convenience function which looks for an archive
|
||||||
// at filesystem path `src`, and unpacks it at `dst`.
|
// at filesystem path `src`, and unpacks it at `dst`.
|
||||||
func UntarPath(src, dst string) error {
|
func UntarPath(src, dst string) error {
|
||||||
|
@ -124,11 +156,55 @@ func UntarPath(src, dst string) error {
|
||||||
// intermediary disk IO.
|
// intermediary disk IO.
|
||||||
//
|
//
|
||||||
func CopyWithTar(src, dst string) error {
|
func CopyWithTar(src, dst string) error {
|
||||||
archive, err := Tar(src, Uncompressed)
|
srcSt, err := os.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return Untar(archive, dst)
|
var dstExists bool
|
||||||
|
dstSt, err := os.Stat(dst)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dstExists = true
|
||||||
|
}
|
||||||
|
// Things that can go wrong if the source is a directory
|
||||||
|
if srcSt.IsDir() {
|
||||||
|
// The destination exists and is a regular file
|
||||||
|
if dstExists && !dstSt.IsDir() {
|
||||||
|
return fmt.Errorf("Can't copy a directory over a regular file")
|
||||||
|
}
|
||||||
|
// Things that can go wrong if the source is a regular file
|
||||||
|
} else {
|
||||||
|
utils.Debugf("The destination exists, it's a directory, and doesn't end in /")
|
||||||
|
// The destination exists, it's a directory, and doesn't end in /
|
||||||
|
if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' {
|
||||||
|
return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create the destination
|
||||||
|
var dstDir string
|
||||||
|
if dst[len(dst)-1] == '/' {
|
||||||
|
// The destination ends in /
|
||||||
|
// --> dst is the holding directory
|
||||||
|
dstDir = dst
|
||||||
|
} else {
|
||||||
|
// The destination doesn't end in /
|
||||||
|
// --> dst is the file
|
||||||
|
dstDir = path.Dir(dst)
|
||||||
|
}
|
||||||
|
if !dstExists {
|
||||||
|
// Create the holding directory if necessary
|
||||||
|
utils.Debugf("Creating the holding directory %s", dstDir)
|
||||||
|
if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !srcSt.IsDir() {
|
||||||
|
return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir)
|
||||||
|
}
|
||||||
|
return TarUntar(src, nil, dstDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdStream executes a command, and returns its stdout as a stream.
|
// CmdStream executes a command, and returns its stdout as a stream.
|
||||||
|
|
|
@ -195,15 +195,15 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||||
|
|
||||||
origPath := path.Join(b.context, orig)
|
origPath := path.Join(b.context, orig)
|
||||||
destPath := path.Join(container.RootfsPath(), dest)
|
destPath := path.Join(container.RootfsPath(), dest)
|
||||||
|
// Preserve the trailing '/'
|
||||||
|
if dest[len(dest)-1] == '/' {
|
||||||
|
destPath = destPath + "/"
|
||||||
|
}
|
||||||
fi, err := os.Stat(origPath)
|
fi, err := os.Stat(origPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
if err := os.MkdirAll(destPath, 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,9 +138,39 @@ curl was installed within the image.
|
||||||
|
|
||||||
``ADD <src> <dest>``
|
``ADD <src> <dest>``
|
||||||
|
|
||||||
The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path
|
The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
|
||||||
of the container.
|
|
||||||
The context must be set in order to use this instruction. (see examples)
|
`<src>` must be the path to a file or directory relative to the source directory being built (also called the
|
||||||
|
context of the build).
|
||||||
|
|
||||||
|
`<dest>` is the path at which the source will be copied in the destination container.
|
||||||
|
|
||||||
|
The copy obeys the following rules:
|
||||||
|
|
||||||
|
If `<src>` is a directory, the entire directory is copied, including filesystem metadata.
|
||||||
|
|
||||||
|
If `<src>` is a tar archive in a recognized compression format (identity, gzip, bzip2 or xz), it
|
||||||
|
is unpacked as a directory.
|
||||||
|
|
||||||
|
When a directory is copied or unpacked, it has the same behavior as 'tar -x': the result is the union of
|
||||||
|
a) whatever existed at the destination path and b) the contents of the source tree, with conflicts resolved
|
||||||
|
in favor of b on a file-by-file basis.
|
||||||
|
|
||||||
|
If `<src>` is any other kind of file, it is copied individually along with its metadata.
|
||||||
|
|
||||||
|
If `<dest>` doesn't exist, it is created along with all missing directories in its path. All new
|
||||||
|
files and directories are created with mode 0700, uid and gid 0.
|
||||||
|
|
||||||
|
If `<dest>` ends with a trailing slash '/', the contents of `<src>` is copied `inside` it.
|
||||||
|
For example "ADD foo /usr/src/" creates /usr/src/foo in the container. If `<dest>` already exists,
|
||||||
|
it MUST be a directory.
|
||||||
|
|
||||||
|
If `<dest>` does not end with a trailing slash '/', the contents of `<src>` is copied `over` it.
|
||||||
|
For example "ADD foo /usr/src" creates /usr/src with the contents of the "foo". If `<dest>` already
|
||||||
|
exists, it MUST be of the same type as the source.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
3. Dockerfile Examples
|
3. Dockerfile Examples
|
||||||
======================
|
======================
|
||||||
|
|
Loading…
Add table
Reference in a new issue