github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/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 }