github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/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/engine-api/types"
    17  	"github.com/docker/engine-api/types/container"
    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  // Builder is a Dockerfile builder
    45  // It implements the builder.Backend interface.
    46  type Builder struct {
    47  	options *types.ImageBuildOptions
    48  
    49  	Stdout io.Writer
    50  	Stderr io.Writer
    51  
    52  	docker  builder.Backend
    53  	context builder.Context
    54  
    55  	dockerfile       *parser.Node
    56  	runConfig        *container.Config // runconfig for cmd, run, entrypoint etc.
    57  	flags            *BFlags
    58  	tmpContainers    map[string]struct{}
    59  	image            string // imageID
    60  	noBaseImage      bool
    61  	maintainer       string
    62  	cmdSet           bool
    63  	disableCommit    bool
    64  	cacheBusted      bool
    65  	cancelled        chan struct{}
    66  	cancelOnce       sync.Once
    67  	allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
    68  
    69  	// TODO: remove once docker.Commit can receive a tag
    70  	id     string
    71  	Output io.Writer
    72  }
    73  
    74  // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
    75  // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
    76  // will be read from the Context passed to Build().
    77  func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
    78  	if config == nil {
    79  		config = new(types.ImageBuildOptions)
    80  	}
    81  	if config.BuildArgs == nil {
    82  		config.BuildArgs = make(map[string]string)
    83  	}
    84  	b = &Builder{
    85  		options:          config,
    86  		Stdout:           os.Stdout,
    87  		Stderr:           os.Stderr,
    88  		docker:           backend,
    89  		context:          context,
    90  		runConfig:        new(container.Config),
    91  		tmpContainers:    map[string]struct{}{},
    92  		cancelled:        make(chan struct{}),
    93  		id:               stringid.GenerateNonCryptoID(),
    94  		allowedBuildArgs: make(map[string]bool),
    95  	}
    96  	if dockerfile != nil {
    97  		b.dockerfile, err = parser.Parse(dockerfile)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  
   103  	return b, nil
   104  }
   105  
   106  // Build runs the Dockerfile builder from a context and a docker object that allows to make calls
   107  // to Docker.
   108  //
   109  // This will (barring errors):
   110  //
   111  // * read the dockerfile from context
   112  // * parse the dockerfile if not already parsed
   113  // * walk the AST and execute it by dispatching to handlers. If Remove
   114  //   or ForceRemove is set, additional cleanup around containers happens after
   115  //   processing.
   116  // * Print a happy message and return the image ID.
   117  // * NOT tag the image, that is responsibility of the caller.
   118  //
   119  func (b *Builder) Build() (string, error) {
   120  	// If Dockerfile was not parsed yet, extract it from the Context
   121  	if b.dockerfile == nil {
   122  		if err := b.readDockerfile(); err != nil {
   123  			return "", err
   124  		}
   125  	}
   126  
   127  	var shortImgID string
   128  	for i, n := range b.dockerfile.Children {
   129  		select {
   130  		case <-b.cancelled:
   131  			logrus.Debug("Builder: build cancelled!")
   132  			fmt.Fprintf(b.Stdout, "Build cancelled")
   133  			return "", fmt.Errorf("Build cancelled")
   134  		default:
   135  			// Not cancelled yet, keep going...
   136  		}
   137  		if err := b.dispatch(i, n); err != nil {
   138  			if b.options.ForceRemove {
   139  				b.clearTmp()
   140  			}
   141  			return "", err
   142  		}
   143  		shortImgID = stringid.TruncateID(b.image)
   144  		fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
   145  		if b.options.Remove {
   146  			b.clearTmp()
   147  		}
   148  	}
   149  
   150  	// check if there are any leftover build-args that were passed but not
   151  	// consumed during build. Return an error, if there are any.
   152  	leftoverArgs := []string{}
   153  	for arg := range b.options.BuildArgs {
   154  		if !b.isBuildArgAllowed(arg) {
   155  			leftoverArgs = append(leftoverArgs, arg)
   156  		}
   157  	}
   158  	if len(leftoverArgs) > 0 {
   159  		return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
   160  	}
   161  
   162  	if b.image == "" {
   163  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   164  	}
   165  
   166  	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
   167  	return b.image, nil
   168  }
   169  
   170  // Cancel cancels an ongoing Dockerfile build.
   171  func (b *Builder) Cancel() {
   172  	b.cancelOnce.Do(func() {
   173  		close(b.cancelled)
   174  	})
   175  }
   176  
   177  // BuildFromConfig will do build directly from parameter 'changes', which comes
   178  // from Dockerfile entries, it will:
   179  // - call parse.Parse() to get AST root from Dockerfile entries
   180  // - do build by calling builder.dispatch() to call all entries' handling routines
   181  // TODO: remove?
   182  func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
   183  	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	// ensure that the commands are valid
   189  	for _, n := range ast.Children {
   190  		if !validCommitCommands[n.Value] {
   191  			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
   192  		}
   193  	}
   194  
   195  	b, err := NewBuilder(nil, nil, nil, nil)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	b.runConfig = config
   200  	b.Stdout = ioutil.Discard
   201  	b.Stderr = ioutil.Discard
   202  	b.disableCommit = true
   203  
   204  	for i, n := range ast.Children {
   205  		if err := b.dispatch(i, n); err != nil {
   206  			return nil, err
   207  		}
   208  	}
   209  
   210  	return b.runConfig, nil
   211  }