mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
7a7357dae1
This enables docker cp and ADD/COPY docker build support for LCOW. Originally, the graphdriver.Get() interface returned a local path to the container root filesystem. This does not work for LCOW, so the Get() method now returns an interface that LCOW implements to support copying to and from the container. Signed-off-by: Akash Gupta <akagup@microsoft.com>
183 lines
5.4 KiB
Go
183 lines
5.4 KiB
Go
package remotecontext
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/containerd/continuity/driver"
|
|
"github.com/docker/docker/api/types/backend"
|
|
"github.com/docker/docker/builder"
|
|
"github.com/docker/docker/builder/dockerfile/parser"
|
|
"github.com/docker/docker/builder/dockerignore"
|
|
"github.com/docker/docker/pkg/fileutils"
|
|
"github.com/docker/docker/pkg/urlutil"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ClientSessionRemote is identifier for client-session context transport
|
|
const ClientSessionRemote = "client-session"
|
|
|
|
// Detect returns a context and dockerfile from remote location or local
|
|
// archive. progressReader is only used if remoteURL is actually a URL
|
|
// (not empty, and not a Git endpoint).
|
|
func Detect(config backend.BuildConfig) (remote builder.Source, dockerfile *parser.Result, err error) {
|
|
remoteURL := config.Options.RemoteContext
|
|
dockerfilePath := config.Options.Dockerfile
|
|
|
|
switch {
|
|
case remoteURL == "":
|
|
remote, dockerfile, err = newArchiveRemote(config.Source, dockerfilePath)
|
|
case remoteURL == ClientSessionRemote:
|
|
res, err := parser.Parse(config.Source)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return nil, res, nil
|
|
case urlutil.IsGitURL(remoteURL):
|
|
remote, dockerfile, err = newGitRemote(remoteURL, dockerfilePath)
|
|
case urlutil.IsURL(remoteURL):
|
|
remote, dockerfile, err = newURLRemote(remoteURL, dockerfilePath, config.ProgressWriter.ProgressReaderFunc)
|
|
default:
|
|
err = fmt.Errorf("remoteURL (%s) could not be recognized as URL", remoteURL)
|
|
}
|
|
return
|
|
}
|
|
|
|
func newArchiveRemote(rc io.ReadCloser, dockerfilePath string) (builder.Source, *parser.Result, error) {
|
|
defer rc.Close()
|
|
c, err := FromArchive(rc)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return withDockerfileFromContext(c.(modifiableContext), dockerfilePath)
|
|
}
|
|
|
|
func withDockerfileFromContext(c modifiableContext, dockerfilePath string) (builder.Source, *parser.Result, error) {
|
|
df, err := openAt(c, dockerfilePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
if dockerfilePath == builder.DefaultDockerfileName {
|
|
lowercase := strings.ToLower(dockerfilePath)
|
|
if _, err := StatAt(c, lowercase); err == nil {
|
|
return withDockerfileFromContext(c, lowercase)
|
|
}
|
|
}
|
|
return nil, nil, errors.Errorf("Cannot locate specified Dockerfile: %s", dockerfilePath) // backwards compatible error
|
|
}
|
|
c.Close()
|
|
return nil, nil, err
|
|
}
|
|
|
|
res, err := readAndParseDockerfile(dockerfilePath, df)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
df.Close()
|
|
|
|
if err := removeDockerfile(c, dockerfilePath); err != nil {
|
|
c.Close()
|
|
return nil, nil, err
|
|
}
|
|
|
|
return c, res, nil
|
|
}
|
|
|
|
func newGitRemote(gitURL string, dockerfilePath string) (builder.Source, *parser.Result, error) {
|
|
c, err := MakeGitContext(gitURL) // TODO: change this to NewLazySource
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return withDockerfileFromContext(c.(modifiableContext), dockerfilePath)
|
|
}
|
|
|
|
func newURLRemote(url string, dockerfilePath string, progressReader func(in io.ReadCloser) io.ReadCloser) (builder.Source, *parser.Result, error) {
|
|
var dockerfile io.ReadCloser
|
|
dockerfileFoundErr := errors.New("found-dockerfile")
|
|
c, err := MakeRemoteContext(url, map[string]func(io.ReadCloser) (io.ReadCloser, error){
|
|
mimeTypes.TextPlain: func(rc io.ReadCloser) (io.ReadCloser, error) {
|
|
dockerfile = rc
|
|
return nil, dockerfileFoundErr
|
|
},
|
|
// fallback handler (tar context)
|
|
"": func(rc io.ReadCloser) (io.ReadCloser, error) {
|
|
return progressReader(rc), nil
|
|
},
|
|
})
|
|
switch {
|
|
case err == dockerfileFoundErr:
|
|
res, err := parser.Parse(dockerfile)
|
|
return nil, res, err
|
|
case err != nil:
|
|
return nil, nil, err
|
|
}
|
|
return withDockerfileFromContext(c.(modifiableContext), dockerfilePath)
|
|
}
|
|
|
|
func removeDockerfile(c modifiableContext, filesToRemove ...string) error {
|
|
f, err := openAt(c, ".dockerignore")
|
|
// Note that a missing .dockerignore file isn't treated as an error
|
|
switch {
|
|
case os.IsNotExist(err):
|
|
return nil
|
|
case err != nil:
|
|
return err
|
|
}
|
|
excludes, err := dockerignore.ReadAll(f)
|
|
if err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
f.Close()
|
|
filesToRemove = append([]string{".dockerignore"}, filesToRemove...)
|
|
for _, fileToRemove := range filesToRemove {
|
|
if rm, _ := fileutils.Matches(fileToRemove, excludes); rm {
|
|
if err := c.Remove(fileToRemove); err != nil {
|
|
logrus.Errorf("failed to remove %s: %v", fileToRemove, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readAndParseDockerfile(name string, rc io.Reader) (*parser.Result, error) {
|
|
br := bufio.NewReader(rc)
|
|
if _, err := br.Peek(1); err != nil {
|
|
if err == io.EOF {
|
|
return nil, errors.Errorf("the Dockerfile (%s) cannot be empty", name)
|
|
}
|
|
return nil, errors.Wrap(err, "unexpected error reading Dockerfile")
|
|
}
|
|
return parser.Parse(br)
|
|
}
|
|
|
|
func openAt(remote builder.Source, path string) (driver.File, error) {
|
|
fullPath, err := FullPath(remote, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return remote.Root().Open(fullPath)
|
|
}
|
|
|
|
// StatAt is a helper for calling Stat on a path from a source
|
|
func StatAt(remote builder.Source, path string) (os.FileInfo, error) {
|
|
fullPath, err := FullPath(remote, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return remote.Root().Stat(fullPath)
|
|
}
|
|
|
|
// FullPath is a helper for getting a full path for a path from a source
|
|
func FullPath(remote builder.Source, path string) (string, error) {
|
|
fullPath, err := remote.Root().ResolveScopedPath(path, true)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Forbidden path outside the build context: %s (%s)", path, fullPath) // backwards compat with old error
|
|
}
|
|
return fullPath, nil
|
|
}
|