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