1
0
Fork 0
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:
Doug Davis 2015-02-04 09:34:25 -08:00
parent f208201375
commit e4f02abb51
8 changed files with 85 additions and 29 deletions

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -1,8 +0,0 @@
FROM dockerfile/rabbitmq
RUN
rabbitmq-plugins enable \
rabbitmq_shovel \
rabbitmq_shovel_management \
rabbitmq_federation \
rabbitmq_federation_management

View file

@ -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

View file

@ -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")
}