github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/builder/dockerfile/builder.go (about)

     1  package dockerfile // import "github.com/docker/docker/builder/dockerfile"
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"runtime"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/backend"
    14  	"github.com/docker/docker/api/types/container"
    15  	"github.com/docker/docker/builder"
    16  	"github.com/docker/docker/builder/dockerfile/instructions"
    17  	"github.com/docker/docker/builder/dockerfile/parser"
    18  	"github.com/docker/docker/builder/dockerfile/shell"
    19  	"github.com/docker/docker/builder/fscache"
    20  	"github.com/docker/docker/builder/remotecontext"
    21  	"github.com/docker/docker/errdefs"
    22  	"github.com/docker/docker/pkg/idtools"
    23  	"github.com/docker/docker/pkg/streamformatter"
    24  	"github.com/docker/docker/pkg/stringid"
    25  	"github.com/docker/docker/pkg/system"
    26  	"github.com/moby/buildkit/session"
    27  	"github.com/pkg/errors"
    28  	"github.com/sirupsen/logrus"
    29  	"golang.org/x/net/context"
    30  	"golang.org/x/sync/syncmap"
    31  )
    32  
    33  var validCommitCommands = map[string]bool{
    34  	"cmd":         true,
    35  	"entrypoint":  true,
    36  	"healthcheck": true,
    37  	"env":         true,
    38  	"expose":      true,
    39  	"label":       true,
    40  	"onbuild":     true,
    41  	"user":        true,
    42  	"volume":      true,
    43  	"workdir":     true,
    44  }
    45  
    46  const (
    47  	stepFormat = "Step %d/%d : %v"
    48  )
    49  
    50  // SessionGetter is object used to get access to a session by uuid
    51  type SessionGetter interface {
    52  	Get(ctx context.Context, uuid string) (session.Caller, error)
    53  }
    54  
    55  // BuildManager is shared across all Builder objects
    56  type BuildManager struct {
    57  	idMappings *idtools.IDMappings
    58  	backend    builder.Backend
    59  	pathCache  pathCache // TODO: make this persistent
    60  	sg         SessionGetter
    61  	fsCache    *fscache.FSCache
    62  }
    63  
    64  // NewBuildManager creates a BuildManager
    65  func NewBuildManager(b builder.Backend, sg SessionGetter, fsCache *fscache.FSCache, idMappings *idtools.IDMappings) (*BuildManager, error) {
    66  	bm := &BuildManager{
    67  		backend:    b,
    68  		pathCache:  &syncmap.Map{},
    69  		sg:         sg,
    70  		idMappings: idMappings,
    71  		fsCache:    fsCache,
    72  	}
    73  	if err := fsCache.RegisterTransport(remotecontext.ClientSessionRemote, NewClientSessionTransport()); err != nil {
    74  		return nil, err
    75  	}
    76  	return bm, nil
    77  }
    78  
    79  // Build starts a new build from a BuildConfig
    80  func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (*builder.Result, error) {
    81  	buildsTriggered.Inc()
    82  	if config.Options.Dockerfile == "" {
    83  		config.Options.Dockerfile = builder.DefaultDockerfileName
    84  	}
    85  
    86  	source, dockerfile, err := remotecontext.Detect(config)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	defer func() {
    91  		if source != nil {
    92  			if err := source.Close(); err != nil {
    93  				logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
    94  			}
    95  		}
    96  	}()
    97  
    98  	ctx, cancel := context.WithCancel(ctx)
    99  	defer cancel()
   100  
   101  	if src, err := bm.initializeClientSession(ctx, cancel, config.Options); err != nil {
   102  		return nil, err
   103  	} else if src != nil {
   104  		source = src
   105  	}
   106  
   107  	os := runtime.GOOS
   108  	optionsPlatform := system.ParsePlatform(config.Options.Platform)
   109  	if dockerfile.OS != "" {
   110  		if optionsPlatform.OS != "" && optionsPlatform.OS != dockerfile.OS {
   111  			return nil, fmt.Errorf("invalid platform")
   112  		}
   113  		os = dockerfile.OS
   114  	} else if optionsPlatform.OS != "" {
   115  		os = optionsPlatform.OS
   116  	}
   117  	config.Options.Platform = os
   118  	dockerfile.OS = os
   119  
   120  	builderOptions := builderOptions{
   121  		Options:        config.Options,
   122  		ProgressWriter: config.ProgressWriter,
   123  		Backend:        bm.backend,
   124  		PathCache:      bm.pathCache,
   125  		IDMappings:     bm.idMappings,
   126  	}
   127  	return newBuilder(ctx, builderOptions).build(source, dockerfile)
   128  }
   129  
   130  func (bm *BuildManager) initializeClientSession(ctx context.Context, cancel func(), options *types.ImageBuildOptions) (builder.Source, error) {
   131  	if options.SessionID == "" || bm.sg == nil {
   132  		return nil, nil
   133  	}
   134  	logrus.Debug("client is session enabled")
   135  
   136  	connectCtx, cancelCtx := context.WithTimeout(ctx, sessionConnectTimeout)
   137  	defer cancelCtx()
   138  
   139  	c, err := bm.sg.Get(connectCtx, options.SessionID)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	go func() {
   144  		<-c.Context().Done()
   145  		cancel()
   146  	}()
   147  	if options.RemoteContext == remotecontext.ClientSessionRemote {
   148  		st := time.Now()
   149  		csi, err := NewClientSessionSourceIdentifier(ctx, bm.sg, options.SessionID)
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  		src, err := bm.fsCache.SyncFrom(ctx, csi)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		logrus.Debugf("sync-time: %v", time.Since(st))
   158  		return src, nil
   159  	}
   160  	return nil, nil
   161  }
   162  
   163  // builderOptions are the dependencies required by the builder
   164  type builderOptions struct {
   165  	Options        *types.ImageBuildOptions
   166  	Backend        builder.Backend
   167  	ProgressWriter backend.ProgressWriter
   168  	PathCache      pathCache
   169  	IDMappings     *idtools.IDMappings
   170  }
   171  
   172  // Builder is a Dockerfile builder
   173  // It implements the builder.Backend interface.
   174  type Builder struct {
   175  	options *types.ImageBuildOptions
   176  
   177  	Stdout io.Writer
   178  	Stderr io.Writer
   179  	Aux    *streamformatter.AuxFormatter
   180  	Output io.Writer
   181  
   182  	docker    builder.Backend
   183  	clientCtx context.Context
   184  
   185  	idMappings       *idtools.IDMappings
   186  	disableCommit    bool
   187  	imageSources     *imageSources
   188  	pathCache        pathCache
   189  	containerManager *containerManager
   190  	imageProber      ImageProber
   191  }
   192  
   193  // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
   194  func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
   195  	config := options.Options
   196  	if config == nil {
   197  		config = new(types.ImageBuildOptions)
   198  	}
   199  
   200  	b := &Builder{
   201  		clientCtx:        clientCtx,
   202  		options:          config,
   203  		Stdout:           options.ProgressWriter.StdoutFormatter,
   204  		Stderr:           options.ProgressWriter.StderrFormatter,
   205  		Aux:              options.ProgressWriter.AuxFormatter,
   206  		Output:           options.ProgressWriter.Output,
   207  		docker:           options.Backend,
   208  		idMappings:       options.IDMappings,
   209  		imageSources:     newImageSources(clientCtx, options),
   210  		pathCache:        options.PathCache,
   211  		imageProber:      newImageProber(options.Backend, config.CacheFrom, config.NoCache),
   212  		containerManager: newContainerManager(options.Backend),
   213  	}
   214  
   215  	return b
   216  }
   217  
   218  // Build runs the Dockerfile builder by parsing the Dockerfile and executing
   219  // the instructions from the file.
   220  func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*builder.Result, error) {
   221  	defer b.imageSources.Unmount()
   222  
   223  	addNodesForLabelOption(dockerfile.AST, b.options.Labels)
   224  
   225  	stages, metaArgs, err := instructions.Parse(dockerfile.AST)
   226  	if err != nil {
   227  		if instructions.IsUnknownInstruction(err) {
   228  			buildsFailed.WithValues(metricsUnknownInstructionError).Inc()
   229  		}
   230  		return nil, errdefs.InvalidParameter(err)
   231  	}
   232  	if b.options.Target != "" {
   233  		targetIx, found := instructions.HasStage(stages, b.options.Target)
   234  		if !found {
   235  			buildsFailed.WithValues(metricsBuildTargetNotReachableError).Inc()
   236  			return nil, errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
   237  		}
   238  		stages = stages[:targetIx+1]
   239  	}
   240  
   241  	dockerfile.PrintWarnings(b.Stderr)
   242  	dispatchState, err := b.dispatchDockerfileWithCancellation(stages, metaArgs, dockerfile.EscapeToken, source)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  	if dispatchState.imageID == "" {
   247  		buildsFailed.WithValues(metricsDockerfileEmptyError).Inc()
   248  		return nil, errors.New("No image was generated. Is your Dockerfile empty?")
   249  	}
   250  	return &builder.Result{ImageID: dispatchState.imageID, FromImage: dispatchState.baseImage}, nil
   251  }
   252  
   253  func emitImageID(aux *streamformatter.AuxFormatter, state *dispatchState) error {
   254  	if aux == nil || state.imageID == "" {
   255  		return nil
   256  	}
   257  	return aux.Emit(types.BuildResult{ID: state.imageID})
   258  }
   259  
   260  func processMetaArg(meta instructions.ArgCommand, shlex *shell.Lex, args *buildArgs) error {
   261  	// shell.Lex currently only support the concatenated string format
   262  	envs := convertMapToEnvList(args.GetAllAllowed())
   263  	if err := meta.Expand(func(word string) (string, error) {
   264  		return shlex.ProcessWord(word, envs)
   265  	}); err != nil {
   266  		return err
   267  	}
   268  	args.AddArg(meta.Key, meta.Value)
   269  	args.AddMetaArg(meta.Key, meta.Value)
   270  	return nil
   271  }
   272  
   273  func printCommand(out io.Writer, currentCommandIndex int, totalCommands int, cmd interface{}) int {
   274  	fmt.Fprintf(out, stepFormat, currentCommandIndex, totalCommands, cmd)
   275  	fmt.Fprintln(out)
   276  	return currentCommandIndex + 1
   277  }
   278  
   279  func (b *Builder) dispatchDockerfileWithCancellation(parseResult []instructions.Stage, metaArgs []instructions.ArgCommand, escapeToken rune, source builder.Source) (*dispatchState, error) {
   280  	dispatchRequest := dispatchRequest{}
   281  	buildArgs := newBuildArgs(b.options.BuildArgs)
   282  	totalCommands := len(metaArgs) + len(parseResult)
   283  	currentCommandIndex := 1
   284  	for _, stage := range parseResult {
   285  		totalCommands += len(stage.Commands)
   286  	}
   287  	shlex := shell.NewLex(escapeToken)
   288  	for _, meta := range metaArgs {
   289  		currentCommandIndex = printCommand(b.Stdout, currentCommandIndex, totalCommands, &meta)
   290  
   291  		err := processMetaArg(meta, shlex, buildArgs)
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  	}
   296  
   297  	stagesResults := newStagesBuildResults()
   298  
   299  	for _, stage := range parseResult {
   300  		if err := stagesResults.checkStageNameAvailable(stage.Name); err != nil {
   301  			return nil, err
   302  		}
   303  		dispatchRequest = newDispatchRequest(b, escapeToken, source, buildArgs, stagesResults)
   304  
   305  		currentCommandIndex = printCommand(b.Stdout, currentCommandIndex, totalCommands, stage.SourceCode)
   306  		if err := initializeStage(dispatchRequest, &stage); err != nil {
   307  			return nil, err
   308  		}
   309  		dispatchRequest.state.updateRunConfig()
   310  		fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(dispatchRequest.state.imageID))
   311  		for _, cmd := range stage.Commands {
   312  			select {
   313  			case <-b.clientCtx.Done():
   314  				logrus.Debug("Builder: build cancelled!")
   315  				fmt.Fprint(b.Stdout, "Build cancelled\n")
   316  				buildsFailed.WithValues(metricsBuildCanceled).Inc()
   317  				return nil, errors.New("Build cancelled")
   318  			default:
   319  				// Not cancelled yet, keep going...
   320  			}
   321  
   322  			currentCommandIndex = printCommand(b.Stdout, currentCommandIndex, totalCommands, cmd)
   323  
   324  			if err := dispatch(dispatchRequest, cmd); err != nil {
   325  				return nil, err
   326  			}
   327  			dispatchRequest.state.updateRunConfig()
   328  			fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(dispatchRequest.state.imageID))
   329  
   330  		}
   331  		if err := emitImageID(b.Aux, dispatchRequest.state); err != nil {
   332  			return nil, err
   333  		}
   334  		buildArgs.MergeReferencedArgs(dispatchRequest.state.buildArgs)
   335  		if err := commitStage(dispatchRequest.state, stagesResults); err != nil {
   336  			return nil, err
   337  		}
   338  	}
   339  	buildArgs.WarnOnUnusedBuildArgs(b.Stdout)
   340  	return dispatchRequest.state, nil
   341  }
   342  
   343  func addNodesForLabelOption(dockerfile *parser.Node, labels map[string]string) {
   344  	if len(labels) == 0 {
   345  		return
   346  	}
   347  
   348  	node := parser.NodeFromLabels(labels)
   349  	dockerfile.Children = append(dockerfile.Children, node)
   350  }
   351  
   352  // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
   353  // It will:
   354  // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
   355  // - Do build by calling builder.dispatch() to call all entries' handling routines
   356  //
   357  // BuildFromConfig is used by the /commit endpoint, with the changes
   358  // coming from the query parameter of the same name.
   359  //
   360  // TODO: Remove?
   361  func BuildFromConfig(config *container.Config, changes []string, os string) (*container.Config, error) {
   362  	if !system.IsOSSupported(os) {
   363  		return nil, errdefs.InvalidParameter(system.ErrNotSupportedOperatingSystem)
   364  	}
   365  	if len(changes) == 0 {
   366  		return config, nil
   367  	}
   368  
   369  	dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
   370  	if err != nil {
   371  		return nil, errdefs.InvalidParameter(err)
   372  	}
   373  
   374  	b := newBuilder(context.Background(), builderOptions{
   375  		Options: &types.ImageBuildOptions{NoCache: true},
   376  	})
   377  
   378  	// ensure that the commands are valid
   379  	for _, n := range dockerfile.AST.Children {
   380  		if !validCommitCommands[n.Value] {
   381  			return nil, errdefs.InvalidParameter(errors.Errorf("%s is not a valid change command", n.Value))
   382  		}
   383  	}
   384  
   385  	b.Stdout = ioutil.Discard
   386  	b.Stderr = ioutil.Discard
   387  	b.disableCommit = true
   388  
   389  	commands := []instructions.Command{}
   390  	for _, n := range dockerfile.AST.Children {
   391  		cmd, err := instructions.ParseCommand(n)
   392  		if err != nil {
   393  			return nil, errdefs.InvalidParameter(err)
   394  		}
   395  		commands = append(commands, cmd)
   396  	}
   397  
   398  	dispatchRequest := newDispatchRequest(b, dockerfile.EscapeToken, nil, newBuildArgs(b.options.BuildArgs), newStagesBuildResults())
   399  	// We make mutations to the configuration, ensure we have a copy
   400  	dispatchRequest.state.runConfig = copyRunConfig(config)
   401  	dispatchRequest.state.imageID = config.Image
   402  	dispatchRequest.state.operatingSystem = os
   403  	for _, cmd := range commands {
   404  		err := dispatch(dispatchRequest, cmd)
   405  		if err != nil {
   406  			return nil, errdefs.InvalidParameter(err)
   407  		}
   408  		dispatchRequest.state.updateRunConfig()
   409  	}
   410  
   411  	return dispatchRequest.state.runConfig, nil
   412  }
   413  
   414  func convertMapToEnvList(m map[string]string) []string {
   415  	result := []string{}
   416  	for k, v := range m {
   417  		result = append(result, k+"="+v)
   418  	}
   419  	return result
   420  }