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