mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
64aac182d6
This reverts 26103. 26103 was trying to make it so that if someone did:
docker build --build-arg FOO .
and FOO wasn't set as an env var then it would pick-up FOO from the
Dockerfile's ARG cmd. However, it went too far and removed the ability
to specify a build arg w/o any value. Meaning it required the --build-arg
param to always be in the form "name=value", and not just "name".
This PR does the right fix - it allows just "name" and it'll grab the value
from the env vars if set. If "name" isn't set in the env then it still needs
to send "name" to the server so that a warning can be printed about an
unused --build-arg. And this is why buildArgs in the options is now a
*string instead of just a string - 'nil' == mentioned but no value.
Closes #29084
Signed-off-by: Doug Davis <dug@us.ibm.com>
(cherry picked from commit cdb8ea90b0
)
Signed-off-by: Victor Vieux <vieux@docker.com>
517 lines
15 KiB
Go
517 lines
15 KiB
Go
package dockerfile
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/strslice"
|
|
"github.com/docker/go-connections/nat"
|
|
)
|
|
|
|
type commandWithFunction struct {
|
|
name string
|
|
function func(args []string) error
|
|
}
|
|
|
|
func TestCommandsExactlyOneArgument(t *testing.T) {
|
|
commands := []commandWithFunction{
|
|
{"MAINTAINER", func(args []string) error { return maintainer(nil, args, nil, "") }},
|
|
{"FROM", func(args []string) error { return from(nil, args, nil, "") }},
|
|
{"WORKDIR", func(args []string) error { return workdir(nil, args, nil, "") }},
|
|
{"USER", func(args []string) error { return user(nil, args, nil, "") }},
|
|
{"STOPSIGNAL", func(args []string) error { return stopSignal(nil, args, nil, "") }}}
|
|
|
|
for _, command := range commands {
|
|
err := command.function([]string{})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Error should be present for %s command", command.name)
|
|
}
|
|
|
|
expectedError := errExactlyOneArgument(command.name)
|
|
|
|
if err.Error() != expectedError.Error() {
|
|
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommandsAtLeastOneArgument(t *testing.T) {
|
|
commands := []commandWithFunction{
|
|
{"ENV", func(args []string) error { return env(nil, args, nil, "") }},
|
|
{"LABEL", func(args []string) error { return label(nil, args, nil, "") }},
|
|
{"ONBUILD", func(args []string) error { return onbuild(nil, args, nil, "") }},
|
|
{"HEALTHCHECK", func(args []string) error { return healthcheck(nil, args, nil, "") }},
|
|
{"EXPOSE", func(args []string) error { return expose(nil, args, nil, "") }},
|
|
{"VOLUME", func(args []string) error { return volume(nil, args, nil, "") }}}
|
|
|
|
for _, command := range commands {
|
|
err := command.function([]string{})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Error should be present for %s command", command.name)
|
|
}
|
|
|
|
expectedError := errAtLeastOneArgument(command.name)
|
|
|
|
if err.Error() != expectedError.Error() {
|
|
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommandsAtLeastTwoArguments(t *testing.T) {
|
|
commands := []commandWithFunction{
|
|
{"ADD", func(args []string) error { return add(nil, args, nil, "") }},
|
|
{"COPY", func(args []string) error { return dispatchCopy(nil, args, nil, "") }}}
|
|
|
|
for _, command := range commands {
|
|
err := command.function([]string{"arg1"})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Error should be present for %s command", command.name)
|
|
}
|
|
|
|
expectedError := errAtLeastTwoArguments(command.name)
|
|
|
|
if err.Error() != expectedError.Error() {
|
|
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommandsTooManyArguments(t *testing.T) {
|
|
commands := []commandWithFunction{
|
|
{"ENV", func(args []string) error { return env(nil, args, nil, "") }},
|
|
{"LABEL", func(args []string) error { return label(nil, args, nil, "") }}}
|
|
|
|
for _, command := range commands {
|
|
err := command.function([]string{"arg1", "arg2", "arg3"})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Error should be present for %s command", command.name)
|
|
}
|
|
|
|
expectedError := errTooManyArguments(command.name)
|
|
|
|
if err.Error() != expectedError.Error() {
|
|
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommandseBlankNames(t *testing.T) {
|
|
bflags := &BFlags{}
|
|
config := &container.Config{}
|
|
|
|
b := &Builder{flags: bflags, runConfig: config, disableCommit: true}
|
|
|
|
commands := []commandWithFunction{
|
|
{"ENV", func(args []string) error { return env(b, args, nil, "") }},
|
|
{"LABEL", func(args []string) error { return label(b, args, nil, "") }},
|
|
}
|
|
|
|
for _, command := range commands {
|
|
err := command.function([]string{"", ""})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Error should be present for %s command", command.name)
|
|
}
|
|
|
|
expectedError := errBlankCommandNames(command.name)
|
|
|
|
if err.Error() != expectedError.Error() {
|
|
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEnv2Variables(t *testing.T) {
|
|
variables := []string{"var1", "val1", "var2", "val2"}
|
|
|
|
bflags := &BFlags{}
|
|
config := &container.Config{}
|
|
|
|
b := &Builder{flags: bflags, runConfig: config, disableCommit: true}
|
|
|
|
if err := env(b, variables, nil, ""); err != nil {
|
|
t.Fatalf("Error when executing env: %s", err.Error())
|
|
}
|
|
|
|
expectedVar1 := fmt.Sprintf("%s=%s", variables[0], variables[1])
|
|
expectedVar2 := fmt.Sprintf("%s=%s", variables[2], variables[3])
|
|
|
|
if b.runConfig.Env[0] != expectedVar1 {
|
|
t.Fatalf("Wrong env output for first variable. Got: %s. Should be: %s", b.runConfig.Env[0], expectedVar1)
|
|
}
|
|
|
|
if b.runConfig.Env[1] != expectedVar2 {
|
|
t.Fatalf("Wrong env output for second variable. Got: %s, Should be: %s", b.runConfig.Env[1], expectedVar2)
|
|
}
|
|
}
|
|
|
|
func TestMaintainer(t *testing.T) {
|
|
maintainerEntry := "Some Maintainer <maintainer@example.com>"
|
|
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
if err := maintainer(b, []string{maintainerEntry}, nil, ""); err != nil {
|
|
t.Fatalf("Error when executing maintainer: %s", err.Error())
|
|
}
|
|
|
|
if b.maintainer != maintainerEntry {
|
|
t.Fatalf("Maintainer in builder should be set to %s. Got: %s", maintainerEntry, b.maintainer)
|
|
}
|
|
}
|
|
|
|
func TestLabel(t *testing.T) {
|
|
labelName := "label"
|
|
labelValue := "value"
|
|
|
|
labelEntry := []string{labelName, labelValue}
|
|
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
if err := label(b, labelEntry, nil, ""); err != nil {
|
|
t.Fatalf("Error when executing label: %s", err.Error())
|
|
}
|
|
|
|
if val, ok := b.runConfig.Labels[labelName]; ok {
|
|
if val != labelValue {
|
|
t.Fatalf("Label %s should have value %s, had %s instead", labelName, labelValue, val)
|
|
}
|
|
} else {
|
|
t.Fatalf("Label %s should be present but it is not", labelName)
|
|
}
|
|
}
|
|
|
|
func TestFrom(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
err := from(b, []string{"scratch"}, nil, "")
|
|
|
|
if runtime.GOOS == "windows" {
|
|
if err == nil {
|
|
t.Fatalf("Error not set on Windows")
|
|
}
|
|
|
|
expectedError := "Windows does not support FROM scratch"
|
|
|
|
if !strings.Contains(err.Error(), expectedError) {
|
|
t.Fatalf("Error message not correct on Windows. Should be: %s, got: %s", expectedError, err.Error())
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Error when executing from: %s", err.Error())
|
|
}
|
|
|
|
if b.image != "" {
|
|
t.Fatalf("Image shoule be empty, got: %s", b.image)
|
|
}
|
|
|
|
if b.noBaseImage != true {
|
|
t.Fatalf("Image should not have any base image, got: %v", b.noBaseImage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOnbuildIllegalTriggers(t *testing.T) {
|
|
triggers := []struct{ command, expectedError string }{
|
|
{"ONBUILD", "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed"},
|
|
{"MAINTAINER", "MAINTAINER isn't allowed as an ONBUILD trigger"},
|
|
{"FROM", "FROM isn't allowed as an ONBUILD trigger"}}
|
|
|
|
for _, trigger := range triggers {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
err := onbuild(b, []string{trigger.command}, nil, "")
|
|
|
|
if err == nil {
|
|
t.Fatalf("Error should not be nil")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), trigger.expectedError) {
|
|
t.Fatalf("Error message not correct. Should be: %s, got: %s", trigger.expectedError, err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOnbuild(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
err := onbuild(b, []string{"ADD", ".", "/app/src"}, nil, "ONBUILD ADD . /app/src")
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
expectedOnbuild := "ADD . /app/src"
|
|
|
|
if b.runConfig.OnBuild[0] != expectedOnbuild {
|
|
t.Fatalf("Wrong ONBUILD command. Expected: %s, got: %s", expectedOnbuild, b.runConfig.OnBuild[0])
|
|
}
|
|
}
|
|
|
|
func TestWorkdir(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
workingDir := "/app"
|
|
|
|
if runtime.GOOS == "windows" {
|
|
workingDir = "C:\app"
|
|
}
|
|
|
|
err := workdir(b, []string{workingDir}, nil, "")
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.WorkingDir != workingDir {
|
|
t.Fatalf("WorkingDir should be set to %s, got %s", workingDir, b.runConfig.WorkingDir)
|
|
}
|
|
|
|
}
|
|
|
|
func TestCmd(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
command := "./executable"
|
|
|
|
err := cmd(b, []string{command}, nil, "")
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
var expectedCommand strslice.StrSlice
|
|
|
|
if runtime.GOOS == "windows" {
|
|
expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
|
|
} else {
|
|
expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
|
|
}
|
|
|
|
if !compareStrSlice(b.runConfig.Cmd, expectedCommand) {
|
|
t.Fatalf("Command should be set to %s, got %s", command, b.runConfig.Cmd)
|
|
}
|
|
|
|
if !b.cmdSet {
|
|
t.Fatalf("Command should be marked as set")
|
|
}
|
|
}
|
|
|
|
func compareStrSlice(slice1, slice2 strslice.StrSlice) bool {
|
|
if len(slice1) != len(slice2) {
|
|
return false
|
|
}
|
|
|
|
for i := range slice1 {
|
|
if slice1[i] != slice2[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func TestHealthcheckNone(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
if err := healthcheck(b, []string{"NONE"}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.Healthcheck == nil {
|
|
t.Fatal("Healthcheck should be set, got nil")
|
|
}
|
|
|
|
expectedTest := strslice.StrSlice(append([]string{"NONE"}))
|
|
|
|
if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) {
|
|
t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test)
|
|
}
|
|
}
|
|
|
|
func TestHealthcheckCmd(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{flags: make(map[string]*Flag)}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
if err := healthcheck(b, []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.Healthcheck == nil {
|
|
t.Fatal("Healthcheck should be set, got nil")
|
|
}
|
|
|
|
expectedTest := strslice.StrSlice(append([]string{"CMD-SHELL"}, "curl -f http://localhost/ || exit 1"))
|
|
|
|
if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) {
|
|
t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test)
|
|
}
|
|
}
|
|
|
|
func TestEntrypoint(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
entrypointCmd := "/usr/sbin/nginx"
|
|
|
|
if err := entrypoint(b, []string{entrypointCmd}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.Entrypoint == nil {
|
|
t.Fatalf("Entrypoint should be set")
|
|
}
|
|
|
|
var expectedEntrypoint strslice.StrSlice
|
|
|
|
if runtime.GOOS == "windows" {
|
|
expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
|
|
} else {
|
|
expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
|
|
}
|
|
|
|
if !compareStrSlice(expectedEntrypoint, b.runConfig.Entrypoint) {
|
|
t.Fatalf("Entrypoint command should be set to %s, got %s", expectedEntrypoint, b.runConfig.Entrypoint)
|
|
}
|
|
}
|
|
|
|
func TestExpose(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
exposedPort := "80"
|
|
|
|
if err := expose(b, []string{exposedPort}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.ExposedPorts == nil {
|
|
t.Fatalf("ExposedPorts should be set")
|
|
}
|
|
|
|
if len(b.runConfig.ExposedPorts) != 1 {
|
|
t.Fatalf("ExposedPorts should contain only 1 element. Got %s", b.runConfig.ExposedPorts)
|
|
}
|
|
|
|
portsMapping, err := nat.ParsePortSpec(exposedPort)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error when parsing port spec: %s", err.Error())
|
|
}
|
|
|
|
if _, ok := b.runConfig.ExposedPorts[portsMapping[0].Port]; !ok {
|
|
t.Fatalf("Port %s should be present. Got %s", exposedPort, b.runConfig.ExposedPorts)
|
|
}
|
|
}
|
|
|
|
func TestUser(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
userCommand := "foo"
|
|
|
|
if err := user(b, []string{userCommand}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.User != userCommand {
|
|
t.Fatalf("User should be set to %s, got %s", userCommand, b.runConfig.User)
|
|
}
|
|
}
|
|
|
|
func TestVolume(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
exposedVolume := "/foo"
|
|
|
|
if err := volume(b, []string{exposedVolume}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.Volumes == nil {
|
|
t.Fatalf("Volumes should be set")
|
|
}
|
|
|
|
if len(b.runConfig.Volumes) != 1 {
|
|
t.Fatalf("Volumes should contain only 1 element. Got %s", b.runConfig.Volumes)
|
|
}
|
|
|
|
if _, ok := b.runConfig.Volumes[exposedVolume]; !ok {
|
|
t.Fatalf("Volume %s should be present. Got %s", exposedVolume, b.runConfig.Volumes)
|
|
}
|
|
}
|
|
|
|
func TestStopSignal(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
signal := "SIGKILL"
|
|
|
|
if err := stopSignal(b, []string{signal}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.StopSignal != signal {
|
|
t.Fatalf("StopSignal should be set to %s, got %s", signal, b.runConfig.StopSignal)
|
|
}
|
|
}
|
|
|
|
func TestArg(t *testing.T) {
|
|
buildOptions := &types.ImageBuildOptions{BuildArgs: make(map[string]*string)}
|
|
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true, allowedBuildArgs: make(map[string]bool), options: buildOptions}
|
|
|
|
argName := "foo"
|
|
argVal := "bar"
|
|
argDef := fmt.Sprintf("%s=%s", argName, argVal)
|
|
|
|
if err := arg(b, []string{argDef}, nil, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
allowed, ok := b.allowedBuildArgs[argName]
|
|
|
|
if !ok {
|
|
t.Fatalf("%s argument should be allowed as a build arg", argName)
|
|
}
|
|
|
|
if !allowed {
|
|
t.Fatalf("%s argument was present in map but disallowed as a build arg", argName)
|
|
}
|
|
|
|
val, ok := b.options.BuildArgs[argName]
|
|
|
|
if !ok {
|
|
t.Fatalf("%s argument should be a build arg", argName)
|
|
}
|
|
|
|
if *val != "bar" {
|
|
t.Fatalf("%s argument should have default value 'bar', got %s", argName, val)
|
|
}
|
|
}
|
|
|
|
func TestShell(t *testing.T) {
|
|
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
|
|
|
shellCmd := "powershell"
|
|
|
|
attrs := make(map[string]bool)
|
|
attrs["json"] = true
|
|
|
|
if err := shell(b, []string{shellCmd}, attrs, ""); err != nil {
|
|
t.Fatalf("Error should be empty, got: %s", err.Error())
|
|
}
|
|
|
|
if b.runConfig.Shell == nil {
|
|
t.Fatalf("Shell should be set")
|
|
}
|
|
|
|
expectedShell := strslice.StrSlice([]string{shellCmd})
|
|
|
|
if !compareStrSlice(expectedShell, b.runConfig.Shell) {
|
|
t.Fatalf("Shell should be set to %s, got %s", expectedShell, b.runConfig.Shell)
|
|
}
|
|
}
|