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 }