2015-03-24 23:57:23 -04:00
package client
import (
2015-07-21 17:47:44 -04:00
"archive/tar"
2015-03-24 23:57:23 -04:00
"bufio"
2015-10-27 20:29:21 -04:00
"bytes"
2015-03-24 23:57:23 -04:00
"fmt"
"io"
"os"
"path/filepath"
2015-07-21 17:47:44 -04:00
"regexp"
2015-03-24 23:57:23 -04:00
"runtime"
2016-02-03 18:41:26 -05:00
"golang.org/x/net/context"
2015-03-24 23:57:23 -04:00
"github.com/docker/docker/api"
2016-02-09 16:19:09 -05:00
"github.com/docker/docker/builder"
2015-12-14 05:15:00 -05:00
"github.com/docker/docker/builder/dockerignore"
2015-05-05 00:18:28 -04:00
Cli "github.com/docker/docker/cli"
2015-07-22 22:26:06 -04:00
"github.com/docker/docker/opts"
2015-03-24 23:57:23 -04:00
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
2015-03-17 22:18:41 -04:00
"github.com/docker/docker/pkg/jsonmessage"
2015-03-24 23:57:23 -04:00
flag "github.com/docker/docker/pkg/mflag"
2015-11-13 19:59:01 -05:00
"github.com/docker/docker/pkg/progress"
2015-03-17 22:18:41 -04:00
"github.com/docker/docker/pkg/streamformatter"
2015-03-24 23:57:23 -04:00
"github.com/docker/docker/pkg/urlutil"
2015-12-04 16:55:15 -05:00
"github.com/docker/docker/reference"
2015-12-21 15:06:46 -05:00
runconfigopts "github.com/docker/docker/runconfig/opts"
2016-01-04 19:05:26 -05:00
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
2015-12-16 12:26:49 -05:00
"github.com/docker/go-units"
2015-03-24 23:57:23 -04:00
)
2016-01-12 16:18:24 -05:00
type translatorFunc func ( reference . NamedTagged ) ( reference . Canonical , error )
2015-03-25 13:34:41 -04:00
// CmdBuild builds a new image from the source code at a given path.
//
// If '-' is provided instead of a path or URL, Docker will build an image from either a Dockerfile or tar archive read from STDIN.
//
// Usage: docker build [OPTIONS] PATH | URL | -
2015-03-24 23:57:23 -04:00
func ( cli * DockerCli ) CmdBuild ( args ... string ) error {
2015-10-08 08:46:21 -04:00
cmd := Cli . Subcmd ( "build" , [ ] string { "PATH | URL | -" } , Cli . DockerCommands [ "build" ] . Description , true )
2015-08-30 09:48:03 -04:00
flTags := opts . NewListOpts ( validateTag )
cmd . Var ( & flTags , [ ] string { "t" , "-tag" } , "Name and optionally a tag in the 'name:tag' format" )
2015-10-27 20:29:21 -04:00
suppressOutput := cmd . Bool ( [ ] string { "q" , "-quiet" } , false , "Suppress the build output and print image ID on success" )
2015-11-09 09:37:24 -05:00
noCache := cmd . Bool ( [ ] string { "-no-cache" } , false , "Do not use cache when building the image" )
rm := cmd . Bool ( [ ] string { "-rm" } , true , "Remove intermediate containers after a successful build" )
2015-03-24 23:57:23 -04:00
forceRm := cmd . Bool ( [ ] string { "-force-rm" } , false , "Always remove intermediate containers" )
pull := cmd . Bool ( [ ] string { "-pull" } , false , "Always attempt to pull a newer version of the image" )
dockerfileName := cmd . String ( [ ] string { "f" , "-file" } , "" , "Name of the Dockerfile (Default is 'PATH/Dockerfile')" )
flMemoryString := cmd . String ( [ ] string { "m" , "-memory" } , "" , "Memory limit" )
2015-12-29 20:23:35 -05:00
flMemorySwap := cmd . String ( [ ] string { "-memory-swap" } , "" , "Swap limit equal to memory plus swap: '-1' to enable unlimited swap" )
2015-09-09 02:30:56 -04:00
flShmSize := cmd . String ( [ ] string { "-shm-size" } , "" , "Size of /dev/shm, default value is 64MB" )
2015-10-16 15:05:18 -04:00
flCPUShares := cmd . Int64 ( [ ] string { "#c" , "-cpu-shares" } , 0 , "CPU shares (relative weight)" )
2015-07-20 20:55:30 -04:00
flCPUPeriod := cmd . Int64 ( [ ] string { "-cpu-period" } , 0 , "Limit the CPU CFS (Completely Fair Scheduler) period" )
flCPUQuota := cmd . Int64 ( [ ] string { "-cpu-quota" } , 0 , "Limit the CPU CFS (Completely Fair Scheduler) quota" )
2015-03-25 22:31:29 -04:00
flCPUSetCpus := cmd . String ( [ ] string { "-cpuset-cpus" } , "" , "CPUs in which to allow execution (0-3, 0,1)" )
2015-04-13 22:00:48 -04:00
flCPUSetMems := cmd . String ( [ ] string { "-cpuset-mems" } , "" , "MEMs in which to allow execution (0-3, 0,1)" )
2015-03-26 19:14:31 -04:00
flCgroupParent := cmd . String ( [ ] string { "-cgroup-parent" } , "" , "Optional parent cgroup for the container" )
2015-12-29 11:28:21 -05:00
flBuildArg := opts . NewListOpts ( runconfigopts . ValidateEnv )
2014-11-14 13:59:14 -05:00
cmd . Var ( & flBuildArg , [ ] string { "-build-arg" } , "Set build-time variables" )
2016-02-03 15:07:00 -05:00
isolation := cmd . String ( [ ] string { "-isolation" } , "" , "Container isolation technology" )
2015-07-22 22:26:06 -04:00
2016-03-16 17:52:34 -04:00
flLabels := opts . NewListOpts ( nil )
cmd . Var ( & flLabels , [ ] string { "-label" } , "Set metadata for an image" )
2015-12-21 15:06:46 -05:00
ulimits := make ( map [ string ] * units . Ulimit )
flUlimits := runconfigopts . NewUlimitOpt ( & ulimits )
2015-07-22 22:26:06 -04:00
cmd . Var ( flUlimits , [ ] string { "-ulimit" } , "Ulimit options" )
2015-03-24 23:57:23 -04:00
cmd . Require ( flag . Exact , 1 )
2015-07-03 05:26:09 -04:00
2015-07-20 17:53:52 -04:00
// For trusted pull on "FROM <image>" instruction.
addTrustedFlags ( cmd , true )
2015-03-28 21:22:46 -04:00
cmd . ParseFlags ( args , true )
2015-03-24 23:57:23 -04:00
var (
2016-02-03 18:41:26 -05:00
ctx io . ReadCloser
err error
2015-03-24 23:57:23 -04:00
)
2015-07-20 17:53:52 -04:00
specifiedContext := cmd . Arg ( 0 )
2015-03-24 23:57:23 -04:00
2015-07-20 17:53:52 -04:00
var (
contextDir string
tempDir string
relDockerfile string
2015-10-27 20:29:21 -04:00
progBuff io . Writer
buildBuff io . Writer
2015-07-20 17:53:52 -04:00
)
2015-03-24 23:57:23 -04:00
2015-10-27 20:29:21 -04:00
progBuff = cli . out
buildBuff = cli . out
if * suppressOutput {
progBuff = bytes . NewBuffer ( nil )
buildBuff = bytes . NewBuffer ( nil )
}
2015-07-20 17:53:52 -04:00
switch {
case specifiedContext == "-" :
2016-02-11 14:59:59 -05:00
ctx , relDockerfile , err = builder . GetContextFromReader ( cli . in , * dockerfileName )
2016-01-16 05:59:13 -05:00
case urlutil . IsGitURL ( specifiedContext ) :
2016-02-11 14:59:59 -05:00
tempDir , relDockerfile , err = builder . GetContextFromGitURL ( specifiedContext , * dockerfileName )
2015-07-20 17:53:52 -04:00
case urlutil . IsURL ( specifiedContext ) :
2016-02-11 14:59:59 -05:00
ctx , relDockerfile , err = builder . GetContextFromURL ( progBuff , specifiedContext , * dockerfileName )
2015-07-20 17:53:52 -04:00
default :
2016-02-11 14:59:59 -05:00
contextDir , relDockerfile , err = builder . GetContextFromLocalDir ( specifiedContext , * dockerfileName )
2015-07-20 17:53:52 -04:00
}
2015-03-24 23:57:23 -04:00
2015-07-20 17:53:52 -04:00
if err != nil {
2015-10-27 20:29:21 -04:00
if * suppressOutput && urlutil . IsURL ( specifiedContext ) {
fmt . Fprintln ( cli . err , progBuff )
}
2015-07-20 17:53:52 -04:00
return fmt . Errorf ( "unable to prepare context: %s" , err )
}
2015-03-24 23:57:23 -04:00
2015-07-20 17:53:52 -04:00
if tempDir != "" {
defer os . RemoveAll ( tempDir )
contextDir = tempDir
}
2015-03-24 23:57:23 -04:00
2016-02-03 18:41:26 -05:00
if ctx == nil {
2016-01-12 16:18:24 -05:00
// And canonicalize dockerfile name to a platform-independent one
relDockerfile , err = archive . CanonicalTarNameForPath ( relDockerfile )
2015-09-06 13:26:40 -04:00
if err != nil {
2016-01-12 16:18:24 -05:00
return fmt . Errorf ( "cannot canonicalize dockerfile path %s: %v" , relDockerfile , err )
}
f , err := os . Open ( filepath . Join ( contextDir , ".dockerignore" ) )
if err != nil && ! os . IsNotExist ( err ) {
2015-09-06 13:26:40 -04:00
return err
}
2016-01-12 16:18:24 -05:00
var excludes [ ] string
if err == nil {
excludes , err = dockerignore . ReadAll ( f )
if err != nil {
return err
}
}
2015-03-24 23:57:23 -04:00
2016-02-09 16:19:09 -05:00
if err := builder . ValidateContextDirectory ( contextDir , excludes ) ; err != nil {
2016-01-12 16:18:24 -05:00
return fmt . Errorf ( "Error checking context: '%s'." , err )
}
2015-07-20 17:53:52 -04:00
2016-01-12 16:18:24 -05:00
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The daemon will remove them for us, if needed, after it
// parses the Dockerfile. Ignore errors here, as they will have been
// caught by validateContextDirectory above.
var includes = [ ] string { "." }
keepThem1 , _ := fileutils . Matches ( ".dockerignore" , excludes )
keepThem2 , _ := fileutils . Matches ( relDockerfile , excludes )
if keepThem1 || keepThem2 {
includes = append ( includes , ".dockerignore" , relDockerfile )
}
2015-03-24 23:57:23 -04:00
2016-02-03 18:41:26 -05:00
ctx , err = archive . TarWithOptions ( contextDir , & archive . TarOptions {
2016-01-12 16:18:24 -05:00
Compression : archive . Uncompressed ,
ExcludePatterns : excludes ,
IncludeFiles : includes ,
} )
2016-01-05 13:32:43 -05:00
if err != nil {
2016-01-12 16:18:24 -05:00
return err
2016-01-05 13:32:43 -05:00
}
2016-01-12 16:18:24 -05:00
}
2016-01-05 13:32:43 -05:00
2016-01-12 16:18:24 -05:00
var resolvedTags [ ] * resolvedTag
if isTrusted ( ) {
2016-01-05 13:32:43 -05:00
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
2016-02-03 18:41:26 -05:00
ctx = replaceDockerfileTarWrapper ( ctx , relDockerfile , cli . trustedReference , & resolvedTags )
2016-01-05 13:32:43 -05:00
}
2015-07-21 17:47:44 -04:00
2015-03-24 23:57:23 -04:00
// Setup an upload progress bar
2015-10-27 20:29:21 -04:00
progressOutput := streamformatter . NewStreamFormatter ( ) . NewProgressOutput ( progBuff , true )
2015-11-13 19:59:01 -05:00
2016-02-03 18:41:26 -05:00
var body io . Reader = progress . NewProgressReader ( ctx , progressOutput , 0 , "" , "Sending build context to Docker daemon" )
2015-03-24 23:57:23 -04:00
var memory int64
if * flMemoryString != "" {
parsedMemory , err := units . RAMInBytes ( * flMemoryString )
if err != nil {
return err
}
memory = parsedMemory
}
var memorySwap int64
if * flMemorySwap != "" {
if * flMemorySwap == "-1" {
memorySwap = - 1
} else {
parsedMemorySwap , err := units . RAMInBytes ( * flMemorySwap )
if err != nil {
return err
}
memorySwap = parsedMemorySwap
}
}
2015-12-29 15:49:17 -05:00
var shmSize int64
if * flShmSize != "" {
shmSize , err = units . RAMInBytes ( * flShmSize )
if err != nil {
return err
}
}
2015-12-04 17:02:06 -05:00
options := types . ImageBuildOptions {
2015-12-03 18:27:01 -05:00
Context : body ,
Memory : memory ,
MemorySwap : memorySwap ,
Tags : flTags . GetAll ( ) ,
SuppressOutput : * suppressOutput ,
NoCache : * noCache ,
Remove : * rm ,
ForceRemove : * forceRm ,
PullParent : * pull ,
2016-02-03 15:07:00 -05:00
Isolation : container . Isolation ( * isolation ) ,
2015-12-03 18:27:01 -05:00
CPUSetCPUs : * flCPUSetCpus ,
CPUSetMems : * flCPUSetMems ,
CPUShares : * flCPUShares ,
CPUQuota : * flCPUQuota ,
CPUPeriod : * flCPUPeriod ,
CgroupParent : * flCgroupParent ,
Dockerfile : relDockerfile ,
2015-12-29 15:49:17 -05:00
ShmSize : shmSize ,
2015-12-03 18:27:01 -05:00
Ulimits : flUlimits . GetList ( ) ,
2015-12-29 15:49:17 -05:00
BuildArgs : runconfigopts . ConvertKVStringsToMap ( flBuildArg . GetAll ( ) ) ,
2016-03-01 12:04:35 -05:00
AuthConfigs : cli . retrieveAuthConfigs ( ) ,
2016-03-16 17:52:34 -04:00
Labels : runconfigopts . ConvertKVStringsToMap ( flLabels . GetAll ( ) ) ,
2015-07-22 22:26:06 -04:00
}
2016-02-03 18:41:26 -05:00
response , err := cli . client . ImageBuild ( context . Background ( ) , options )
2014-11-14 13:59:14 -05:00
if err != nil {
return err
}
2016-03-16 11:38:13 -04:00
defer response . Body . Close ( )
2014-11-14 13:59:14 -05:00
2015-12-21 18:02:44 -05:00
err = jsonmessage . DisplayJSONMessagesStream ( response . Body , buildBuff , cli . outFd , cli . isTerminalOut , nil )
2015-03-24 23:57:23 -04:00
if err != nil {
2015-12-03 18:27:01 -05:00
if jerr , ok := err . ( * jsonmessage . JSONError ) ; ok {
// If no error code is set, default to 1
if jerr . Code == 0 {
jerr . Code = 1
2015-06-04 09:30:14 -04:00
}
2015-10-27 20:29:21 -04:00
if * suppressOutput {
fmt . Fprintf ( cli . err , "%s%s" , progBuff , buildBuff )
}
2015-12-03 18:27:01 -05:00
return Cli . StatusError { Status : jerr . Message , StatusCode : jerr . Code }
2015-06-04 09:30:14 -04:00
}
}
2016-01-06 13:26:37 -05:00
// Windows: show error message about modified file permissions if the
// daemon isn't running Windows.
if response . OSType != "windows" && runtime . GOOS == "windows" {
2015-12-03 18:27:01 -05:00
fmt . Fprintln ( cli . err , ` SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories. ` )
2015-07-24 19:35:11 -04:00
}
2015-10-27 20:29:21 -04:00
// Everything worked so if -q was provided the output from the daemon
// should be just the image ID and we'll print that to stdout.
if * suppressOutput {
fmt . Fprintf ( cli . out , "%s" , buildBuff )
}
2016-01-05 13:32:43 -05:00
if isTrusted ( ) {
// Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite.
for _ , resolved := range resolvedTags {
if err := cli . tagTrusted ( resolved . digestRef , resolved . tagRef ) ; err != nil {
return err
}
2015-07-24 19:35:11 -04:00
}
}
return nil
2015-03-24 23:57:23 -04:00
}
2015-07-20 17:53:52 -04:00
2015-08-30 09:48:03 -04:00
// validateTag checks if the given image name can be resolved.
func validateTag ( rawRepo string ) ( string , error ) {
2015-12-11 14:00:13 -05:00
_ , err := reference . ParseNamed ( rawRepo )
2015-11-18 17:20:54 -05:00
if err != nil {
2015-08-30 09:48:03 -04:00
return "" , err
}
return rawRepo , nil
}
2015-07-21 17:47:44 -04:00
var dockerfileFromLinePattern = regexp . MustCompile ( ` (?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+) ` )
2015-07-24 19:35:11 -04:00
// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
type resolvedTag struct {
2015-11-18 17:20:54 -05:00
digestRef reference . Canonical
tagRef reference . NamedTagged
2015-07-24 19:35:11 -04:00
}
2015-07-21 17:47:44 -04:00
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
// "FROM <image>" instructions to a digest reference. `translator` is a
// function that takes a repository name and tag reference and returns a
// trusted digest reference.
2016-01-12 16:18:24 -05:00
func rewriteDockerfileFrom ( dockerfile io . Reader , translator translatorFunc ) ( newDockerfile [ ] byte , resolvedTags [ ] * resolvedTag , err error ) {
2015-07-21 17:47:44 -04:00
scanner := bufio . NewScanner ( dockerfile )
2016-01-12 16:18:24 -05:00
buf := bytes . NewBuffer ( nil )
2015-07-21 17:47:44 -04:00
// Scan the lines of the Dockerfile, looking for a "FROM" line.
for scanner . Scan ( ) {
line := scanner . Text ( )
matches := dockerfileFromLinePattern . FindStringSubmatch ( line )
2015-12-31 08:57:58 -05:00
if matches != nil && matches [ 1 ] != api . NoBaseImageSpecifier {
2015-07-21 17:47:44 -04:00
// Replace the line with a resolved "FROM repo@digest"
2015-11-18 17:20:54 -05:00
ref , err := reference . ParseNamed ( matches [ 1 ] )
if err != nil {
return nil , nil , err
2015-07-21 17:47:44 -04:00
}
2015-12-10 14:01:34 -05:00
ref = reference . WithDefaultTag ( ref )
if ref , ok := ref . ( reference . NamedTagged ) ; ok && isTrusted ( ) {
trustedRef , err := translator ( ref )
2015-07-21 17:47:44 -04:00
if err != nil {
2015-07-24 19:35:11 -04:00
return nil , nil , err
2015-07-21 17:47:44 -04:00
}
2015-11-18 17:20:54 -05:00
line = dockerfileFromLinePattern . ReplaceAllLiteralString ( line , fmt . Sprintf ( "FROM %s" , trustedRef . String ( ) ) )
2015-07-24 19:35:11 -04:00
resolvedTags = append ( resolvedTags , & resolvedTag {
digestRef : trustedRef ,
2015-12-10 14:01:34 -05:00
tagRef : ref ,
2015-07-24 19:35:11 -04:00
} )
2015-07-21 17:47:44 -04:00
}
}
2016-01-12 16:18:24 -05:00
_ , err := fmt . Fprintln ( buf , line )
2015-07-21 17:47:44 -04:00
if err != nil {
2015-07-24 19:35:11 -04:00
return nil , nil , err
2015-07-21 17:47:44 -04:00
}
}
2016-01-12 16:18:24 -05:00
return buf . Bytes ( ) , resolvedTags , scanner . Err ( )
2015-07-21 17:47:44 -04:00
}
// replaceDockerfileTarWrapper wraps the given input tar archive stream and
// replaces the entry with the given Dockerfile name with the contents of the
// new Dockerfile. Returns a new tar archive stream with the replaced
// Dockerfile.
2016-01-12 16:18:24 -05:00
func replaceDockerfileTarWrapper ( inputTarStream io . ReadCloser , dockerfileName string , translator translatorFunc , resolvedTags * [ ] * resolvedTag ) io . ReadCloser {
2015-07-21 17:47:44 -04:00
pipeReader , pipeWriter := io . Pipe ( )
go func ( ) {
tarReader := tar . NewReader ( inputTarStream )
tarWriter := tar . NewWriter ( pipeWriter )
defer inputTarStream . Close ( )
for {
hdr , err := tarReader . Next ( )
if err == io . EOF {
// Signals end of archive.
tarWriter . Close ( )
pipeWriter . Close ( )
return
}
if err != nil {
pipeWriter . CloseWithError ( err )
return
}
var content io . Reader = tarReader
if hdr . Name == dockerfileName {
// This entry is the Dockerfile. Since the tar archive was
// generated from a directory on the local filesystem, the
// Dockerfile will only appear once in the archive.
2016-01-12 16:18:24 -05:00
var newDockerfile [ ] byte
newDockerfile , * resolvedTags , err = rewriteDockerfileFrom ( content , translator )
if err != nil {
pipeWriter . CloseWithError ( err )
return
}
hdr . Size = int64 ( len ( newDockerfile ) )
content = bytes . NewBuffer ( newDockerfile )
2015-07-21 17:47:44 -04:00
}
if err := tarWriter . WriteHeader ( hdr ) ; err != nil {
pipeWriter . CloseWithError ( err )
return
}
if _ , err := io . Copy ( tarWriter , content ) ; err != nil {
pipeWriter . CloseWithError ( err )
return
}
}
} ( )
return pipeReader
}