package builder
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/docker/docker/builder/dockerfile/parser"
"github.com/fsouza/go-dockerclient"
kapi "k8s.io/kubernetes/pkg/api"
"github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/generate/git"
"github.com/openshift/origin/pkg/util/docker/dockerfile"
"github.com/openshift/source-to-image/pkg/tar"
)
func TestInsertEnvAfterFrom(t *testing.T) {
tests := map[string]struct {
original string
env []kapi.EnvVar
want string
}{
"no FROM instruction": {
original: `RUN echo "invalid Dockerfile"
`,
env: []kapi.EnvVar{
{Name: "PATH", Value: "/bin"},
},
want: `RUN echo "invalid Dockerfile"
`},
"empty env": {
original: `FROM busybox
`,
env: []kapi.EnvVar{},
want: `FROM busybox
`},
"single FROM instruction": {
original: `FROM busybox
RUN echo "hello world"
`,
env: []kapi.EnvVar{
{Name: "PATH", Value: "/bin"},
},
want: `FROM busybox
ENV "PATH"="/bin"
RUN echo "hello world"
`},
"multiple FROM instructions": {
original: `FROM scratch
FROM busybox
RUN echo "hello world"
`,
env: []kapi.EnvVar{
{Name: "PATH", Value: "/bin"},
{Name: "GOPATH", Value: "/go"},
{Name: "PATH", Value: "/go/bin:$PATH"},
},
want: `FROM scratch
ENV "PATH"="/bin" "GOPATH"="/go" "PATH"="/go/bin:$PATH"
FROM busybox
ENV "PATH"="/bin" "GOPATH"="/go" "PATH"="/go/bin:$PATH"
RUN echo "hello world"`},
}
for name, test := range tests {
got, err := parser.Parse(strings.NewReader(test.original))
if err != nil {
t.Errorf("%s: %v", name, err)
continue
}
want, err := parser.Parse(strings.NewReader(test.want))
if err != nil {
t.Errorf("%s: %v", name, err)
continue
}
insertEnvAfterFrom(got, test.env)
if !bytes.Equal(dockerfile.ParseTreeToDockerfile(got), dockerfile.ParseTreeToDockerfile(want)) {
t.Errorf("%s: insertEnvAfterFrom(node, %+v) = %+v; want %+v", name, test.env, got, want)
t.Logf("resulting Dockerfile:\n%s", dockerfile.ParseTreeToDockerfile(got))
}
}
}
func TestReplaceLastFrom(t *testing.T) {
tests := []struct {
original string
image string
want string
}{
{
original: `# no FROM instruction`,
image: "centos",
want: ``,
},
{
original: `FROM scratch
# FROM busybox
RUN echo "hello world"
`,
image: "centos",
want: `FROM centos
RUN echo "hello world"
`,
},
{
original: `FROM scratch
FROM busybox
RUN echo "hello world"
`,
image: "centos",
want: `FROM scratch
FROM centos
RUN echo "hello world"
`,
},
}
for i, test := range tests {
got, err := parser.Parse(strings.NewReader(test.original))
if err != nil {
t.Errorf("test[%d]: %v", i, err)
continue
}
want, err := parser.Parse(strings.NewReader(test.want))
if err != nil {
t.Errorf("test[%d]: %v", i, err)
continue
}
replaceLastFrom(got, test.image)
if !bytes.Equal(dockerfile.ParseTreeToDockerfile(got), dockerfile.ParseTreeToDockerfile(want)) {
t.Errorf("test[%d]: replaceLastFrom(node, %+v) = %+v; want %+v", i, test.image, got, want)
t.Logf("resulting Dockerfile:\n%s", dockerfile.ParseTreeToDockerfile(got))
}
}
}
// TestDockerfilePath validates that we can use a Dockefile with a custom name, and in a sub-directory
func TestDockerfilePath(t *testing.T) {
tests := []struct {
contextDir string
dockerfilePath string
dockerStrategy *api.DockerBuildStrategy
}{
// default Dockerfile path
{
dockerfilePath: "Dockerfile",
dockerStrategy: &api.DockerBuildStrategy{},
},
// custom Dockerfile path in the root context
{
dockerfilePath: "mydockerfile",
dockerStrategy: &api.DockerBuildStrategy{
DockerfilePath: "mydockerfile",
},
},
// custom Dockerfile path in a sub directory
{
dockerfilePath: "dockerfiles/mydockerfile",
dockerStrategy: &api.DockerBuildStrategy{
DockerfilePath: "dockerfiles/mydockerfile",
},
},
// custom Dockerfile path in a sub directory
// with a contextDir
{
contextDir: "somedir",
dockerfilePath: "dockerfiles/mydockerfile",
dockerStrategy: &api.DockerBuildStrategy{
DockerfilePath: "dockerfiles/mydockerfile",
},
},
}
for _, test := range tests {
buildDir, err := ioutil.TempDir("", "dockerfile-path")
if err != nil {
t.Errorf("failed to create tmpdir: %v", err)
continue
}
absoluteDockerfilePath := filepath.Join(buildDir, test.contextDir, test.dockerfilePath)
dockerfileContent := "FROM openshift/origin-base"
if err = os.MkdirAll(filepath.Dir(absoluteDockerfilePath), os.FileMode(0750)); err != nil {
t.Errorf("failed to create directory %s: %v", filepath.Dir(absoluteDockerfilePath), err)
continue
}
if err = ioutil.WriteFile(absoluteDockerfilePath, []byte(dockerfileContent), os.FileMode(0644)); err != nil {
t.Errorf("failed to write dockerfile to %s: %v", absoluteDockerfilePath, err)
continue
}
build := &api.Build{
Spec: api.BuildSpec{
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
URI: "http://github.com/openshift/origin.git",
},
ContextDir: test.contextDir,
},
Strategy: api.BuildStrategy{
DockerStrategy: test.dockerStrategy,
},
Output: api.BuildOutput{
To: &kapi.ObjectReference{
Kind: "DockerImage",
Name: "test/test-result:latest",
},
},
},
},
}
dockerClient := &FakeDocker{
buildImageFunc: func(opts docker.BuildImageOptions) error {
if opts.Dockerfile != test.dockerfilePath {
t.Errorf("Unexpected dockerfile path: %s (expected: %s)", opts.Dockerfile, test.dockerfilePath)
}
return nil
},
}
dockerBuilder := &DockerBuilder{
dockerClient: dockerClient,
build: build,
gitClient: git.NewRepository(),
tar: tar.New(),
}
// this will validate that the Dockerfile is readable
// and append some labels to the Dockerfile
if err = dockerBuilder.addBuildParameters(buildDir); err != nil {
t.Errorf("failed to add build parameters: %v", err)
continue
}
// check that our Dockerfile has been modified
dockerfileData, err := ioutil.ReadFile(absoluteDockerfilePath)
if err != nil {
t.Errorf("failed to read dockerfile %s: %v", absoluteDockerfilePath, err)
continue
}
if !strings.Contains(string(dockerfileData), dockerfileContent) {
t.Errorf("Updated Dockerfile content does not contains the original Dockerfile content.\n\nOriginal content:\n%s\n\nUpdated content:\n%s\n", dockerfileContent, string(dockerfileData))
continue
}
// check that the docker client is called with the right Dockerfile parameter
if err = dockerBuilder.dockerBuild(buildDir, "", []api.SecretBuildSource{}); err != nil {
t.Errorf("failed to build: %v", err)
continue
}
}
}
func TestEmptySource(t *testing.T) {
build := &api.Build{
Spec: api.BuildSpec{
CommonSpec: api.CommonSpec{
Source: api.BuildSource{},
Strategy: api.BuildStrategy{
DockerStrategy: &api.DockerBuildStrategy{},
},
Output: api.BuildOutput{
To: &kapi.ObjectReference{
Kind: "DockerImage",
Name: "test/test-result:latest",
},
},
},
},
}
dockerBuilder := &DockerBuilder{
build: build,
}
if err := dockerBuilder.Build(); err == nil {
t.Error("Should have received error on docker build")
} else {
if !strings.Contains(err.Error(), "must provide a value for at least one of source, binary, images, or dockerfile") {
t.Errorf("Did not receive correct error: %v", err)
}
}
}
func TestGetDockerfileFrom(t *testing.T) {
tests := map[string]struct {
dockerfileContent string
want []string
}{
"no FROM instruction": {
dockerfileContent: `RUN echo "invalid Dockerfile"
`,
want: []string{},
},
"single FROM instruction": {
dockerfileContent: `FROM scratch
RUN echo "hello world"
`,
want: []string{"scratch"},
},
"multi FROM instruction": {
dockerfileContent: `FROM scratch
FROM busybox
RUN echo "hello world"
`,
want: []string{"scratch", "busybox"},
},
}
for i, test := range tests {
buildDir, err := ioutil.TempDir("", "dockerfile-path")
if err != nil {
t.Errorf("failed to create tmpdir: %v", err)
continue
}
dockerfilePath := filepath.Join(buildDir, defaultDockerfilePath)
dockerfileContent := test.dockerfileContent
if err = os.MkdirAll(filepath.Dir(dockerfilePath), os.FileMode(0750)); err != nil {
t.Errorf("failed to create directory %s: %v", filepath.Dir(dockerfilePath), err)
continue
}
if err = ioutil.WriteFile(dockerfilePath, []byte(dockerfileContent), os.FileMode(0644)); err != nil {
t.Errorf("failed to write dockerfile to %s: %v", dockerfilePath, err)
continue
}
froms := getDockerfileFrom(dockerfilePath)
if len(froms) != len(test.want) {
t.Errorf("test[%s]: getDockerfileFrom(dockerfilepath, %s) = %+v; want %+v", i, dockerfilePath, froms, test.want)
t.Logf("Dockerfile froms::\n%v", froms)
continue
}
for fi := range froms {
if froms[fi] != test.want[fi] {
t.Errorf("test[%s]: getDockerfileFrom(dockerfilepath, %s) = %+v; want %+v", i, dockerfilePath, froms, test.want)
t.Logf("Dockerfile froms::\n%v", froms)
break
}
}
}
}