github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/build/kubefile/parser/line_parsers.go (about)

     1  // Copyright © 2022 Alibaba Group Holding Ltd.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package parser
    16  
    17  // line parsers are dispatch calls that parse a single unit of text into a
    18  // Node object which contains the whole statement. Dockerfiles have varied
    19  // (but not usually unique, see ONBUILD for a unique example) parsing rules
    20  // per-command, and these unify the processing in a way that makes it
    21  // manageable.
    22  
    23  import (
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"strings"
    28  	"unicode"
    29  	"unicode/utf8"
    30  )
    31  
    32  var (
    33  	errDockerfileNotStringArray = errors.New("when using JSON array syntax, arrays must be comprised of strings only")
    34  )
    35  
    36  const (
    37  	commandLabel = "LABEL"
    38  )
    39  
    40  // ignore the current argument. This will still leave a command parsed, but
    41  // will not incorporate the arguments into the ast.
    42  func parseIgnore(rest string, d *Directive) (*Node, map[string]bool, error) {
    43  	return &Node{}, nil, nil
    44  }
    45  
    46  // helper to parse words (i.e space delimited or quoted strings) in a statement.
    47  // The quotes are preserved as part of this function and they are stripped later
    48  // as part of processWords().
    49  func parseWords(rest string, d *Directive) []string {
    50  	const (
    51  		inSpaces = iota // looking for start of a word
    52  		inWord
    53  		inQuote
    54  	)
    55  
    56  	words := []string{}
    57  	phase := inSpaces
    58  	word := ""
    59  	quote := '\000'
    60  	blankOK := false
    61  	var ch rune
    62  	var chWidth int
    63  
    64  	for pos := 0; pos <= len(rest); pos += chWidth {
    65  		if pos != len(rest) {
    66  			ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
    67  		}
    68  
    69  		if phase == inSpaces { // Looking for start of word
    70  			if pos == len(rest) { // end of input
    71  				break
    72  			}
    73  			if unicode.IsSpace(ch) { // skip spaces
    74  				continue
    75  			}
    76  			phase = inWord // found it, fall through
    77  		}
    78  		if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
    79  			if blankOK || len(word) > 0 {
    80  				words = append(words, word)
    81  			}
    82  			break
    83  		}
    84  		if phase == inWord {
    85  			if unicode.IsSpace(ch) {
    86  				phase = inSpaces
    87  				if blankOK || len(word) > 0 {
    88  					words = append(words, word)
    89  				}
    90  				word = ""
    91  				blankOK = false
    92  				continue
    93  			}
    94  			if ch == '\'' || ch == '"' {
    95  				quote = ch
    96  				blankOK = true
    97  				phase = inQuote
    98  			}
    99  			if ch == d.escapeToken {
   100  				if pos+chWidth == len(rest) {
   101  					continue // just skip an escape token at end of line
   102  				}
   103  				// If we're not quoted and we see an escape token, then always just
   104  				// add the escape token plus the char to the word, even if the char
   105  				// is a quote.
   106  				word += string(ch)
   107  				pos += chWidth
   108  				ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
   109  			}
   110  			word += string(ch)
   111  			continue
   112  		}
   113  		if phase == inQuote {
   114  			if ch == quote {
   115  				phase = inWord
   116  			}
   117  			// The escape token is special except for ' quotes - can't escape anything for '
   118  			if ch == d.escapeToken && quote != '\'' {
   119  				if pos+chWidth == len(rest) {
   120  					phase = inWord
   121  					continue // just skip the escape token at end
   122  				}
   123  				pos += chWidth
   124  				word += string(ch)
   125  				ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
   126  			}
   127  			word += string(ch)
   128  		}
   129  	}
   130  
   131  	return words
   132  }
   133  
   134  // parse environment like statements. Note that this does *not* handle
   135  // variable interpolation, which will be handled in the evaluator.
   136  func parseNameVal(rest string, key string, d *Directive) (*Node, error) {
   137  	// This is kind of tricky because we need to support the old
   138  	// variant:   KEY name value
   139  	// as well as the new one:    KEY name=value ...
   140  	// The trigger to know which one is being used will be whether we hit
   141  	// a space or = first.  space ==> old, "=" ==> new
   142  
   143  	words := parseWords(rest, d)
   144  	if len(words) == 0 {
   145  		return nil, nil
   146  	}
   147  
   148  	// Old format (KEY name value)
   149  	if !strings.Contains(words[0], "=") {
   150  		parts := tokenWhitespace.Split(rest, 2)
   151  		if len(parts) < 2 {
   152  			return nil, fmt.Errorf(key + " must have two arguments")
   153  		}
   154  		return newKeyValueNode(parts[0], parts[1]), nil
   155  	}
   156  
   157  	var rootNode *Node
   158  	var prevNode *Node
   159  	for _, word := range words {
   160  		if !strings.Contains(word, "=") {
   161  			return nil, fmt.Errorf("syntax error - can't find = in %q. Must be of the form: name=value", word)
   162  		}
   163  
   164  		parts := strings.SplitN(word, "=", 2)
   165  		node := newKeyValueNode(parts[0], parts[1])
   166  		rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
   167  	}
   168  
   169  	return rootNode, nil
   170  }
   171  
   172  func newKeyValueNode(key, value string) *Node {
   173  	return &Node{
   174  		Value: key,
   175  		Next:  &Node{Value: value},
   176  	}
   177  }
   178  
   179  func appendKeyValueNode(node, rootNode, prevNode *Node) (*Node, *Node) {
   180  	if rootNode == nil {
   181  		rootNode = node
   182  	}
   183  	if prevNode != nil {
   184  		prevNode.Next = node
   185  	}
   186  
   187  	prevNode = node.Next
   188  	return rootNode, prevNode
   189  }
   190  
   191  //func parseEnv(rest string, d *Directive) (*Node, map[string]bool, error) {
   192  //	node, err := parseNameVal(rest, "ENV", d)
   193  //	return node, nil, err
   194  //}
   195  
   196  //nolint:unparam
   197  func parseLabel(rest string, d *Directive) (*Node, map[string]bool, error) {
   198  	node, err := parseNameVal(rest, commandLabel, d)
   199  	return node, nil, err
   200  }
   201  
   202  // NodeFromLabels returns a Node for the injected labels
   203  //func NodeFromLabels(labels map[string]string) *Node {
   204  //	keys := []string{}
   205  //	for key := range labels {
   206  //		keys = append(keys, key)
   207  //	}
   208  //	// Sort the label to have a repeatable order
   209  //	sort.Strings(keys)
   210  //
   211  //	labelPairs := []string{}
   212  //	var rootNode *Node
   213  //	var prevNode *Node
   214  //	for _, key := range keys {
   215  //		value := labels[key]
   216  //		labelPairs = append(labelPairs, fmt.Sprintf("%q='%s'", key, value))
   217  //		// Value must be single quoted to prevent env variable expansion
   218  //		// See https://github.com/docker/docker/issues/26027
   219  //		node := newKeyValueNode(key, "'"+value+"'")
   220  //		rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
   221  //	}
   222  //
   223  //	return &Node{
   224  //		Value:    command.Label,
   225  //		Original: commandLabel + " " + strings.Join(labelPairs, " "),
   226  //		Next:     rootNode,
   227  //	}
   228  //}
   229  
   230  // parses a statement containing one or more keyword definition(s) and/or
   231  // value assignments, like `name1 name2= name3="" name4=value`.
   232  // Note that this is a stricter format than the old format of assignment,
   233  // allowed by parseNameVal(), in a way that this only allows assignment of the
   234  // form `keyword=[<value>]` like  `name2=`, `name3=""`, and `name4=value` above.
   235  // In addition, a keyword definition alone is of the form `keyword` like `name1`
   236  // above. And the assignments `name2=` and `name3=""` are equivalent and
   237  // assign an empty value to the respective keywords.
   238  //
   239  //nolint:unparam
   240  func parseNameOrNameVal(rest string, d *Directive) (*Node, map[string]bool, error) {
   241  	words := parseWords(rest, d)
   242  	if len(words) == 0 {
   243  		return nil, nil, nil
   244  	}
   245  
   246  	var (
   247  		rootnode *Node
   248  		prevNode *Node
   249  	)
   250  	for i, word := range words {
   251  		node := &Node{}
   252  		node.Value = word
   253  		if i == 0 {
   254  			rootnode = node
   255  		} else {
   256  			prevNode.Next = node
   257  		}
   258  		prevNode = node
   259  	}
   260  
   261  	return rootnode, nil, nil
   262  }
   263  
   264  // parses a whitespace-delimited set of arguments. The result is effectively a
   265  // linked list of string arguments.
   266  //
   267  //nolint:unparam
   268  func parseStringsWhitespaceDelimited(rest string, d *Directive) (*Node, map[string]bool, error) {
   269  	if rest == "" {
   270  		return nil, nil, nil
   271  	}
   272  
   273  	node := &Node{}
   274  	rootnode := node
   275  	prevnode := node
   276  	for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp
   277  		prevnode = node
   278  		node.Value = str
   279  		node.Next = &Node{}
   280  		node = node.Next
   281  	}
   282  
   283  	// XXX to get around regexp.Split *always* providing an empty string at the
   284  	// end due to how our loop is constructed, nil out the last node in the
   285  	// chain.
   286  	prevnode.Next = nil
   287  
   288  	return rootnode, nil, nil
   289  }
   290  
   291  // parseString just wraps the string in quotes and returns a working node.
   292  //
   293  //nolint:unparam
   294  func parseString(rest string, d *Directive) (*Node, map[string]bool, error) {
   295  	if rest == "" {
   296  		return nil, nil, nil
   297  	}
   298  	n := &Node{}
   299  	n.Value = rest
   300  	return n, nil, nil
   301  }
   302  
   303  // parseJSON converts JSON arrays to an AST.
   304  //
   305  //nolint:unparam
   306  func parseJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
   307  	rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
   308  	if !strings.HasPrefix(rest, "[") {
   309  		return nil, nil, fmt.Errorf(`error parsing "%s" as a JSON array`, rest)
   310  	}
   311  
   312  	var myJSON []interface{}
   313  	if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil {
   314  		return nil, nil, err
   315  	}
   316  
   317  	var top, prev *Node
   318  	for _, str := range myJSON {
   319  		s, ok := str.(string)
   320  		if !ok {
   321  			return nil, nil, errDockerfileNotStringArray
   322  		}
   323  
   324  		node := &Node{Value: s}
   325  		if prev == nil {
   326  			top = node
   327  		} else {
   328  			prev.Next = node
   329  		}
   330  		prev = node
   331  	}
   332  
   333  	return top, map[string]bool{"json": true}, nil
   334  }
   335  
   336  // parseMaybeJSON determines if the argument appears to be a JSON array. If
   337  // so, passes to parseJSON; if not, quotes the result and returns a single
   338  // node.
   339  func parseMaybeJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
   340  	if rest == "" {
   341  		return nil, nil, nil
   342  	}
   343  
   344  	node, attrs, err := parseJSON(rest, d)
   345  
   346  	if err == nil {
   347  		return node, attrs, nil
   348  	}
   349  	if err == errDockerfileNotStringArray {
   350  		return nil, nil, err
   351  	}
   352  
   353  	node = &Node{}
   354  	node.Value = rest
   355  	return node, nil, nil
   356  }
   357  
   358  // parseMaybeJSONToList determines if the argument appears to be a JSON array. If
   359  // so, passes to parseJSON; if not, attempts to parse it as a whitespace
   360  // delimited string.
   361  func parseMaybeJSONToList(rest string, d *Directive) (*Node, map[string]bool, error) {
   362  	node, attrs, err := parseJSON(rest, d)
   363  
   364  	if err == nil {
   365  		return node, attrs, nil
   366  	}
   367  	if err == errDockerfileNotStringArray {
   368  		return nil, nil, err
   369  	}
   370  
   371  	return parseStringsWhitespaceDelimited(rest, d)
   372  }