github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/builder/dockerfile/builder.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/builder"
    14  	"github.com/docker/docker/builder/dockerfile/parser"
    15  	"github.com/docker/docker/pkg/stringid"
    16  	"github.com/docker/docker/pkg/ulimit"
    17  	"github.com/docker/docker/runconfig"
    18  )
    19  
    20  var validCommitCommands = map[string]bool{
    21  	"cmd":        true,
    22  	"entrypoint": true,
    23  	"env":        true,
    24  	"expose":     true,
    25  	"label":      true,
    26  	"onbuild":    true,
    27  	"user":       true,
    28  	"volume":     true,
    29  	"workdir":    true,
    30  }
    31  
    32  // BuiltinAllowedBuildArgs is list of built-in allowed build args
    33  var BuiltinAllowedBuildArgs = map[string]bool{
    34  	"HTTP_PROXY":  true,
    35  	"http_proxy":  true,
    36  	"HTTPS_PROXY": true,
    37  	"https_proxy": true,
    38  	"FTP_PROXY":   true,
    39  	"ftp_proxy":   true,
    40  	"NO_PROXY":    true,
    41  	"no_proxy":    true,
    42  }
    43  
    44  // Config constitutes the configuration for a Dockerfile builder.
    45  type Config struct {
    46  	// only used if Dockerfile has to be extracted from Context
    47  	DockerfileName string
    48  
    49  	Verbose     bool
    50  	UseCache    bool
    51  	Remove      bool
    52  	ForceRemove bool
    53  	Pull        bool
    54  	BuildArgs   map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'.
    55  	Isolation   runconfig.IsolationLevel
    56  
    57  	// resource constraints
    58  	// TODO: factor out to be reused with Run ?
    59  
    60  	Memory       int64
    61  	MemorySwap   int64
    62  	ShmSize      *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.Backend
    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  }
   100  
   101  // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
   102  // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
   103  // will be read from the Context passed to Build().
   104  func NewBuilder(config *Config, docker builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
   105  	if config == nil {
   106  		config = new(Config)
   107  	}
   108  	if config.BuildArgs == nil {
   109  		config.BuildArgs = make(map[string]string)
   110  	}
   111  	b = &Builder{
   112  		Config:           config,
   113  		Stdout:           os.Stdout,
   114  		Stderr:           os.Stderr,
   115  		docker:           docker,
   116  		context:          context,
   117  		runConfig:        new(runconfig.Config),
   118  		tmpContainers:    map[string]struct{}{},
   119  		cancelled:        make(chan struct{}),
   120  		id:               stringid.GenerateNonCryptoID(),
   121  		allowedBuildArgs: make(map[string]bool),
   122  	}
   123  	if dockerfile != nil {
   124  		b.dockerfile, err = parser.Parse(dockerfile)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  	}
   129  
   130  	return b, nil
   131  }
   132  
   133  // Build runs the Dockerfile builder from a context and a docker object that allows to make calls
   134  // to Docker.
   135  //
   136  // This will (barring errors):
   137  //
   138  // * read the dockerfile from context
   139  // * parse the dockerfile if not already parsed
   140  // * walk the AST and execute it by dispatching to handlers. If Remove
   141  //   or ForceRemove is set, additional cleanup around containers happens after
   142  //   processing.
   143  // * Print a happy message and return the image ID.
   144  // * NOT tag the image, that is responsibility of the caller.
   145  //
   146  func (b *Builder) Build() (string, error) {
   147  	// If Dockerfile was not parsed yet, extract it from the Context
   148  	if b.dockerfile == nil {
   149  		if err := b.readDockerfile(); err != nil {
   150  			return "", err
   151  		}
   152  	}
   153  
   154  	var shortImgID string
   155  	for i, n := range b.dockerfile.Children {
   156  		select {
   157  		case <-b.cancelled:
   158  			logrus.Debug("Builder: build cancelled!")
   159  			fmt.Fprintf(b.Stdout, "Build cancelled")
   160  			return "", fmt.Errorf("Build cancelled")
   161  		default:
   162  			// Not cancelled yet, keep going...
   163  		}
   164  		if err := b.dispatch(i, n); err != nil {
   165  			if b.ForceRemove {
   166  				b.clearTmp()
   167  			}
   168  			return "", err
   169  		}
   170  		shortImgID = stringid.TruncateID(b.image)
   171  		fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
   172  		if b.Remove {
   173  			b.clearTmp()
   174  		}
   175  	}
   176  
   177  	// check if there are any leftover build-args that were passed but not
   178  	// consumed during build. Return an error, if there are any.
   179  	leftoverArgs := []string{}
   180  	for arg := range b.BuildArgs {
   181  		if !b.isBuildArgAllowed(arg) {
   182  			leftoverArgs = append(leftoverArgs, arg)
   183  		}
   184  	}
   185  	if len(leftoverArgs) > 0 {
   186  		return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
   187  	}
   188  
   189  	if b.image == "" {
   190  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   191  	}
   192  
   193  	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
   194  	return b.image, nil
   195  }
   196  
   197  // Cancel cancels an ongoing Dockerfile build.
   198  func (b *Builder) Cancel() {
   199  	b.cancelOnce.Do(func() {
   200  		close(b.cancelled)
   201  	})
   202  }
   203  
   204  // BuildFromConfig will do build directly from parameter 'changes', which comes
   205  // from Dockerfile entries, it will:
   206  // - call parse.Parse() to get AST root from Dockerfile entries
   207  // - do build by calling builder.dispatch() to call all entries' handling routines
   208  // TODO: remove?
   209  func BuildFromConfig(config *runconfig.Config, changes []string) (*runconfig.Config, error) {
   210  	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	// ensure that the commands are valid
   216  	for _, n := range ast.Children {
   217  		if !validCommitCommands[n.Value] {
   218  			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
   219  		}
   220  	}
   221  
   222  	b, err := NewBuilder(nil, nil, nil, nil)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	b.runConfig = config
   227  	b.Stdout = ioutil.Discard
   228  	b.Stderr = ioutil.Discard
   229  	b.disableCommit = true
   230  
   231  	for i, n := range ast.Children {
   232  		if err := b.dispatch(i, n); err != nil {
   233  			return nil, err
   234  		}
   235  	}
   236  
   237  	return b.runConfig, nil
   238  }