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