github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/builder/dockerfile/dbuilder.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	apierrors "github.com/docker/docker/api/errors"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/backend"
    16  	"github.com/docker/docker/api/types/container"
    17  	"github.com/docker/docker/builder"
    18  	"github.com/docker/docker/builder/dockerfile/parser"
    19  	"github.com/docker/docker/image"
    20  	"github.com/docker/docker/pkg/stringid"
    21  	"github.com/docker/docker/reference"
    22  	perrors "github.com/pkg/errors"
    23  	"golang.org/x/net/context"
    24  )
    25  
    26  var validCommitCommands = map[string]bool{
    27  	"cmd":         true,
    28  	"entrypoint":  true,
    29  	"healthcheck": true,
    30  	"env":         true,
    31  	"expose":      true,
    32  	"label":       true,
    33  	"onbuild":     true,
    34  	"user":        true,
    35  	"volume":      true,
    36  	"workdir":     true,
    37  }
    38  
    39  // BuiltinAllowedBuildArgs is list of built-in allowed build args
    40  var BuiltinAllowedBuildArgs = map[string]bool{
    41  	"HTTP_PROXY":  true,
    42  	"http_proxy":  true,
    43  	"HTTPS_PROXY": true,
    44  	"https_proxy": true,
    45  	"FTP_PROXY":   true,
    46  	"ftp_proxy":   true,
    47  	"NO_PROXY":    true,
    48  	"no_proxy":    true,
    49  }
    50  
    51  // Builder is a Dockerfile builder
    52  // It implements the builder.Backend interface.
    53  type Builder struct {
    54  	options *types.ImageBuildOptions
    55  
    56  	Stdout io.Writer
    57  	Stderr io.Writer
    58  	Output io.Writer
    59  
    60  	docker    builder.Backend
    61  	context   builder.Context
    62  	clientCtx context.Context
    63  	cancel    context.CancelFunc
    64  
    65  	dockerfile       *parser.Node
    66  	runConfig        *container.Config // runconfig for cmd, run, entrypoint etc.
    67  	flags            *BFlags
    68  	tmpContainers    map[string]struct{}
    69  	image            string // imageID
    70  	noBaseImage      bool
    71  	maintainer       string
    72  	cmdSet           bool
    73  	disableCommit    bool
    74  	cacheBusted      bool
    75  	allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
    76  	directive        parser.Directive
    77  
    78  	// TODO: remove once docker.Commit can receive a tag
    79  	id string
    80  
    81  	imageCache builder.ImageCache
    82  	from       builder.Image
    83  }
    84  
    85  // BuildManager implements builder.Backend and is shared across all Builder objects.
    86  type BuildManager struct {
    87  	backend builder.Backend
    88  }
    89  
    90  // NewBuildManager creates a BuildManager.
    91  func NewBuildManager(b builder.Backend) (bm *BuildManager) {
    92  	return &BuildManager{backend: b}
    93  }
    94  
    95  // BuildFromContext builds a new image from a given context.
    96  func (bm *BuildManager) BuildFromContext(ctx context.Context, src io.ReadCloser, remote string, buildOptions *types.ImageBuildOptions, pg backend.ProgressWriter) (string, error) {
    97  	if buildOptions.Squash && !bm.backend.HasExperimental() {
    98  		return "", apierrors.NewBadRequestError(errors.New("squash is only supported with experimental mode"))
    99  	}
   100  	buildContext, dockerfileName, err := builder.DetectContextFromRemoteURL(src, remote, pg.ProgressReaderFunc)
   101  	if err != nil {
   102  		return "", err
   103  	}
   104  	defer func() {
   105  		if err := buildContext.Close(); err != nil {
   106  			logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
   107  		}
   108  	}()
   109  
   110  	if len(dockerfileName) > 0 {
   111  		buildOptions.Dockerfile = dockerfileName
   112  	}
   113  	b, err := NewBuilder(ctx, buildOptions, bm.backend, builder.DockerIgnoreContext{ModifiableContext: buildContext}, nil)
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  	return b.build(pg.StdoutFormatter, pg.StderrFormatter, pg.Output)
   118  }
   119  
   120  // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
   121  // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
   122  // will be read from the Context passed to Build().
   123  func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, buildContext builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
   124  	if config == nil {
   125  		config = new(types.ImageBuildOptions)
   126  	}
   127  	if config.BuildArgs == nil {
   128  		config.BuildArgs = make(map[string]*string)
   129  	}
   130  	ctx, cancel := context.WithCancel(clientCtx)
   131  	b = &Builder{
   132  		clientCtx:        ctx,
   133  		cancel:           cancel,
   134  		options:          config,
   135  		Stdout:           os.Stdout,
   136  		Stderr:           os.Stderr,
   137  		docker:           backend,
   138  		context:          buildContext,
   139  		runConfig:        new(container.Config),
   140  		tmpContainers:    map[string]struct{}{},
   141  		id:               stringid.GenerateNonCryptoID(),
   142  		allowedBuildArgs: make(map[string]bool),
   143  		directive: parser.Directive{
   144  			EscapeSeen:           false,
   145  			LookingForDirectives: true,
   146  		},
   147  	}
   148  	if icb, ok := backend.(builder.ImageCacheBuilder); ok {
   149  		b.imageCache = icb.MakeImageCache(config.CacheFrom)
   150  	}
   151  
   152  	parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape
   153  
   154  	if dockerfile != nil {
   155  		b.dockerfile, err = parser.Parse(dockerfile, &b.directive)
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  	}
   160  
   161  	return b, nil
   162  }
   163  
   164  // sanitizeRepoAndTags parses the raw "t" parameter received from the client
   165  // to a slice of repoAndTag.
   166  // It also validates each repoName and tag.
   167  func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
   168  	var (
   169  		repoAndTags []reference.Named
   170  		// This map is used for deduplicating the "-t" parameter.
   171  		uniqNames = make(map[string]struct{})
   172  	)
   173  	for _, repo := range names {
   174  		if repo == "" {
   175  			continue
   176  		}
   177  
   178  		ref, err := reference.ParseNamed(repo)
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  
   183  		ref = reference.WithDefaultTag(ref)
   184  
   185  		if _, isCanonical := ref.(reference.Canonical); isCanonical {
   186  			return nil, errors.New("build tag cannot contain a digest")
   187  		}
   188  
   189  		if _, isTagged := ref.(reference.NamedTagged); !isTagged {
   190  			ref, err = reference.WithTag(ref, reference.DefaultTag)
   191  			if err != nil {
   192  				return nil, err
   193  			}
   194  		}
   195  
   196  		nameWithTag := ref.String()
   197  
   198  		if _, exists := uniqNames[nameWithTag]; !exists {
   199  			uniqNames[nameWithTag] = struct{}{}
   200  			repoAndTags = append(repoAndTags, ref)
   201  		}
   202  	}
   203  	return repoAndTags, nil
   204  }
   205  
   206  // build runs the Dockerfile builder from a context and a docker object that allows to make calls
   207  // to Docker.
   208  //
   209  // This will (barring errors):
   210  //
   211  // * read the dockerfile from context
   212  // * parse the dockerfile if not already parsed
   213  // * walk the AST and execute it by dispatching to handlers. If Remove
   214  //   or ForceRemove is set, additional cleanup around containers happens after
   215  //   processing.
   216  // * Tag image, if applicable.
   217  // * Print a happy message and return the image ID.
   218  //
   219  func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (string, error) {
   220  	b.Stdout = stdout
   221  	b.Stderr = stderr
   222  	b.Output = out
   223  
   224      fmt.Println("dockerfile/builder.go build()")
   225  
   226  	// If Dockerfile was not parsed yet, extract it from the Context
   227  	if b.dockerfile == nil {
   228  		if err := b.readDockerfile(); err != nil {
   229  			return "", err
   230  		}
   231  	}
   232  
   233  	repoAndTags, err := sanitizeRepoAndTags(b.options.Tags)
   234  	if err != nil {
   235  		return "", err
   236  	}
   237  
   238  	if len(b.options.Labels) > 0 {
   239  		line := "LABEL "
   240  		for k, v := range b.options.Labels {
   241  			line += fmt.Sprintf("%q='%s' ", k, v)
   242  		}
   243  		_, node, err := parser.ParseLine(line, &b.directive, false)
   244  		if err != nil {
   245  			return "", err
   246  		}
   247  		b.dockerfile.Children = append(b.dockerfile.Children, node)
   248  	}
   249  
   250  	var shortImgID string
   251  	total := len(b.dockerfile.Children)
   252  	for _, n := range b.dockerfile.Children {
   253  		if err := b.checkDispatch(n, false); err != nil {
   254  			return "", err
   255  		}
   256  	}
   257  
   258      //var tmpFirstComtainerID string
   259      tmpFirstContainerID := ""
   260  	for i, n := range b.dockerfile.Children {
   261          fmt.Println("dockerfile/builder.go for()", i)
   262          if i >= 1 {
   263  //           fmt.Println("dockerfile/builder.go  build()  stop the build function!!!")
   264  //           break
   265          }
   266  
   267  		select {
   268  		case <-b.clientCtx.Done():
   269  			logrus.Debug("Builder: build cancelled!")
   270  			fmt.Fprintf(b.Stdout, "Build cancelled")
   271  			return "", fmt.Errorf("Build cancelled")
   272  		default:
   273  			// Not cancelled yet, keep going...
   274  		}
   275  
   276  
   277  /*		if id, err := b.dispatch(i, total, n, tmpFirstContainerID); err != nil {
   278  			if b.options.ForceRemove {
   279  //				b.clearTmp()
   280                  fmt.Println("builder.go/build() Don't force remove tmpContainer")
   281  			}
   282  			return "", err
   283  		}else {
   284           tmpFirstContainerID = id
   285             if id == "" {
   286                id = "nullll"
   287             }
   288             fmt.Println("dockerfile/evaluator.go dispatches() ", id)
   289          }
   290  */
   291  
   292  		if _, err := b.dispatch(i, total, n, tmpFirstContainerID); err != nil {
   293  			if b.options.ForceRemove {
   294  				b.clearTmp()
   295                  fmt.Println("builder.go/build() Don't force remove tmpContainer")
   296  			}
   297  			return "", err
   298  		}else {
   299  //         tmpFirstContainerID = id
   300  //           if id == "" {
   301  //              id = "nullll"
   302  //           }
   303  //           fmt.Println("dockerfile/evaluator.go dispatches() ", id)
   304             fmt.Println("dockerfile/evaluator.go dispatches()  completely!")
   305           }
   306  
   307  		shortImgID = stringid.TruncateID(b.image)
   308  		fmt.Fprintf(b.Stdout, " ---> %s builder.go/build()\n", shortImgID)
   309  		if b.options.Remove {
   310  			b.clearTmp()
   311              fmt.Println("builder.go/build() Don't remove tmpContainer")
   312  		}
   313  	}
   314  
   315  	// check if there are any leftover build-args that were passed but not
   316  	// consumed during build. Return a warning, if there are any.
   317  	leftoverArgs := []string{}
   318  	for arg := range b.options.BuildArgs {
   319  		if !b.isBuildArgAllowed(arg) {
   320  			leftoverArgs = append(leftoverArgs, arg)
   321  		}
   322  	}
   323  
   324  	if len(leftoverArgs) > 0 {
   325  		fmt.Fprintf(b.Stderr, "[Warning] One or more build-args %v were not consumed\n", leftoverArgs)
   326  	}
   327  
   328  	if b.image == "" {
   329  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   330  	}
   331  
   332  	if b.options.Squash {
   333  		var fromID string
   334  		if b.from != nil {
   335  			fromID = b.from.ImageID()
   336  		}
   337  		b.image, err = b.docker.SquashImage(b.image, fromID)
   338  		if err != nil {
   339  			return "", perrors.Wrap(err, "error squashing image")
   340  		}
   341  	}
   342  
   343  	imageID := image.ID(b.image)
   344  	for _, rt := range repoAndTags {
   345  		if err := b.docker.TagImageWithReference(imageID, rt); err != nil {
   346  			return "", err
   347  		}
   348  	}
   349  
   350      fmt.Println("dockerfile/builder.go  Successfully built %s", shortImgID)
   351  	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
   352  	return b.image, nil
   353  }
   354  
   355  // Cancel cancels an ongoing Dockerfile build.
   356  func (b *Builder) Cancel() {
   357  	b.cancel()
   358  }
   359  
   360  // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
   361  // It will:
   362  // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
   363  // - Do build by calling builder.dispatch() to call all entries' handling routines
   364  //
   365  // BuildFromConfig is used by the /commit endpoint, with the changes
   366  // coming from the query parameter of the same name.
   367  //
   368  // TODO: Remove?
   369  func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
   370      fmt.Println("dockerfile/builder.go  BuildFromConfig()")
   371  	b, err := NewBuilder(context.Background(), nil, nil, nil, nil)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")), &b.directive)
   377  	if err != nil {
   378  		return nil, err
   379  	}
   380  
   381  	// ensure that the commands are valid
   382  	for _, n := range ast.Children {
   383  		if !validCommitCommands[n.Value] {
   384  			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
   385  		}
   386  	}
   387  
   388  	b.runConfig = config
   389  	b.Stdout = ioutil.Discard
   390  	b.Stderr = ioutil.Discard
   391  	b.disableCommit = true
   392  
   393  	total := len(ast.Children)
   394  	for _, n := range ast.Children {
   395  		if err := b.checkDispatch(n, false); err != nil {
   396  			return nil, err
   397  		}
   398  	}
   399  
   400  	for i, n := range ast.Children {
   401  		if _, err := b.dispatch(i, total, n, ""); err != nil {
   402  			return nil, err
   403  		}
   404  	}
   405  
   406  	return b.runConfig, nil
   407  }