aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2019-03-13 08:01:28 -0400
committerDaniel J Walsh <dwalsh@redhat.com>2019-03-13 08:21:11 -0400
commitadad93342c9cfd8e6d5f6c591d44754c91be9e5c (patch)
tree5c3b81d5aa9b76139184f91f36150469c2214876 /vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go
parent8b637bd78c8c120d07f245d9a837e52afd47c72a (diff)
downloadpodman-adad93342c9cfd8e6d5f6c591d44754c91be9e5c.tar.gz
podman-adad93342c9cfd8e6d5f6c591d44754c91be9e5c.tar.bz2
podman-adad93342c9cfd8e6d5f6c591d44754c91be9e5c.zip
Update vendor of Buildah and imagebuilder
Fixes the testing issues we are hitting. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
Diffstat (limited to 'vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go')
-rw-r--r--vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go399
1 files changed, 399 insertions, 0 deletions
diff --git a/vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go
new file mode 100644
index 000000000..82d912b26
--- /dev/null
+++ b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go
@@ -0,0 +1,399 @@
+package parser
+
+// line parsers are dispatch calls that parse a single unit of text into a
+// Node object which contains the whole statement. Dockerfiles have varied
+// (but not usually unique, see ONBUILD for a unique example) parsing rules
+// per-command, and these unify the processing in a way that makes it
+// manageable.
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "sort"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/openshift/imagebuilder/dockerfile/command"
+)
+
+var (
+ errDockerfileNotStringArray = errors.New("when using JSON array syntax, arrays must be comprised of strings only")
+)
+
+const (
+ commandLabel = "LABEL"
+)
+
+// ignore the current argument. This will still leave a command parsed, but
+// will not incorporate the arguments into the ast.
+func parseIgnore(rest string, d *Directive) (*Node, map[string]bool, error) {
+ return &Node{}, nil, nil
+}
+
+// used for onbuild. Could potentially be used for anything that represents a
+// statement with sub-statements.
+//
+// ONBUILD RUN foo bar -> (onbuild (run foo bar))
+//
+func parseSubCommand(rest string, d *Directive) (*Node, map[string]bool, error) {
+ if rest == "" {
+ return nil, nil, nil
+ }
+
+ child, err := newNodeFromLine(rest, d)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return &Node{Children: []*Node{child}}, nil, nil
+}
+
+// helper to parse words (i.e space delimited or quoted strings) in a statement.
+// The quotes are preserved as part of this function and they are stripped later
+// as part of processWords().
+func parseWords(rest string, d *Directive) []string {
+ const (
+ inSpaces = iota // looking for start of a word
+ inWord
+ inQuote
+ )
+
+ words := []string{}
+ phase := inSpaces
+ word := ""
+ quote := '\000'
+ blankOK := false
+ var ch rune
+ var chWidth int
+
+ for pos := 0; pos <= len(rest); pos += chWidth {
+ if pos != len(rest) {
+ ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
+ }
+
+ if phase == inSpaces { // Looking for start of word
+ if pos == len(rest) { // end of input
+ break
+ }
+ if unicode.IsSpace(ch) { // skip spaces
+ continue
+ }
+ phase = inWord // found it, fall through
+ }
+ if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
+ if blankOK || len(word) > 0 {
+ words = append(words, word)
+ }
+ break
+ }
+ if phase == inWord {
+ if unicode.IsSpace(ch) {
+ phase = inSpaces
+ if blankOK || len(word) > 0 {
+ words = append(words, word)
+ }
+ word = ""
+ blankOK = false
+ continue
+ }
+ if ch == '\'' || ch == '"' {
+ quote = ch
+ blankOK = true
+ phase = inQuote
+ }
+ if ch == d.escapeToken {
+ if pos+chWidth == len(rest) {
+ continue // just skip an escape token at end of line
+ }
+ // If we're not quoted and we see an escape token, then always just
+ // add the escape token plus the char to the word, even if the char
+ // is a quote.
+ word += string(ch)
+ pos += chWidth
+ ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
+ }
+ word += string(ch)
+ continue
+ }
+ if phase == inQuote {
+ if ch == quote {
+ phase = inWord
+ }
+ // The escape token is special except for ' quotes - can't escape anything for '
+ if ch == d.escapeToken && quote != '\'' {
+ if pos+chWidth == len(rest) {
+ phase = inWord
+ continue // just skip the escape token at end
+ }
+ pos += chWidth
+ word += string(ch)
+ ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
+ }
+ word += string(ch)
+ }
+ }
+
+ return words
+}
+
+// parse environment like statements. Note that this does *not* handle
+// variable interpolation, which will be handled in the evaluator.
+func parseNameVal(rest string, key string, d *Directive) (*Node, error) {
+ // This is kind of tricky because we need to support the old
+ // variant: KEY name value
+ // as well as the new one: KEY name=value ...
+ // The trigger to know which one is being used will be whether we hit
+ // a space or = first. space ==> old, "=" ==> new
+
+ words := parseWords(rest, d)
+ if len(words) == 0 {
+ return nil, nil
+ }
+
+ // Old format (KEY name value)
+ if !strings.Contains(words[0], "=") {
+ parts := tokenWhitespace.Split(rest, 2)
+ if len(parts) < 2 {
+ return nil, fmt.Errorf(key + " must have two arguments")
+ }
+ return newKeyValueNode(parts[0], parts[1]), nil
+ }
+
+ var rootNode *Node
+ var prevNode *Node
+ for _, word := range words {
+ if !strings.Contains(word, "=") {
+ return nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word)
+ }
+
+ parts := strings.SplitN(word, "=", 2)
+ node := newKeyValueNode(parts[0], parts[1])
+ rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
+ }
+
+ return rootNode, nil
+}
+
+func newKeyValueNode(key, value string) *Node {
+ return &Node{
+ Value: key,
+ Next: &Node{Value: value},
+ }
+}
+
+func appendKeyValueNode(node, rootNode, prevNode *Node) (*Node, *Node) {
+ if rootNode == nil {
+ rootNode = node
+ }
+ if prevNode != nil {
+ prevNode.Next = node
+ }
+
+ prevNode = node.Next
+ return rootNode, prevNode
+}
+
+func parseEnv(rest string, d *Directive) (*Node, map[string]bool, error) {
+ node, err := parseNameVal(rest, "ENV", d)
+ return node, nil, err
+}
+
+func parseLabel(rest string, d *Directive) (*Node, map[string]bool, error) {
+ node, err := parseNameVal(rest, commandLabel, d)
+ return node, nil, err
+}
+
+// NodeFromLabels returns a Node for the injected labels
+func NodeFromLabels(labels map[string]string) *Node {
+ keys := []string{}
+ for key := range labels {
+ keys = append(keys, key)
+ }
+ // Sort the label to have a repeatable order
+ sort.Strings(keys)
+
+ labelPairs := []string{}
+ var rootNode *Node
+ var prevNode *Node
+ for _, key := range keys {
+ value := labels[key]
+ labelPairs = append(labelPairs, fmt.Sprintf("%q='%s'", key, value))
+ // Value must be single quoted to prevent env variable expansion
+ // See https://github.com/docker/docker/issues/26027
+ node := newKeyValueNode(key, "'"+value+"'")
+ rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
+ }
+
+ return &Node{
+ Value: command.Label,
+ Original: commandLabel + " " + strings.Join(labelPairs, " "),
+ Next: rootNode,
+ }
+}
+
+// parses a statement containing one or more keyword definition(s) and/or
+// value assignments, like `name1 name2= name3="" name4=value`.
+// Note that this is a stricter format than the old format of assignment,
+// allowed by parseNameVal(), in a way that this only allows assignment of the
+// form `keyword=[<value>]` like `name2=`, `name3=""`, and `name4=value` above.
+// In addition, a keyword definition alone is of the form `keyword` like `name1`
+// above. And the assignments `name2=` and `name3=""` are equivalent and
+// assign an empty value to the respective keywords.
+func parseNameOrNameVal(rest string, d *Directive) (*Node, map[string]bool, error) {
+ words := parseWords(rest, d)
+ if len(words) == 0 {
+ return nil, nil, nil
+ }
+
+ var (
+ rootnode *Node
+ prevNode *Node
+ )
+ for i, word := range words {
+ node := &Node{}
+ node.Value = word
+ if i == 0 {
+ rootnode = node
+ } else {
+ prevNode.Next = node
+ }
+ prevNode = node
+ }
+
+ return rootnode, nil, nil
+}
+
+// parses a whitespace-delimited set of arguments. The result is effectively a
+// linked list of string arguments.
+func parseStringsWhitespaceDelimited(rest string, d *Directive) (*Node, map[string]bool, error) {
+ if rest == "" {
+ return nil, nil, nil
+ }
+
+ node := &Node{}
+ rootnode := node
+ prevnode := node
+ for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp
+ prevnode = node
+ node.Value = str
+ node.Next = &Node{}
+ node = node.Next
+ }
+
+ // XXX to get around regexp.Split *always* providing an empty string at the
+ // end due to how our loop is constructed, nil out the last node in the
+ // chain.
+ prevnode.Next = nil
+
+ return rootnode, nil, nil
+}
+
+// parseString just wraps the string in quotes and returns a working node.
+func parseString(rest string, d *Directive) (*Node, map[string]bool, error) {
+ if rest == "" {
+ return nil, nil, nil
+ }
+ n := &Node{}
+ n.Value = rest
+ return n, nil, nil
+}
+
+// parseJSON converts JSON arrays to an AST.
+func parseJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
+ rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
+ if !strings.HasPrefix(rest, "[") {
+ return nil, nil, fmt.Errorf(`Error parsing "%s" as a JSON array`, rest)
+ }
+
+ var myJSON []interface{}
+ if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil {
+ return nil, nil, err
+ }
+
+ var top, prev *Node
+ for _, str := range myJSON {
+ s, ok := str.(string)
+ if !ok {
+ return nil, nil, errDockerfileNotStringArray
+ }
+
+ node := &Node{Value: s}
+ if prev == nil {
+ top = node
+ } else {
+ prev.Next = node
+ }
+ prev = node
+ }
+
+ return top, map[string]bool{"json": true}, nil
+}
+
+// parseMaybeJSON determines if the argument appears to be a JSON array. If
+// so, passes to parseJSON; if not, quotes the result and returns a single
+// node.
+func parseMaybeJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
+ if rest == "" {
+ return nil, nil, nil
+ }
+
+ node, attrs, err := parseJSON(rest, d)
+
+ if err == nil {
+ return node, attrs, nil
+ }
+ if err == errDockerfileNotStringArray {
+ return nil, nil, err
+ }
+
+ node = &Node{}
+ node.Value = rest
+ return node, nil, nil
+}
+
+// parseMaybeJSONToList determines if the argument appears to be a JSON array. If
+// so, passes to parseJSON; if not, attempts to parse it as a whitespace
+// delimited string.
+func parseMaybeJSONToList(rest string, d *Directive) (*Node, map[string]bool, error) {
+ node, attrs, err := parseJSON(rest, d)
+
+ if err == nil {
+ return node, attrs, nil
+ }
+ if err == errDockerfileNotStringArray {
+ return nil, nil, err
+ }
+
+ return parseStringsWhitespaceDelimited(rest, d)
+}
+
+// The HEALTHCHECK command is like parseMaybeJSON, but has an extra type argument.
+func parseHealthConfig(rest string, d *Directive) (*Node, map[string]bool, error) {
+ // Find end of first argument
+ var sep int
+ for ; sep < len(rest); sep++ {
+ if unicode.IsSpace(rune(rest[sep])) {
+ break
+ }
+ }
+ next := sep
+ for ; next < len(rest); next++ {
+ if !unicode.IsSpace(rune(rest[next])) {
+ break
+ }
+ }
+
+ if sep == 0 {
+ return nil, nil, nil
+ }
+
+ typ := rest[:sep]
+ cmd, attrs, err := parseMaybeJSON(rest[next:], d)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return &Node{Value: typ, Next: cmd}, attrs, err
+}