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