| // 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/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 := shell.EnvsFromSlice(append(runConfigEnv, d.state.buildArgs.FilterAllowed(runConfigEnv)...)) | |
| if ex, ok := cmd.(instructions.SupportsSingleWordExpansion); ok { | |
| err := ex.Expand(func(word string) (string, error) { | |
| newword, _, err := d.shlex.ProcessWord(word, envs) | |
| return newword, err | |
| }) | |
| 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 <image>` 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 := convertKVStringsToMap(s.runConfig.Env) | |
| if _, ok := envMap["PATH"]; !ok { | |
| s.runConfig.Env = append(s.runConfig.Env, "PATH="+defaultPath) | |
| } | |
| } |