package dockerhelper import ( "archive/tar" "fmt" "io" "io/ioutil" "path/filepath" "strings" docker "github.com/fsouza/go-dockerclient" s2itar "github.com/openshift/source-to-image/pkg/tar" s2iutil "github.com/openshift/source-to-image/pkg/util" ) // removeLeadingDirectoryAdapter wraps a tar.Reader and strips the first leading // directory name of all of the files in the archive. An error is returned in // the case that files with differing first leading directory names are // encountered. type removeLeadingDirectoryAdapter struct { s2itar.Reader leadingDir string setLeadingDir bool } func (adapter removeLeadingDirectoryAdapter) Next() (*tar.Header, error) { for { header, err := adapter.Reader.Next() if err != nil { return nil, err } trimmedName := strings.Trim(header.Name, "/") paths := strings.SplitN(trimmedName, "/", 2) // ensure leading directory is consistent throughout the tar file if !adapter.setLeadingDir { adapter.setLeadingDir = true adapter.leadingDir = paths[0] } if adapter.leadingDir != paths[0] { return nil, fmt.Errorf("inconsistent leading directory at %s, type %c", header.Name, header.Typeflag) } if len(paths) == 1 { if header.Typeflag == tar.TypeDir { // this is the leading directory itself: drop it _, err = io.Copy(ioutil.Discard, adapter) if err != nil { return nil, err } continue } return nil, fmt.Errorf("unexpected non-directory %s, type %c", header.Name, header.Typeflag) } header.Name = paths[1] return header, err } } func newContainerDownloader(client *docker.Client, container, path string) io.ReadCloser { r, w := io.Pipe() go func() { opts := docker.DownloadFromContainerOptions{ Path: path, OutputStream: w, } w.CloseWithError(client.DownloadFromContainer(container, opts)) }() return r } func newContainerUploader(client *docker.Client, container, path string) (io.WriteCloser, <-chan error) { r, w := io.Pipe() errch := make(chan error, 1) go func() { opts := docker.UploadToContainerOptions{ Path: path, InputStream: r, } errch <- client.UploadToContainer(container, opts) }() return w, errch } type readCloser struct { io.Reader io.Closer } // StreamFileFromContainer returns an io.ReadCloser from which the contents of a // file in a remote container can be read. func StreamFileFromContainer(client *docker.Client, container, src string) (io.ReadCloser, error) { downloader := newContainerDownloader(client, container, src) tarReader := tar.NewReader(downloader) header, err := tarReader.Next() if err != nil { return nil, err } if header.Name != filepath.Base(src) || (header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA) { return nil, fmt.Errorf("unexpected tar file content %s, type %c", header.Name, header.Typeflag) } return readCloser{Reader: tarReader, Closer: downloader}, nil } // DownloadDirFromContainer downloads an entire directory of files from a remote // container. func DownloadDirFromContainer(client *docker.Client, container, src, dst string) error { downloader := newContainerDownloader(client, container, src) defer downloader.Close() tarReader := &removeLeadingDirectoryAdapter{Reader: tar.NewReader(downloader)} t := s2itar.New(s2iutil.NewFileSystem()) return t.ExtractTarStreamFromTarReader(dst, tarReader, nil) } // UploadFileToContainer uploads a file to a remote container. func UploadFileToContainer(client *docker.Client, container, src, dest string) error { uploader, errch := newContainerUploader(client, container, filepath.Dir(dest)) t := s2itar.New(s2iutil.NewFileSystem()) tarWriter := s2itar.RenameAdapter{Writer: tar.NewWriter(uploader), Old: filepath.Base(src), New: filepath.Base(dest)} err := t.CreateTarStreamToTarWriter(src, true, tarWriter, nil) if err == nil { err = tarWriter.Close() } uploader.Close() if err != nil { return err } return <-errch }