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  }