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 }