github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/command/image/build/context_test.go (about) 1 package build 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "testing" 13 14 "github.com/docker/docker/pkg/archive" 15 "github.com/docker/docker/pkg/fileutils" 16 "gotest.tools/assert" 17 is "gotest.tools/assert/cmp" 18 ) 19 20 const dockerfileContents = "FROM busybox" 21 22 var prepareEmpty = func(t *testing.T) (string, func()) { 23 return "", func() {} 24 } 25 26 var prepareNoFiles = func(t *testing.T) (string, func()) { 27 return createTestTempDir(t, "", "builder-context-test") 28 } 29 30 var prepareOneFile = func(t *testing.T) (string, func()) { 31 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 32 createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) 33 return contextDir, cleanup 34 } 35 36 func testValidateContextDirectory(t *testing.T, prepare func(t *testing.T) (string, func()), excludes []string) { 37 contextDir, cleanup := prepare(t) 38 defer cleanup() 39 40 err := ValidateContextDirectory(contextDir, excludes) 41 assert.NilError(t, err) 42 } 43 44 func TestGetContextFromLocalDirNoDockerfile(t *testing.T) { 45 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 46 defer cleanup() 47 48 _, _, err := GetContextFromLocalDir(contextDir, "") 49 assert.ErrorContains(t, err, "Dockerfile") 50 } 51 52 func TestGetContextFromLocalDirNotExistingDir(t *testing.T) { 53 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 54 defer cleanup() 55 56 fakePath := filepath.Join(contextDir, "fake") 57 58 _, _, err := GetContextFromLocalDir(fakePath, "") 59 assert.ErrorContains(t, err, "fake") 60 } 61 62 func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) { 63 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 64 defer cleanup() 65 66 fakePath := filepath.Join(contextDir, "fake") 67 68 _, _, err := GetContextFromLocalDir(contextDir, fakePath) 69 assert.ErrorContains(t, err, "fake") 70 } 71 72 func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) { 73 contextDir, dirCleanup := createTestTempDir(t, "", "builder-context-test") 74 defer dirCleanup() 75 76 createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) 77 78 chdirCleanup := chdir(t, contextDir) 79 defer chdirCleanup() 80 81 absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "") 82 assert.NilError(t, err) 83 84 assert.Check(t, is.Equal(contextDir, absContextDir)) 85 assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile)) 86 } 87 88 func TestGetContextFromLocalDirWithDockerfile(t *testing.T) { 89 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 90 defer cleanup() 91 92 createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) 93 94 absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "") 95 assert.NilError(t, err) 96 97 assert.Check(t, is.Equal(contextDir, absContextDir)) 98 assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile)) 99 } 100 101 func TestGetContextFromLocalDirLocalFile(t *testing.T) { 102 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 103 defer cleanup() 104 105 createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) 106 testFilename := createTestTempFile(t, contextDir, "tmpTest", "test", 0777) 107 108 absContextDir, relDockerfile, err := GetContextFromLocalDir(testFilename, "") 109 110 if err == nil { 111 t.Fatalf("Error should not be nil") 112 } 113 114 if absContextDir != "" { 115 t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir) 116 } 117 118 if relDockerfile != "" { 119 t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile) 120 } 121 } 122 123 func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) { 124 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 125 defer cleanup() 126 127 chdirCleanup := chdir(t, contextDir) 128 defer chdirCleanup() 129 130 createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) 131 132 absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName) 133 assert.NilError(t, err) 134 135 assert.Check(t, is.Equal(contextDir, absContextDir)) 136 assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile)) 137 } 138 139 func TestGetContextFromReaderString(t *testing.T) { 140 tarArchive, relDockerfile, err := GetContextFromReader(ioutil.NopCloser(strings.NewReader(dockerfileContents)), "") 141 142 if err != nil { 143 t.Fatalf("Error when executing GetContextFromReader: %s", err) 144 } 145 146 tarReader := tar.NewReader(tarArchive) 147 148 _, err = tarReader.Next() 149 150 if err != nil { 151 t.Fatalf("Error when reading tar archive: %s", err) 152 } 153 154 buff := new(bytes.Buffer) 155 buff.ReadFrom(tarReader) 156 contents := buff.String() 157 158 _, err = tarReader.Next() 159 160 if err != io.EOF { 161 t.Fatalf("Tar stream too long: %s", err) 162 } 163 164 assert.NilError(t, tarArchive.Close()) 165 166 if dockerfileContents != contents { 167 t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents) 168 } 169 170 if relDockerfile != DefaultDockerfileName { 171 t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile) 172 } 173 } 174 175 func TestGetContextFromReaderTar(t *testing.T) { 176 contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") 177 defer cleanup() 178 179 createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) 180 181 tarStream, err := archive.Tar(contextDir, archive.Uncompressed) 182 assert.NilError(t, err) 183 184 tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName) 185 assert.NilError(t, err) 186 187 tarReader := tar.NewReader(tarArchive) 188 189 header, err := tarReader.Next() 190 assert.NilError(t, err) 191 192 if header.Name != DefaultDockerfileName { 193 t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name) 194 } 195 196 buff := new(bytes.Buffer) 197 buff.ReadFrom(tarReader) 198 contents := buff.String() 199 200 _, err = tarReader.Next() 201 202 if err != io.EOF { 203 t.Fatalf("Tar stream too long: %s", err) 204 } 205 206 assert.NilError(t, tarArchive.Close()) 207 208 if dockerfileContents != contents { 209 t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents) 210 } 211 212 if relDockerfile != DefaultDockerfileName { 213 t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile) 214 } 215 } 216 217 func TestValidateContextDirectoryEmptyContext(t *testing.T) { 218 // This isn't a valid test on Windows. See https://play.golang.org/p/RR6z6jxR81. 219 // The test will ultimately end up calling filepath.Abs(""). On Windows, 220 // golang will error. On Linux, golang will return /. Due to there being 221 // drive letters on Windows, this is probably the correct behaviour for 222 // Windows. 223 if runtime.GOOS == "windows" { 224 t.Skip("Invalid test on Windows") 225 } 226 testValidateContextDirectory(t, prepareEmpty, []string{}) 227 } 228 229 func TestValidateContextDirectoryContextWithNoFiles(t *testing.T) { 230 testValidateContextDirectory(t, prepareNoFiles, []string{}) 231 } 232 233 func TestValidateContextDirectoryWithOneFile(t *testing.T) { 234 testValidateContextDirectory(t, prepareOneFile, []string{}) 235 } 236 237 func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) { 238 testValidateContextDirectory(t, prepareOneFile, []string{DefaultDockerfileName}) 239 } 240 241 // createTestTempDir creates a temporary directory for testing. 242 // It returns the created path and a cleanup function which is meant to be used as deferred call. 243 // When an error occurs, it terminates the test. 244 func createTestTempDir(t *testing.T, dir, prefix string) (string, func()) { 245 path, err := ioutil.TempDir(dir, prefix) 246 assert.NilError(t, err) 247 return path, func() { assert.NilError(t, os.RemoveAll(path)) } 248 } 249 250 // createTestTempFile creates a temporary file within dir with specific contents and permissions. 251 // When an error occurs, it terminates the test 252 func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.FileMode) string { 253 filePath := filepath.Join(dir, filename) 254 err := ioutil.WriteFile(filePath, []byte(contents), perm) 255 assert.NilError(t, err) 256 return filePath 257 } 258 259 // chdir changes current working directory to dir. 260 // It returns a function which changes working directory back to the previous one. 261 // This function is meant to be executed as a deferred call. 262 // When an error occurs, it terminates the test. 263 func chdir(t *testing.T, dir string) func() { 264 workingDirectory, err := os.Getwd() 265 assert.NilError(t, err) 266 assert.NilError(t, os.Chdir(dir)) 267 return func() { assert.NilError(t, os.Chdir(workingDirectory)) } 268 } 269 270 func TestIsArchive(t *testing.T) { 271 var testcases = []struct { 272 doc string 273 header []byte 274 expected bool 275 }{ 276 { 277 doc: "nil is not a valid header", 278 header: nil, 279 expected: false, 280 }, 281 { 282 doc: "invalid header bytes", 283 header: []byte{0x00, 0x01, 0x02}, 284 expected: false, 285 }, 286 { 287 doc: "header for bzip2 archive", 288 header: []byte{0x42, 0x5A, 0x68}, 289 expected: true, 290 }, 291 { 292 doc: "header for 7zip archive is not supported", 293 header: []byte{0x50, 0x4b, 0x03, 0x04}, 294 expected: false, 295 }, 296 } 297 for _, testcase := range testcases { 298 assert.Check(t, is.Equal(testcase.expected, IsArchive(testcase.header)), testcase.doc) 299 } 300 } 301 302 func TestDetectArchiveReader(t *testing.T) { 303 var testcases = []struct { 304 file string 305 desc string 306 expected bool 307 }{ 308 { 309 file: "../testdata/tar.test", 310 desc: "tar file without pax headers", 311 expected: true, 312 }, 313 { 314 file: "../testdata/gittar.test", 315 desc: "tar file with pax headers", 316 expected: true, 317 }, 318 { 319 file: "../testdata/Dockerfile.test", 320 desc: "not a tar file", 321 expected: false, 322 }, 323 } 324 for _, testcase := range testcases { 325 content, err := os.Open(testcase.file) 326 assert.NilError(t, err) 327 defer content.Close() 328 329 _, isArchive, err := DetectArchiveReader(content) 330 assert.NilError(t, err) 331 assert.Check(t, is.Equal(testcase.expected, isArchive), testcase.file) 332 } 333 } 334 335 func mustPatternMatcher(t *testing.T, patterns []string) *fileutils.PatternMatcher { 336 t.Helper() 337 pm, err := fileutils.NewPatternMatcher(patterns) 338 if err != nil { 339 t.Fatal("failed to construct pattern matcher: ", err) 340 } 341 return pm 342 } 343 344 func TestWildcardMatches(t *testing.T) { 345 match, _ := filepathMatches(mustPatternMatcher(t, []string{"*"}), "fileutils.go") 346 if !match { 347 t.Errorf("failed to get a wildcard match, got %v", match) 348 } 349 } 350 351 // A simple pattern match should return true. 352 func TestPatternMatches(t *testing.T) { 353 match, _ := filepathMatches(mustPatternMatcher(t, []string{"*.go"}), "fileutils.go") 354 if !match { 355 t.Errorf("failed to get a match, got %v", match) 356 } 357 } 358 359 // An exclusion followed by an inclusion should return true. 360 func TestExclusionPatternMatchesPatternBefore(t *testing.T) { 361 match, _ := filepathMatches(mustPatternMatcher(t, []string{"!fileutils.go", "*.go"}), "fileutils.go") 362 if !match { 363 t.Errorf("failed to get true match on exclusion pattern, got %v", match) 364 } 365 } 366 367 // A folder pattern followed by an exception should return false. 368 func TestPatternMatchesFolderExclusions(t *testing.T) { 369 match, _ := filepathMatches(mustPatternMatcher(t, []string{"docs", "!docs/README.md"}), "docs/README.md") 370 if match { 371 t.Errorf("failed to get a false match on exclusion pattern, got %v", match) 372 } 373 } 374 375 // A folder pattern followed by an exception should return false. 376 func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) { 377 match, _ := filepathMatches(mustPatternMatcher(t, []string{"docs/", "!docs/README.md"}), "docs/README.md") 378 if match { 379 t.Errorf("failed to get a false match on exclusion pattern, got %v", match) 380 } 381 } 382 383 // A folder pattern followed by an exception should return false. 384 func TestPatternMatchesFolderWildcardExclusions(t *testing.T) { 385 match, _ := filepathMatches(mustPatternMatcher(t, []string{"docs/*", "!docs/README.md"}), "docs/README.md") 386 if match { 387 t.Errorf("failed to get a false match on exclusion pattern, got %v", match) 388 } 389 } 390 391 // A pattern followed by an exclusion should return false. 392 func TestExclusionPatternMatchesPatternAfter(t *testing.T) { 393 match, _ := filepathMatches(mustPatternMatcher(t, []string{"*.go", "!fileutils.go"}), "fileutils.go") 394 if match { 395 t.Errorf("failed to get false match on exclusion pattern, got %v", match) 396 } 397 } 398 399 // A filename evaluating to . should return false. 400 func TestExclusionPatternMatchesWholeDirectory(t *testing.T) { 401 match, _ := filepathMatches(mustPatternMatcher(t, []string{"*.go"}), ".") 402 if match { 403 t.Errorf("failed to get false match on ., got %v", match) 404 } 405 } 406 407 // Matches with no patterns 408 func TestMatchesWithNoPatterns(t *testing.T) { 409 matches, err := filepathMatches(mustPatternMatcher(t, []string{}), "/any/path/there") 410 if err != nil { 411 t.Fatal(err) 412 } 413 if matches { 414 t.Fatalf("Should not have match anything") 415 } 416 }