github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/builder/dockerfile/builder.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/builder"
    15  	"github.com/docker/docker/builder/dockerfile/parser"
    16  	"github.com/docker/docker/pkg/stringid"
    17  	"github.com/docker/docker/reference"
    18  	"github.com/docker/engine-api/types"
    19  	"github.com/docker/engine-api/types/container"
    20  )
    21  
    22  var validCommitCommands = map[string]bool{
    23  	"cmd":        true,
    24  	"entrypoint": true,
    25  	"env":        true,
    26  	"expose":     true,
    27  	"label":      true,
    28  	"onbuild":    true,
    29  	"user":       true,
    30  	"volume":     true,
    31  	"workdir":    true,
    32  }
    33  
    34  // BuiltinAllowedBuildArgs is list of built-in allowed build args
    35  var BuiltinAllowedBuildArgs = map[string]bool{
    36  	"HTTP_PROXY":  true,
    37  	"http_proxy":  true,
    38  	"HTTPS_PROXY": true,
    39  	"https_proxy": true,
    40  	"FTP_PROXY":   true,
    41  	"ftp_proxy":   true,
    42  	"NO_PROXY":    true,
    43  	"no_proxy":    true,
    44  }
    45  
    46  // Builder is a Dockerfile builder
    47  // It implements the builder.Backend interface.
    48  type Builder struct {
    49  	options *types.ImageBuildOptions
    50  
    51  	Stdout io.Writer
    52  	Stderr io.Writer
    53  	Output io.Writer
    54  
    55  	docker  builder.Backend
    56  	context builder.Context
    57  
    58  	dockerfile       *parser.Node
    59  	runConfig        *container.Config // runconfig for cmd, run, entrypoint etc.
    60  	flags            *BFlags
    61  	tmpContainers    map[string]struct{}
    62  	image            string // imageID
    63  	noBaseImage      bool
    64  	maintainer       string
    65  	cmdSet           bool
    66  	disableCommit    bool
    67  	cacheBusted      bool
    68  	cancelled        chan struct{}
    69  	cancelOnce       sync.Once
    70  	allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
    71  
    72  	// TODO: remove once docker.Commit can receive a tag
    73  	id string
    74  }
    75  
    76  // BuildManager implements builder.Backend and is shared across all Builder objects.
    77  type BuildManager struct {
    78  	backend builder.Backend
    79  }
    80  
    81  // NewBuildManager creates a BuildManager.
    82  func NewBuildManager(b builder.Backend) (bm *BuildManager) {
    83  	return &BuildManager{backend: b}
    84  }
    85  
    86  // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
    87  // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
    88  // will be read from the Context passed to Build().
    89  func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
    90  	if config == nil {
    91  		config = new(types.ImageBuildOptions)
    92  	}
    93  	if config.BuildArgs == nil {
    94  		config.BuildArgs = make(map[string]string)
    95  	}
    96  	b = &Builder{
    97  		options:          config,
    98  		Stdout:           os.Stdout,
    99  		Stderr:           os.Stderr,
   100  		docker:           backend,
   101  		context:          context,
   102  		runConfig:        new(container.Config),
   103  		tmpContainers:    map[string]struct{}{},
   104  		cancelled:        make(chan struct{}),
   105  		id:               stringid.GenerateNonCryptoID(),
   106  		allowedBuildArgs: make(map[string]bool),
   107  	}
   108  	if dockerfile != nil {
   109  		b.dockerfile, err = parser.Parse(dockerfile)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  	}
   114  
   115  	return b, nil
   116  }
   117  
   118  // sanitizeRepoAndTags parses the raw "t" parameter received from the client
   119  // to a slice of repoAndTag.
   120  // It also validates each repoName and tag.
   121  func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
   122  	var (
   123  		repoAndTags []reference.Named
   124  		// This map is used for deduplicating the "-t" parameter.
   125  		uniqNames = make(map[string]struct{})
   126  	)
   127  	for _, repo := range names {
   128  		if repo == "" {
   129  			continue
   130  		}
   131  
   132  		ref, err := reference.ParseNamed(repo)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  
   137  		ref = reference.WithDefaultTag(ref)
   138  
   139  		if _, isCanonical := ref.(reference.Canonical); isCanonical {
   140  			return nil, errors.New("build tag cannot contain a digest")
   141  		}
   142  
   143  		if _, isTagged := ref.(reference.NamedTagged); !isTagged {
   144  			ref, err = reference.WithTag(ref, reference.DefaultTag)
   145  		}
   146  
   147  		nameWithTag := ref.String()
   148  
   149  		if _, exists := uniqNames[nameWithTag]; !exists {
   150  			uniqNames[nameWithTag] = struct{}{}
   151  			repoAndTags = append(repoAndTags, ref)
   152  		}
   153  	}
   154  	return repoAndTags, nil
   155  }
   156  
   157  // Build creates a NewBuilder, which builds the image.
   158  func (bm *BuildManager) Build(config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error) {
   159  	b, err := NewBuilder(config, bm.backend, context, nil)
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  	img, err := b.build(config, context, stdout, stderr, out, clientGone)
   164  	return img, err
   165  
   166  }
   167  
   168  // build runs the Dockerfile builder from a context and a docker object that allows to make calls
   169  // to Docker.
   170  //
   171  // This will (barring errors):
   172  //
   173  // * read the dockerfile from context
   174  // * parse the dockerfile if not already parsed
   175  // * walk the AST and execute it by dispatching to handlers. If Remove
   176  //   or ForceRemove is set, additional cleanup around containers happens after
   177  //   processing.
   178  // * Tag image, if applicable.
   179  // * Print a happy message and return the image ID.
   180  //
   181  func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error) {
   182  	b.options = config
   183  	b.context = context
   184  	b.Stdout = stdout
   185  	b.Stderr = stderr
   186  	b.Output = out
   187  
   188  	// If Dockerfile was not parsed yet, extract it from the Context
   189  	if b.dockerfile == nil {
   190  		if err := b.readDockerfile(); err != nil {
   191  			return "", err
   192  		}
   193  	}
   194  
   195  	finished := make(chan struct{})
   196  	defer close(finished)
   197  	go func() {
   198  		select {
   199  		case <-finished:
   200  		case <-clientGone:
   201  			b.cancelOnce.Do(func() {
   202  				close(b.cancelled)
   203  			})
   204  		}
   205  
   206  	}()
   207  
   208  	repoAndTags, err := sanitizeRepoAndTags(config.Tags)
   209  	if err != nil {
   210  		return "", err
   211  	}
   212  
   213  	var shortImgID string
   214  	for i, n := range b.dockerfile.Children {
   215  		select {
   216  		case <-b.cancelled:
   217  			logrus.Debug("Builder: build cancelled!")
   218  			fmt.Fprintf(b.Stdout, "Build cancelled")
   219  			return "", fmt.Errorf("Build cancelled")
   220  		default:
   221  			// Not cancelled yet, keep going...
   222  		}
   223  		if err := b.dispatch(i, n); err != nil {
   224  			if b.options.ForceRemove {
   225  				b.clearTmp()
   226  			}
   227  			return "", err
   228  		}
   229  		shortImgID = stringid.TruncateID(b.image)
   230  		fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
   231  		if b.options.Remove {
   232  			b.clearTmp()
   233  		}
   234  	}
   235  
   236  	// check if there are any leftover build-args that were passed but not
   237  	// consumed during build. Return an error, if there are any.
   238  	leftoverArgs := []string{}
   239  	for arg := range b.options.BuildArgs {
   240  		if !b.isBuildArgAllowed(arg) {
   241  			leftoverArgs = append(leftoverArgs, arg)
   242  		}
   243  	}
   244  	if len(leftoverArgs) > 0 {
   245  		return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
   246  	}
   247  
   248  	if b.image == "" {
   249  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   250  	}
   251  
   252  	for _, rt := range repoAndTags {
   253  		if err := b.docker.TagImage(rt, b.image); err != nil {
   254  			return "", err
   255  		}
   256  	}
   257  
   258  	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
   259  	return b.image, nil
   260  }
   261  
   262  // Cancel cancels an ongoing Dockerfile build.
   263  func (b *Builder) Cancel() {
   264  	b.cancelOnce.Do(func() {
   265  		close(b.cancelled)
   266  	})
   267  }
   268  
   269  // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
   270  // It will:
   271  // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
   272  // - Do build by calling builder.dispatch() to call all entries' handling routines
   273  //
   274  // BuildFromConfig is used by the /commit endpoint, with the changes
   275  // coming from the query parameter of the same name.
   276  //
   277  // TODO: Remove?
   278  func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
   279  	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	// ensure that the commands are valid
   285  	for _, n := range ast.Children {
   286  		if !validCommitCommands[n.Value] {
   287  			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
   288  		}
   289  	}
   290  
   291  	b, err := NewBuilder(nil, nil, nil, nil)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  	b.runConfig = config
   296  	b.Stdout = ioutil.Discard
   297  	b.Stderr = ioutil.Discard
   298  	b.disableCommit = true
   299  
   300  	for i, n := range ast.Children {
   301  		if err := b.dispatch(i, n); err != nil {
   302  			return nil, err
   303  		}
   304  	}
   305  
   306  	return b.runConfig, nil
   307  }