]>
Commit | Line | Data |
---|---|---|
9b12e4fe JC |
1 | // Copyright 2015 go-dockerclient authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style | |
3 | // license that can be found in the LICENSE file. | |
4 | ||
5 | package docker | |
6 | ||
7 | import ( | |
8 | "fmt" | |
9 | "io" | |
10 | "io/ioutil" | |
11 | "os" | |
12 | "path" | |
13 | "path/filepath" | |
14 | "strings" | |
15 | ||
16 | "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive" | |
17 | "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils" | |
18 | ) | |
19 | ||
20 | func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) { | |
21 | excludes, err := parseDockerignore(srcPath) | |
22 | if err != nil { | |
23 | return nil, err | |
24 | } | |
25 | ||
26 | includes := []string{"."} | |
27 | ||
28 | // If .dockerignore mentions .dockerignore or the Dockerfile | |
29 | // then make sure we send both files over to the daemon | |
30 | // because Dockerfile is, obviously, needed no matter what, and | |
31 | // .dockerignore is needed to know if either one needs to be | |
32 | // removed. The deamon will remove them for us, if needed, after it | |
33 | // parses the Dockerfile. | |
34 | // | |
35 | // https://github.com/docker/docker/issues/8330 | |
36 | // | |
37 | forceIncludeFiles := []string{".dockerignore", dockerfilePath} | |
38 | ||
39 | for _, includeFile := range forceIncludeFiles { | |
40 | if includeFile == "" { | |
41 | continue | |
42 | } | |
43 | keepThem, err := fileutils.Matches(includeFile, excludes) | |
44 | if err != nil { | |
45 | return nil, fmt.Errorf("cannot match .dockerfile: '%s', error: %s", includeFile, err) | |
46 | } | |
47 | if keepThem { | |
48 | includes = append(includes, includeFile) | |
49 | } | |
50 | } | |
51 | ||
52 | if err := validateContextDirectory(srcPath, excludes); err != nil { | |
53 | return nil, err | |
54 | } | |
55 | tarOpts := &archive.TarOptions{ | |
56 | ExcludePatterns: excludes, | |
57 | IncludeFiles: includes, | |
58 | Compression: archive.Uncompressed, | |
59 | NoLchown: true, | |
60 | } | |
61 | return archive.TarWithOptions(srcPath, tarOpts) | |
62 | } | |
63 | ||
64 | // validateContextDirectory checks if all the contents of the directory | |
65 | // can be read and returns an error if some files can't be read. | |
66 | // Symlinks which point to non-existing files don't trigger an error | |
67 | func validateContextDirectory(srcPath string, excludes []string) error { | |
68 | return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error { | |
69 | // skip this directory/file if it's not in the path, it won't get added to the context | |
70 | if relFilePath, err := filepath.Rel(srcPath, filePath); err != nil { | |
71 | return err | |
72 | } else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil { | |
73 | return err | |
74 | } else if skip { | |
75 | if f.IsDir() { | |
76 | return filepath.SkipDir | |
77 | } | |
78 | return nil | |
79 | } | |
80 | ||
81 | if err != nil { | |
82 | if os.IsPermission(err) { | |
83 | return fmt.Errorf("can't stat '%s'", filePath) | |
84 | } | |
85 | if os.IsNotExist(err) { | |
86 | return nil | |
87 | } | |
88 | return err | |
89 | } | |
90 | ||
91 | // skip checking if symlinks point to non-existing files, such symlinks can be useful | |
92 | // also skip named pipes, because they hanging on open | |
93 | if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 { | |
94 | return nil | |
95 | } | |
96 | ||
97 | if !f.IsDir() { | |
98 | currentFile, err := os.Open(filePath) | |
99 | if err != nil && os.IsPermission(err) { | |
100 | return fmt.Errorf("no permission to read from '%s'", filePath) | |
101 | } | |
102 | currentFile.Close() | |
103 | } | |
104 | return nil | |
105 | }) | |
106 | } | |
107 | ||
108 | func parseDockerignore(root string) ([]string, error) { | |
109 | var excludes []string | |
110 | ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore")) | |
111 | if err != nil && !os.IsNotExist(err) { | |
112 | return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err) | |
113 | } | |
114 | excludes = strings.Split(string(ignore), "\n") | |
115 | ||
116 | return excludes, nil | |
117 | } |