github.com/kobeld/docker@v1.12.0-rc1/builder/dockerfile/evaluator_test.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/docker/docker/builder"
    11  	"github.com/docker/docker/builder/dockerfile/parser"
    12  	"github.com/docker/docker/pkg/archive"
    13  	"github.com/docker/docker/pkg/reexec"
    14  	"github.com/docker/engine-api/types"
    15  	"github.com/docker/engine-api/types/container"
    16  )
    17  
    18  type dispatchTestCase struct {
    19  	name, dockerfile, expectedError string
    20  	files                           map[string]string
    21  }
    22  
    23  func init() {
    24  	reexec.Init()
    25  }
    26  
    27  func initDispatchTestCases() []dispatchTestCase {
    28  	dispatchTestCases := []dispatchTestCase{{
    29  		name: "copyEmptyWhitespace",
    30  		dockerfile: `COPY
    31  	quux \
    32        bar`,
    33  		expectedError: "COPY requires at least one argument",
    34  	},
    35  		{
    36  			name:          "ONBUILD forbidden FROM",
    37  			dockerfile:    "ONBUILD FROM scratch",
    38  			expectedError: "FROM isn't allowed as an ONBUILD trigger",
    39  			files:         nil,
    40  		},
    41  		{
    42  			name:          "ONBUILD forbidden MAINTAINER",
    43  			dockerfile:    "ONBUILD MAINTAINER docker.io",
    44  			expectedError: "MAINTAINER isn't allowed as an ONBUILD trigger",
    45  			files:         nil,
    46  		},
    47  		{
    48  			name:          "ARG two arguments",
    49  			dockerfile:    "ARG foo bar",
    50  			expectedError: "ARG requires exactly one argument definition",
    51  			files:         nil,
    52  		},
    53  		{
    54  			name:          "MAINTAINER unknown flag",
    55  			dockerfile:    "MAINTAINER --boo joe@example.com",
    56  			expectedError: "Unknown flag: boo",
    57  			files:         nil,
    58  		},
    59  		{
    60  			name:          "ADD multiple files to file",
    61  			dockerfile:    "ADD file1.txt file2.txt test",
    62  			expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
    63  			files:         map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
    64  		},
    65  		{
    66  			name:          "JSON ADD multiple files to file",
    67  			dockerfile:    `ADD ["file1.txt", "file2.txt", "test"]`,
    68  			expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
    69  			files:         map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
    70  		},
    71  		{
    72  			name:          "Wildcard ADD multiple files to file",
    73  			dockerfile:    "ADD file*.txt test",
    74  			expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
    75  			files:         map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
    76  		},
    77  		{
    78  			name:          "Wildcard JSON ADD multiple files to file",
    79  			dockerfile:    `ADD ["file*.txt", "test"]`,
    80  			expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
    81  			files:         map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
    82  		},
    83  		{
    84  			name:          "COPY multiple files to file",
    85  			dockerfile:    "COPY file1.txt file2.txt test",
    86  			expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
    87  			files:         map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
    88  		},
    89  		{
    90  			name:          "JSON COPY multiple files to file",
    91  			dockerfile:    `COPY ["file1.txt", "file2.txt", "test"]`,
    92  			expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
    93  			files:         map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
    94  		},
    95  		{
    96  			name:          "ADD multiple files to file with whitespace",
    97  			dockerfile:    `ADD [ "test file1.txt", "test file2.txt", "test" ]`,
    98  			expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
    99  			files:         map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
   100  		},
   101  		{
   102  			name:          "COPY multiple files to file with whitespace",
   103  			dockerfile:    `COPY [ "test file1.txt", "test file2.txt", "test" ]`,
   104  			expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
   105  			files:         map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
   106  		},
   107  		{
   108  			name:          "COPY wildcard no files",
   109  			dockerfile:    `COPY file*.txt /tmp/`,
   110  			expectedError: "No source files were specified",
   111  			files:         nil,
   112  		},
   113  		{
   114  			name:          "COPY url",
   115  			dockerfile:    `COPY https://index.docker.io/robots.txt /`,
   116  			expectedError: "Source can't be a URL for COPY",
   117  			files:         nil,
   118  		},
   119  		{
   120  			name:          "Chaining ONBUILD",
   121  			dockerfile:    `ONBUILD ONBUILD RUN touch foobar`,
   122  			expectedError: "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
   123  			files:         nil,
   124  		},
   125  		{
   126  			name:          "Invalid instruction",
   127  			dockerfile:    `foo bar`,
   128  			expectedError: "Unknown instruction: FOO",
   129  			files:         nil,
   130  		}}
   131  
   132  	return dispatchTestCases
   133  }
   134  
   135  func TestDispatch(t *testing.T) {
   136  	testCases := initDispatchTestCases()
   137  
   138  	for _, testCase := range testCases {
   139  		executeTestCase(t, testCase)
   140  	}
   141  }
   142  
   143  func executeTestCase(t *testing.T, testCase dispatchTestCase) {
   144  	contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
   145  	defer cleanup()
   146  
   147  	for filename, content := range testCase.files {
   148  		createTestTempFile(t, contextDir, filename, content, 0777)
   149  	}
   150  
   151  	tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
   152  
   153  	if err != nil {
   154  		t.Fatalf("Error when creating tar stream: %s", err)
   155  	}
   156  
   157  	defer func() {
   158  		if err = tarStream.Close(); err != nil {
   159  			t.Fatalf("Error when closing tar stream: %s", err)
   160  		}
   161  	}()
   162  
   163  	context, err := builder.MakeTarSumContext(tarStream)
   164  
   165  	if err != nil {
   166  		t.Fatalf("Error when creating tar context: %s", err)
   167  	}
   168  
   169  	defer func() {
   170  		if err = context.Close(); err != nil {
   171  			t.Fatalf("Error when closing tar context: %s", err)
   172  		}
   173  	}()
   174  
   175  	r := strings.NewReader(testCase.dockerfile)
   176  	n, err := parser.Parse(r)
   177  
   178  	if err != nil {
   179  		t.Fatalf("Error when parsing Dockerfile: %s", err)
   180  	}
   181  
   182  	config := &container.Config{}
   183  	options := &types.ImageBuildOptions{}
   184  
   185  	b := &Builder{runConfig: config, options: options, Stdout: ioutil.Discard, context: context}
   186  
   187  	err = b.dispatch(0, n.Children[0])
   188  
   189  	if err == nil {
   190  		t.Fatalf("No error when executing test %s", testCase.name)
   191  	}
   192  
   193  	if !strings.Contains(err.Error(), testCase.expectedError) {
   194  		t.Fatalf("Wrong error message. Should be \"%s\". Got \"%s\"", testCase.expectedError, err.Error())
   195  	}
   196  
   197  }
   198  
   199  // createTestTempDir creates a temporary directory for testing.
   200  // It returns the created path and a cleanup function which is meant to be used as deferred call.
   201  // When an error occurs, it terminates the test.
   202  func createTestTempDir(t *testing.T, dir, prefix string) (string, func()) {
   203  	path, err := ioutil.TempDir(dir, prefix)
   204  
   205  	if err != nil {
   206  		t.Fatalf("Error when creating directory %s with prefix %s: %s", dir, prefix, err)
   207  	}
   208  
   209  	return path, func() {
   210  		err = os.RemoveAll(path)
   211  
   212  		if err != nil {
   213  			t.Fatalf("Error when removing directory %s: %s", path, err)
   214  		}
   215  	}
   216  }
   217  
   218  // createTestTempFile creates a temporary file within dir with specific contents and permissions.
   219  // When an error occurs, it terminates the test
   220  func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.FileMode) string {
   221  	filePath := filepath.Join(dir, filename)
   222  	err := ioutil.WriteFile(filePath, []byte(contents), perm)
   223  
   224  	if err != nil {
   225  		t.Fatalf("Error when creating %s file: %s", filename, err)
   226  	}
   227  
   228  	return filePath
   229  }