Browse code

Move builder cli helper functions to own pkg

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2016/12/23 06:25:02
Showing 10 changed files
1 1
deleted file mode 100644
... ...
@@ -1,260 +0,0 @@
1
-package builder
2
-
3
-import (
4
-	"bufio"
5
-	"fmt"
6
-	"io"
7
-	"io/ioutil"
8
-	"os"
9
-	"os/exec"
10
-	"path/filepath"
11
-	"runtime"
12
-	"strings"
13
-
14
-	"github.com/docker/docker/pkg/archive"
15
-	"github.com/docker/docker/pkg/fileutils"
16
-	"github.com/docker/docker/pkg/gitutils"
17
-	"github.com/docker/docker/pkg/httputils"
18
-	"github.com/docker/docker/pkg/ioutils"
19
-	"github.com/docker/docker/pkg/progress"
20
-	"github.com/docker/docker/pkg/streamformatter"
21
-)
22
-
23
-// ValidateContextDirectory checks if all the contents of the directory
24
-// can be read and returns an error if some files can't be read
25
-// symlinks which point to non-existing files don't trigger an error
26
-func ValidateContextDirectory(srcPath string, excludes []string) error {
27
-	contextRoot, err := getContextRoot(srcPath)
28
-	if err != nil {
29
-		return err
30
-	}
31
-	return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error {
32
-		if err != nil {
33
-			if os.IsPermission(err) {
34
-				return fmt.Errorf("can't stat '%s'", filePath)
35
-			}
36
-			if os.IsNotExist(err) {
37
-				return nil
38
-			}
39
-			return err
40
-		}
41
-
42
-		// skip this directory/file if it's not in the path, it won't get added to the context
43
-		if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil {
44
-			return err
45
-		} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
46
-			return err
47
-		} else if skip {
48
-			if f.IsDir() {
49
-				return filepath.SkipDir
50
-			}
51
-			return nil
52
-		}
53
-
54
-		// skip checking if symlinks point to non-existing files, such symlinks can be useful
55
-		// also skip named pipes, because they hanging on open
56
-		if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
57
-			return nil
58
-		}
59
-
60
-		if !f.IsDir() {
61
-			currentFile, err := os.Open(filePath)
62
-			if err != nil && os.IsPermission(err) {
63
-				return fmt.Errorf("no permission to read from '%s'", filePath)
64
-			}
65
-			currentFile.Close()
66
-		}
67
-		return nil
68
-	})
69
-}
70
-
71
-// GetContextFromReader will read the contents of the given reader as either a
72
-// Dockerfile or tar archive. Returns a tar archive used as a context and a
73
-// path to the Dockerfile inside the tar.
74
-func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
75
-	buf := bufio.NewReader(r)
76
-
77
-	magic, err := buf.Peek(archive.HeaderSize)
78
-	if err != nil && err != io.EOF {
79
-		return nil, "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
80
-	}
81
-
82
-	if archive.IsArchive(magic) {
83
-		return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
84
-	}
85
-
86
-	// Input should be read as a Dockerfile.
87
-	tmpDir, err := ioutil.TempDir("", "docker-build-context-")
88
-	if err != nil {
89
-		return nil, "", fmt.Errorf("unbale to create temporary context directory: %v", err)
90
-	}
91
-
92
-	f, err := os.Create(filepath.Join(tmpDir, DefaultDockerfileName))
93
-	if err != nil {
94
-		return nil, "", err
95
-	}
96
-	_, err = io.Copy(f, buf)
97
-	if err != nil {
98
-		f.Close()
99
-		return nil, "", err
100
-	}
101
-
102
-	if err := f.Close(); err != nil {
103
-		return nil, "", err
104
-	}
105
-	if err := r.Close(); err != nil {
106
-		return nil, "", err
107
-	}
108
-
109
-	tar, err := archive.Tar(tmpDir, archive.Uncompressed)
110
-	if err != nil {
111
-		return nil, "", err
112
-	}
113
-
114
-	return ioutils.NewReadCloserWrapper(tar, func() error {
115
-		err := tar.Close()
116
-		os.RemoveAll(tmpDir)
117
-		return err
118
-	}), DefaultDockerfileName, nil
119
-
120
-}
121
-
122
-// GetContextFromGitURL uses a Git URL as context for a `docker build`. The
123
-// git repo is cloned into a temporary directory used as the context directory.
124
-// Returns the absolute path to the temporary context directory, the relative
125
-// path of the dockerfile in that context directory, and a non-nil error on
126
-// success.
127
-func GetContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
128
-	if _, err := exec.LookPath("git"); err != nil {
129
-		return "", "", fmt.Errorf("unable to find 'git': %v", err)
130
-	}
131
-	if absContextDir, err = gitutils.Clone(gitURL); err != nil {
132
-		return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
133
-	}
134
-
135
-	return getDockerfileRelPath(absContextDir, dockerfileName)
136
-}
137
-
138
-// GetContextFromURL uses a remote URL as context for a `docker build`. The
139
-// remote resource is downloaded as either a Dockerfile or a tar archive.
140
-// Returns the tar archive used for the context and a path of the
141
-// dockerfile inside the tar.
142
-func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
143
-	response, err := httputils.Download(remoteURL)
144
-	if err != nil {
145
-		return nil, "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
146
-	}
147
-	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true)
148
-
149
-	// Pass the response body through a progress reader.
150
-	progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
151
-
152
-	return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
153
-}
154
-
155
-// GetContextFromLocalDir uses the given local directory as context for a
156
-// `docker build`. Returns the absolute path to the local context directory,
157
-// the relative path of the dockerfile in that context directory, and a non-nil
158
-// error on success.
159
-func GetContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
160
-	// When using a local context directory, when the Dockerfile is specified
161
-	// with the `-f/--file` option then it is considered relative to the
162
-	// current directory and not the context directory.
163
-	if dockerfileName != "" {
164
-		if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
165
-			return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
166
-		}
167
-	}
168
-
169
-	return getDockerfileRelPath(localDir, dockerfileName)
170
-}
171
-
172
-// getDockerfileRelPath uses the given context directory for a `docker build`
173
-// and returns the absolute path to the context directory, the relative path of
174
-// the dockerfile in that context directory, and a non-nil error on success.
175
-func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
176
-	if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
177
-		return "", "", fmt.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err)
178
-	}
179
-
180
-	// The context dir might be a symbolic link, so follow it to the actual
181
-	// target directory.
182
-	//
183
-	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
184
-	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
185
-	// paths (those starting with \\). This hack means that when using links
186
-	// on UNC paths, they will not be followed.
187
-	if !isUNC(absContextDir) {
188
-		absContextDir, err = filepath.EvalSymlinks(absContextDir)
189
-		if err != nil {
190
-			return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
191
-		}
192
-	}
193
-
194
-	stat, err := os.Lstat(absContextDir)
195
-	if err != nil {
196
-		return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
197
-	}
198
-
199
-	if !stat.IsDir() {
200
-		return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
201
-	}
202
-
203
-	absDockerfile := givenDockerfile
204
-	if absDockerfile == "" {
205
-		// No -f/--file was specified so use the default relative to the
206
-		// context directory.
207
-		absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName)
208
-
209
-		// Just to be nice ;-) look for 'dockerfile' too but only
210
-		// use it if we found it, otherwise ignore this check
211
-		if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
212
-			altPath := filepath.Join(absContextDir, strings.ToLower(DefaultDockerfileName))
213
-			if _, err = os.Lstat(altPath); err == nil {
214
-				absDockerfile = altPath
215
-			}
216
-		}
217
-	}
218
-
219
-	// If not already an absolute path, the Dockerfile path should be joined to
220
-	// the base directory.
221
-	if !filepath.IsAbs(absDockerfile) {
222
-		absDockerfile = filepath.Join(absContextDir, absDockerfile)
223
-	}
224
-
225
-	// Evaluate symlinks in the path to the Dockerfile too.
226
-	//
227
-	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
228
-	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
229
-	// paths (those starting with \\). This hack means that when using links
230
-	// on UNC paths, they will not be followed.
231
-	if !isUNC(absDockerfile) {
232
-		absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
233
-		if err != nil {
234
-			return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
235
-		}
236
-	}
237
-
238
-	if _, err := os.Lstat(absDockerfile); err != nil {
239
-		if os.IsNotExist(err) {
240
-			return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
241
-		}
242
-		return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
243
-	}
244
-
245
-	if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
246
-		return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
247
-	}
248
-
249
-	if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
250
-		return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
251
-	}
252
-
253
-	return absContextDir, relDockerfile, nil
254
-}
255
-
256
-// isUNC returns true if the path is UNC (one starting \\). It always returns
257
-// false on Linux.
258
-func isUNC(path string) bool {
259
-	return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
260
-}
261 1
deleted file mode 100644
... ...
@@ -1,307 +0,0 @@
1
-package builder
2
-
3
-import (
4
-	"archive/tar"
5
-	"bytes"
6
-	"io"
7
-	"io/ioutil"
8
-	"path/filepath"
9
-	"runtime"
10
-	"strings"
11
-	"testing"
12
-
13
-	"github.com/docker/docker/pkg/archive"
14
-)
15
-
16
-var prepareEmpty = func(t *testing.T) (string, func()) {
17
-	return "", func() {}
18
-}
19
-
20
-var prepareNoFiles = func(t *testing.T) (string, func()) {
21
-	return createTestTempDir(t, "", "builder-context-test")
22
-}
23
-
24
-var prepareOneFile = func(t *testing.T) (string, func()) {
25
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
26
-	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
27
-	return contextDir, cleanup
28
-}
29
-
30
-func testValidateContextDirectory(t *testing.T, prepare func(t *testing.T) (string, func()), excludes []string) {
31
-	contextDir, cleanup := prepare(t)
32
-	defer cleanup()
33
-
34
-	err := ValidateContextDirectory(contextDir, excludes)
35
-
36
-	if err != nil {
37
-		t.Fatalf("Error should be nil, got: %s", err)
38
-	}
39
-}
40
-
41
-func TestGetContextFromLocalDirNoDockerfile(t *testing.T) {
42
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
43
-	defer cleanup()
44
-
45
-	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
46
-
47
-	if err == nil {
48
-		t.Fatalf("Error should not be nil")
49
-	}
50
-
51
-	if absContextDir != "" {
52
-		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
53
-	}
54
-
55
-	if relDockerfile != "" {
56
-		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
57
-	}
58
-}
59
-
60
-func TestGetContextFromLocalDirNotExistingDir(t *testing.T) {
61
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
62
-	defer cleanup()
63
-
64
-	fakePath := filepath.Join(contextDir, "fake")
65
-
66
-	absContextDir, relDockerfile, err := GetContextFromLocalDir(fakePath, "")
67
-
68
-	if err == nil {
69
-		t.Fatalf("Error should not be nil")
70
-	}
71
-
72
-	if absContextDir != "" {
73
-		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
74
-	}
75
-
76
-	if relDockerfile != "" {
77
-		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
78
-	}
79
-}
80
-
81
-func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
82
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
83
-	defer cleanup()
84
-
85
-	fakePath := filepath.Join(contextDir, "fake")
86
-
87
-	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, fakePath)
88
-
89
-	if err == nil {
90
-		t.Fatalf("Error should not be nil")
91
-	}
92
-
93
-	if absContextDir != "" {
94
-		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
95
-	}
96
-
97
-	if relDockerfile != "" {
98
-		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
99
-	}
100
-}
101
-
102
-func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
103
-	contextDir, dirCleanup := createTestTempDir(t, "", "builder-context-test")
104
-	defer dirCleanup()
105
-
106
-	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
107
-
108
-	chdirCleanup := chdir(t, contextDir)
109
-	defer chdirCleanup()
110
-
111
-	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
112
-
113
-	if err != nil {
114
-		t.Fatalf("Error when getting context from local dir: %s", err)
115
-	}
116
-
117
-	if absContextDir != contextDir {
118
-		t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir)
119
-	}
120
-
121
-	if relDockerfile != DefaultDockerfileName {
122
-		t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile)
123
-	}
124
-}
125
-
126
-func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
127
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
128
-	defer cleanup()
129
-
130
-	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
131
-
132
-	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
133
-
134
-	if err != nil {
135
-		t.Fatalf("Error when getting context from local dir: %s", err)
136
-	}
137
-
138
-	if absContextDir != contextDir {
139
-		t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir)
140
-	}
141
-
142
-	if relDockerfile != DefaultDockerfileName {
143
-		t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile)
144
-	}
145
-}
146
-
147
-func TestGetContextFromLocalDirLocalFile(t *testing.T) {
148
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
149
-	defer cleanup()
150
-
151
-	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
152
-	testFilename := createTestTempFile(t, contextDir, "tmpTest", "test", 0777)
153
-
154
-	absContextDir, relDockerfile, err := GetContextFromLocalDir(testFilename, "")
155
-
156
-	if err == nil {
157
-		t.Fatalf("Error should not be nil")
158
-	}
159
-
160
-	if absContextDir != "" {
161
-		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
162
-	}
163
-
164
-	if relDockerfile != "" {
165
-		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
166
-	}
167
-}
168
-
169
-func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) {
170
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
171
-	defer cleanup()
172
-
173
-	chdirCleanup := chdir(t, contextDir)
174
-	defer chdirCleanup()
175
-
176
-	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
177
-
178
-	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName)
179
-
180
-	if err != nil {
181
-		t.Fatalf("Error when getting context from local dir: %s", err)
182
-	}
183
-
184
-	if absContextDir != contextDir {
185
-		t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir)
186
-	}
187
-
188
-	if relDockerfile != DefaultDockerfileName {
189
-		t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile)
190
-	}
191
-
192
-}
193
-
194
-func TestGetContextFromReaderString(t *testing.T) {
195
-	tarArchive, relDockerfile, err := GetContextFromReader(ioutil.NopCloser(strings.NewReader(dockerfileContents)), "")
196
-
197
-	if err != nil {
198
-		t.Fatalf("Error when executing GetContextFromReader: %s", err)
199
-	}
200
-
201
-	tarReader := tar.NewReader(tarArchive)
202
-
203
-	_, err = tarReader.Next()
204
-
205
-	if err != nil {
206
-		t.Fatalf("Error when reading tar archive: %s", err)
207
-	}
208
-
209
-	buff := new(bytes.Buffer)
210
-	buff.ReadFrom(tarReader)
211
-	contents := buff.String()
212
-
213
-	_, err = tarReader.Next()
214
-
215
-	if err != io.EOF {
216
-		t.Fatalf("Tar stream too long: %s", err)
217
-	}
218
-
219
-	if err = tarArchive.Close(); err != nil {
220
-		t.Fatalf("Error when closing tar stream: %s", err)
221
-	}
222
-
223
-	if dockerfileContents != contents {
224
-		t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
225
-	}
226
-
227
-	if relDockerfile != DefaultDockerfileName {
228
-		t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
229
-	}
230
-}
231
-
232
-func TestGetContextFromReaderTar(t *testing.T) {
233
-	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
234
-	defer cleanup()
235
-
236
-	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
237
-
238
-	tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
239
-
240
-	if err != nil {
241
-		t.Fatalf("Error when creating tar: %s", err)
242
-	}
243
-
244
-	tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName)
245
-
246
-	if err != nil {
247
-		t.Fatalf("Error when executing GetContextFromReader: %s", err)
248
-	}
249
-
250
-	tarReader := tar.NewReader(tarArchive)
251
-
252
-	header, err := tarReader.Next()
253
-
254
-	if err != nil {
255
-		t.Fatalf("Error when reading tar archive: %s", err)
256
-	}
257
-
258
-	if header.Name != DefaultDockerfileName {
259
-		t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name)
260
-	}
261
-
262
-	buff := new(bytes.Buffer)
263
-	buff.ReadFrom(tarReader)
264
-	contents := buff.String()
265
-
266
-	_, err = tarReader.Next()
267
-
268
-	if err != io.EOF {
269
-		t.Fatalf("Tar stream too long: %s", err)
270
-	}
271
-
272
-	if err = tarArchive.Close(); err != nil {
273
-		t.Fatalf("Error when closing tar stream: %s", err)
274
-	}
275
-
276
-	if dockerfileContents != contents {
277
-		t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
278
-	}
279
-
280
-	if relDockerfile != DefaultDockerfileName {
281
-		t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
282
-	}
283
-}
284
-
285
-func TestValidateContextDirectoryEmptyContext(t *testing.T) {
286
-	// This isn't a valid test on Windows. See https://play.golang.org/p/RR6z6jxR81.
287
-	// The test will ultimately end up calling filepath.Abs(""). On Windows,
288
-	// golang will error. On Linux, golang will return /. Due to there being
289
-	// drive letters on Windows, this is probably the correct behaviour for
290
-	// Windows.
291
-	if runtime.GOOS == "windows" {
292
-		t.Skip("Invalid test on Windows")
293
-	}
294
-	testValidateContextDirectory(t, prepareEmpty, []string{})
295
-}
296
-
297
-func TestValidateContextDirectoryContextWithNoFiles(t *testing.T) {
298
-	testValidateContextDirectory(t, prepareNoFiles, []string{})
299
-}
300
-
301
-func TestValidateContextDirectoryWithOneFile(t *testing.T) {
302
-	testValidateContextDirectory(t, prepareOneFile, []string{})
303
-}
304
-
305
-func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) {
306
-	testValidateContextDirectory(t, prepareOneFile, []string{DefaultDockerfileName})
307
-}
308 1
deleted file mode 100644
... ...
@@ -1,11 +0,0 @@
1
-// +build !windows
2
-
3
-package builder
4
-
5
-import (
6
-	"path/filepath"
7
-)
8
-
9
-func getContextRoot(srcPath string) (string, error) {
10
-	return filepath.Join(srcPath, "."), nil
11
-}
12 1
deleted file mode 100644
... ...
@@ -1,17 +0,0 @@
1
-// +build windows
2
-
3
-package builder
4
-
5
-import (
6
-	"path/filepath"
7
-
8
-	"github.com/docker/docker/pkg/longpath"
9
-)
10
-
11
-func getContextRoot(srcPath string) (string, error) {
12
-	cr, err := filepath.Abs(srcPath)
13
-	if err != nil {
14
-		return "", err
15
-	}
16
-	return longpath.AddPrefix(cr), nil
17
-}
... ...
@@ -59,29 +59,3 @@ func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.Fi
59 59
 
