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  }