github.com/tompao/docker@v1.9.1/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 resposibility 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 "runtime" 25 "strings" 26 27 "github.com/docker/docker/builder/dockerfile/command" 28 "github.com/docker/docker/builder/dockerfile/parser" 29 ) 30 31 // Environment variable interpolation will happen on these statements only. 32 var replaceEnvAllowed = map[string]struct{}{ 33 command.Env: {}, 34 command.Label: {}, 35 command.Add: {}, 36 command.Copy: {}, 37 command.Workdir: {}, 38 command.Expose: {}, 39 command.Volume: {}, 40 command.User: {}, 41 command.StopSignal: {}, 42 command.Arg: {}, 43 } 44 45 var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error 46 47 func init() { 48 evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{ 49 command.Env: env, 50 command.Label: label, 51 command.Maintainer: maintainer, 52 command.Add: add, 53 command.Copy: dispatchCopy, // copy() is a go builtin 54 command.From: from, 55 command.Onbuild: onbuild, 56 command.Workdir: workdir, 57 command.Run: run, 58 command.Cmd: cmd, 59 command.Entrypoint: entrypoint, 60 command.Expose: expose, 61 command.Volume: volume, 62 command.User: user, 63 command.StopSignal: stopSignal, 64 command.Arg: arg, 65 } 66 } 67 68 // This method is the entrypoint to all statement handling routines. 69 // 70 // Almost all nodes will have this structure: 71 // Child[Node, Node, Node] where Child is from parser.Node.Children and each 72 // node comes from parser.Node.Next. This forms a "line" with a statement and 73 // arguments and we process them in this normalized form by hitting 74 // evaluateTable with the leaf nodes of the command and the Builder object. 75 // 76 // ONBUILD is a special case; in this case the parser will emit: 77 // Child[Node, Child[Node, Node...]] where the first node is the literal 78 // "onbuild" and the child entrypoint is the command of the ONBUILD statement, 79 // such as `RUN` in ONBUILD RUN foo. There is special case logic in here to 80 // deal with that, at least until it becomes more of a general concern with new 81 // features. 82 func (b *Builder) dispatch(stepN int, ast *parser.Node) error { 83 cmd := ast.Value 84 upperCasedCmd := strings.ToUpper(cmd) 85 86 // To ensure the user is given a decent error message if the platform 87 // on which the daemon is running does not support a builder command. 88 if err := platformSupports(strings.ToLower(cmd)); err != nil { 89 return err 90 } 91 92 attrs := ast.Attributes 93 original := ast.Original 94 flags := ast.Flags 95 strs := []string{} 96 msg := fmt.Sprintf("Step %d : %s", stepN+1, upperCasedCmd) 97 98 if len(ast.Flags) > 0 { 99 msg += " " + strings.Join(ast.Flags, " ") 100 } 101 102 if cmd == "onbuild" { 103 if ast.Next == nil { 104 return fmt.Errorf("ONBUILD requires at least one argument") 105 } 106 ast = ast.Next.Children[0] 107 strs = append(strs, ast.Value) 108 msg += " " + ast.Value 109 110 if len(ast.Flags) > 0 { 111 msg += " " + strings.Join(ast.Flags, " ") 112 } 113 114 } 115 116 // count the number of nodes that we are going to traverse first 117 // so we can pre-create the argument and message array. This speeds up the 118 // allocation of those list a lot when they have a lot of arguments 119 cursor := ast 120 var n int 121 for cursor.Next != nil { 122 cursor = cursor.Next 123 n++ 124 } 125 l := len(strs) 126 strList := make([]string, n+l) 127 copy(strList, strs) 128 msgList := make([]string, n) 129 130 var i int 131 // Append the build-time args to config-environment. 132 // This allows builder config to override the variables, making the behavior similar to 133 // a shell script i.e. `ENV foo bar` overrides value of `foo` passed in build 134 // context. But `ENV foo $foo` will use the value from build context if one 135 // isn't already been defined by a previous ENV primitive. 136 // Note, we get this behavior because we know that ProcessWord() will 137 // stop on the first occurrence of a variable name and not notice 138 // a subsequent one. So, putting the buildArgs list after the Config.Env 139 // list, in 'envs', is safe. 140 envs := b.runConfig.Env 141 for key, val := range b.BuildArgs { 142 if !b.isBuildArgAllowed(key) { 143 // skip build-args that are not in allowed list, meaning they have 144 // not been defined by an "ARG" Dockerfile command yet. 145 // This is an error condition but only if there is no "ARG" in the entire 146 // Dockerfile, so we'll generate any necessary errors after we parsed 147 // the entire file (see 'leftoverArgs' processing in evaluator.go ) 148 continue 149 } 150 envs = append(envs, fmt.Sprintf("%s=%s", key, val)) 151 } 152 for ast.Next != nil { 153 ast = ast.Next 154 var str string 155 str = ast.Value 156 if _, ok := replaceEnvAllowed[cmd]; ok { 157 var err error 158 str, err = ProcessWord(ast.Value, envs) 159 if err != nil { 160 return err 161 } 162 } 163 strList[i+l] = str 164 msgList[i] = ast.Value 165 i++ 166 } 167 168 msg += " " + strings.Join(msgList, " ") 169 fmt.Fprintln(b.Stdout, msg) 170 171 // XXX yes, we skip any cmds that are not valid; the parser should have 172 // picked these out already. 173 if f, ok := evaluateTable[cmd]; ok { 174 b.flags = NewBFlags() 175 b.flags.Args = flags 176 return f(b, strList, attrs, original) 177 } 178 179 return fmt.Errorf("Unknown instruction: %s", upperCasedCmd) 180 } 181 182 // platformSupports is a short-term function to give users a quality error 183 // message if a Dockerfile uses a command not supported on the platform. 184 func platformSupports(command string) error { 185 if runtime.GOOS != "windows" { 186 return nil 187 } 188 switch command { 189 case "expose", "volume", "user", "stopsignal", "arg": 190 return fmt.Errorf("The daemon on this platform does not support the command '%s'", command) 191 } 192 return nil 193 }