60 60
 	return filePath
61 61
 }
62
-
63
-// chdir changes current working directory to dir.
64
-// It returns a function which changes working directory back to the previous one.
65
-// This function is meant to be executed as a deferred call.
66
-// When an error occurs, it terminates the test.
67
-func chdir(t *testing.T, dir string) func() {
68
-	workingDirectory, err := os.Getwd()
69
-
70
-	if err != nil {
71
-		t.Fatalf("Error when retrieving working directory: %s", err)
72
-	}
73
-
74
-	err = os.Chdir(dir)
75
-
76
-	if err != nil {
77
-		t.Fatalf("Error when changing directory to %s: %s", dir, err)
78
-	}
79
-
80
-	return func() {
81
-		err = os.Chdir(workingDirectory)
82
-
83
-		if err != nil {
84
-			t.Fatalf("Error when changing back to working directory (%s): %s", workingDirectory, err)
85
-		}
86
-	}
87
-}
... ...
@@ -16,10 +16,10 @@ import (
16 16
 	"github.com/docker/docker/api"
17 17
 	"github.com/docker/docker/api/types"
18 18
 	"github.com/docker/docker/api/types/container"
19
-	"github.com/docker/docker/builder"
20 19
 	"github.com/docker/docker/builder/dockerignore"
21 20
 	"github.com/docker/docker/cli"
22 21
 	"github.com/docker/docker/cli/command"
22
+	"github.com/docker/docker/cli/command/image/build"
23 23
 	"github.com/docker/docker/opts"
24 24
 	"github.com/docker/docker/pkg/archive"
25 25
 	"github.com/docker/docker/pkg/fileutils"
... ...
@@ -29,7 +29,7 @@ import (
29 29
 	"github.com/docker/docker/pkg/urlutil"
30 30
 	"github.com/docker/docker/reference"
31 31
 	runconfigopts "github.com/docker/docker/runconfig/opts"
32
-	"github.com/docker/go-units"
32
+	units "github.com/docker/go-units"
33 33
 	"github.com/spf13/cobra"
34 34
 )
35 35
 
... ...
@@ -156,13 +156,13 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
156 156
 
157 157
 	switch {
158 158
 	case specifiedContext == "-":
159
-		buildCtx, relDockerfile, err = builder.GetContextFromReader(dockerCli.In(), options.dockerfileName)
159
+		buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
160 160
 	case urlutil.IsGitURL(specifiedContext):
161
-		tempDir, relDockerfile, err = builder.GetContextFromGitURL(specifiedContext, options.dockerfileName)
161
+		tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName)
162 162
 	case urlutil.IsURL(specifiedContext):
163
-		buildCtx, relDockerfile, err = builder.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
163
+		buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
164 164
 	default:
165
-		contextDir, relDockerfile, err = builder.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
165
+		contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
166 166
 	}
