mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add support for no-arg commands in Dockerfile
We're hoping to add some new commands that don't have any args so this PR will enable that by removing all of the hard-coded checks that require commands to have at least one arg. It also adds some checks to each command so we're consistent in the error message we get. Added a test for this too. We actually had this check in at least 3 different places (twice in the parser and once in most cmds), this removes 2 of them (the parser ones). Had to remove/modify some testcases because its now legal to have certain commands w/o args - e.g. RUN. This was actually inconsistent because we used to allow "RUN []" but not "RUN" even though they would generate (almost) the same net result. Now we're consistent. Signed-off-by: Doug Davis <dug@us.ibm.com>
This commit is contained in:
parent
f208201375
commit
e4f02abb51
8 changed files with 85 additions and 29 deletions
|
@ -39,7 +39,7 @@ func nullDispatch(b *Builder, args []string, attributes map[string]bool, origina
|
||||||
//
|
//
|
||||||
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("ENV is missing arguments")
|
return fmt.Errorf("ENV requires at least one argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args)%2 != 0 {
|
if len(args)%2 != 0 {
|
||||||
|
@ -78,7 +78,7 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
|
||||||
// Sets the maintainer metadata.
|
// Sets the maintainer metadata.
|
||||||
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("MAINTAINER requires only one argument")
|
return fmt.Errorf("MAINTAINER requires exactly one argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.maintainer = args[0]
|
b.maintainer = args[0]
|
||||||
|
@ -159,6 +159,10 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
|
||||||
// cases.
|
// cases.
|
||||||
//
|
//
|
||||||
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return fmt.Errorf("ONBUILD requires at least one argument")
|
||||||
|
}
|
||||||
|
|
||||||
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
|
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
|
||||||
switch triggerInstruction {
|
switch triggerInstruction {
|
||||||
case "ONBUILD":
|
case "ONBUILD":
|
||||||
|
@ -327,6 +331,10 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
|
||||||
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
portsTab := args
|
portsTab := args
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
return fmt.Errorf("EXPOSE requires at least one argument")
|
||||||
|
}
|
||||||
|
|
||||||
if b.Config.ExposedPorts == nil {
|
if b.Config.ExposedPorts == nil {
|
||||||
b.Config.ExposedPorts = make(nat.PortSet)
|
b.Config.ExposedPorts = make(nat.PortSet)
|
||||||
}
|
}
|
||||||
|
@ -373,7 +381,7 @@ func user(b *Builder, args []string, attributes map[string]bool, original string
|
||||||
//
|
//
|
||||||
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("Volume cannot be empty")
|
return fmt.Errorf("VOLUME requires at least one argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Config.Volumes == nil {
|
if b.Config.Volumes == nil {
|
||||||
|
|
|
@ -240,6 +240,9 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
||||||
msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
|
msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
|
||||||
|
|
||||||
if cmd == "onbuild" {
|
if cmd == "onbuild" {
|
||||||
|
if ast.Next == nil {
|
||||||
|
return fmt.Errorf("ONBUILD requires at least one argument")
|
||||||
|
}
|
||||||
ast = ast.Next.Children[0]
|
ast = ast.Next.Children[0]
|
||||||
strs = append(strs, ast.Value)
|
strs = append(strs, ast.Value)
|
||||||
msg += " " + ast.Value
|
msg += " " + ast.Value
|
||||||
|
|
|
@ -30,6 +30,10 @@ func parseIgnore(rest string) (*Node, map[string]bool, error) {
|
||||||
// ONBUILD RUN foo bar -> (onbuild (run foo bar))
|
// ONBUILD RUN foo bar -> (onbuild (run foo bar))
|
||||||
//
|
//
|
||||||
func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
||||||
|
if rest == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
_, child, err := parseLine(rest)
|
_, child, err := parseLine(rest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -133,7 +137,7 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(words) == 0 {
|
if len(words) == 0 {
|
||||||
return nil, nil, fmt.Errorf("ENV must have some arguments")
|
return nil, nil, fmt.Errorf("ENV requires at least one argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old format (ENV name value)
|
// Old format (ENV name value)
|
||||||
|
@ -181,6 +185,10 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||||
// parses a whitespace-delimited set of arguments. The result is effectively a
|
// parses a whitespace-delimited set of arguments. The result is effectively a
|
||||||
// linked list of string arguments.
|
// linked list of string arguments.
|
||||||
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
||||||
|
if rest == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
rootnode := node
|
rootnode := node
|
||||||
prevnode := node
|
prevnode := node
|
||||||
|
@ -201,6 +209,9 @@ func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error
|
||||||
|
|
||||||
// parsestring just wraps the string in quotes and returns a working node.
|
// parsestring just wraps the string in quotes and returns a working node.
|
||||||
func parseString(rest string) (*Node, map[string]bool, error) {
|
func parseString(rest string) (*Node, map[string]bool, error) {
|
||||||
|
if rest == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
n := &Node{}
|
n := &Node{}
|
||||||
n.Value = rest
|
n.Value = rest
|
||||||
return n, nil, nil
|
return n, nil, nil
|
||||||
|
@ -235,7 +246,9 @@ func parseJSON(rest string) (*Node, map[string]bool, error) {
|
||||||
// so, passes to parseJSON; if not, quotes the result and returns a single
|
// so, passes to parseJSON; if not, quotes the result and returns a single
|
||||||
// node.
|
// node.
|
||||||
func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
|
func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
|
||||||
rest = strings.TrimSpace(rest)
|
if rest == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
node, attrs, err := parseJSON(rest)
|
node, attrs, err := parseJSON(rest)
|
||||||
|
|
||||||
|
@ -255,8 +268,6 @@ func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
|
||||||
// so, passes to parseJSON; if not, attmpts to parse it as a whitespace
|
// so, passes to parseJSON; if not, attmpts to parse it as a whitespace
|
||||||
// delimited string.
|
// delimited string.
|
||||||
func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) {
|
func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) {
|
||||||
rest = strings.TrimSpace(rest)
|
|
||||||
|
|
||||||
node, attrs, err := parseJSON(rest)
|
node, attrs, err := parseJSON(rest)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -78,10 +77,6 @@ func parseLine(line string) (string, *Node, error) {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 0 {
|
|
||||||
return "", nil, fmt.Errorf("Instruction %q is empty; cannot continue", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
node.Value = cmd
|
node.Value = cmd
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
FROM dockerfile/rabbitmq
|
|
||||||
|
|
||||||
RUN
|
|
||||||
rabbitmq-plugins enable \
|
|
||||||
rabbitmq_shovel \
|
|
||||||
rabbitmq_shovel_management \
|
|
||||||
rabbitmq_federation \
|
|
||||||
rabbitmq_federation_management
|
|
|
@ -1,2 +0,0 @@
|
||||||
<html>
|
|
||||||
</html>
|
|
|
@ -1,7 +1,6 @@
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -50,17 +49,19 @@ func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
|
||||||
// splitCommand takes a single line of text and parses out the cmd and args,
|
// splitCommand takes a single line of text and parses out the cmd and args,
|
||||||
// which are used for dispatching to more exact parsing functions.
|
// which are used for dispatching to more exact parsing functions.
|
||||||
func splitCommand(line string) (string, string, error) {
|
func splitCommand(line string) (string, string, error) {
|
||||||
|
var args string
|
||||||
|
|
||||||
// Make sure we get the same results irrespective of leading/trailing spaces
|
// Make sure we get the same results irrespective of leading/trailing spaces
|
||||||
cmdline := TOKEN_WHITESPACE.Split(strings.TrimSpace(line), 2)
|
cmdline := TOKEN_WHITESPACE.Split(strings.TrimSpace(line), 2)
|
||||||
|
cmd := strings.ToLower(cmdline[0])
|
||||||
|
|
||||||
if len(cmdline) != 2 {
|
if len(cmdline) == 2 {
|
||||||
return "", "", fmt.Errorf("We do not understand this file. Please ensure it is a valid Dockerfile. Parser error at %q", line)
|
args = strings.TrimSpace(cmdline[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := strings.ToLower(cmdline[0])
|
|
||||||
// the cmd should never have whitespace, but it's possible for the args to
|
// the cmd should never have whitespace, but it's possible for the args to
|
||||||
// have trailing whitespace.
|
// have trailing whitespace.
|
||||||
return cmd, strings.TrimSpace(cmdline[1]), nil
|
return cmd, args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// covers comments and empty lines. Lines should be trimmed before passing to
|
// covers comments and empty lines. Lines should be trimmed before passing to
|
||||||
|
|
|
@ -49,14 +49,14 @@ func TestBuildEmptyWhitespace(t *testing.T) {
|
||||||
name,
|
name,
|
||||||
`
|
`
|
||||||
FROM busybox
|
FROM busybox
|
||||||
RUN
|
COPY
|
||||||
quux \
|
quux \
|
||||||
bar
|
bar
|
||||||
`,
|
`,
|
||||||
true)
|
true)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("no error when dealing with a RUN statement with no content on the same line")
|
t.Fatal("no error when dealing with a COPY statement with no content on the same line")
|
||||||
}
|
}
|
||||||
|
|
||||||
logDone("build - statements with whitespace and no content should generate a parse error")
|
logDone("build - statements with whitespace and no content should generate a parse error")
|
||||||
|
@ -4822,3 +4822,51 @@ func TestBuildVolumeFileExistsinContainer(t *testing.T) {
|
||||||
|
|
||||||
logDone("build - errors when volume is specified where a file exists")
|
logDone("build - errors when volume is specified where a file exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildMissingArgs(t *testing.T) {
|
||||||
|
// test to make sure these cmds w/o any args will generate an error
|
||||||
|
// Notice some commands are missing because its ok for them to
|
||||||
|
// not have any args - like: CMD, RUN, ENTRYPOINT
|
||||||
|
cmds := []string{
|
||||||
|
"ADD",
|
||||||
|
"COPY",
|
||||||
|
"ENV",
|
||||||
|
"EXPOSE",
|
||||||
|
"FROM",
|
||||||
|
"MAINTAINER",
|
||||||
|
"ONBUILD",
|
||||||
|
"USER",
|
||||||
|
"VOLUME",
|
||||||
|
"WORKDIR",
|
||||||
|
}
|
||||||
|
|
||||||
|
defer deleteAllContainers()
|
||||||
|
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
var dockerfile string
|
||||||
|
|
||||||
|
if cmd == "FROM" {
|
||||||
|
dockerfile = cmd
|
||||||
|
} else {
|
||||||
|
// Add FROM to make sure we don't complain about it missing
|
||||||
|
dockerfile = "FROM busybox\n" + cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, err := fakeContext(dockerfile, map[string]string{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer ctx.Close()
|
||||||
|
var out string
|
||||||
|
if out, err = buildImageFromContext("args", ctx, true); err == nil {
|
||||||
|
t.Fatalf("%s was supposed to fail:%s", cmd, out)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), cmd+" requires") {
|
||||||
|
t.Fatalf("%s returned the wrong type of error:%s", cmd, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("build - verify missing args")
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue