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