2019-03-24 12:30:29 +08:00
package commands
import (
"context"
2021-06-30 13:41:21 +08:00
"encoding/json"
2021-08-20 22:09:56 +08:00
"fmt"
2019-03-24 12:30:29 +08:00
"os"
2019-10-21 14:02:37 +08:00
"path/filepath"
2019-03-24 12:30:29 +08:00
"strings"
2019-04-25 10:29:56 +08:00
"github.com/docker/buildx/build"
2021-04-09 14:20:26 +08:00
"github.com/docker/buildx/util/buildflags"
2019-04-25 10:29:56 +08:00
"github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress"
2021-07-03 12:39:57 +08:00
"github.com/docker/buildx/util/tracing"
2019-03-24 12:30:29 +08:00
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
2021-10-26 18:53:35 +08:00
dockeropts "github.com/docker/cli/opts"
2021-06-30 13:41:21 +08:00
"github.com/docker/docker/pkg/ioutils"
2021-10-15 22:03:44 +08:00
"github.com/docker/go-units"
2019-04-17 14:15:58 +08:00
"github.com/moby/buildkit/client"
2019-03-24 12:30:29 +08:00
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/appcontext"
2019-04-17 01:55:13 +08:00
"github.com/pkg/errors"
2021-10-27 03:36:37 +08:00
"github.com/sirupsen/logrus"
2019-03-24 12:30:29 +08:00
"github.com/spf13/cobra"
2019-04-05 15:04:19 +08:00
"github.com/spf13/pflag"
2019-03-24 12:30:29 +08:00
)
2021-06-30 13:41:21 +08:00
const defaultTargetName = "default"
2019-03-24 12:30:29 +08:00
type buildOptions struct {
contextPath string
dockerfileName string
2019-04-17 01:55:13 +08:00
2021-10-26 18:53:35 +08:00
allow [ ] string
buildArgs [ ] string
2019-04-17 06:43:15 +08:00
cacheFrom [ ] string
2019-04-18 14:07:01 +08:00
cacheTo [ ] string
2019-04-17 11:10:34 +08:00
extraHosts [ ] string
2021-10-26 18:53:35 +08:00
imageIDFile string
labels [ ] string
2019-04-17 11:48:38 +08:00
networkMode string
2021-10-26 18:53:35 +08:00
outputs [ ] string
platforms [ ] string
2021-08-19 11:05:15 +08:00
quiet bool
2021-10-26 18:53:35 +08:00
secrets [ ] string
shmSize dockeropts . MemBytes
ssh [ ] string
tags [ ] string
target string
ulimits * dockeropts . UlimitOpt
commonOptions
2019-03-24 12:30:29 +08:00
}
2019-04-05 15:04:19 +08:00
type commonOptions struct {
2021-06-30 13:41:21 +08:00
builder string
2021-10-26 18:53:35 +08:00
metadataFile string
2021-06-30 13:41:21 +08:00
noCache * bool
progress string
pull * bool
2021-10-26 18:53:35 +08:00
2021-02-17 15:42:08 +08:00
// golangci-lint#826
// nolint:structcheck
2019-10-17 06:04:54 +08:00
exportPush bool
2021-02-17 15:42:08 +08:00
// nolint:structcheck
2019-10-17 06:04:54 +08:00
exportLoad bool
2019-04-05 15:04:19 +08:00
}
2021-07-03 12:39:57 +08:00
func runBuild ( dockerCli command . Cli , in buildOptions ) ( err error ) {
2019-03-24 12:30:29 +08:00
ctx := appcontext . Context ( )
2021-07-03 12:39:57 +08:00
ctx , end , err := tracing . TraceCurrentCommand ( ctx , "build" )
if err != nil {
return err
}
defer func ( ) {
end ( err )
} ( )
2019-10-17 06:10:17 +08:00
noCache := false
if in . noCache != nil {
noCache = * in . noCache
}
pull := false
if in . pull != nil {
pull = * in . pull
}
2021-08-19 11:05:15 +08:00
if in . quiet && in . progress != "auto" && in . progress != "quiet" {
return errors . Errorf ( "progress=%s and quiet cannot be used together" , in . progress )
} else if in . quiet {
in . progress = "quiet"
}
2019-03-24 12:30:29 +08:00
opts := build . Options {
Inputs : build . Inputs {
ContextPath : in . contextPath ,
DockerfilePath : in . dockerfileName ,
InStream : os . Stdin ,
} ,
2019-07-31 07:32:36 +08:00
BuildArgs : listToMap ( in . buildArgs , true ) ,
2019-04-17 11:10:34 +08:00
ExtraHosts : in . extraHosts ,
2021-10-26 18:53:35 +08:00
ImageIDFile : in . imageIDFile ,
Labels : listToMap ( in . labels , false ) ,
2019-04-17 11:48:38 +08:00
NetworkMode : in . networkMode ,
2021-10-26 18:53:35 +08:00
NoCache : noCache ,
Pull : pull ,
2021-10-20 03:05:52 +08:00
ShmSize : in . shmSize ,
2021-10-26 18:53:35 +08:00
Tags : in . tags ,
Target : in . target ,
2021-10-15 22:03:44 +08:00
Ulimits : in . ulimits ,
2019-03-24 12:30:29 +08:00
}
2019-04-18 07:48:52 +08:00
platforms , err := platformutil . Parse ( in . platforms )
2019-03-24 12:30:29 +08:00
if err != nil {
return err
}
opts . Platforms = platforms
2019-05-15 08:37:34 +08:00
opts . Session = append ( opts . Session , authprovider . NewDockerAuthProvider ( os . Stderr ) )
2019-03-24 12:30:29 +08:00
2021-04-09 14:20:26 +08:00
secrets , err := buildflags . ParseSecretSpecs ( in . secrets )
2019-03-24 12:30:29 +08:00
if err != nil {
return err
}
opts . Session = append ( opts . Session , secrets )
2021-04-02 02:08:56 +08:00
sshSpecs := in . ssh
2021-04-09 14:20:26 +08:00
if len ( sshSpecs ) == 0 && buildflags . IsGitSSH ( in . contextPath ) {
2021-04-02 02:08:56 +08:00
sshSpecs = [ ] string { "default" }
}
2021-04-09 14:20:26 +08:00
ssh , err := buildflags . ParseSSHSpecs ( sshSpecs )
2019-03-24 12:30:29 +08:00
if err != nil {
return err
}
opts . Session = append ( opts . Session , ssh )
2021-04-09 14:20:26 +08:00
outputs , err := buildflags . ParseOutputs ( in . outputs )
2019-03-24 12:30:29 +08:00
if err != nil {
return err
}
2019-04-17 14:15:58 +08:00
if in . exportPush {
if in . exportLoad {
return errors . Errorf ( "push and load may not be set together at the moment" )
}
if len ( outputs ) == 0 {
outputs = [ ] client . ExportEntry { {
Type : "image" ,
Attrs : map [ string ] string {
"push" : "true" ,
} ,
} }
} else {
switch outputs [ 0 ] . Type {
case "image" :
outputs [ 0 ] . Attrs [ "push" ] = "true"
default :
return errors . Errorf ( "push and %q output can't be used together" , outputs [ 0 ] . Type )
}
}
}
if in . exportLoad {
if len ( outputs ) == 0 {
outputs = [ ] client . ExportEntry { {
Type : "docker" ,
Attrs : map [ string ] string { } ,
} }
} else {
switch outputs [ 0 ] . Type {
case "docker" :
default :
return errors . Errorf ( "load and %q output can't be used together" , outputs [ 0 ] . Type )
}
}
}
2019-03-24 12:30:29 +08:00
opts . Exports = outputs
2021-04-09 14:20:26 +08:00
cacheImports , err := buildflags . ParseCacheEntry ( in . cacheFrom )
2019-04-18 14:07:01 +08:00
if err != nil {
return err
}
opts . CacheFrom = cacheImports
2021-04-09 14:20:26 +08:00
cacheExports , err := buildflags . ParseCacheEntry ( in . cacheTo )
2019-04-18 14:07:01 +08:00
if err != nil {
return err
}
opts . CacheTo = cacheExports
2021-04-09 14:20:26 +08:00
allow , err := buildflags . ParseEntitlements ( in . allow )
2019-07-09 06:58:38 +08:00
if err != nil {
return err
}
opts . Allow = allow
2019-10-21 14:02:37 +08:00
// key string used for kubernetes "sticky" mode
contextPathHash , err := filepath . Abs ( in . contextPath )
if err != nil {
contextPathHash = in . contextPath
}
2021-08-20 22:09:56 +08:00
imageID , err := buildTargets ( ctx , dockerCli , map [ string ] build . Options { defaultTargetName : opts } , in . progress , contextPathHash , in . builder , in . metadataFile )
if err != nil {
return err
}
if in . quiet {
fmt . Println ( imageID )
}
return nil
2019-04-05 15:04:19 +08:00
}
2021-08-20 22:09:56 +08:00
func buildTargets ( ctx context . Context , dockerCli command . Cli , opts map [ string ] build . Options , progressMode , contextPathHash , instance string , metadataFile string ) ( imageID string , err error ) {
2020-03-26 07:32:46 +08:00
dis , err := getInstanceOrDefault ( ctx , dockerCli , instance , contextPathHash )
2019-03-24 12:30:29 +08:00
if err != nil {
2021-08-20 22:09:56 +08:00
return "" , err
2019-03-24 12:30:29 +08:00
}
ctx2 , cancel := context . WithCancel ( context . TODO ( ) )
defer cancel ( )
2021-08-19 11:05:15 +08:00
2020-09-21 10:46:39 +08:00
printer := progress . NewPrinter ( ctx2 , os . Stderr , progressMode )
2021-06-30 13:41:21 +08:00
resp , err := build . Build ( ctx , dis , opts , dockerAPI ( dockerCli ) , dockerCli . ConfigFile ( ) , printer )
2020-09-21 10:46:39 +08:00
err1 := printer . Wait ( )
if err == nil {
err = err1
}
2021-06-30 13:41:21 +08:00
if err != nil {
2021-08-20 22:09:56 +08:00
return "" , err
2021-06-30 13:41:21 +08:00
}
if len ( metadataFile ) > 0 && resp != nil {
mdatab , err := json . MarshalIndent ( resp [ defaultTargetName ] . ExporterResponse , "" , " " )
if err != nil {
2021-08-20 22:09:56 +08:00
return "" , err
2021-06-30 13:41:21 +08:00
}
if err := ioutils . AtomicWriteFile ( metadataFile , mdatab , 0644 ) ; err != nil {
2021-08-20 22:09:56 +08:00
return "" , err
2021-06-30 13:41:21 +08:00
}
}
2019-03-24 12:30:29 +08:00
2021-08-20 22:09:56 +08:00
return resp [ defaultTargetName ] . ExporterResponse [ "containerimage.digest" ] , err
2019-03-24 12:30:29 +08:00
}
2021-10-15 22:03:44 +08:00
func newBuildOptions ( ) buildOptions {
ulimits := make ( map [ string ] * units . Ulimit )
return buildOptions {
2021-10-26 18:53:35 +08:00
ulimits : dockeropts . NewUlimitOpt ( & ulimits ) ,
2021-10-15 22:03:44 +08:00
}
}
2020-04-28 05:37:17 +08:00
func buildCmd ( dockerCli command . Cli , rootOpts * rootOptions ) * cobra . Command {
2021-10-15 22:03:44 +08:00
options := newBuildOptions ( )
2019-03-24 12:30:29 +08:00
cmd := & cobra . Command {
Use : "build [OPTIONS] PATH | URL | -" ,
Aliases : [ ] string { "b" } ,
Short : "Start a build" ,
Args : cli . ExactArgs ( 1 ) ,
2020-05-01 04:01:45 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
options . contextPath = args [ 0 ]
options . builder = rootOpts . builder
2021-10-27 03:36:37 +08:00
cmd . Flags ( ) . VisitAll ( checkWarnedFlags )
2020-05-01 04:01:45 +08:00
return runBuild ( dockerCli , options )
} ,
2019-03-24 12:30:29 +08:00
}
2021-10-26 18:53:35 +08:00
var platformsDefault [ ] string
if v := os . Getenv ( "DOCKER_DEFAULT_PLATFORM" ) ; v != "" {
platformsDefault = [ ] string { v }
}
2019-03-24 12:30:29 +08:00
flags := cmd . Flags ( )
2021-10-26 18:53:35 +08:00
flags . StringSliceVar ( & options . extraHosts , "add-host" , [ ] string { } , "Add a custom host-to-IP mapping (format: `host:ip`)" )
flags . SetAnnotation ( "add-host" , "docs.external.url" , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host" } )
flags . StringSliceVar ( & options . allow , "allow" , [ ] string { } , "Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`)" )
2019-04-17 14:15:58 +08:00
2019-03-24 12:30:29 +08:00
flags . StringArrayVar ( & options . buildArgs , "build-arg" , [ ] string { } , "Set build-time variables" )
2021-03-24 02:07:57 +08:00
flags . SetAnnotation ( "build-arg" , "docs.external.url" , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg" } )
2021-10-26 18:53:35 +08:00
flags . StringArrayVar ( & options . cacheFrom , "cache-from" , [ ] string { } , "External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`)" )
flags . StringArrayVar ( & options . cacheTo , "cache-to" , [ ] string { } , "Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`)" )
2021-09-29 21:33:19 +08:00
flags . StringVarP ( & options . dockerfileName , "file" , "f" , "" , "Name of the Dockerfile (default: `PATH/Dockerfile`)" )
2021-03-24 02:07:57 +08:00
flags . SetAnnotation ( "file" , "docs.external.url" , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f" } )
2019-04-17 01:55:13 +08:00
2021-10-26 18:53:35 +08:00
flags . StringVar ( & options . imageIDFile , "iidfile" , "" , "Write the image ID to the file" )
2019-03-24 12:30:29 +08:00
flags . StringArrayVar ( & options . labels , "label" , [ ] string { } , "Set metadata for an image" )
2019-04-17 01:55:13 +08:00
2021-10-26 18:53:35 +08:00
flags . BoolVar ( & options . exportLoad , "load" , false , "Shorthand for `--output=type=docker`" )
2019-03-24 12:30:29 +08:00
2021-10-26 18:53:35 +08:00
flags . StringVar ( & options . networkMode , "network" , "default" , "Set the networking mode for the RUN instructions during build" )
2019-04-17 01:55:13 +08:00
2021-10-26 18:53:35 +08:00
flags . StringArrayVarP ( & options . outputs , "output" , "o" , [ ] string { } , "Output destination (format: `type=local,dest=path`)" )
flags . StringArrayVar ( & options . platforms , "platform" , platformsDefault , "Set target platform for build" )
flags . BoolVar ( & options . exportPush , "push" , false , "Shorthand for `--output=type=registry`" )
2019-07-09 06:58:38 +08:00
2019-04-17 01:55:13 +08:00
flags . BoolVarP ( & options . quiet , "quiet" , "q" , false , "Suppress the build output and print image ID on success" )
2021-10-26 18:53:35 +08:00
flags . StringArrayVar ( & options . secrets , "secret" , [ ] string { } , "Secret file to expose to the build (format: `id=mysecret,src=/local/secret`)" )
2021-10-20 03:05:52 +08:00
flags . Var ( & options . shmSize , "shm-size" , "Size of `/dev/shm`" )
2021-10-26 18:53:35 +08:00
flags . StringArrayVar ( & options . ssh , "ssh" , [ ] string { } , "SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)" )
flags . StringArrayVarP ( & options . tags , "tag" , "t" , [ ] string { } , "Name and optionally a tag (format: `name:tag`)" )
flags . SetAnnotation ( "tag" , "docs.external.url" , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t" } )
flags . StringVar ( & options . target , "target" , "" , "Set the target build stage to build." )
flags . SetAnnotation ( "target" , "docs.external.url" , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target" } )
2021-10-15 22:03:44 +08:00
flags . Var ( options . ulimits , "ulimit" , "Ulimit options" )
2021-08-19 11:05:15 +08:00
2019-04-17 01:55:13 +08:00
// hidden flags
var ignore string
var ignoreSlice [ ] string
var ignoreBool bool
var ignoreInt int64
2021-10-26 18:53:35 +08:00
flags . StringVar ( & ignore , "cgroup-parent" , "" , "Optional parent cgroup for the container" )
flags . MarkHidden ( "cgroup-parent" )
2021-10-27 03:36:37 +08:00
//flags.SetAnnotation("cgroup-parent", "flag-warn", []string{"cgroup-parent is not implemented."})
2021-10-26 18:53:35 +08:00
2019-04-17 01:55:13 +08:00
flags . BoolVar ( & ignoreBool , "compress" , false , "Compress the build context using gzip" )
flags . MarkHidden ( "compress" )
2021-10-26 18:53:35 +08:00
flags . StringVar ( & ignore , "isolation" , "" , "Container isolation technology" )
flags . MarkHidden ( "isolation" )
2021-10-27 03:36:37 +08:00
flags . SetAnnotation ( "isolation" , "flag-warn" , [ ] string { "isolation flag is deprecated with BuildKit." } )
2021-10-26 18:53:35 +08:00
flags . StringSliceVar ( & ignoreSlice , "security-opt" , [ ] string { } , "Security options" )
flags . MarkHidden ( "security-opt" )
2021-10-27 03:36:37 +08:00
flags . SetAnnotation ( "security-opt" , "flag-warn" , [ ] string { ` security-opt flag is deprecated. "RUN --security=insecure" should be used with BuildKit. ` } )
flags . BoolVar ( & ignoreBool , "squash" , false , "Squash newly built layers into a single new layer" )
flags . MarkHidden ( "squash" )
flags . SetAnnotation ( "squash" , "flag-warn" , [ ] string { "experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency." } )
2021-10-26 18:53:35 +08:00
2019-04-17 01:55:13 +08:00
flags . StringVarP ( & ignore , "memory" , "m" , "" , "Memory limit" )
flags . MarkHidden ( "memory" )
2021-10-26 18:53:35 +08:00
2021-09-29 21:33:19 +08:00
flags . StringVar ( & ignore , "memory-swap" , "" , "Swap limit equal to memory plus swap: `-1` to enable unlimited swap" )
2019-04-17 01:55:13 +08:00
flags . MarkHidden ( "memory-swap" )
2021-10-26 18:53:35 +08:00
2019-04-17 01:55:13 +08:00
flags . Int64VarP ( & ignoreInt , "cpu-shares" , "c" , 0 , "CPU shares (relative weight)" )
flags . MarkHidden ( "cpu-shares" )
2021-10-26 18:53:35 +08:00
2019-04-17 01:55:13 +08:00
flags . Int64Var ( & ignoreInt , "cpu-period" , 0 , "Limit the CPU CFS (Completely Fair Scheduler) period" )
flags . MarkHidden ( "cpu-period" )
2021-10-26 18:53:35 +08:00
2019-04-17 01:55:13 +08:00
flags . Int64Var ( & ignoreInt , "cpu-quota" , 0 , "Limit the CPU CFS (Completely Fair Scheduler) quota" )
flags . MarkHidden ( "cpu-quota" )
2021-10-26 18:53:35 +08:00
2021-09-29 21:33:19 +08:00
flags . StringVar ( & ignore , "cpuset-cpus" , "" , "CPUs in which to allow execution (`0-3`, `0,1`)" )
2019-04-17 01:55:13 +08:00
flags . MarkHidden ( "cpuset-cpus" )
2021-10-26 18:53:35 +08:00
2021-09-29 21:33:19 +08:00
flags . StringVar ( & ignore , "cpuset-mems" , "" , "MEMs in which to allow execution (`0-3`, `0,1`)" )
2019-04-17 01:55:13 +08:00
flags . MarkHidden ( "cpuset-mems" )
2021-10-26 18:53:35 +08:00
2019-05-15 09:06:23 +08:00
flags . BoolVar ( & ignoreBool , "rm" , true , "Remove intermediate containers after a successful build" )
flags . MarkHidden ( "rm" )
2021-10-26 18:53:35 +08:00
2019-05-15 09:06:23 +08:00
flags . BoolVar ( & ignoreBool , "force-rm" , false , "Always remove intermediate containers" )
flags . MarkHidden ( "force-rm" )
2019-03-24 12:30:29 +08:00
2020-03-26 07:32:46 +08:00
commonBuildFlags ( & options . commonOptions , flags )
2019-03-24 12:30:29 +08:00
return cmd
}
2020-03-26 07:32:46 +08:00
func commonBuildFlags ( options * commonOptions , flags * pflag . FlagSet ) {
2020-05-01 03:25:30 +08:00
options . noCache = flags . Bool ( "no-cache" , false , "Do not use cache when building the image" )
2021-09-29 21:33:19 +08:00
flags . StringVar ( & options . progress , "progress" , "auto" , "Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output" )
2020-05-01 03:25:30 +08:00
options . pull = flags . Bool ( "pull" , false , "Always attempt to pull a newer version of the image" )
2021-06-30 13:41:21 +08:00
flags . StringVar ( & options . metadataFile , "metadata-file" , "" , "Write build result metadata to the file" )
2019-04-05 15:04:19 +08:00
}
2021-10-27 03:36:37 +08:00
func checkWarnedFlags ( f * pflag . Flag ) {
if ! f . Changed {
return
}
for t , m := range f . Annotations {
switch t {
case "flag-warn" :
logrus . Warn ( m [ 0 ] )
break
}
}
}
2019-07-31 07:32:36 +08:00
func listToMap ( values [ ] string , defaultEnv bool ) map [ string ] string {
2019-03-24 12:30:29 +08:00
result := make ( map [ string ] string , len ( values ) )
for _ , value := range values {
kv := strings . SplitN ( value , "=" , 2 )
if len ( kv ) == 1 {
2019-07-31 07:32:36 +08:00
if defaultEnv {
2020-01-07 04:02:06 +08:00
v , ok := os . LookupEnv ( kv [ 0 ] )
if ok {
result [ kv [ 0 ] ] = v
}
2019-07-31 07:32:36 +08:00
} else {
result [ kv [ 0 ] ] = ""
}
2019-03-24 12:30:29 +08:00
} else {
result [ kv [ 0 ] ] = kv [ 1 ]
}
}
return result
}