github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/builder/dockerfile/evaluator.go (about) 1 // Package dockerfile is the evaluation step in the Dockerfile parse/evaluate pipeline. 2 // 3 // It incorporates a dispatch table based on the parser.Node values (see the 4 // parser package for more information) that are yielded from the parser itself. 5 // Calling NewBuilder with the BuildOpts struct can be used to customize the 6 // experience for execution purposes only. Parsing is controlled in the parser 7 // package, and this division of responsibility should be respected. 8 // 9 // Please see the jump table targets for the actual invocations, most of which 10 // will call out to the functions in internals.go to deal with their tasks. 11 // 12 // ONBUILD is a special case, which is covered in the onbuild() func in 13 // dispatchers.go. 14 // 15 // The evaluator uses the concept of "steps", which are usually each processable 16 // line in the Dockerfile. Each step is numbered and certain actions are taken 17 // before and after each step, such as creating an image ID and removing temporary 18 // containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which 19 // includes its own set of steps (usually only one of them). 20 package dockerfile 21 22 import ( 23 "fmt" 24 "strings" 25 26 "github.com/docker/docker/builder/dockerfile/command" 27 "github.com/docker/docker/builder/dockerfile/parser" 28 ) 29 30 // Environment variable interpolation will happen on these statements only. 31 var replaceEnvAllowed = map[string]bool{ 32 command.Env: true, 33 command.Label: true, 34 command.Add: true, 35 command.Copy: true, 36 command.Workdir: true, 37 command.Expose: true, 38 command.Volume: true, 39 command.User: true, 40 command.StopSignal: true, 41 command.Arg: true, 42 } 43 44 // Certain commands are allowed to have their args split into more 45 // words after env var replacements. Meaning: 46 // ENV foo="123 456" 47 // EXPOSE $foo 48 // should result in the same thing as: 49 // EXPOSE 123 456 50 // and not treat "123 456" as a single word. 51 // Note that: EXPOSE "$foo" and EXPOSE $foo are not the same thing. 52 // Quotes will cause it to still be treated as single word. 53 var allowWordExpansion = map[string]bool{ 54 command.Expose: true, 55 } 56 57 var evaluateTable map[string]func(*Builder, []string, map[string]bool, string, string, bool) (string, error) 58 59 func init() { 60 evaluateTable = map[string]func(*Builder, []string, map[string]bool, string, string, bool) (string, error){ 61 command.Add: add, 62 command.Arg: arg, 63 command.Cmd: cmd, 64 command.Copy: dispatchCopy, // copy() is a go builtin 65 command.Entrypoint: entrypoint, 66 command.Env: env, 67 command.Expose: expose, 68 command.From: from, 69 command.Healthcheck: healthcheck, 70 command.Label: label, 71 command.Maintainer: maintainer, 72 command.Onbuild: onbuild, 73 command.Run: run, 74 command.Shell: shell, 75 command.StopSignal: stopSignal, 76 command.User: user, 77 command.Volume: volume, 78 command.Workdir: workdir, 79 } 80 } 81 82 // This method is the entrypoint to all statement handling routines. 83 // 84 // Almost all nodes will have this structure: 85 // Child[Node, Node, Node] where Child is from parser.Node.Children and each 86 // node comes from parser.Node.Next. This forms a "line" with a statement and 87 // arguments and we process them in this normalized form by hitting 88 // evaluateTable with the leaf nodes of the command and the Builder object. 89 // 90 // ONBUILD is a special case; in this case the parser will emit: 91 // Child[Node, Child[Node, Node...]] where the first node is the literal 92 // "onbuild" and the child entrypoint is the command of the ONBUILD statement, 93 // such as `RUN` in ONBUILD RUN foo. There is special case logic in here to 94 // deal with that, at least until it becomes more of a general concern with new 95 // features. 96 97 //id string, c *container.Container 98 func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node, id string) (string, error) { 99 fmt.Println("dockerfile/evaluator.go dispatchers() tmpContainerID ", id) 100 cmd := ast.Value 101 upperCasedCmd := strings.ToUpper(cmd) 102 103 // To ensure the user is given a decent error message if the platform 104 // on which the daemon is running does not support a builder command. 105 if err := platformSupports(strings.ToLower(cmd)); err != nil { 106 return "",err 107 } 108 109 attrs := ast.Attributes 110 original := ast.Original 111 flags := ast.Flags 112 strList := []string{} 113 msg := fmt.Sprintf("Step %d/%d : %s", stepN+1, stepTotal, upperCasedCmd) 114 115 fmt.Println("dockerfile/evalutor.go/dispatch() : ", upperCasedCmd) 116 117 if len(ast.Flags) > 0 { 118 msg += " " + strings.Join(ast.Flags, " ") 119 } 120 121 if cmd == "onbuild" { 122 if ast.Next == nil { 123 return "", fmt.Errorf("ONBUILD requires at least one argument") 124 } 125 ast = ast.Next.Children[0] 126 strList = append(strList, ast.Value) 127 msg += " " + ast.Value 128 129 if len(ast.Flags) > 0 { 130 msg += " " + strings.Join(ast.Flags, " ") 131 } 132 133 } 134 135 // count the number of nodes that we are going to traverse first 136 // so we can pre-create the argument and message array. This speeds up the 137 // allocation of those list a lot when they have a lot of arguments 138 cursor := ast 139 var n int 140 for cursor.Next != nil { 141 cursor = cursor.Next 142 n++ 143 } 144 msgList := make([]string, n) 145 146 var i int 147 // Append the build-time args to config-environment. 148 // This allows builder config to override the variables, making the behavior similar to 149 // a shell script i.e. `ENV foo bar` overrides value of `foo` passed in build 150 // context. But `ENV foo $foo` will use the value from build context if one 151 // isn't already been defined by a previous ENV primitive. 152 // Note, we get this behavior because we know that ProcessWord() will 153 // stop on the first occurrence of a variable name and not notice 154 // a subsequent one. So, putting the buildArgs list after the Config.Env 155 // list, in 'envs', is safe. 156 envs := b.runConfig.Env 157 for key, val := range b.options.BuildArgs { 158 if !b.isBuildArgAllowed(key) { 159 // skip build-args that are not in allowed list, meaning they have 160 // not been defined by an "ARG" Dockerfile command yet. 161 // This is an error condition but only if there is no "ARG" in the entire 162 // Dockerfile, so we'll generate any necessary errors after we parsed 163 // the entire file (see 'leftoverArgs' processing in evaluator.go ) 164 continue 165 } 166 envs = append(envs, fmt.Sprintf("%s=%s", key, *val)) 167 } 168 for ast.Next != nil { 169 ast = ast.Next 170 var str string 171 str = ast.Value 172 if replaceEnvAllowed[cmd] { 173 var err error 174 var words []string 175 176 if allowWordExpansion[cmd] { 177 words, err = ProcessWords(str, envs, b.directive.EscapeToken) 178 if err != nil { 179 return "", err 180 } 181 strList = append(strList, words...) 182 } else { 183 str, err = ProcessWord(str, envs, b.directive.EscapeToken) 184 if err != nil { 185 return "", err 186 } 187 strList = append(strList, str) 188 } 189 } else { 190 strList = append(strList, str) 191 } 192 msgList[i] = ast.Value 193 i++ 194 } 195 196 msg += " " + strings.Join(msgList, " ") 197 fmt.Fprintln(b.Stdout, msg) 198 199 200 finishedFlag := false 201 if stepN == stepTotal { 202 finishedFlag = true 203 } 204 205 // XXX yes, we skip any cmds that are not valid; the parser should have 206 // picked these out already. 207 if f, ok := evaluateTable[cmd]; ok { 208 fmt.Println("dockerfile/evaluator.go evaluateTable[] ", f) 209 b.flags = NewBFlags() 210 b.flags.Args = flags 211 return f(b, strList, attrs, original, id, finishedFlag) 212 } 213 214 return "", fmt.Errorf("Unknown instruction: %s", upperCasedCmd) 215 } 216 217 // checkDispatch does a simple check for syntax errors of the Dockerfile. 218 // Because some of the instructions can only be validated through runtime, 219 // arg, env, etc., this syntax check will not be complete and could not replace 220 // the runtime check. Instead, this function is only a helper that allows 221 // user to find out the obvious error in Dockerfile earlier on. 222 // onbuild bool: indicate if instruction XXX is part of `ONBUILD XXX` trigger 223 func (b *Builder) checkDispatch(ast *parser.Node, onbuild bool) error { 224 cmd := ast.Value 225 upperCasedCmd := strings.ToUpper(cmd) 226 227 // To ensure the user is given a decent error message if the platform 228 // on which the daemon is running does not support a builder command. 229 if err := platformSupports(strings.ToLower(cmd)); err != nil { 230 return err 231 } 232 233 // The instruction itself is ONBUILD, we will make sure it follows with at 234 // least one argument 235 if upperCasedCmd == "ONBUILD" { 236 if ast.Next == nil { 237 return fmt.Errorf("ONBUILD requires at least one argument") 238 } 239 } 240 241 // The instruction is part of ONBUILD trigger (not the instruction itself) 242 if onbuild { 243 switch upperCasedCmd { 244 case "ONBUILD": 245 return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") 246 case "MAINTAINER", "FROM": 247 return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd) 248 } 249 } 250 251 if _, ok := evaluateTable[cmd]; ok { 252 return nil 253 } 254 255 return fmt.Errorf("Unknown instruction: %s", upperCasedCmd) 256 }