github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/builder/remotecontext/detect.go (about)

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