2018-02-05 16:05:59 -05:00
|
|
|
package remotecontext // import "github.com/docker/docker/builder/remotecontext"
|
2017-03-20 18:22:29 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
2018-08-14 18:07:46 -04:00
|
|
|
"runtime"
|
2017-03-20 18:22:29 -04:00
|
|
|
"strings"
|
|
|
|
|
2017-08-03 20:22:00 -04:00
|
|
|
"github.com/containerd/continuity/driver"
|
2017-04-13 14:37:32 -04:00
|
|
|
"github.com/docker/docker/api/types/backend"
|
2017-03-20 18:22:29 -04:00
|
|
|
"github.com/docker/docker/builder"
|
|
|
|
"github.com/docker/docker/builder/dockerignore"
|
|
|
|
"github.com/docker/docker/pkg/fileutils"
|
|
|
|
"github.com/docker/docker/pkg/urlutil"
|
2018-06-02 12:46:53 -04:00
|
|
|
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
2017-03-20 18:22:29 -04:00
|
|
|
"github.com/pkg/errors"
|
2017-07-26 17:42:13 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-03-20 18:22:29 -04:00
|
|
|
)
|
|
|
|
|
2017-05-15 17:54:27 -04:00
|
|
|
// ClientSessionRemote is identifier for client-session context transport
|
|
|
|
const ClientSessionRemote = "client-session"
|
|
|
|
|
2017-03-20 18:22:29 -04:00
|
|
|
// Detect returns a context and dockerfile from remote location or local
|
2018-07-02 20:12:56 -04:00
|
|
|
// archive.
|
2017-04-13 14:37:32 -04:00
|
|
|
func Detect(config backend.BuildConfig) (remote builder.Source, dockerfile *parser.Result, err error) {
|
|
|
|
remoteURL := config.Options.RemoteContext
|
|
|
|
dockerfilePath := config.Options.Dockerfile
|
|
|
|
|
2017-03-20 18:22:29 -04:00
|
|
|
switch {
|
|
|
|
case remoteURL == "":
|
2017-04-13 14:37:32 -04:00
|
|
|
remote, dockerfile, err = newArchiveRemote(config.Source, dockerfilePath)
|
2017-05-15 17:54:27 -04:00
|
|
|
case remoteURL == ClientSessionRemote:
|
|
|
|
res, err := parser.Parse(config.Source)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return nil, res, nil
|
2017-03-20 18:22:29 -04:00
|
|
|
case urlutil.IsGitURL(remoteURL):
|
|
|
|
remote, dockerfile, err = newGitRemote(remoteURL, dockerfilePath)
|
|
|
|
case urlutil.IsURL(remoteURL):
|
2017-04-13 14:37:32 -04:00
|
|
|
remote, dockerfile, err = newURLRemote(remoteURL, dockerfilePath, config.ProgressWriter.ProgressReaderFunc)
|
2017-03-20 18:22:29 -04:00
|
|
|
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) {
|
2017-05-23 19:29:13 -04:00
|
|
|
defer rc.Close()
|
2017-05-15 17:54:27 -04:00
|
|
|
c, err := FromArchive(rc)
|
2017-03-20 18:22:29 -04:00
|
|
|
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) {
|
2017-05-25 16:02:29 -04:00
|
|
|
c, err := MakeGitContext(gitURL) // TODO: change this to NewLazySource
|
2017-03-20 18:22:29 -04:00
|
|
|
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) {
|
2017-11-08 13:06:57 -05:00
|
|
|
contentType, content, err := downloadRemote(url)
|
|
|
|
if err != nil {
|
2017-03-20 18:22:29 -04:00
|
|
|
return nil, nil, err
|
|
|
|
}
|
2017-11-08 13:06:57 -05:00
|
|
|
defer content.Close()
|
|
|
|
|
|
|
|
switch contentType {
|
|
|
|
case mimeTypes.TextPlain:
|
|
|
|
res, err := parser.Parse(progressReader(content))
|
|
|
|
return nil, res, err
|
|
|
|
default:
|
|
|
|
source, err := FromArchive(progressReader(content))
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return withDockerfileFromContext(source.(modifiableContext), dockerfilePath)
|
|
|
|
}
|
2017-03-20 18:22:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2017-05-05 01:33:40 -04:00
|
|
|
if err != nil {
|
2017-06-28 02:49:12 -04:00
|
|
|
f.Close()
|
2017-05-05 01:33:40 -04:00
|
|
|
return err
|
|
|
|
}
|
2017-03-20 18:22:29 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-08-03 20:22:00 -04:00
|
|
|
func openAt(remote builder.Source, path string) (driver.File, error) {
|
2017-03-20 18:22:29 -04:00
|
|
|
fullPath, err := FullPath(remote, path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-08-03 20:22:00 -04:00
|
|
|
return remote.Root().Open(fullPath)
|
2017-03-20 18:22:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2017-08-03 20:22:00 -04:00
|
|
|
return remote.Root().Stat(fullPath)
|
2017-03-20 18:22:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// FullPath is a helper for getting a full path for a path from a source
|
|
|
|
func FullPath(remote builder.Source, path string) (string, error) {
|
2017-08-03 20:22:00 -04:00
|
|
|
fullPath, err := remote.Root().ResolveScopedPath(path, true)
|
2017-03-20 18:22:29 -04:00
|
|
|
if err != nil {
|
2018-08-14 18:07:46 -04:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
return "", fmt.Errorf("failed to resolve scoped path %s (%s): %s. Possible cause is a forbidden path outside the build context", path, fullPath, err)
|
|
|
|
}
|
2017-03-20 18:22:29 -04:00
|
|
|
return "", fmt.Errorf("Forbidden path outside the build context: %s (%s)", path, fullPath) // backwards compat with old error
|
|
|
|
}
|
|
|
|
return fullPath, nil
|
|
|
|
}
|