2016-05-08 17:22:06 +00:00
|
|
|
package dockerfile
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
2016-09-06 18:18:12 +00:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
2016-05-08 17:22:06 +00:00
|
|
|
"github.com/docker/docker/builder"
|
|
|
|
"github.com/docker/docker/builder/dockerfile/parser"
|
|
|
|
"github.com/docker/docker/pkg/archive"
|
|
|
|
"github.com/docker/docker/pkg/reexec"
|
|
|
|
)
|
|
|
|
|
|
|
|
type dispatchTestCase struct {
|
|
|
|
name, dockerfile, expectedError string
|
2016-05-21 16:33:16 +00:00
|
|
|
files map[string]string
|
2016-05-08 17:22:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
reexec.Init()
|
|
|
|
}
|
|
|
|
|
|
|
|
func initDispatchTestCases() []dispatchTestCase {
|
|
|
|
dispatchTestCases := []dispatchTestCase{{
|
|
|
|
name: "copyEmptyWhitespace",
|
|
|
|
dockerfile: `COPY
|
|
|
|
quux \
|
|
|
|
bar`,
|
2016-08-08 14:08:55 +00:00
|
|
|
expectedError: "COPY requires at least two arguments",
|
2016-05-08 17:22:06 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ONBUILD forbidden FROM",
|
|
|
|
dockerfile: "ONBUILD FROM scratch",
|
|
|
|
expectedError: "FROM isn't allowed as an ONBUILD trigger",
|
2016-05-21 16:33:16 +00:00
|
|
|
files: nil,
|
2016-05-08 17:22:06 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ONBUILD forbidden MAINTAINER",
|
|
|
|
dockerfile: "ONBUILD MAINTAINER docker.io",
|
|
|
|
expectedError: "MAINTAINER isn't allowed as an ONBUILD trigger",
|
2016-05-21 16:33:16 +00:00
|
|
|
files: nil,
|
2016-05-08 17:22:06 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ARG two arguments",
|
|
|
|
dockerfile: "ARG foo bar",
|
2016-08-26 09:06:07 +00:00
|
|
|
expectedError: "ARG requires exactly one argument",
|
2016-05-21 16:33:16 +00:00
|
|
|
files: nil,
|
2016-05-08 17:22:06 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "MAINTAINER unknown flag",
|
|
|
|
dockerfile: "MAINTAINER --boo joe@example.com",
|
|
|
|
expectedError: "Unknown flag: boo",
|
2016-05-21 16:33:16 +00:00
|
|
|
files: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ADD multiple files to file",
|
|
|
|
dockerfile: "ADD file1.txt file2.txt test",
|
|
|
|
expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "JSON ADD multiple files to file",
|
|
|
|
dockerfile: `ADD ["file1.txt", "file2.txt", "test"]`,
|
|
|
|
expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
2016-05-22 17:39:35 +00:00
|
|
|
name: "Wildcard ADD multiple files to file",
|
2016-05-21 16:33:16 +00:00
|
|
|
dockerfile: "ADD file*.txt test",
|
|
|
|
expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
2016-05-22 17:39:35 +00:00
|
|
|
name: "Wildcard JSON ADD multiple files to file",
|
2016-05-21 16:33:16 +00:00
|
|
|
dockerfile: `ADD ["file*.txt", "test"]`,
|
|
|
|
expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "COPY multiple files to file",
|
|
|
|
dockerfile: "COPY file1.txt file2.txt test",
|
|
|
|
expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "JSON COPY multiple files to file",
|
|
|
|
dockerfile: `COPY ["file1.txt", "file2.txt", "test"]`,
|
|
|
|
expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"file1.txt": "test1", "file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ADD multiple files to file with whitespace",
|
|
|
|
dockerfile: `ADD [ "test file1.txt", "test file2.txt", "test" ]`,
|
|
|
|
expectedError: "When using ADD with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "COPY multiple files to file with whitespace",
|
|
|
|
dockerfile: `COPY [ "test file1.txt", "test file2.txt", "test" ]`,
|
|
|
|
expectedError: "When using COPY with more than one source file, the destination must be a directory and end with a /",
|
|
|
|
files: map[string]string{"test file1.txt": "test1", "test file2.txt": "test2"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "COPY wildcard no files",
|
|
|
|
dockerfile: `COPY file*.txt /tmp/`,
|
|
|
|
expectedError: "No source files were specified",
|
|
|
|
files: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "COPY url",
|
|
|
|
dockerfile: `COPY https://index.docker.io/robots.txt /`,
|
|
|
|
expectedError: "Source can't be a URL for COPY",
|
|
|
|
files: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Chaining ONBUILD",
|
|
|
|
dockerfile: `ONBUILD ONBUILD RUN touch foobar`,
|
|
|
|
expectedError: "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
|
|
|
|
files: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Invalid instruction",
|
|
|
|
dockerfile: `foo bar`,
|
|
|
|
expectedError: "Unknown instruction: FOO",
|
|
|
|
files: nil,
|
2016-05-08 17:22:06 +00:00
|
|
|
}}
|
|
|
|
|
|
|
|
return dispatchTestCases
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDispatch(t *testing.T) {
|
|
|
|
testCases := initDispatchTestCases()
|
|
|
|
|
|
|
|
for _, testCase := range testCases {
|
|
|
|
executeTestCase(t, testCase)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func executeTestCase(t *testing.T, testCase dispatchTestCase) {
|
|
|
|
contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test")
|
|
|
|
defer cleanup()
|
|
|
|
|
2016-05-21 16:33:16 +00:00
|
|
|
for filename, content := range testCase.files {
|
|
|
|
createTestTempFile(t, contextDir, filename, content, 0777)
|
|
|
|
}
|
|
|
|
|
2016-05-08 17:22:06 +00:00
|
|
|
tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error when creating tar stream: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err = tarStream.Close(); err != nil {
|
|
|
|
t.Fatalf("Error when closing tar stream: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
context, err := builder.MakeTarSumContext(tarStream)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error when creating tar context: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err = context.Close(); err != nil {
|
|
|
|
t.Fatalf("Error when closing tar context: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
r := strings.NewReader(testCase.dockerfile)
|
2016-06-27 20:20:47 +00:00
|
|
|
d := parser.Directive{}
|
|
|
|
parser.SetEscapeToken(parser.DefaultEscapeToken, &d)
|
|
|
|
n, err := parser.Parse(r, &d)
|
2016-05-08 17:22:06 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error when parsing Dockerfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
config := &container.Config{}
|
|
|
|
options := &types.ImageBuildOptions{}
|
|
|
|
|
|
|
|
b := &Builder{runConfig: config, options: options, Stdout: ioutil.Discard, context: context}
|
|
|
|
|
2016-07-24 08:06:10 +00:00
|
|
|
err = b.dispatch(0, len(n.Children), n.Children[0])
|
2016-05-08 17:22:06 +00:00
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("No error when executing test %s", testCase.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(err.Error(), testCase.expectedError) {
|
|
|
|
t.Fatalf("Wrong error message. Should be \"%s\". Got \"%s\"", testCase.expectedError, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|