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