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