mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
a74cc83345
The test was passing previously because the preamble was already buffered. After the change to return Scanner.Err() the final read error on the buffer was no longer being ignored. Signed-off-by: Daniel Nephin <dnephin@docker.com>
180 lines
5.2 KiB
Go
180 lines
5.2 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) {
|
|
contentType, content, err := downloadRemote(url)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|