github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/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 "reflect" 24 "strconv" 25 "strings" 26 27 "github.com/docker/docker/api/types/container" 28 "github.com/docker/docker/builder" 29 "github.com/docker/docker/builder/dockerfile/instructions" 30 "github.com/docker/docker/pkg/system" 31 "github.com/docker/docker/runconfig/opts" 32 "github.com/pkg/errors" 33 ) 34 35 func dispatch(d dispatchRequest, cmd instructions.Command) (err error) { 36 if c, ok := cmd.(instructions.PlatformSpecific); ok { 37 optionsOS := system.ParsePlatform(d.builder.options.Platform).OS 38 err := c.CheckPlatform(optionsOS) 39 if err != nil { 40 return validationError{err} 41 } 42 } 43 runConfigEnv := d.state.runConfig.Env 44 envs := append(runConfigEnv, d.state.buildArgs.FilterAllowed(runConfigEnv)...) 45 46 if ex, ok := cmd.(instructions.SupportsSingleWordExpansion); ok { 47 err := ex.Expand(func(word string) (string, error) { 48 return d.shlex.ProcessWord(word, envs) 49 }) 50 if err != nil { 51 return validationError{err} 52 } 53 } 54 55 defer func() { 56 if d.builder.options.ForceRemove { 57 d.builder.containerManager.RemoveAll(d.builder.Stdout) 58 return 59 } 60 if d.builder.options.Remove && err == nil { 61 d.builder.containerManager.RemoveAll(d.builder.Stdout) 62 return 63 } 64 }() 65 switch c := cmd.(type) { 66 case *instructions.EnvCommand: 67 return dispatchEnv(d, c) 68 case *instructions.MaintainerCommand: 69 return dispatchMaintainer(d, c) 70 case *instructions.LabelCommand: 71 return dispatchLabel(d, c) 72 case *instructions.AddCommand: 73 return dispatchAdd(d, c) 74 case *instructions.CopyCommand: 75 return dispatchCopy(d, c) 76 case *instructions.OnbuildCommand: 77 return dispatchOnbuild(d, c) 78 case *instructions.WorkdirCommand: 79 return dispatchWorkdir(d, c) 80 case *instructions.RunCommand: 81 return dispatchRun(d, c) 82 case *instructions.CmdCommand: 83 return dispatchCmd(d, c) 84 case *instructions.HealthCheckCommand: 85 return dispatchHealthcheck(d, c) 86 case *instructions.EntrypointCommand: 87 return dispatchEntrypoint(d, c) 88 case *instructions.ExposeCommand: 89 return dispatchExpose(d, c, envs) 90 case *instructions.UserCommand: 91 return dispatchUser(d, c) 92 case *instructions.VolumeCommand: 93 return dispatchVolume(d, c) 94 case *instructions.StopSignalCommand: 95 return dispatchStopSignal(d, c) 96 case *instructions.ArgCommand: 97 return dispatchArg(d, c) 98 case *instructions.ShellCommand: 99 return dispatchShell(d, c) 100 } 101 return errors.Errorf("unsupported command type: %v", reflect.TypeOf(cmd)) 102 } 103 104 // dispatchState is a data object which is modified by dispatchers 105 type dispatchState struct { 106 runConfig *container.Config 107 maintainer string 108 cmdSet bool 109 imageID string 110 baseImage builder.Image 111 stageName string 112 buildArgs *buildArgs 113 } 114 115 func newDispatchState(baseArgs *buildArgs) *dispatchState { 116 args := baseArgs.Clone() 117 args.ResetAllowed() 118 return &dispatchState{runConfig: &container.Config{}, buildArgs: args} 119 } 120 121 type stagesBuildResults struct { 122 flat []*container.Config 123 indexed map[string]*container.Config 124 } 125 126 func newStagesBuildResults() *stagesBuildResults { 127 return &stagesBuildResults{ 128 indexed: make(map[string]*container.Config), 129 } 130 } 131 132 func (r *stagesBuildResults) getByName(name string) (*container.Config, bool) { 133 c, ok := r.indexed[strings.ToLower(name)] 134 return c, ok 135 } 136 137 func (r *stagesBuildResults) validateIndex(i int) error { 138 if i == len(r.flat) { 139 return errors.New("refers to current build stage") 140 } 141 if i < 0 || i > len(r.flat) { 142 return errors.New("index out of bounds") 143 } 144 return nil 145 } 146 147 func (r *stagesBuildResults) get(nameOrIndex string) (*container.Config, error) { 148 if c, ok := r.getByName(nameOrIndex); ok { 149 return c, nil 150 } 151 ix, err := strconv.ParseInt(nameOrIndex, 10, 0) 152 if err != nil { 153 return nil, nil 154 } 155 if err := r.validateIndex(int(ix)); err != nil { 156 return nil, err 157 } 158 return r.flat[ix], nil 159 } 160 161 func (r *stagesBuildResults) checkStageNameAvailable(name string) error { 162 if name != "" { 163 if _, ok := r.getByName(name); ok { 164 return errors.Errorf("%s stage name already used", name) 165 } 166 } 167 return nil 168 } 169 170 func (r *stagesBuildResults) commitStage(name string, config *container.Config) error { 171 if name != "" { 172 if _, ok := r.getByName(name); ok { 173 return errors.Errorf("%s stage name already used", name) 174 } 175 r.indexed[strings.ToLower(name)] = config 176 } 177 r.flat = append(r.flat, config) 178 return nil 179 } 180 181 func commitStage(state *dispatchState, stages *stagesBuildResults) error { 182 return stages.commitStage(state.stageName, state.runConfig) 183 } 184 185 type dispatchRequest struct { 186 state *dispatchState 187 shlex *ShellLex 188 builder *Builder 189 source builder.Source 190 stages *stagesBuildResults 191 } 192 193 func newDispatchRequest(builder *Builder, escapeToken rune, source builder.Source, buildArgs *buildArgs, stages *stagesBuildResults) dispatchRequest { 194 return dispatchRequest{ 195 state: newDispatchState(buildArgs), 196 shlex: NewShellLex(escapeToken), 197 builder: builder, 198 source: source, 199 stages: stages, 200 } 201 } 202 203 func (s *dispatchState) updateRunConfig() { 204 s.runConfig.Image = s.imageID 205 } 206 207 // hasFromImage returns true if the builder has processed a `FROM <image>` line 208 func (s *dispatchState) hasFromImage() bool { 209 return s.imageID != "" || (s.baseImage != nil && s.baseImage.ImageID() == "") 210 } 211 212 func (s *dispatchState) beginStage(stageName string, image builder.Image) { 213 s.stageName = stageName 214 s.imageID = image.ImageID() 215 216 if image.RunConfig() != nil { 217 // copy avoids referencing the same instance when 2 stages have the same base 218 s.runConfig = copyRunConfig(image.RunConfig()) 219 } else { 220 s.runConfig = &container.Config{} 221 } 222 s.baseImage = image 223 s.setDefaultPath() 224 s.runConfig.OpenStdin = false 225 s.runConfig.StdinOnce = false 226 } 227 228 // Add the default PATH to runConfig.ENV if one exists for the operating system and there 229 // is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS 230 func (s *dispatchState) setDefaultPath() { 231 defaultPath := system.DefaultPathEnv(s.baseImage.OperatingSystem()) 232 if defaultPath == "" { 233 return 234 } 235 envMap := opts.ConvertKVStringsToMap(s.runConfig.Env) 236 if _, ok := envMap["PATH"]; !ok { 237 s.runConfig.Env = append(s.runConfig.Env, "PATH="+defaultPath) 238 } 239 }