// Package dockerfile is the evaluation step in the Dockerfile parse/evaluate pipeline. // // It incorporates a dispatch table based on the parser.Node values (see the // parser package for more information) that are yielded from the parser itself. // Calling newBuilder with the BuildOpts struct can be used to customize the // experience for execution purposes only. Parsing is controlled in the parser // package, and this division of responsibility should be respected. // // Please see the jump table targets for the actual invocations, most of which // will call out to the functions in internals.go to deal with their tasks. // // ONBUILD is a special case, which is covered in the onbuild() func in // dispatchers.go. // // The evaluator uses the concept of "steps", which are usually each processable // line in the Dockerfile. Each step is numbered and certain actions are taken // before and after each step, such as creating an image ID and removing temporary // containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which // includes its own set of steps (usually only one of them). package dockerfile // import "github.com/docker/docker/builder/dockerfile" import ( "context" "reflect" "strconv" "strings" "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder" "github.com/docker/docker/errdefs" "github.com/docker/docker/image" "github.com/docker/docker/oci" "github.com/docker/docker/runconfig/opts" "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/shell" "github.com/pkg/errors" ) func dispatch(ctx context.Context, d dispatchRequest, cmd instructions.Command) (err error) { if c, ok := cmd.(instructions.PlatformSpecific); ok { err := c.CheckPlatform(d.state.operatingSystem) if err != nil { return errdefs.InvalidParameter(err) } } runConfigEnv := d.state.runConfig.Env envs := append(runConfigEnv, d.state.buildArgs.FilterAllowed(runConfigEnv)...) if ex, ok := cmd.(instructions.SupportsSingleWordExpansion); ok { err := ex.Expand(func(word string) (string, error) { return d.shlex.ProcessWord(word, envs) }) if err != nil { return errdefs.InvalidParameter(err) } } defer func() { if d.builder.options.ForceRemove { d.builder.containerManager.RemoveAll(d.builder.Stdout) return } if d.builder.options.Remove && err == nil { d.builder.containerManager.RemoveAll(d.builder.Stdout) return } }() switch c := cmd.(type) { case *instructions.EnvCommand: return dispatchEnv(ctx, d, c) case *instructions.MaintainerCommand: return dispatchMaintainer(ctx, d, c) case *instructions.LabelCommand: return dispatchLabel(ctx, d, c) case *instructions.AddCommand: return dispatchAdd(ctx, d, c) case *instructions.CopyCommand: return dispatchCopy(ctx, d, c) case *instructions.OnbuildCommand: return dispatchOnbuild(ctx, d, c) case *instructions.WorkdirCommand: return dispatchWorkdir(ctx, d, c) case *instructions.RunCommand: return dispatchRun(ctx, d, c) case *instructions.CmdCommand: return dispatchCmd(ctx, d, c) case *instructions.HealthCheckCommand: return dispatchHealthcheck(ctx, d, c) case *instructions.EntrypointCommand: return dispatchEntrypoint(ctx, d, c) case *instructions.ExposeCommand: return dispatchExpose(ctx, d, c, envs) case *instructions.UserCommand: return dispatchUser(ctx, d, c) case *instructions.VolumeCommand: return dispatchVolume(ctx, d, c) case *instructions.StopSignalCommand: return dispatchStopSignal(ctx, d, c) case *instructions.ArgCommand: return dispatchArg(ctx, d, c) case *instructions.ShellCommand: return dispatchShell(ctx, d, c) } return errors.Errorf("unsupported command type: %v", reflect.TypeOf(cmd)) } // dispatchState is a data object which is modified by dispatchers type dispatchState struct { runConfig *container.Config maintainer string cmdSet bool imageID string baseImage builder.Image stageName string buildArgs *BuildArgs operatingSystem string } func newDispatchState(baseArgs *BuildArgs) *dispatchState { args := baseArgs.Clone() args.ResetAllowed() return &dispatchState{runConfig: &container.Config{}, buildArgs: args} } type stagesBuildResults struct { flat []*container.Config indexed map[string]*container.Config } func newStagesBuildResults() *stagesBuildResults { return &stagesBuildResults{ indexed: make(map[string]*container.Config), } } func (r *stagesBuildResults) getByName(name string) (*container.Config, bool) { c, ok := r.indexed[strings.ToLower(name)] return c, ok } func (r *stagesBuildResults) validateIndex(i int) error { if i == len(r.flat) { return errors.New("refers to current build stage") } if i < 0 || i > len(r.flat) { return errors.New("index out of bounds") } return nil } func (r *stagesBuildResults) get(nameOrIndex string) (*container.Config, error) { if c, ok := r.getByName(nameOrIndex); ok { return c, nil } ix, err := strconv.ParseInt(nameOrIndex, 10, 0) if err != nil { return nil, nil } if err := r.validateIndex(int(ix)); err != nil { return nil, err } return r.flat[ix], nil } func (r *stagesBuildResults) checkStageNameAvailable(name string) error { if name != "" { if _, ok := r.getByName(name); ok { return errors.Errorf("%s stage name already used", name) } } return nil } func (r *stagesBuildResults) commitStage(name string, config *container.Config) error { if name != "" { if _, ok := r.getByName(name); ok { return errors.Errorf("%s stage name already used", name) } r.indexed[strings.ToLower(name)] = config } r.flat = append(r.flat, config) return nil } func commitStage(state *dispatchState, stages *stagesBuildResults) error { return stages.commitStage(state.stageName, state.runConfig) } type dispatchRequest struct { state *dispatchState shlex *shell.Lex builder *Builder source builder.Source stages *stagesBuildResults } func newDispatchRequest(builder *Builder, escapeToken rune, source builder.Source, buildArgs *BuildArgs, stages *stagesBuildResults) dispatchRequest { return dispatchRequest{ state: newDispatchState(buildArgs), shlex: shell.NewLex(escapeToken), builder: builder, source: source, stages: stages, } } func (s *dispatchState) updateRunConfig() { s.runConfig.Image = s.imageID } // hasFromImage returns true if the builder has processed a `FROM ` line func (s *dispatchState) hasFromImage() bool { return s.imageID != "" || (s.baseImage != nil && s.baseImage.ImageID() == "") } func (s *dispatchState) beginStage(stageName string, img builder.Image) error { s.stageName = stageName s.imageID = img.ImageID() s.operatingSystem = img.OperatingSystem() if err := image.CheckOS(s.operatingSystem); err != nil { return err } if img.RunConfig() != nil { // copy avoids referencing the same instance when 2 stages have the same base s.runConfig = copyRunConfig(img.RunConfig()) } else { s.runConfig = &container.Config{} } s.baseImage = img s.setDefaultPath() s.runConfig.OpenStdin = false s.runConfig.StdinOnce = false return nil } // Add the default PATH to runConfig.ENV if one exists for the operating system and there // is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS func (s *dispatchState) setDefaultPath() { // TODO(thaJeztah): use github.com/moby/buildkit/util/system.DefaultPathEnv() once https://github.com/moby/buildkit/pull/3158 is resolved. defaultPath := oci.DefaultPathEnv(s.operatingSystem) if defaultPath == "" { return } envMap := opts.ConvertKVStringsToMap(s.runConfig.Env) if _, ok := envMap["PATH"]; !ok { s.runConfig.Env = append(s.runConfig.Env, "PATH="+defaultPath) } }