167 167
 
168 168
 	if err != nil {
... ...
@@ -198,7 +198,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
198 198
 			}
199 199
 		}
200 200
 
201
-		if err := builder.ValidateContextDirectory(contextDir, excludes); err != nil {
201
+		if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
202 202
 			return fmt.Errorf("Error checking context: '%s'.", err)
203 203
 		}
204 204
 
205 205
new file mode 100644
... ...
@@ -0,0 +1,265 @@
0
+package build
1
+
2
+import (
3
+	"bufio"
4
+	"fmt"
5
+	"io"
6
+	"io/ioutil"
7
+	"os"
8
+	"os/exec"
9
+	"path/filepath"
10
+	"runtime"
11
+	"strings"
12
+
13
+	"github.com/docker/docker/pkg/archive"
14
+	"github.com/docker/docker/pkg/fileutils"
15
+	"github.com/docker/docker/pkg/gitutils"
16
+	"github.com/docker/docker/pkg/httputils"
17
+	"github.com/docker/docker/pkg/ioutils"
18
+	"github.com/docker/docker/pkg/progress"
19
+	"github.com/docker/docker/pkg/streamformatter"
20
+)
21
+
22
+const (
23
+	// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
24
+	DefaultDockerfileName string = "Dockerfile"
25
+)
26
+
27
+// ValidateContextDirectory checks if all the contents of the directory
28
+// can be read and returns an error if some files can't be read
29
+// symlinks which point to non-existing files don't trigger an error
30
+func ValidateContextDirectory(srcPath string, excludes []string) error {
31
+	contextRoot, err := getContextRoot(srcPath)
32
+	if err != nil {
33
+		return err
34
+	}
35
+	return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error {
36
+		if err != nil {
37
+			if os.IsPermission(err) {
38
+				return fmt.Errorf("can't stat '%s'", filePath)
39
+			}
40
+			if os.IsNotExist(err) {
41
+				return nil
42
+			}
43
+			return err
44
+		}
45
+
46
+		// skip this directory/file if it's not in the path, it won't get added to the context
47
+		if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil {
48
+			return err
49
+		} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
50
+			return err
51
+		} else if skip {
52
+			if f.IsDir() {
53
+				return filepath.SkipDir
54
+			}
55
+			return nil
56
+		}
57
+
58
+		// skip checking if symlinks point to non-existing files, such symlinks can be useful
59
+		// also skip named pipes, because they hanging on open
60
+		if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
61
+			return nil
62
+		}
63
+
64
+		if !f.IsDir() {
65
+			currentFile, err := os.Open(filePath)
66
+			if err != nil && os.IsPermission(err) {
67
+				return fmt.Errorf("no permission to read from '%s'", filePath)
68
+			}
69
+			currentFile.Close()
70
+		}
71
+		return nil
72
+	})
73
+}
74
+
75
+// GetContextFromReader will read the contents of the given reader as either a
76
+// Dockerfile or tar archive. Returns a tar archive used as a context and a
77
+// path to the Dockerfile inside the tar.
78
+func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
79
+	buf := bufio.NewReader(r)
80
+
81
+	magic, err := buf.Peek(archive.HeaderSize)
82
+	if err != nil && err != io.EOF {
83
+		return nil, "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
84
+	}
85
+
86
+	if archive.IsArchive(magic) {
87
+		return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
88
+	}
89
+
90
+	// Input should be read as a Dockerfile.
91
+	tmpDir, err := ioutil.TempDir("", "docker-build-context-")
92
+	if err != nil {
93
+		return nil, "", fmt.Errorf("unbale to create temporary context directory: %v", err)
94
+	}
95
+
96
+	f, err := os.Create(filepath.Join(tmpDir, DefaultDockerfileName))
97
+	if err != nil {
98
+		return nil, "", err
99
+	}
100
+	_, err = io.Copy(f, buf)
101
+	if err != nil {
102
+		f.Close()
103
+		return nil, "", err
104
+	}
105
+
106
+	if err := f.Close(); err != nil {
107
+		return nil, "", err
108
+	}
109
+	if err := r.Close(); err != nil {
110
+		return nil, "", err
111
+	}
112
+
113
+	tar, err := archive.Tar(tmpDir, archive.Uncompressed)
114
+	if err != nil {
115
+		return nil, "", err
116
+	}
117
+
118
+	return ioutils.NewReadCloserWrapper(tar, func() error {
119
+		err := tar.Close()
120
+		os.RemoveAll(tmpDir)
121
+		return err
122
+	}), DefaultDockerfileName, nil
123
+
124
+}
125
+
126
+// GetContextFromGitURL uses a Git URL as context for a `docker build`. The
127
+// git repo is cloned into a temporary directory used as the context directory.
128
+// Returns the absolute path to the temporary context directory, the relative
129
+// path of the dockerfile in that context directory, and a non-nil error on
130
+// success.
131
+func GetContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
132
+	if _, err := exec.LookPath("git"); err != nil {
133
+		return "", "", fmt.Errorf("unable to find 'git': %v", err)
134
+	}
135
+	if absContextDir, err = gitutils.Clone(gitURL); err != nil {
136
+		return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
137
+	}
138
+
139
+	return getDockerfileRelPath(absContextDir, dockerfileName)
140
+}
141
+
142
+// GetContextFromURL uses a remote URL as context for a `docker build`. The
143
+// remote resource is downloaded as either a Dockerfile or a tar archive.
144
+// Returns the tar archive used for the context and a path of the
145
+// dockerfile inside the tar.
146
+func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
147
+	response, err := httputils.Download(remoteURL)
148
+	if err != nil {
149
+		return nil, "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
150
+	}
151
+	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true)
152
+
153
+	// Pass the response body through a progress reader.
154
+	progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
155
+
156
+	return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
157
+}
158
+
159
+// GetContextFromLocalDir uses the given local directory as context for a
160
+// `docker build`. Returns the absolute path to the local context directory,
161
+// the relative path of the dockerfile in that context directory, and a non-nil
162
+// error on success.
163
+func GetContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
164
+	// When using a local context directory, when the Dockerfile is specified
165
+	// with the `-f/--file` option then it is considered relative to the
166
+	// current directory and not the context directory.
167
+	if dockerfileName != "" {
168
+		if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
169
+			return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
170
+		}
171
+	}
172
+
173
+	return getDockerfileRelPath(localDir, dockerfileName)
174
+}
175
+
176
+// getDockerfileRelPath uses the given context directory for a `docker build`
177
+// and returns the absolute path to the context directory, the relative path of
178
+// the dockerfile in that context directory, and a non-nil error on success.
179
+func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
180
+	if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
181
+		return "", "", fmt.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err)
182
+	}
183
+
184
+	// The context dir might be a symbolic link, so follow it to the actual
185
+	// target directory.
186
+	//
187
+	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
188
+	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
189
+	// paths (those starting with \\). This hack means that when using links
190
+	// on UNC paths, they will not be followed.
191
+	if !isUNC(absContextDir) {
192
+		absContextDir, err = filepath.EvalSymlinks(absContextDir)
193
+		if err != nil {
194
+			return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
195
+		}
196
+	}
197
+
198
+	stat, err := os.Lstat(absContextDir)
199
+	if err != nil {
200
+		return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
201
+	}
202
+
203
+	if !stat.IsDir() {
204
+		return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
205
+	}
206
+
207
+	absDockerfile := givenDockerfile
208
+	if absDockerfile == "" {
209
+		// No -f/--file was specified so use the default relative to the
210
+		// context directory.
211
+		absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName)
212
+
213
+		// Just to be nice ;-) look for 'dockerfile' too but only
214
+		// use it if we found it, otherwise ignore this check
215
+		if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
216
+			altPath := filepath.Join(absContextDir, strings.ToLower(DefaultDockerfileName))
217
+			if _, err = os.Lstat(altPath); err == nil {
218
+				absDockerfile = altPath
219
+			}
220
+		}
221
+	}
222
+
223
+	// If not already an absolute path, the Dockerfile path should be joined to
224
+	// the base directory.
225
+	if !filepath.IsAbs(absDockerfile) {
226
+		absDockerfile = filepath.Join(absContextDir, absDockerfile)
227
+	}
228
+
229
+	// Evaluate symlinks in the path to the Dockerfile too.
230
+	//
231
+	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
232
+	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
233
+	// paths (those starting with \\). This hack means that when using links
234
+	// on UNC paths, they will not be followed.
235
+	if !isUNC(absDockerfile) {
236
+		absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
237
+		if err != nil {
238
+			return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
239
+		}
240
+	}
241
+
242
+	if _, err := os.Lstat(absDockerfile); err != nil {
243
+		if os.IsNotExist(err) {
244
+			return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
245
+		}
246
+		return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
247
+	}
248
+
249
+	if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
250
+		return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
251
+	}
252
+
253
+	if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
254
+		return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
255
+	}
256
+
257
+	return absContextDir, relDockerfile, nil
258
+}
259
+
260
+// isUNC returns true if the path is UNC (one starting \\). It always returns
261
+// false on Linux.
262
+func isUNC(path string) bool {
263
+	return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
264
+}
0 265
new file mode 100644
... ...
@@ -0,0 +1,383 @@
0
+package build
1
+
2
+import (
3
+	"archive/tar"
4
+	"bytes"
5
+	"io"
6
+	"io/ioutil"
7
+	"os"
8
+	"path/filepath"
9
+	"runtime"
10
+	"strings"
11
+	"testing"
12
+
13
+	"github.com/docker/docker/pkg/archive"
14
+)
15
+
16
+const dockerfileContents = "FROM busybox"
17
+
18
+var prepareEmpty = func(t *testing.T) (string, func()) {
19
+	return "", func() {}
20
+}
21
+
22
+var prepareNoFiles = func(t *testing.T) (string, func()) {
23
+	return createTestTempDir(t, "", "builder-context-test")
24
+}
25
+
26
+var prepareOneFile = func(t *testing.T) (string, func()) {
27
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
28
+	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
29
+	return contextDir, cleanup
30
+}
31
+
32
+func testValidateContextDirectory(t *testing.T, prepare func(t *testing.T) (string, func()), excludes []string) {
33
+	contextDir, cleanup := prepare(t)
34
+	defer cleanup()
35
+
36
+	err := ValidateContextDirectory(contextDir, excludes)
37
+
38
+	if err != nil {
39
+		t.Fatalf("Error should be nil, got: %s", err)
40
+	}
41
+}
42
+
43
+func TestGetContextFromLocalDirNoDockerfile(t *testing.T) {
44
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
45
+	defer cleanup()
46
+
47
+	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
48
+
49
+	if err == nil {
50
+		t.Fatalf("Error should not be nil")
51
+	}
52
+
53
+	if absContextDir != "" {
54
+		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
55
+	}
56
+
57
+	if relDockerfile != "" {
58
+		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
59
+	}
60
+}
61
+
62
+func TestGetContextFromLocalDirNotExistingDir(t *testing.T) {
63
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
64
+	defer cleanup()
65
+
66
+	fakePath := filepath.Join(contextDir, "fake")
67
+
68
+	absContextDir, relDockerfile, err := GetContextFromLocalDir(fakePath, "")
69
+
70
+	if err == nil {
71
+		t.Fatalf("Error should not be nil")
72
+	}
73
+
74
+	if absContextDir != "" {
75
+		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
76
+	}
77
+
78
+	if relDockerfile != "" {
79
+		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
80
+	}
81
+}
82
+
83
+func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
84
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
85
+	defer cleanup()
86
+
87
+	fakePath := filepath.Join(contextDir, "fake")
88
+
89
+	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, fakePath)
90
+
91
+	if err == nil {
92
+		t.Fatalf("Error should not be nil")
93
+	}
94
+
95
+	if absContextDir != "" {
96
+		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
97
+	}
98
+
99
+	if relDockerfile != "" {
100
+		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
101
+	}
102
+}
103
+
104
+func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
105
+	contextDir, dirCleanup := createTestTempDir(t, "", "builder-context-test")
106
+	defer dirCleanup()
107
+
108
+	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
109
+
110
+	chdirCleanup := chdir(t, contextDir)
111
+	defer chdirCleanup()
112
+
113
+	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
114
+
115
+	if err != nil {
116
+		t.Fatalf("Error when getting context from local dir: %s", err)
117
+	}
118
+
119
+	if absContextDir != contextDir {
120
+		t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir)
121
+	}
122
+
123
+	if relDockerfile != DefaultDockerfileName {
124
+		t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile)
125
+	}
126
+}
127
+
128
+func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
129
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
130
+	defer cleanup()
131
+
132
+	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
133
+
134
+	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
135
+
136
+	if err != nil {
137
+		t.Fatalf("Error when getting context from local dir: %s", err)
138
+	}
139
+
140
+	if absContextDir != contextDir {
141
+		t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir)
142
+	}
143
+
144
+	if relDockerfile != DefaultDockerfileName {
145
+		t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile)
146
+	}
147
+}
148
+
149
+func TestGetContextFromLocalDirLocalFile(t *testing.T) {
150
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
151
+	defer cleanup()
152
+
153
+	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
154
+	testFilename := createTestTempFile(t, contextDir, "tmpTest", "test", 0777)
155
+
156
+	absContextDir, relDockerfile, err := GetContextFromLocalDir(testFilename, "")
157
+
158
+	if err == nil {
159
+		t.Fatalf("Error should not be nil")
160
+	}
161
+
162
+	if absContextDir != "" {
163
+		t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir)
164
+	}
165
+
166
+	if relDockerfile != "" {
167
+		t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile)
168
+	}
169
+}
170
+
171
+func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) {
172
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
173
+	defer cleanup()
174
+
175
+	chdirCleanup := chdir(t, contextDir)
176
+	defer chdirCleanup()
177
+
178
+	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
179
+
180
+	absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName)
181
+
182
+	if err != nil {
183
+		t.Fatalf("Error when getting context from local dir: %s", err)
184
+	}
185
+
186
+	if absContextDir != contextDir {
187
+		t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir)
188
+	}
189
+
190
+	if relDockerfile != DefaultDockerfileName {
191
+		t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile)
192
+	}
193
+
194
+}
195
+
196
+func TestGetContextFromReaderString(t *testing.T) {
197
+	tarArchive, relDockerfile, err := GetContextFromReader(ioutil.NopCloser(strings.NewReader(dockerfileContents)), "")
198
+
199
+	if err != nil {
200
+		t.Fatalf("Error when executing GetContextFromReader: %s", err)
201
+	}
202
+
203
+	tarReader := tar.NewReader(tarArchive)
204
+
205
+	_, err = tarReader.Next()
206
+
207
+	if err != nil {
208
+		t.Fatalf("Error when reading tar archive: %s", err)
209
+	}
210
+
211
+	buff := new(bytes.Buffer)
212
+	buff.ReadFrom(tarReader)
213
+	contents := buff.String()
214
+
215
+	_, err = tarReader.Next()
216
+
217
+	if err != io.EOF {
218
+		t.Fatalf("Tar stream too long: %s", err)
219
+	}
220
+
221
+	if err = tarArchive.Close(); err != nil {
222
+		t.Fatalf("Error when closing tar stream: %s", err)
223
+	}
224
+
225
+	if dockerfileContents != contents {
226
+		t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
227
+	}
228
+
229
+	if relDockerfile != DefaultDockerfileName {
230
+		t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
231
+	}
232
+}
233
+
234
+func TestGetContextFromReaderTar(t *testing.T) {
235
+	contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
236
+	defer cleanup()
237
+
238
+	createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
239
+
240
+	tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
241
+
242
+	if err != nil {
243
+		t.Fatalf("Error when creating tar: %s", err)
244
+	}
245
+
246
+	tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName)
247
+
248
+	if err != nil {
249
+		t.Fatalf("Error when executing GetContextFromReader: %s", err)
250
+	}
251
+
252
+	tarReader := tar.NewReader(tarArchive)
253
+
254
+	header, err := tarReader.Next()
255
+
256
+	if err != nil {
257
+		t.Fatalf("Error when reading tar archive: %s", err)
258
+	}
259
+
260
+	if header.Name != DefaultDockerfileName {
261
+		t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name)
262
+	}
263
+
264
+	buff := new(bytes.Buffer)
265
+	buff.ReadFrom(tarReader)
266
+	contents := buff.String()
267
+
268
+	_, err = tarReader.Next()
269
+
270
+	if err != io.EOF {
271
+		t.Fatalf("Tar stream too long: %s", err)
272
+	}
273
+
274
+	if err = tarArchive.Close(); err != nil {
275
+		t.Fatalf("Error when closing tar stream: %s", err)
276
+	}
277
+
278
+	if dockerfileContents != contents {
279
+		t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
280
+	}
281
+
282
+	if relDockerfile != DefaultDockerfileName {
283
+		t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
284
+	}
285
+}
286
+
287
+func TestValidateContextDirectoryEmptyContext(t *testing.T) {
288
+	// This isn't a valid test on Windows. See https://play.golang.org/p/RR6z6jxR81.
289
+	// The test will ultimately end up calling filepath.Abs(""). On Windows,
290
+	// golang will error. On Linux, golang will return /. Due to there being
291
+	// drive letters on Windows, this is probably the correct behaviour for
292
+	// Windows.
293
+	if runtime.GOOS == "windows" {
294
+		t.Skip("Invalid test on Windows")
295
+	}
296
+	testValidateContextDirectory(t, prepareEmpty, []string{})
297
+}
298
+
299
+func TestValidateContextDirectoryContextWithNoFiles(t *testing.T) {
300
+	testValidateContextDirectory(t, prepareNoFiles, []string{})
301
+}
302
+
303
+func TestValidateContextDirectoryWithOneFile(t *testing.T) {
304
+	testValidateContextDirectory(t, prepareOneFile, []string{})
305
+}
306
+
307
+func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) {
308
+	testValidateContextDirectory(t, prepareOneFile, []string{DefaultDockerfileName})
309
+}
310
+
311
+// createTestTempDir creates a temporary directory for testing.
312
+// It returns the created path and a cleanup function which is meant to be used as deferred call.
313
+// When an error occurs, it terminates the test.
314
+func createTestTempDir(t *testing.T, dir, prefix string) (string, func()) {
315
+	path, err := ioutil.TempDir(dir, prefix)
316
+
317
+	if err != nil {
318
+		t.Fatalf("Error when creating directory %s with prefix %s: %s", dir, prefix, err)
319
+	}
320
+
321
+	return path, func() {
322
+		err = os.RemoveAll(path)
323
+
324
+		if err != nil {
325
+			t.Fatalf("Error when removing directory %s: %s", path, err)
326
+		}
327
+	}
328
+}
329
+
330
+// createTestTempSubdir creates a temporary directory for testing.
331
+// It returns the created path but doesn't provide a cleanup function,
332
+// so createTestTempSubdir should be used only for creating temporary subdirectories
333
+// whose parent directories are properly cleaned up.
334
+// When an error occurs, it terminates the test.
335
+func createTestTempSubdir(t *testing.T, dir, prefix string) string {
336
+	path, err := ioutil.TempDir(dir, prefix)
337
+
338
+	if err != nil {
339
+		t.Fatalf("Error when creating directory %s with prefix %s: %s", dir, prefix, err)
340
+	}
341
+
342
+	return path
343
+}
344
+
345
+// createTestTempFile creates a temporary file within dir with specific contents and permissions.
346
+// When an error occurs, it terminates the test
347
+func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.FileMode) string {
348
+	filePath := filepath.Join(dir, filename)
349
+	err := ioutil.WriteFile(filePath, []byte(contents), perm)
350
+
351
+	if err != nil {
352
+		t.Fatalf("Error when creating %s file: %s", filename, err)
353
+	}
354
+
355
+	return filePath
356
+}
357
+
358
+// chdir changes current working directory to dir.
359
+// It returns a function which changes working directory back to the previous one.
360
+// This function is meant to be executed as a deferred call.
361
+// When an error occurs, it terminates the test.
362
+func chdir(t *testing.T, dir string) func() {
363
+	workingDirectory, err := os.Getwd()
364
+
365
+	if err != nil {
366
+		t.Fatalf("Error when retrieving working directory: %s", err)
367
+	}
368
+
369
+	err = os.Chdir(dir)
370
+
371
+	if err != nil {
372
+		t.Fatalf("Error when changing directory to %s: %s", dir, err)
373
+	}
374
+
375
+	return func() {
376
+		err = os.Chdir(workingDirectory)
377
+
378
+		if err != nil {
379
+			t.Fatalf("Error when changing back to working directory (%s): %s", workingDirectory, err)
380
+		}
381
+	}
382
+}
0 383
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+// +build !windows
1
+
2
+package build
3
+
4
+import (
5
+	"path/filepath"
6
+)
7
+
8
+func getContextRoot(srcPath string) (string, error) {
9
+	return filepath.Join(srcPath, "."), nil
10
+}
0 11
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+// +build windows
1
+
2
+package build
3
+
4
+import (
5
+	"path/filepath"
6
+
7
+	"github.com/docker/docker/pkg/longpath"
8
+)
9
+
10
+func getContextRoot(srcPath string) (string, error) {
11
+	cr, err := filepath.Abs(srcPath)
12
+	if err != nil {
13
+		return "", err
14
+	}
15
+	return longpath.AddPrefix(cr), nil
16
+}