2016-09-08 13:11:39 -04:00
package image
import (
"archive/tar"
"bufio"
"bytes"
2018-05-03 21:02:44 -04:00
"context"
2017-04-06 08:33:56 -04:00
"encoding/json"
2016-09-08 13:11:39 -04:00
"fmt"
"io"
2017-04-06 08:33:56 -04:00
"io/ioutil"
2016-09-08 13:11:39 -04:00
"os"
2018-02-17 19:40:55 -05:00
"path/filepath"
2016-09-08 13:11:39 -04:00
"regexp"
"runtime"
2018-08-06 17:17:03 -04:00
"strconv"
2018-02-17 19:40:55 -05:00
"strings"
2016-09-08 13:11:39 -04:00
2017-04-17 18:07:56 -04:00
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
2017-05-15 08:45:19 -04:00
"github.com/docker/cli/opts"
2017-01-11 16:54:52 -05:00
"github.com/docker/distribution/reference"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/archive"
2017-09-08 13:45:19 -04:00
"github.com/docker/docker/pkg/idtools"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
2016-12-22 16:25:02 -05:00
units "github.com/docker/go-units"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2017-08-07 05:52:40 -04:00
"github.com/sirupsen/logrus"
2016-09-08 13:11:39 -04:00
"github.com/spf13/cobra"
)
2018-05-22 19:23:15 -04:00
var errStdinConflict = errors . New ( "invalid argument: can't use stdin for both build context and dockerfile" )
2016-09-08 13:11:39 -04:00
type buildOptions struct {
context string
dockerfileName string
tags opts . ListOpts
2016-09-29 17:59:52 -04:00
labels opts . ListOpts
2016-09-08 13:11:39 -04:00
buildArgs opts . ListOpts
2017-01-13 10:01:58 -05:00
extraHosts opts . ListOpts
2016-12-23 14:09:12 -05:00
ulimits * opts . UlimitOpt
2017-03-06 16:01:04 -05:00
memory opts . MemBytes
memorySwap opts . MemSwapBytes
2016-12-28 17:44:07 -05:00
shmSize opts . MemBytes
2016-09-08 13:11:39 -04:00
cpuShares int64
cpuPeriod int64
cpuQuota int64
cpuSetCpus string
cpuSetMems string
cgroupParent string
isolation string
quiet bool
noCache bool
2018-08-06 18:44:28 -04:00
progress string
2016-09-08 13:11:39 -04:00
rm bool
forceRm bool
pull bool
2016-09-22 17:38:00 -04:00
cacheFrom [ ] string
2016-08-18 04:35:23 -04:00
compress bool
2016-06-07 15:15:50 -04:00
securityOpt [ ] string
2016-03-06 07:29:23 -05:00
networkMode string
2016-04-21 12:08:37 -04:00
squash bool
2017-04-10 18:27:42 -04:00
target string
2017-04-06 08:33:56 -04:00
imageIDFile string
2017-05-15 17:14:31 -04:00
stream bool
2017-08-02 20:31:32 -04:00
platform string
2018-03-08 08:35:17 -05:00
untrusted bool
2018-08-11 15:04:13 -04:00
secrets [ ] string
2016-09-08 13:11:39 -04:00
}
2017-04-06 15:36:15 -04:00
// dockerfileFromStdin returns true when the user specified that the Dockerfile
// should be read from stdin instead of a file
func ( o buildOptions ) dockerfileFromStdin ( ) bool {
return o . dockerfileName == "-"
}
// contextFromStdin returns true when the user specified that the build context
// should be read from stdin
func ( o buildOptions ) contextFromStdin ( ) bool {
return o . context == "-"
}
2017-06-21 15:13:44 -04:00
func newBuildOptions ( ) buildOptions {
2016-09-08 13:11:39 -04:00
ulimits := make ( map [ string ] * units . Ulimit )
2017-06-21 15:13:44 -04:00
return buildOptions {
2017-01-13 10:01:58 -05:00
tags : opts . NewListOpts ( validateTag ) ,
buildArgs : opts . NewListOpts ( opts . ValidateEnv ) ,
ulimits : opts . NewUlimitOpt ( & ulimits ) ,
labels : opts . NewListOpts ( opts . ValidateEnv ) ,
extraHosts : opts . NewListOpts ( opts . ValidateExtraHost ) ,
2016-09-08 13:11:39 -04:00
}
2017-06-21 15:13:44 -04:00
}
// NewBuildCommand creates a new `docker build` command
func NewBuildCommand ( dockerCli command . Cli ) * cobra . Command {
options := newBuildOptions ( )
2016-09-08 13:11:39 -04:00
cmd := & cobra . Command {
Use : "build [OPTIONS] PATH | URL | -" ,
Short : "Build an image from a Dockerfile" ,
Args : cli . ExactArgs ( 1 ) ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
options . context = args [ 0 ]
return runBuild ( dockerCli , options )
} ,
}
flags := cmd . Flags ( )
flags . VarP ( & options . tags , "tag" , "t" , "Name and optionally a tag in the 'name:tag' format" )
flags . Var ( & options . buildArgs , "build-arg" , "Set build-time variables" )
flags . Var ( options . ulimits , "ulimit" , "Ulimit options" )
flags . StringVarP ( & options . dockerfileName , "file" , "f" , "" , "Name of the Dockerfile (Default is 'PATH/Dockerfile')" )
2017-03-06 16:01:04 -05:00
flags . VarP ( & options . memory , "memory" , "m" , "Memory limit" )
flags . Var ( & options . memorySwap , "memory-swap" , "Swap limit equal to memory plus swap: '-1' to enable unlimited swap" )
2016-12-28 17:44:07 -05:00
flags . Var ( & options . shmSize , "shm-size" , "Size of /dev/shm" )
2016-09-08 13:11:39 -04:00
flags . Int64VarP ( & options . cpuShares , "cpu-shares" , "c" , 0 , "CPU shares (relative weight)" )
flags . Int64Var ( & options . cpuPeriod , "cpu-period" , 0 , "Limit the CPU CFS (Completely Fair Scheduler) period" )
flags . Int64Var ( & options . cpuQuota , "cpu-quota" , 0 , "Limit the CPU CFS (Completely Fair Scheduler) quota" )
flags . StringVar ( & options . cpuSetCpus , "cpuset-cpus" , "" , "CPUs in which to allow execution (0-3, 0,1)" )
flags . StringVar ( & options . cpuSetMems , "cpuset-mems" , "" , "MEMs in which to allow execution (0-3, 0,1)" )
flags . StringVar ( & options . cgroupParent , "cgroup-parent" , "" , "Optional parent cgroup for the container" )
flags . StringVar ( & options . isolation , "isolation" , "" , "Container isolation technology" )
2016-09-29 17:59:52 -04:00
flags . Var ( & options . labels , "label" , "Set metadata for an image" )
2016-09-08 13:11:39 -04:00
flags . BoolVar ( & options . noCache , "no-cache" , false , "Do not use cache when building the image" )
flags . BoolVar ( & options . rm , "rm" , true , "Remove intermediate containers after a successful build" )
flags . BoolVar ( & options . forceRm , "force-rm" , false , "Always remove intermediate containers" )
flags . BoolVarP ( & options . quiet , "quiet" , "q" , false , "Suppress the build output and print image ID on success" )
flags . BoolVar ( & options . pull , "pull" , false , "Always attempt to pull a newer version of the image" )
2016-09-22 17:38:00 -04:00
flags . StringSliceVar ( & options . cacheFrom , "cache-from" , [ ] string { } , "Images to consider as cache sources" )
2016-08-18 04:35:23 -04:00
flags . BoolVar ( & options . compress , "compress" , false , "Compress the build context using gzip" )
2016-06-07 15:15:50 -04:00
flags . StringSliceVar ( & options . securityOpt , "security-opt" , [ ] string { } , "Security options" )
2016-12-09 10:15:26 -05:00
flags . StringVar ( & options . networkMode , "network" , "default" , "Set the networking mode for the RUN instructions during build" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( "network" , "version" , [ ] string { "1.25" } )
2017-01-13 10:01:58 -05:00
flags . Var ( & options . extraHosts , "add-host" , "Add a custom host-to-IP mapping (host:ip)" )
2017-04-10 18:27:42 -04:00
flags . StringVar ( & options . target , "target" , "" , "Set the target build stage to build." )
2017-04-06 08:33:56 -04:00
flags . StringVar ( & options . imageIDFile , "iidfile" , "" , "Write the image ID to the file" )
2016-09-08 13:11:39 -04:00
2018-03-08 14:56:56 -05:00
command . AddTrustVerificationFlags ( flags , & options . untrusted , dockerCli . ContentTrustEnabled ( ) )
2017-08-02 20:31:32 -04:00
command . AddPlatformFlag ( flags , & options . platform )
2016-09-08 13:11:39 -04:00
2016-11-02 20:43:32 -04:00
flags . BoolVar ( & options . squash , "squash" , false , "Squash newly built layers into a single new layer" )
flags . SetAnnotation ( "squash" , "experimental" , nil )
2016-11-02 20:43:32 -04:00
flags . SetAnnotation ( "squash" , "version" , [ ] string { "1.25" } )
2016-04-21 12:08:37 -04:00
2017-05-15 17:14:31 -04:00
flags . BoolVar ( & options . stream , "stream" , false , "Stream attaches to server to negotiate build context" )
flags . SetAnnotation ( "stream" , "experimental" , nil )
flags . SetAnnotation ( "stream" , "version" , [ ] string { "1.31" } )
2018-08-06 18:44:28 -04:00
flags . StringVar ( & options . progress , "progress" , "auto" , "Set type of progress output (only if BuildKit enabled) (auto, plain, tty). Use plain to show container output" )
2018-08-11 15:04:13 -04:00
flags . StringArrayVar ( & options . secrets , "secret" , [ ] string { } , "Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret" )
flags . SetAnnotation ( "secret" , "version" , [ ] string { "1.39" } )
2016-09-08 13:11:39 -04:00
return cmd
}
// lastProgressOutput is the same as progress.Output except
// that it only output with the last update. It is used in
2017-02-16 10:56:53 -05:00
// non terminal scenarios to suppress verbose messages
2016-09-08 13:11:39 -04:00
type lastProgressOutput struct {
output progress . Output
}
// WriteProgress formats progress information from a ProgressReader.
func ( out * lastProgressOutput ) WriteProgress ( prog progress . Progress ) error {
if ! prog . LastUpdate {
return nil
}
return out . output . WriteProgress ( prog )
}
2017-05-02 16:14:43 -04:00
// nolint: gocyclo
2017-06-21 17:20:49 -04:00
func runBuild ( dockerCli command . Cli , options buildOptions ) error {
2018-08-06 17:17:03 -04:00
if buildkitEnv := os . Getenv ( "DOCKER_BUILDKIT" ) ; buildkitEnv != "" {
enableBuildkit , err := strconv . ParseBool ( buildkitEnv )
if err != nil {
return errors . Wrap ( err , "DOCKER_BUILDKIT environment variable expects boolean value" )
}
if enableBuildkit {
return runBuildBuildKit ( dockerCli , options )
}
} else if dockerCli . ServerInfo ( ) . BuildkitVersion == types . BuilderBuildKit {
2018-08-06 17:16:11 -04:00
return runBuildBuildKit ( dockerCli , options )
2018-04-18 19:36:26 -04:00
}
2016-09-08 13:11:39 -04:00
var (
2016-11-07 03:17:04 -05:00
buildCtx io . ReadCloser
2017-02-21 15:07:45 -05:00
dockerfileCtx io . ReadCloser
2016-11-07 03:17:04 -05:00
err error
2016-09-08 13:11:39 -04:00
contextDir string
tempDir string
relDockerfile string
progBuff io . Writer
buildBuff io . Writer
2017-05-15 17:14:31 -04:00
remote string
2016-09-08 13:11:39 -04:00
)
2016-11-02 20:43:32 -04:00
2018-06-01 12:34:00 -04:00
if options . compress && options . stream {
return errors . New ( "--compress conflicts with --stream options" )
}
2017-04-06 15:36:15 -04:00
if options . dockerfileFromStdin ( ) {
if options . contextFromStdin ( ) {
2018-05-22 19:23:15 -04:00
return errStdinConflict
2017-04-06 15:36:15 -04:00
}
dockerfileCtx = dockerCli . In ( )
}
2016-11-07 03:17:04 -05:00
specifiedContext := options . context
2016-09-08 13:11:39 -04:00
progBuff = dockerCli . Out ( )
buildBuff = dockerCli . Out ( )
if options . quiet {
progBuff = bytes . NewBuffer ( nil )
buildBuff = bytes . NewBuffer ( nil )
}
2017-04-06 08:33:56 -04:00
if options . imageIDFile != "" {
// Avoid leaving a stale file if we eventually fail
if err := os . Remove ( options . imageIDFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrap ( err , "Removing image ID file" )
}
}
2016-09-08 13:11:39 -04:00
switch {
2017-04-06 15:36:15 -04:00
case options . contextFromStdin ( ) :
2017-05-15 17:14:31 -04:00
// buildCtx is tar archive. if stdin was dockerfile then it is wrapped
2016-12-22 16:25:02 -05:00
buildCtx , relDockerfile , err = build . GetContextFromReader ( dockerCli . In ( ) , options . dockerfileName )
2017-01-24 08:19:31 -05:00
case isLocalDir ( specifiedContext ) :
contextDir , relDockerfile , err = build . GetContextFromLocalDir ( specifiedContext , options . dockerfileName )
2018-02-17 19:40:55 -05:00
if err == nil && strings . HasPrefix ( relDockerfile , ".." + string ( filepath . Separator ) ) {
// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
dockerfileCtx , err = os . Open ( options . dockerfileName )
if err != nil {
return errors . Errorf ( "unable to open Dockerfile: %v" , err )
}
defer dockerfileCtx . Close ( )
}
2016-09-08 13:11:39 -04:00
case urlutil . IsGitURL ( specifiedContext ) :
2016-12-22 16:25:02 -05:00
tempDir , relDockerfile , err = build . GetContextFromGitURL ( specifiedContext , options . dockerfileName )
2016-09-08 13:11:39 -04:00
case urlutil . IsURL ( specifiedContext ) :
2016-12-22 16:25:02 -05:00
buildCtx , relDockerfile , err = build . GetContextFromURL ( progBuff , specifiedContext , options . dockerfileName )
2016-09-08 13:11:39 -04:00
default :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "unable to prepare context: path %q not found" , specifiedContext )
2016-09-08 13:11:39 -04:00
}
if err != nil {
if options . quiet && urlutil . IsURL ( specifiedContext ) {
fmt . Fprintln ( dockerCli . Err ( ) , progBuff )
}
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "unable to prepare context: %s" , err )
2016-09-08 13:11:39 -04:00
}
if tempDir != "" {
defer os . RemoveAll ( tempDir )
contextDir = tempDir
}
2017-05-15 17:14:31 -04:00
// read from a directory into tar archive
if buildCtx == nil && ! options . stream {
2017-04-16 15:01:15 -04:00
excludes , err := build . ReadDockerignore ( contextDir )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
2016-12-22 16:25:02 -05:00
if err := build . ValidateContextDirectory ( contextDir , excludes ) ; err != nil {
2017-04-16 15:01:15 -04:00
return errors . Errorf ( "error checking context: '%s'." , err )
2016-09-08 13:11:39 -04:00
}
2017-04-06 15:36:15 -04:00
// And canonicalize dockerfile name to a platform-independent one
2018-06-29 11:17:00 -04:00
relDockerfile = archive . CanonicalTarNameForPath ( relDockerfile )
2016-09-08 13:11:39 -04:00
2017-04-16 15:01:15 -04:00
excludes = build . TrimBuildFilesFromExcludes ( excludes , relDockerfile , options . dockerfileFromStdin ( ) )
2016-09-08 13:11:39 -04:00
buildCtx , err = archive . TarWithOptions ( contextDir , & archive . TarOptions {
ExcludePatterns : excludes ,
2018-08-06 17:17:03 -04:00
ChownOpts : & idtools . Identity { UID : 0 , GID : 0 } ,
2016-09-08 13:11:39 -04:00
} )
if err != nil {
return err
}
}
2018-02-17 19:40:55 -05:00
// replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
2017-05-15 17:14:31 -04:00
if dockerfileCtx != nil && buildCtx != nil {
2017-04-16 16:06:16 -04:00
buildCtx , relDockerfile , err = build . AddDockerfileToBuildContext ( dockerfileCtx , buildCtx )
2017-02-21 15:07:45 -05:00
if err != nil {
return err
}
}
2018-02-17 19:40:55 -05:00
// if streaming and Dockerfile was not from stdin then read from file
2017-05-15 17:14:31 -04:00
// to the same reader that is usually stdin
if options . stream && dockerfileCtx == nil {
dockerfileCtx , err = os . Open ( relDockerfile )
if err != nil {
return errors . Wrapf ( err , "failed to open %s" , relDockerfile )
}
defer dockerfileCtx . Close ( )
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2016-09-08 13:11:39 -04:00
var resolvedTags [ ] * resolvedTag
2018-03-08 14:56:56 -05:00
if ! options . untrusted {
2016-08-29 14:45:29 -04:00
translator := func ( ctx context . Context , ref reference . NamedTagged ) ( reference . Canonical , error ) {
2016-12-27 15:51:00 -05:00
return TrustedReference ( ctx , dockerCli , ref , nil )
2016-08-29 14:45:29 -04:00
}
2017-05-15 17:14:31 -04:00
// if there is a tar wrapper, the dockerfile needs to be replaced inside it
if buildCtx != nil {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
2018-03-08 14:56:56 -05:00
buildCtx = replaceDockerfileForContentTrust ( ctx , buildCtx , relDockerfile , translator , & resolvedTags )
2017-05-15 17:14:31 -04:00
} else if dockerfileCtx != nil {
// if there was not archive context still do the possible replacements in Dockerfile
2018-03-14 12:36:23 -04:00
newDockerfile , _ , err := rewriteDockerfileFromForContentTrust ( ctx , dockerfileCtx , translator )
2017-05-15 17:14:31 -04:00
if err != nil {
return err
}
dockerfileCtx = ioutil . NopCloser ( bytes . NewBuffer ( newDockerfile ) )
}
2016-09-08 13:11:39 -04:00
}
2017-06-21 15:13:44 -04:00
if options . compress {
buildCtx , err = build . Compress ( buildCtx )
if err != nil {
return err
}
}
2016-09-08 13:11:39 -04:00
// Setup an upload progress bar
2017-05-01 14:54:56 -04:00
progressOutput := streamformatter . NewProgressOutput ( progBuff )
2016-09-08 13:11:39 -04:00
if ! dockerCli . Out ( ) . IsTerminal ( ) {
progressOutput = & lastProgressOutput { output : progressOutput }
}
2017-10-31 06:21:09 -04:00
// if up to this point nothing has set the context then we must have another
// way for sending it(streaming) and set the context to the Dockerfile
2017-05-15 17:14:31 -04:00
if dockerfileCtx != nil && buildCtx == nil {
buildCtx = dockerfileCtx
}
s , err := trySession ( dockerCli , contextDir )
if err != nil {
return err
}
var body io . Reader
if buildCtx != nil && ! options . stream {
body = progress . NewProgressReader ( buildCtx , progressOutput , 0 , "" , "Sending build context to Docker daemon" )
}
// add context stream to the session
if options . stream && s != nil {
syncDone := make ( chan error ) // used to signal first progress reporting completed.
// progress would also send errors but don't need it here as errors
// are handled by session.Run() and ImageBuild()
if err := addDirToSession ( s , contextDir , progressOutput , syncDone ) ; err != nil {
return err
}
buf := newBufferedWriter ( syncDone , buildBuff )
defer func ( ) {
select {
case <- buf . flushed :
case <- ctx . Done ( ) :
}
} ( )
buildBuff = buf
remote = clientSessionRemote
body = buildCtx
}
2016-09-08 13:11:39 -04:00
2017-06-21 17:20:49 -04:00
configFile := dockerCli . ConfigFile ( )
authConfigs , _ := configFile . GetAllCredentials ( )
2018-06-09 13:25:32 -04:00
buildOptions := imageBuildOptions ( dockerCli , options )
buildOptions . Version = types . BuilderV1
buildOptions . Dockerfile = relDockerfile
buildOptions . AuthConfigs = authConfigs
buildOptions . RemoteContext = remote
2017-05-15 17:14:31 -04:00
if s != nil {
go func ( ) {
2017-09-29 08:18:19 -04:00
logrus . Debugf ( "running session: %v" , s . ID ( ) )
2017-05-15 17:14:31 -04:00
if err := s . Run ( ctx , dockerCli . Client ( ) . DialSession ) ; err != nil {
logrus . Error ( err )
cancel ( ) // cancel progress context
}
} ( )
2017-09-29 08:18:19 -04:00
buildOptions . SessionID = s . ID ( )
2016-09-08 13:11:39 -04:00
}
response , err := dockerCli . Client ( ) . ImageBuild ( ctx , body , buildOptions )
if err != nil {
2016-09-13 00:06:04 -04:00
if options . quiet {
fmt . Fprintf ( dockerCli . Err ( ) , "%s" , progBuff )
}
2017-05-15 17:14:31 -04:00
cancel ( )
2016-09-08 13:11:39 -04:00
return err
}
defer response . Body . Close ( )
2017-04-06 08:33:56 -04:00
imageID := ""
2018-04-19 13:07:27 -04:00
aux := func ( msg jsonmessage . JSONMessage ) {
2017-04-06 08:33:56 -04:00
var result types . BuildResult
2018-04-19 13:07:27 -04:00
if err := json . Unmarshal ( * msg . Aux , & result ) ; err != nil {
2017-04-06 08:33:56 -04:00
fmt . Fprintf ( dockerCli . Err ( ) , "Failed to parse aux message: %s" , err )
} else {
imageID = result . ID
}
}
err = jsonmessage . DisplayJSONMessagesStream ( response . Body , buildBuff , dockerCli . Out ( ) . FD ( ) , dockerCli . Out ( ) . IsTerminal ( ) , aux )
2016-09-08 13:11:39 -04:00
if err != nil {
if jerr , ok := err . ( * jsonmessage . JSONError ) ; ok {
// If no error code is set, default to 1
if jerr . Code == 0 {
jerr . Code = 1
}
if options . quiet {
fmt . Fprintf ( dockerCli . Err ( ) , "%s%s" , progBuff , buildBuff )
}
return cli . StatusError { Status : jerr . Message , StatusCode : jerr . Code }
}
2017-03-21 22:09:02 -04:00
return err
2016-09-08 13:11:39 -04:00
}
// Windows: show error message about modified file permissions if the
// daemon isn't running Windows.
if response . OSType != "windows" && runtime . GOOS == "windows" && ! options . quiet {
2017-03-06 15:07:20 -05:00
fmt . Fprintln ( dockerCli . Out ( ) , "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." )
2016-09-08 13:11:39 -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 options . quiet {
2017-04-06 08:33:56 -04:00
imageID = fmt . Sprintf ( "%s" , buildBuff )
fmt . Fprintf ( dockerCli . Out ( ) , imageID )
2016-09-08 13:11:39 -04:00
}
2017-04-06 08:33:56 -04:00
if options . imageIDFile != "" {
if imageID == "" {
return errors . Errorf ( "Server did not provide an image ID. Cannot write %s" , options . imageIDFile )
}
if err := ioutil . WriteFile ( options . imageIDFile , [ ] byte ( imageID ) , 0666 ) ; err != nil {
return err
}
}
2018-03-08 14:56:56 -05:00
if ! options . untrusted {
2016-09-08 13:11:39 -04:00
// Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite.
for _ , resolved := range resolvedTags {
2016-08-29 14:45:29 -04:00
if err := TagTrusted ( ctx , dockerCli , resolved . digestRef , resolved . tagRef ) ; err != nil {
2016-09-08 13:11:39 -04:00
return err
}
}
}
return nil
}
2017-01-24 08:19:31 -05:00
func isLocalDir ( c string ) bool {
_ , err := os . Stat ( c )
return err == nil
}
2016-09-08 13:11:39 -04:00
type translatorFunc func ( context . Context , reference . NamedTagged ) ( reference . Canonical , error )
// validateTag checks if the given image name can be resolved.
func validateTag ( rawRepo string ) ( string , error ) {
2017-01-11 16:54:52 -05:00
_ , err := reference . ParseNormalizedNamed ( rawRepo )
2016-09-08 13:11:39 -04:00
if err != nil {
return "" , err
}
return rawRepo , nil
}
var dockerfileFromLinePattern = regexp . MustCompile ( ` (?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+) ` )
// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
type resolvedTag struct {
digestRef reference . Canonical
tagRef reference . NamedTagged
}
2018-03-14 12:36:23 -04:00
// rewriteDockerfileFromForContentTrust rewrites the given Dockerfile by resolving images in
2016-09-08 13:11:39 -04:00
// "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.
2018-03-14 12:36:23 -04:00
// This should be called *only* when content trust is enabled
func rewriteDockerfileFromForContentTrust ( ctx context . Context , dockerfile io . Reader , translator translatorFunc ) ( newDockerfile [ ] byte , resolvedTags [ ] * resolvedTag , err error ) {
2016-09-08 13:11:39 -04:00
scanner := bufio . NewScanner ( dockerfile )
buf := bytes . NewBuffer ( nil )
// Scan the lines of the Dockerfile, looking for a "FROM" line.
for scanner . Scan ( ) {
line := scanner . Text ( )
matches := dockerfileFromLinePattern . FindStringSubmatch ( line )
if matches != nil && matches [ 1 ] != api . NoBaseImageSpecifier {
// Replace the line with a resolved "FROM repo@digest"
2017-01-11 16:54:52 -05:00
var ref reference . Named
ref , err = reference . ParseNormalizedNamed ( matches [ 1 ] )
2016-09-08 13:11:39 -04:00
if err != nil {
return nil , nil , err
}
2017-01-25 19:54:18 -05:00
ref = reference . TagNameOnly ( ref )
2018-03-14 12:36:23 -04:00
if ref , ok := ref . ( reference . NamedTagged ) ; ok {
2016-09-08 13:11:39 -04:00
trustedRef , err := translator ( ctx , ref )
if err != nil {
return nil , nil , err
}
2017-01-11 16:54:52 -05:00
line = dockerfileFromLinePattern . ReplaceAllLiteralString ( line , fmt . Sprintf ( "FROM %s" , reference . FamiliarString ( trustedRef ) ) )
2016-09-08 13:11:39 -04:00
resolvedTags = append ( resolvedTags , & resolvedTag {
digestRef : trustedRef ,
tagRef : ref ,
} )
}
}
_ , err := fmt . Fprintln ( buf , line )
if err != nil {
return nil , nil , err
}
}
return buf . Bytes ( ) , resolvedTags , scanner . Err ( )
}
2018-03-08 14:56:56 -05:00
// replaceDockerfileForContentTrust wraps the given input tar archive stream and
// uses the translator to replace the Dockerfile which uses a trusted reference.
// Returns a new tar archive stream with the replaced Dockerfile.
func replaceDockerfileForContentTrust ( ctx context . Context , inputTarStream io . ReadCloser , dockerfileName string , translator translatorFunc , resolvedTags * [ ] * resolvedTag ) io . ReadCloser {
2016-09-08 13:11:39 -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
}
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.
var newDockerfile [ ] byte
2018-03-14 12:36:23 -04:00
newDockerfile , * resolvedTags , err = rewriteDockerfileFromForContentTrust ( ctx , content , translator )
2016-09-08 13:11:39 -04:00
if err != nil {
pipeWriter . CloseWithError ( err )
return
}
hdr . Size = int64 ( len ( newDockerfile ) )
content = bytes . NewBuffer ( newDockerfile )
}
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
}
2018-06-09 13:25:32 -04:00
func imageBuildOptions ( dockerCli command . Cli , options buildOptions ) types . ImageBuildOptions {
configFile := dockerCli . ConfigFile ( )
return types . ImageBuildOptions {
Memory : options . memory . Value ( ) ,
MemorySwap : options . memorySwap . Value ( ) ,
Tags : options . tags . GetAll ( ) ,
SuppressOutput : options . quiet ,
NoCache : options . noCache ,
Remove : options . rm ,
ForceRemove : options . forceRm ,
PullParent : options . pull ,
Isolation : container . Isolation ( options . isolation ) ,
CPUSetCPUs : options . cpuSetCpus ,
CPUSetMems : options . cpuSetMems ,
CPUShares : options . cpuShares ,
CPUQuota : options . cpuQuota ,
CPUPeriod : options . cpuPeriod ,
CgroupParent : options . cgroupParent ,
ShmSize : options . shmSize . Value ( ) ,
Ulimits : options . ulimits . GetList ( ) ,
BuildArgs : configFile . ParseProxyConfig ( dockerCli . Client ( ) . DaemonHost ( ) , options . buildArgs . GetAll ( ) ) ,
Labels : opts . ConvertKVStringsToMap ( options . labels . GetAll ( ) ) ,
CacheFrom : options . cacheFrom ,
SecurityOpt : options . securityOpt ,
NetworkMode : options . networkMode ,
Squash : options . squash ,
ExtraHosts : options . extraHosts . GetAll ( ) ,
Target : options . target ,
Platform : options . platform ,
}
}