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