github.com/tompao/docker@v1.9.1/builder/dockerfile/builder.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"runtime"
    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/daemon"
    17  	"github.com/docker/docker/pkg/stringid"
    18  	"github.com/docker/docker/pkg/ulimit"
    19  	"github.com/docker/docker/runconfig"
    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  // Config constitutes the configuration for a Dockerfile builder.
    47  type Config struct {
    48  	// only used if Dockerfile has to be extracted from Context
    49  	DockerfileName string
    50  
    51  	Verbose     bool
    52  	UseCache    bool
    53  	Remove      bool
    54  	ForceRemove bool
    55  	Pull        bool
    56  	BuildArgs   map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'.
    57  
    58  	// resource constraints
    59  	// TODO: factor out to be reused with Run ?
    60  
    61  	Memory       int64
    62  	MemorySwap   int64
    63  	CPUShares    int64
    64  	CPUPeriod    int64
    65  	CPUQuota     int64
    66  	CPUSetCpus   string
    67  	CPUSetMems   string
    68  	CgroupParent string
    69  	Ulimits      []*ulimit.Ulimit
    70  }
    71  
    72  // Builder is a Dockerfile builder
    73  // It implements the builder.Builder interface.
    74  type Builder struct {
    75  	*Config
    76  
    77  	Stdout io.Writer
    78  	Stderr io.Writer
    79  
    80  	docker  builder.Docker
    81  	context builder.Context
    82  
    83  	dockerfile       *parser.Node
    84  	runConfig        *runconfig.Config // runconfig for cmd, run, entrypoint etc.
    85  	flags            *BFlags
    86  	tmpContainers    map[string]struct{}
    87  	image            string // imageID
    88  	noBaseImage      bool
    89  	maintainer       string
    90  	cmdSet           bool
    91  	disableCommit    bool
    92  	cacheBusted      bool
    93  	cancelled        chan struct{}
    94  	cancelOnce       sync.Once
    95  	allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
    96  
    97  	// TODO: remove once docker.Commit can receive a tag
    98  	id           string
    99  	activeImages []string
   100  }
   101  
   102  // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
   103  // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
   104  // will be read from the Context passed to Build().
   105  func NewBuilder(config *Config, docker builder.Docker, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
   106  	if config == nil {
   107  		config = new(Config)
   108  	}
   109  	if config.BuildArgs == nil {
   110  		config.BuildArgs = make(map[string]string)
   111  	}
   112  	b = &Builder{
   113  		Config:           config,
   114  		Stdout:           os.Stdout,
   115  		Stderr:           os.Stderr,
   116  		docker:           docker,
   117  		context:          context,
   118  		runConfig:        new(runconfig.Config),
   119  		tmpContainers:    map[string]struct{}{},
   120  		cancelled:        make(chan struct{}),
   121  		id:               stringid.GenerateNonCryptoID(),
   122  		allowedBuildArgs: make(map[string]bool),
   123  	}
   124  	if dockerfile != nil {
   125  		b.dockerfile, err = parser.Parse(dockerfile)
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  	}
   130  
   131  	return b, nil
   132  }
   133  
   134  // Build runs the Dockerfile builder from a context and a docker object that allows to make calls
   135  // to Docker.
   136  //
   137  // This will (barring errors):
   138  //
   139  // * read the dockerfile from context
   140  // * parse the dockerfile if not already parsed
   141  // * walk the AST and execute it by dispatching to handlers. If Remove
   142  //   or ForceRemove is set, additional cleanup around containers happens after
   143  //   processing.
   144  // * Print a happy message and return the image ID.
   145  // * NOT tag the image, that is responsibility of the caller.
   146  //
   147  func (b *Builder) Build() (string, error) {
   148  	// TODO: remove once b.docker.Commit can take a tag parameter.
   149  	defer func() {
   150  		b.docker.Release(b.id, b.activeImages)
   151  	}()
   152  
   153  	// If Dockerfile was not parsed yet, extract it from the Context
   154  	if b.dockerfile == nil {
   155  		if err := b.readDockerfile(); err != nil {
   156  			return "", err
   157  		}
   158  	}
   159  
   160  	var shortImgID string
   161  	for i, n := range b.dockerfile.Children {
   162  		select {
   163  		case <-b.cancelled:
   164  			logrus.Debug("Builder: build cancelled!")
   165  			fmt.Fprintf(b.Stdout, "Build cancelled")
   166  			return "", fmt.Errorf("Build cancelled")
   167  		default:
   168  			// Not cancelled yet, keep going...
   169  		}
   170  		if err := b.dispatch(i, n); err != nil {
   171  			if b.ForceRemove {
   172  				b.clearTmp()
   173  			}
   174  			return "", err
   175  		}
   176  		shortImgID = stringid.TruncateID(b.image)
   177  		fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
   178  		if b.Remove {
   179  			b.clearTmp()
   180  		}
   181  	}
   182  
   183  	// check if there are any leftover build-args that were passed but not
   184  	// consumed during build. Return an error, if there are any.
   185  	leftoverArgs := []string{}
   186  	for arg := range b.BuildArgs {
   187  		if !b.isBuildArgAllowed(arg) {
   188  			leftoverArgs = append(leftoverArgs, arg)
   189  		}
   190  	}
   191  	if len(leftoverArgs) > 0 {
   192  		return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
   193  	}
   194  
   195  	if b.image == "" {
   196  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   197  	}
   198  
   199  	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
   200  	return b.image, nil
   201  }
   202  
   203  // Cancel cancels an ongoing Dockerfile build.
   204  func (b *Builder) Cancel() {
   205  	b.cancelOnce.Do(func() {
   206  		close(b.cancelled)
   207  	})
   208  }
   209  
   210  // CommitConfig contains build configs for commit operation
   211  type CommitConfig struct {
   212  	Pause   bool
   213  	Repo    string
   214  	Tag     string
   215  	Author  string
   216  	Comment string
   217  	Changes []string
   218  	Config  *runconfig.Config
   219  }
   220  
   221  // BuildFromConfig will do build directly from parameter 'changes', which comes
   222  // from Dockerfile entries, it will:
   223  // - call parse.Parse() to get AST root from Dockerfile entries
   224  // - do build by calling builder.dispatch() to call all entries' handling routines
   225  // TODO: remove?
   226  func BuildFromConfig(config *runconfig.Config, changes []string) (*runconfig.Config, error) {
   227  	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	// ensure that the commands are valid
   233  	for _, n := range ast.Children {
   234  		if !validCommitCommands[n.Value] {
   235  			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
   236  		}
   237  	}
   238  
   239  	b, err := NewBuilder(nil, nil, nil, nil)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	b.runConfig = config
   244  	b.Stdout = ioutil.Discard
   245  	b.Stderr = ioutil.Discard
   246  	b.disableCommit = true
   247  
   248  	for i, n := range ast.Children {
   249  		if err := b.dispatch(i, n); err != nil {
   250  			return nil, err
   251  		}
   252  	}
   253  
   254  	return b.runConfig, nil
   255  }
   256  
   257  // Commit will create a new image from a container's changes
   258  // TODO: remove daemon, make Commit a method on *Builder ?
   259  func Commit(container *daemon.Container, d *daemon.Daemon, c *CommitConfig) (string, error) {
   260  	// It is not possible to commit a running container on Windows
   261  	if runtime.GOOS == "windows" && container.IsRunning() {
   262  		return "", fmt.Errorf("Windows does not support commit of a running container")
   263  	}
   264  
   265  	if c.Config == nil {
   266  		c.Config = &runconfig.Config{}
   267  	}
   268  
   269  	newConfig, err := BuildFromConfig(c.Config, c.Changes)
   270  	if err != nil {
   271  		return "", err
   272  	}
   273  
   274  	if err := runconfig.Merge(newConfig, container.Config); err != nil {
   275  		return "", err
   276  	}
   277  
   278  	commitCfg := &daemon.ContainerCommitConfig{
   279  		Pause:   c.Pause,
   280  		Repo:    c.Repo,
   281  		Tag:     c.Tag,
   282  		Author:  c.Author,
   283  		Comment: c.Comment,
   284  		Config:  newConfig,
   285  	}
   286  
   287  	img, err := d.Commit(container, commitCfg)
   288  	if err != nil {
   289  		return "", err
   290  	}
   291  	return img.ID, nil
   292  }