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