package dockerclient import ( "fmt" "io" "io/ioutil" "net/http" "net/url" "os" "path" "path/filepath" "strings" ) type CopyInfo struct { os.FileInfo Path string Decompress bool FromDir bool } // CalcCopyInfo identifies the source files selected by a Dockerfile ADD or COPY instruction. func CalcCopyInfo(origPath, rootPath string, allowLocalDecompression, allowWildcards bool) ([]CopyInfo, error) { origPath = trimLeadingPath(origPath) // Deal with wildcards if allowWildcards && containsWildcards(origPath) { matchPath := filepath.Join(rootPath, origPath) var copyInfos []CopyInfo if err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.Name() == "" { // Why are we doing this check? return nil } if match, _ := filepath.Match(matchPath, path); !match { return nil } // Note we set allowWildcards to false in case the name has // a * in it subInfos, err := CalcCopyInfo(trimLeadingPath(strings.TrimPrefix(path, rootPath)), rootPath, allowLocalDecompression, false) if err != nil { return err } copyInfos = append(copyInfos, subInfos...) return nil }); err != nil { return nil, err } return copyInfos, nil } // flatten the root directory so we can rebase it if origPath == "." { var copyInfos []CopyInfo infos, err := ioutil.ReadDir(rootPath) if err != nil { return nil, err } for _, info := range infos { copyInfos = append(copyInfos, CopyInfo{FileInfo: info, Path: info.Name(), Decompress: allowLocalDecompression, FromDir: true}) } return copyInfos, nil } // Must be a dir or a file fi, err := os.Stat(filepath.Join(rootPath, origPath)) if err != nil { return nil, err } origPath = trimTrailingDot(origPath) return []CopyInfo{{FileInfo: fi, Path: origPath, Decompress: allowLocalDecompression}}, nil } func DownloadURL(src, dst string) ([]CopyInfo, string, error) { // get filename from URL u, err := url.Parse(src) if err != nil { return nil, "", err } base := path.Base(u.Path) if base == "." { return nil, "", fmt.Errorf("cannot determine filename from url: %s", u) } resp, err := http.Get(src) if err != nil { return nil, "", err } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, "", fmt.Errorf("server returned a status code >= 400: %s", resp.Status) } tmpDir, err := ioutil.TempDir("", "dockerbuildurl-") if err != nil { return nil, "", err } tmpFileName := filepath.Join(tmpDir, base) tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err != nil { os.RemoveAll(tmpDir) return nil, "", err } if _, err := io.Copy(tmpFile, resp.Body); err != nil { os.RemoveAll(tmpDir) return nil, "", err } if err := tmpFile.Close(); err != nil { os.RemoveAll(tmpDir) return nil, "", err } info, err := os.Stat(tmpFileName) if err != nil { os.RemoveAll(tmpDir) return nil, "", err } return []CopyInfo{{FileInfo: info, Path: base}}, tmpDir, nil } func trimLeadingPath(origPath string) string { // Work in daemon-specific OS filepath semantics origPath = filepath.FromSlash(origPath) if origPath != "" && origPath[0] == os.PathSeparator && len(origPath) > 1 { origPath = origPath[1:] } origPath = strings.TrimPrefix(origPath, "."+string(os.PathSeparator)) return origPath } func trimTrailingDot(origPath string) string { if strings.HasSuffix(origPath, string(os.PathSeparator)+".") { return strings.TrimSuffix(origPath, ".") } return origPath } // containsWildcards checks whether the provided name has a wildcard. func containsWildcards(name string) bool { for i := 0; i < len(name); i++ { ch := name[i] if ch == '\\' { i++ } else if ch == '*' || ch == '?' || ch == '[' { return true } } return false } // isURL returns true if the string appears to be a URL. func isURL(s string) bool { return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") }