github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/builder/remotecontext/detect.go (about)

     1  package remotecontext // import "github.com/docker/docker/builder/remotecontext"
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"github.com/containerd/continuity/driver"
    12  	"github.com/docker/docker/api/types/backend"
    13  	"github.com/docker/docker/builder"
    14  	"github.com/docker/docker/builder/dockerignore"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/docker/docker/pkg/fileutils"
    17  	"github.com/docker/docker/pkg/urlutil"
    18  	"github.com/moby/buildkit/frontend/dockerfile/parser"
    19  	"github.com/pkg/errors"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  // ClientSessionRemote is identifier for client-session context transport
    24  const ClientSessionRemote = "client-session"
    25  
    26  // Detect returns a context and dockerfile from remote location or local
    27  // archive.
    28  func Detect(config backend.BuildConfig) (remote builder.Source, dockerfile *parser.Result, err error) {
    29  	remoteURL := config.Options.RemoteContext
    30  	dockerfilePath := config.Options.Dockerfile
    31  
    32  	switch {
    33  	case remoteURL == "":
    34  		remote, dockerfile, err = newArchiveRemote(config.Source, dockerfilePath)
    35  	case remoteURL == ClientSessionRemote:
    36  		res, err := parser.Parse(config.Source)
    37  		if err != nil {
    38  			return nil, nil, errdefs.InvalidParameter(err)
    39  		}
    40  
    41  		return nil, res, nil
    42  	case urlutil.IsGitURL(remoteURL):
    43  		remote, dockerfile, err = newGitRemote(remoteURL, dockerfilePath)
    44  	case urlutil.IsURL(remoteURL):
    45  		remote, dockerfile, err = newURLRemote(remoteURL, dockerfilePath, config.ProgressWriter.ProgressReaderFunc)
    46  	default:
    47  		err = fmt.Errorf("remoteURL (%s) could not be recognized as URL", remoteURL)
    48  	}
    49  	return
    50  }
    51  
    52  func newArchiveRemote(rc io.ReadCloser, dockerfilePath string) (builder.Source, *parser.Result, error) {
    53  	defer rc.Close()
    54  	c, err := FromArchive(rc)
    55  	if err != nil {
    56  		return nil, nil, err
    57  	}
    58  
    59  	return withDockerfileFromContext(c.(modifiableContext), dockerfilePath)
    60  }
    61  
    62  func withDockerfileFromContext(c modifiableContext, dockerfilePath string) (builder.Source, *parser.Result, error) {
    63  	df, err := openAt(c, dockerfilePath)
    64  	if err != nil {
    65  		if errors.Is(err, os.ErrNotExist) {
    66  			if dockerfilePath == builder.DefaultDockerfileName {
    67  				lowercase := strings.ToLower(dockerfilePath)
    68  				if _, err := StatAt(c, lowercase); err == nil {
    69  					return withDockerfileFromContext(c, lowercase)
    70  				}
    71  			}
    72  			return nil, nil, errors.Errorf("Cannot locate specified Dockerfile: %s", dockerfilePath) // backwards compatible error
    73  		}
    74  		c.Close()
    75  		return nil, nil, err
    76  	}
    77  
    78  	res, err := readAndParseDockerfile(dockerfilePath, df)
    79  	if err != nil {
    80  		return nil, nil, err
    81  	}
    82  
    83  	df.Close()
    84  
    85  	if err := removeDockerfile(c, dockerfilePath); err != nil {
    86  		c.Close()
    87  		return nil, nil, err
    88  	}
    89  
    90  	return c, res, nil
    91  }
    92  
    93  func newGitRemote(gitURL string, dockerfilePath string) (builder.Source, *parser.Result, error) {
    94  	c, err := MakeGitContext(gitURL) // TODO: change this to NewLazySource
    95  	if err != nil {
    96  		return nil, nil, err
    97  	}
    98  	return withDockerfileFromContext(c.(modifiableContext), dockerfilePath)
    99  }
   100  
   101  func newURLRemote(url string, dockerfilePath string, progressReader func(in io.ReadCloser) io.ReadCloser) (builder.Source, *parser.Result, error) {
   102  	contentType, content, err := downloadRemote(url)
   103  	if err != nil {
   104  		return nil, nil, err
   105  	}
   106  	defer content.Close()
   107  
   108  	switch contentType {
   109  	case mimeTypes.TextPlain:
   110  		res, err := parser.Parse(progressReader(content))
   111  		return nil, res, errdefs.InvalidParameter(err)
   112  	default:
   113  		source, err := FromArchive(progressReader(content))
   114  		if err != nil {
   115  			return nil, nil, err
   116  		}
   117  		return withDockerfileFromContext(source.(modifiableContext), dockerfilePath)
   118  	}
   119  }
   120  
   121  func removeDockerfile(c modifiableContext, filesToRemove ...string) error {
   122  	f, err := openAt(c, ".dockerignore")
   123  	// Note that a missing .dockerignore file isn't treated as an error
   124  	switch {
   125  	case os.IsNotExist(err):
   126  		return nil
   127  	case err != nil:
   128  		return err
   129  	}
   130  	excludes, err := dockerignore.ReadAll(f)
   131  	if err != nil {
   132  		f.Close()
   133  		return err
   134  	}
   135  	f.Close()
   136  	filesToRemove = append([]string{".dockerignore"}, filesToRemove...)
   137  	for _, fileToRemove := range filesToRemove {
   138  		if rm, _ := fileutils.Matches(fileToRemove, excludes); rm {
   139  			if err := c.Remove(fileToRemove); err != nil {
   140  				logrus.Errorf("failed to remove %s: %v", fileToRemove, err)
   141  			}
   142  		}
   143  	}
   144  	return nil
   145  }
   146  
   147  func readAndParseDockerfile(name string, rc io.Reader) (*parser.Result, error) {
   148  	br := bufio.NewReader(rc)
   149  	if _, err := br.Peek(1); err != nil {
   150  		if err == io.EOF {
   151  			return nil, errdefs.InvalidParameter(errors.Errorf("the Dockerfile (%s) cannot be empty", name))
   152  		}
   153  		return nil, errors.Wrap(err, "unexpected error reading Dockerfile")
   154  	}
   155  
   156  	dockerfile, err := parser.Parse(br)
   157  	if err != nil {
   158  		return nil, errdefs.InvalidParameter(errors.Wrapf(err, "failed to parse %s", name))
   159  	}
   160  
   161  	return dockerfile, nil
   162  }
   163  
   164  func openAt(remote builder.Source, path string) (driver.File, error) {
   165  	fullPath, err := FullPath(remote, path)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	return remote.Root().Open(fullPath)
   170  }
   171  
   172  // StatAt is a helper for calling Stat on a path from a source
   173  func StatAt(remote builder.Source, path string) (os.FileInfo, error) {
   174  	fullPath, err := FullPath(remote, path)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	return remote.Root().Stat(fullPath)
   179  }
   180  
   181  // FullPath is a helper for getting a full path for a path from a source
   182  func FullPath(remote builder.Source, path string) (string, error) {
   183  	fullPath, err := remote.Root().ResolveScopedPath(path, true)
   184  	if err != nil {
   185  		if runtime.GOOS == "windows" {
   186  			return "", fmt.Errorf("failed to resolve scoped path %s (%s): %s. Possible cause is a forbidden path outside the build context", path, fullPath, err)
   187  		}
   188  		return "", fmt.Errorf("Forbidden path outside the build context: %s (%s)", path, fullPath) // backwards compat with old error
   189  	}
   190  	return fullPath, nil
   191  }