2019-03-24 12:30:29 +08:00
package commands
import (
"context"
2022-02-11 15:15:47 +08:00
"encoding/base64"
2022-05-24 19:00:55 +08:00
"encoding/csv"
2021-06-30 13:41:21 +08:00
"encoding/json"
2023-02-14 19:00:24 +08:00
"fmt"
2021-12-19 14:08:43 +08:00
"io"
2019-03-24 12:30:29 +08:00
"os"
2023-02-01 09:27:43 +08:00
"path/filepath"
2022-08-29 13:14:14 +08:00
"runtime"
2022-05-24 19:00:55 +08:00
"strconv"
2019-03-24 12:30:29 +08:00
"strings"
2022-05-24 19:00:55 +08:00
"github.com/containerd/console"
2023-04-03 17:23:59 +08:00
"github.com/docker/buildx/build"
2023-02-01 00:39:57 +08:00
"github.com/docker/buildx/controller"
cbuild "github.com/docker/buildx/controller/build"
"github.com/docker/buildx/controller/control"
2023-04-10 07:49:48 +08:00
controllererrors "github.com/docker/buildx/controller/errdefs"
2023-02-01 00:39:57 +08:00
controllerapi "github.com/docker/buildx/controller/pb"
2022-05-24 19:00:55 +08:00
"github.com/docker/buildx/monitor"
2022-12-07 18:44:33 +08:00
"github.com/docker/buildx/store"
"github.com/docker/buildx/store/storeutil"
2023-02-09 20:03:58 +08:00
"github.com/docker/buildx/util/buildflags"
2022-08-29 13:14:14 +08:00
"github.com/docker/buildx/util/ioset"
2023-02-14 19:00:24 +08:00
"github.com/docker/buildx/util/progress"
2021-07-03 12:39:57 +08:00
"github.com/docker/buildx/util/tracing"
2021-11-22 17:51:54 +08:00
"github.com/docker/cli-docs-tool/annotation"
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"
2023-04-12 18:12:46 +08:00
"github.com/docker/docker/builder/remotecontext/urlutil"
2021-06-30 13:41:21 +08:00
"github.com/docker/docker/pkg/ioutils"
2023-02-15 20:14:24 +08:00
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
2019-03-24 12:30:29 +08:00
"github.com/moby/buildkit/util/appcontext"
2022-01-05 14:34:30 +08:00
"github.com/moby/buildkit/util/grpcerrors"
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"
2022-01-05 14:34:30 +08:00
"google.golang.org/grpc/codes"
2019-03-24 12:30:29 +08:00
)
type buildOptions struct {
2023-02-09 20:03:58 +08:00
allow [ ] string
buildArgs [ ] string
cacheFrom [ ] string
cacheTo [ ] string
cgroupParent string
contextPath string
contexts [ ] string
dockerfileName string
extraHosts [ ] string
imageIDFile string
labels [ ] string
networkMode string
noCacheFilter [ ] string
outputs [ ] string
platforms [ ] string
printFunc string
secrets [ ] string
shmSize dockeropts . MemBytes
ssh [ ] string
tags [ ] string
target string
ulimits * dockeropts . UlimitOpt
2023-04-10 07:49:48 +08:00
invoke * invokeConfig
noBuild bool
2023-02-14 19:00:24 +08:00
2023-02-15 22:24:09 +08:00
attests [ ] string
sbom string
provenance string
2023-02-09 20:03:58 +08:00
progress string
2023-02-14 19:00:24 +08:00
quiet bool
2023-02-09 20:03:58 +08:00
2023-04-03 20:34:07 +08:00
builder string
metadataFile string
noCache bool
pull bool
exportPush bool
exportLoad bool
2023-02-01 00:39:57 +08:00
control . ControlOptions
2019-04-05 15:04:19 +08:00
}
2023-02-09 20:03:58 +08:00
func ( o * buildOptions ) toControllerOptions ( ) ( controllerapi . BuildOptions , error ) {
var err error
opts := controllerapi . BuildOptions {
Allow : o . allow ,
BuildArgs : listToMap ( o . buildArgs , true ) ,
CgroupParent : o . cgroupParent ,
ContextPath : o . contextPath ,
DockerfileName : o . dockerfileName ,
ExtraHosts : o . extraHosts ,
Labels : listToMap ( o . labels , false ) ,
NetworkMode : o . networkMode ,
NoCacheFilter : o . noCacheFilter ,
Platforms : o . platforms ,
PrintFunc : o . printFunc ,
ShmSize : int64 ( o . shmSize ) ,
Tags : o . tags ,
Target : o . target ,
Ulimits : dockerUlimitToControllerUlimit ( o . ulimits ) ,
2023-04-03 20:34:07 +08:00
Builder : o . builder ,
MetadataFile : o . metadataFile ,
NoCache : o . noCache ,
Pull : o . pull ,
ExportPush : o . exportPush ,
ExportLoad : o . exportLoad ,
2023-02-09 20:03:58 +08:00
}
2023-03-14 17:01:04 +08:00
// TODO: extract env var parsing to a method easily usable by library consumers
if v := os . Getenv ( "SOURCE_DATE_EPOCH" ) ; v != "" {
if _ , ok := opts . BuildArgs [ "SOURCE_DATE_EPOCH" ] ; ! ok {
opts . BuildArgs [ "SOURCE_DATE_EPOCH" ] = v
}
}
2023-02-15 22:24:09 +08:00
inAttests := append ( [ ] string { } , o . attests ... )
if o . provenance != "" {
inAttests = append ( inAttests , buildflags . CanonicalizeAttest ( "provenance" , o . provenance ) )
}
if o . sbom != "" {
inAttests = append ( inAttests , buildflags . CanonicalizeAttest ( "sbom" , o . sbom ) )
}
opts . Attests , err = buildflags . ParseAttests ( inAttests )
if err != nil {
return controllerapi . BuildOptions { } , err
}
2023-02-09 20:03:58 +08:00
opts . NamedContexts , err = buildflags . ParseContextNames ( o . contexts )
if err != nil {
return controllerapi . BuildOptions { } , err
}
opts . Exports , err = buildflags . ParseExports ( o . outputs )
if err != nil {
return controllerapi . BuildOptions { } , err
}
2023-02-15 20:14:24 +08:00
for _ , e := range opts . Exports {
if ( e . Type == client . ExporterLocal || e . Type == client . ExporterTar ) && o . imageIDFile != "" {
return controllerapi . BuildOptions { } , errors . Errorf ( "local and tar exporters are incompatible with image ID file" )
}
}
2023-02-09 20:03:58 +08:00
opts . CacheFrom , err = buildflags . ParseCacheEntry ( o . cacheFrom )
if err != nil {
return controllerapi . BuildOptions { } , err
}
opts . CacheTo , err = buildflags . ParseCacheEntry ( o . cacheTo )
if err != nil {
return controllerapi . BuildOptions { } , err
}
opts . Secrets , err = buildflags . ParseSecretSpecs ( o . secrets )
if err != nil {
return controllerapi . BuildOptions { } , err
}
opts . SSH , err = buildflags . ParseSSHSpecs ( o . ssh )
if err != nil {
return controllerapi . BuildOptions { } , err
}
return opts , nil
}
2023-02-14 19:00:24 +08:00
func ( o * buildOptions ) toProgress ( ) ( string , error ) {
switch o . progress {
case progress . PrinterModeAuto , progress . PrinterModeTty , progress . PrinterModePlain , progress . PrinterModeQuiet :
default :
return "" , errors . Errorf ( "progress=%s is not a valid progress option" , o . progress )
}
if o . quiet {
if o . progress != progress . PrinterModeAuto && o . progress != progress . PrinterModeQuiet {
return "" , errors . Errorf ( "progress=%s and quiet cannot be used together" , o . progress )
}
return progress . PrinterModeQuiet , nil
}
return o . progress , nil
}
2022-08-29 13:14:14 +08:00
func runBuild ( dockerCli command . Cli , in buildOptions ) 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 )
} ( )
2023-02-09 20:03:58 +08:00
opts , err := in . toControllerOptions ( )
if err != nil {
return err
}
2023-02-14 19:00:24 +08:00
progress , err := in . toProgress ( )
if err != nil {
return err
}
2023-02-15 20:14:24 +08:00
// Avoid leaving a stale file if we eventually fail
if in . imageIDFile != "" {
if err := os . Remove ( in . imageIDFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrap ( err , "removing image ID file" )
}
}
2023-04-18 23:35:41 +08:00
resp , _ , err := cbuild . RunBuild ( ctx , dockerCli , opts , os . Stdin , progress , nil , false )
2023-02-14 19:00:24 +08:00
if err != nil {
return err
}
if in . quiet {
fmt . Println ( resp . ExporterResponse [ exptypes . ExporterImageDigestKey ] )
}
2023-02-15 20:14:24 +08:00
if in . imageIDFile != "" {
dgst := resp . ExporterResponse [ exptypes . ExporterImageDigestKey ]
if v , ok := resp . ExporterResponse [ exptypes . ExporterImageConfigDigestKey ] ; ok {
dgst = v
}
return os . WriteFile ( in . imageIDFile , [ ] byte ( dgst ) , 0644 )
}
2023-02-14 19:00:24 +08:00
return nil
2022-08-29 13:14:14 +08:00
}
2019-10-17 06:10:17 +08:00
2020-04-28 05:37:17 +08:00
func buildCmd ( dockerCli command . Cli , rootOpts * rootOptions ) * cobra . Command {
2023-02-09 20:03:58 +08:00
options := buildOptions { }
2022-08-29 13:14:14 +08:00
cFlags := & commonFlags { }
2023-04-10 07:49:48 +08:00
var invokeFlag string
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 {
2023-02-09 20:03:58 +08:00
options . contextPath = args [ 0 ]
2023-04-03 20:34:07 +08:00
options . builder = rootOpts . builder
options . metadataFile = cFlags . metadataFile
options . noCache = false
2022-08-29 13:14:14 +08:00
if cFlags . noCache != nil {
2023-04-03 20:34:07 +08:00
options . noCache = * cFlags . noCache
2022-08-29 13:14:14 +08:00
}
2023-04-03 20:34:07 +08:00
options . pull = false
2022-08-29 13:14:14 +08:00
if cFlags . pull != nil {
2023-04-03 20:34:07 +08:00
options . pull = * cFlags . pull
2022-08-29 13:14:14 +08:00
}
options . progress = cFlags . progress
2021-10-27 03:36:37 +08:00
cmd . Flags ( ) . VisitAll ( checkWarnedFlags )
2022-08-29 13:14:14 +08:00
if isExperimental ( ) {
2023-04-10 07:49:48 +08:00
if invokeFlag != "" {
invokeConfig , err := parseInvokeConfig ( invokeFlag )
if err != nil {
return err
}
options . invoke = & invokeConfig
options . noBuild = invokeFlag == "debug-shell"
}
2022-08-29 13:14:14 +08:00
return launchControllerAndRunBuild ( dockerCli , options )
}
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 ( )
2023-02-09 20:03:58 +08:00
flags . StringSliceVar ( & options . extraHosts , "add-host" , [ ] string { } , ` Add a custom host-to-IP mapping (format: "host:ip") ` )
2023-01-07 22:11:30 +08:00
flags . SetAnnotation ( "add-host" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#add-host" } )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringSliceVar ( & options . allow , "allow" , [ ] string { } , ` Allow extra privileged entitlement (e.g., "network.host", "security.insecure") ` )
2019-04-17 14:15:58 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . buildArgs , "build-arg" , [ ] string { } , "Set build-time variables" )
2021-03-24 02:07:57 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . cacheFrom , "cache-from" , [ ] string { } , ` External cache sources (e.g., "user/app:cache", "type=local,src=path/to/dir") ` )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . cacheTo , "cache-to" , [ ] string { } , ` Cache export destinations (e.g., "user/app:cache", "type=local,dest=path/to/dir") ` )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringVar ( & options . cgroupParent , "cgroup-parent" , "" , "Optional parent cgroup for the container" )
2023-01-07 22:11:30 +08:00
flags . SetAnnotation ( "cgroup-parent" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent" } )
2021-10-29 01:34:33 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . contexts , "build-context" , [ ] string { } , "Additional build contexts (e.g., name=path)" )
2021-12-16 13:02:31 +08:00
2023-02-09 20:03:58 +08:00
flags . StringVarP ( & options . dockerfileName , "file" , "f" , "" , ` Name of the Dockerfile (default: "PATH/Dockerfile") ` )
2023-01-07 22:11:30 +08:00
flags . SetAnnotation ( "file" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#file" } )
2019-04-17 01:55:13 +08:00
2023-02-09 20:03:58 +08:00
flags . StringVar ( & options . imageIDFile , "iidfile" , "" , "Write the image ID to the file" )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . labels , "label" , [ ] string { } , "Set metadata for an image" )
2019-04-17 01:55:13 +08:00
2023-04-03 20:34:07 +08:00
flags . BoolVar ( & options . exportLoad , "load" , false , ` Shorthand for "--output=type=docker" ` )
2019-03-24 12:30:29 +08:00
2023-02-09 20:03:58 +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
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . noCacheFilter , "no-cache-filter" , [ ] string { } , "Do not cache specified stages" )
2021-11-23 12:21:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVarP ( & options . outputs , "output" , "o" , [ ] string { } , ` Output destination (format: "type=local,dest=path") ` )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . platforms , "platform" , platformsDefault , "Set target platform for build" )
2021-10-26 18:53:35 +08:00
2022-05-05 09:48:50 +08:00
if isExperimental ( ) {
2023-02-09 20:03:58 +08:00
flags . StringVar ( & options . printFunc , "print" , "" , "Print result of information request (e.g., outline, targets) [experimental]" )
2022-05-05 09:48:50 +08:00
}
2023-04-03 20:34:07 +08:00
flags . BoolVar ( & options . exportPush , "push" , false , ` Shorthand for "--output=type=registry" ` )
2019-07-09 06:58:38 +08:00
2023-02-09 20:03:58 +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
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . secrets , "secret" , [ ] string { } , ` Secret to expose to the build (format: "id=mysecret[,src=/local/secret]") ` )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . Var ( & options . shmSize , "shm-size" , ` Size of "/dev/shm" ` )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . ssh , "ssh" , [ ] string { } , ` SSH agent socket or keys to expose to the build (format: "default|<id>[=<socket>|<key>[,<key>]]") ` )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVarP ( & options . tags , "tag" , "t" , [ ] string { } , ` Name and optionally a tag (format: "name:tag") ` )
2023-01-07 22:11:30 +08:00
flags . SetAnnotation ( "tag" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#tag" } )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
flags . StringVar ( & options . target , "target" , "" , "Set the target build stage to build" )
2023-01-07 22:11:30 +08:00
flags . SetAnnotation ( "target" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/engine/reference/commandline/build/#target" } )
2021-10-26 18:53:35 +08:00
2023-02-09 20:03:58 +08:00
options . ulimits = dockeropts . NewUlimitOpt ( nil )
flags . Var ( options . ulimits , "ulimit" , "Ulimit options" )
2021-08-19 11:05:15 +08:00
2023-02-09 20:03:58 +08:00
flags . StringArrayVar ( & options . attests , "attest" , [ ] string { } , ` Attestation parameters (format: "type=sbom,generator=image") ` )
2023-02-15 22:24:09 +08:00
flags . StringVar ( & options . sbom , "sbom" , "" , ` Shorthand for "--attest=type=sbom" ` )
flags . StringVar ( & options . provenance , "provenance" , "" , ` Shortand for "--attest=type=provenance" ` )
2022-12-08 02:44:11 +08:00
2022-05-05 09:48:50 +08:00
if isExperimental ( ) {
2023-04-10 07:49:48 +08:00
flags . StringVar ( & invokeFlag , "invoke" , "" , "Invoke a command after the build [experimental]" )
2023-02-01 00:39:57 +08:00
flags . StringVar ( & options . Root , "root" , "" , "Specify root directory of server to connect [experimental]" )
flags . BoolVar ( & options . Detach , "detach" , runtime . GOOS == "linux" , "Detach buildx server (supported only on linux) [experimental]" )
flags . StringVar ( & options . ServerConfig , "server-config" , "" , "Specify buildx server config file (used only when launching new server) [experimental]" )
2022-05-24 19:00:55 +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
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-11-22 17:51:54 +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-11-22 17:51:54 +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-11-22 17:51:54 +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
2022-08-29 13:14:14 +08:00
commonBuildFlags ( cFlags , flags )
2019-03-24 12:30:29 +08:00
return cmd
}
2022-08-29 13:14:14 +08:00
// comomnFlags is a set of flags commonly shared among subcommands.
type commonFlags struct {
metadataFile string
progress string
noCache * bool
pull * bool
}
func commonBuildFlags ( options * commonFlags , 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-11-22 17:51:54 +08:00
flags . StringVar ( & options . progress , "progress" , "auto" , ` Set type of progress output ("auto", "plain", "tty"). Use plain to show container output ` )
2022-02-26 12:39:42 +08:00
options . pull = flags . Bool ( "pull" , false , "Always attempt to pull all referenced images" )
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 ] )
}
}
}
2022-02-11 15:15:47 +08:00
func writeMetadataFile ( filename string , dt interface { } ) error {
b , err := json . MarshalIndent ( dt , "" , " " )
if err != nil {
return err
}
return ioutils . AtomicWriteFile ( filename , b , 0644 )
}
func decodeExporterResponse ( exporterResponse map [ string ] string ) map [ string ] interface { } {
out := make ( map [ string ] interface { } )
for k , v := range exporterResponse {
dt , err := base64 . StdEncoding . DecodeString ( v )
if err != nil {
out [ k ] = v
continue
}
var raw map [ string ] interface { }
if err = json . Unmarshal ( dt , & raw ) ; err != nil || len ( raw ) == 0 {
out [ k ] = v
continue
}
out [ k ] = json . RawMessage ( dt )
}
return out
}
2022-02-23 13:55:44 +08:00
func wrapBuildError ( err error , bake bool ) error {
2022-01-05 14:34:30 +08:00
if err == nil {
return nil
}
st , ok := grpcerrors . AsGRPCStatus ( err )
if ok {
if st . Code ( ) == codes . Unimplemented && strings . Contains ( st . Message ( ) , "unsupported frontend capability moby.buildkit.frontend.contexts" ) {
2022-02-23 13:55:44 +08:00
msg := "current frontend does not support --build-context."
if bake {
msg = "current frontend does not support defining additional contexts for targets."
}
msg += " Named contexts are supported since Dockerfile v1.4. Use #syntax directive in Dockerfile or update to latest BuildKit."
return & wrapped { err , msg }
2022-01-05 14:34:30 +08:00
}
}
return err
}
type wrapped struct {
err error
msg string
}
func ( w * wrapped ) Error ( ) string {
return w . msg
}
func ( w * wrapped ) Unwrap ( ) error {
return w . err
}
2022-05-05 09:48:50 +08:00
func isExperimental ( ) bool {
2022-08-12 09:45:08 +08:00
if v , ok := os . LookupEnv ( "BUILDX_EXPERIMENTAL" ) ; ok {
2022-05-05 09:48:50 +08:00
vv , _ := strconv . ParseBool ( v )
return vv
}
return false
}
2022-12-07 18:44:33 +08:00
func updateLastActivity ( dockerCli command . Cli , ng * store . NodeGroup ) error {
txn , release , err := storeutil . GetStore ( dockerCli )
if err != nil {
return err
}
defer release ( )
return txn . UpdateLastActivity ( ng )
}
2022-08-29 13:14:14 +08:00
func launchControllerAndRunBuild ( dockerCli command . Cli , options buildOptions ) error {
ctx := context . TODO ( )
2023-04-10 07:49:48 +08:00
if options . invoke != nil && ( options . dockerfileName == "-" || options . contextPath == "-" ) {
2022-08-29 13:14:14 +08:00
// stdin must be usable for monitor
return errors . Errorf ( "Dockerfile or context from stdin is not supported with invoke" )
}
2023-02-01 00:39:57 +08:00
c , err := controller . NewController ( ctx , options . ControlOptions , dockerCli )
if err != nil {
return err
2022-08-29 13:14:14 +08:00
}
defer func ( ) {
if err := c . Close ( ) ; err != nil {
logrus . Warnf ( "failed to close server connection %v" , err )
}
} ( )
2023-04-10 07:49:48 +08:00
// Start build
2023-02-09 20:03:58 +08:00
opts , err := options . toControllerOptions ( )
if err != nil {
return err
}
2023-02-14 19:00:24 +08:00
progress , err := options . toProgress ( )
if err != nil {
return err
}
2023-02-15 20:14:24 +08:00
2023-02-01 09:27:43 +08:00
// NOTE: buildx server has the current working directory different from the client
// so we need to resolve paths to abosolute ones in the client.
optsP , err := resolvePaths ( & opts )
if err != nil {
return err
}
opts = * optsP
2022-08-29 13:14:14 +08:00
2023-04-10 07:49:48 +08:00
var ref string
var retErr error
f := ioset . NewSingleForwarder ( )
f . SetReader ( os . Stdin )
if ! options . noBuild {
pr , pw := io . Pipe ( )
f . SetWriter ( pw , func ( ) io . WriteCloser {
pw . Close ( ) // propagate EOF
logrus . Debug ( "propagating stdin close" )
return nil
} )
// Avoid leaving a stale file if we eventually fail
if options . imageIDFile != "" {
if err := os . Remove ( options . imageIDFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrap ( err , "removing image ID file" )
}
}
var resp * client . SolveResponse
ref , resp , err = c . Build ( ctx , opts , pr , os . Stdout , os . Stderr , progress )
if err != nil {
var be * controllererrors . BuildError
if errors . As ( err , & be ) {
ref = be . Ref
retErr = err
// We can proceed to monitor
} else {
return errors . Wrapf ( err , "failed to build" )
}
2023-02-15 20:14:24 +08:00
}
2023-04-10 07:49:48 +08:00
if err := pw . Close ( ) ; err != nil {
logrus . Debug ( "failed to close stdin pipe writer" )
}
if err := pr . Close ( ) ; err != nil {
logrus . Debug ( "failed to close stdin pipe reader" )
}
if options . quiet {
fmt . Println ( resp . ExporterResponse [ exptypes . ExporterImageDigestKey ] )
}
if options . imageIDFile != "" {
dgst := resp . ExporterResponse [ exptypes . ExporterImageDigestKey ]
if v , ok := resp . ExporterResponse [ exptypes . ExporterImageConfigDigestKey ] ; ok {
dgst = v
}
return os . WriteFile ( options . imageIDFile , [ ] byte ( dgst ) , 0644 )
}
2023-02-15 20:14:24 +08:00
}
2023-02-14 19:00:24 +08:00
2022-08-29 13:14:14 +08:00
// post-build operations
2023-04-10 07:49:48 +08:00
if options . invoke != nil && options . invoke . needsMonitor ( retErr ) {
2022-08-29 13:14:14 +08:00
pr2 , pw2 := io . Pipe ( )
f . SetWriter ( pw2 , func ( ) io . WriteCloser {
pw2 . Close ( ) // propagate EOF
return nil
} )
con := console . Current ( )
if err := con . SetRaw ( ) ; err != nil {
if err := c . Disconnect ( ctx , ref ) ; err != nil {
logrus . Warnf ( "disconnect error: %v" , err )
}
return errors . Errorf ( "failed to configure terminal: %v" , err )
}
2023-04-10 07:49:48 +08:00
err = monitor . RunMonitor ( ctx , ref , & opts , options . invoke . InvokeConfig , c , progress , pr2 , os . Stdout , os . Stderr )
2022-08-29 13:14:14 +08:00
con . Reset ( )
if err := pw2 . Close ( ) ; err != nil {
logrus . Debug ( "failed to close monitor stdin pipe reader" )
}
if err != nil {
logrus . Warnf ( "failed to run monitor: %v" , err )
}
} else {
if err := c . Disconnect ( ctx , ref ) ; err != nil {
logrus . Warnf ( "disconnect error: %v" , err )
}
}
return nil
}
2023-04-10 07:49:48 +08:00
type invokeConfig struct {
controllerapi . InvokeConfig
invokeFlag string
}
func ( cfg * invokeConfig ) needsMonitor ( retErr error ) bool {
switch cfg . invokeFlag {
case "debug-shell" :
return true
case "on-error" :
return retErr != nil
default :
return cfg . invokeFlag != ""
}
}
func parseInvokeConfig ( invoke string ) ( cfg invokeConfig , err error ) {
cfg . invokeFlag = invoke
2022-08-29 13:14:14 +08:00
cfg . Tty = true
2023-04-10 07:49:48 +08:00
switch invoke {
case "default" , "debug-shell" :
return cfg , nil
case "on-error" :
// NOTE: we overwrite the command to run because the original one should fail on the failed step.
// TODO: make this configurable via flags or restorable from LLB.
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900
cfg . Cmd = [ ] string { "/bin/sh" }
2022-08-29 13:14:14 +08:00
return cfg , nil
}
csvReader := csv . NewReader ( strings . NewReader ( invoke ) )
fields , err := csvReader . Read ( )
if err != nil {
return cfg , err
}
if len ( fields ) == 1 && ! strings . Contains ( fields [ 0 ] , "=" ) {
cfg . Cmd = [ ] string { fields [ 0 ] }
return cfg , nil
}
cfg . NoUser = true
cfg . NoCwd = true
for _ , field := range fields {
parts := strings . SplitN ( field , "=" , 2 )
if len ( parts ) != 2 {
return cfg , errors . Errorf ( "invalid value %s" , field )
}
key := strings . ToLower ( parts [ 0 ] )
value := parts [ 1 ]
switch key {
case "args" :
cfg . Cmd = append ( cfg . Cmd , value ) // TODO: support JSON
case "entrypoint" :
cfg . Entrypoint = append ( cfg . Entrypoint , value ) // TODO: support JSON
case "env" :
cfg . Env = append ( cfg . Env , value )
case "user" :
cfg . User = value
cfg . NoUser = false
case "cwd" :
cfg . Cwd = value
cfg . NoCwd = false
case "tty" :
cfg . Tty , err = strconv . ParseBool ( value )
if err != nil {
return cfg , errors . Errorf ( "failed to parse tty: %v" , err )
}
default :
return cfg , errors . Errorf ( "unknown key %q" , key )
}
}
return cfg , nil
}
2023-02-09 20:03:58 +08:00
func listToMap ( values [ ] string , defaultEnv bool ) map [ string ] string {
result := make ( map [ string ] string , len ( values ) )
for _ , value := range values {
kv := strings . SplitN ( value , "=" , 2 )
if len ( kv ) == 1 {
if defaultEnv {
v , ok := os . LookupEnv ( kv [ 0 ] )
if ok {
result [ kv [ 0 ] ] = v
}
} else {
result [ kv [ 0 ] ] = ""
}
} else {
result [ kv [ 0 ] ] = kv [ 1 ]
}
2022-08-29 13:14:14 +08:00
}
2023-02-09 20:03:58 +08:00
return result
2022-08-29 13:14:14 +08:00
}
2023-02-09 20:03:58 +08:00
func dockerUlimitToControllerUlimit ( u * dockeropts . UlimitOpt ) * controllerapi . UlimitOpt {
if u == nil {
return nil
2022-08-29 13:14:14 +08:00
}
2023-02-09 20:03:58 +08:00
values := make ( map [ string ] * controllerapi . Ulimit )
for _ , u := range u . GetList ( ) {
values [ u . Name ] = & controllerapi . Ulimit {
Name : u . Name ,
Hard : u . Hard ,
Soft : u . Soft ,
2022-08-29 13:14:14 +08:00
}
}
2023-02-09 20:03:58 +08:00
return & controllerapi . UlimitOpt { Values : values }
2022-08-29 13:14:14 +08:00
}
2023-02-01 09:27:43 +08:00
// resolvePaths resolves all paths contained in controllerapi.BuildOptions
// and replaces them to absolute paths.
func resolvePaths ( options * controllerapi . BuildOptions ) ( _ * controllerapi . BuildOptions , err error ) {
2023-04-17 17:32:12 +08:00
localContext := false
2023-02-01 09:27:43 +08:00
if options . ContextPath != "" && options . ContextPath != "-" {
2023-04-03 17:23:59 +08:00
if ! build . IsRemoteURL ( options . ContextPath ) {
2023-04-17 17:32:12 +08:00
localContext = true
2023-03-28 23:11:52 +08:00
options . ContextPath , err = filepath . Abs ( options . ContextPath )
if err != nil {
return nil , err
}
2023-02-01 09:27:43 +08:00
}
}
if options . DockerfileName != "" && options . DockerfileName != "-" {
2023-04-17 17:32:12 +08:00
if localContext && ! urlutil . IsURL ( options . DockerfileName ) {
2023-04-12 18:12:46 +08:00
options . DockerfileName , err = filepath . Abs ( options . DockerfileName )
if err != nil {
return nil , err
}
2023-02-01 09:27:43 +08:00
}
}
2023-04-17 17:32:12 +08:00
2023-02-01 09:27:43 +08:00
var contexts map [ string ] string
for k , v := range options . NamedContexts {
2023-04-03 17:23:59 +08:00
if build . IsRemoteURL ( v ) || strings . HasPrefix ( v , "docker-image://" ) {
2023-03-02 18:28:56 +08:00
// url prefix, this is a remote path
} else if strings . HasPrefix ( v , "oci-layout://" ) {
// oci layout prefix, this is a local path
p := strings . TrimPrefix ( v , "oci-layout://" )
2023-02-01 09:27:43 +08:00
p , err = filepath . Abs ( p )
if err != nil {
return nil , err
}
2023-03-02 18:28:56 +08:00
v = "oci-layout://" + p
} else {
// no prefix, assume local path
v , err = filepath . Abs ( v )
if err != nil {
return nil , err
}
2023-02-01 09:27:43 +08:00
}
2023-03-02 18:28:56 +08:00
2023-02-01 09:27:43 +08:00
if contexts == nil {
contexts = make ( map [ string ] string )
}
2023-03-02 18:28:56 +08:00
contexts [ k ] = v
2023-02-01 09:27:43 +08:00
}
options . NamedContexts = contexts
var cacheFrom [ ] * controllerapi . CacheOptionsEntry
for _ , co := range options . CacheFrom {
switch co . Type {
case "local" :
var attrs map [ string ] string
for k , v := range co . Attrs {
if attrs == nil {
attrs = make ( map [ string ] string )
}
switch k {
case "src" :
p := v
if p != "" {
p , err = filepath . Abs ( p )
if err != nil {
return nil , err
}
}
attrs [ k ] = p
default :
attrs [ k ] = v
}
}
co . Attrs = attrs
cacheFrom = append ( cacheFrom , co )
default :
cacheFrom = append ( cacheFrom , co )
}
}
options . CacheFrom = cacheFrom
var cacheTo [ ] * controllerapi . CacheOptionsEntry
for _ , co := range options . CacheTo {
switch co . Type {
case "local" :
var attrs map [ string ] string
for k , v := range co . Attrs {
if attrs == nil {
attrs = make ( map [ string ] string )
}
switch k {
case "dest" :
p := v
if p != "" {
p , err = filepath . Abs ( p )
if err != nil {
return nil , err
}
}
attrs [ k ] = p
default :
attrs [ k ] = v
}
}
co . Attrs = attrs
cacheTo = append ( cacheTo , co )
default :
cacheTo = append ( cacheTo , co )
}
}
options . CacheTo = cacheTo
var exports [ ] * controllerapi . ExportEntry
for _ , e := range options . Exports {
if e . Destination != "" && e . Destination != "-" {
e . Destination , err = filepath . Abs ( e . Destination )
if err != nil {
return nil , err
}
}
exports = append ( exports , e )
}
options . Exports = exports
var secrets [ ] * controllerapi . Secret
for _ , s := range options . Secrets {
if s . FilePath != "" {
s . FilePath , err = filepath . Abs ( s . FilePath )
if err != nil {
return nil , err
}
}
secrets = append ( secrets , s )
}
options . Secrets = secrets
var ssh [ ] * controllerapi . SSH
for _ , s := range options . SSH {
var ps [ ] string
for _ , pt := range s . Paths {
p := pt
if p != "" {
p , err = filepath . Abs ( p )
if err != nil {
return nil , err
}
}
ps = append ( ps , p )
}
s . Paths = ps
ssh = append ( ssh , s )
}
options . SSH = ssh
2023-04-03 20:34:07 +08:00
if options . MetadataFile != "" {
options . MetadataFile , err = filepath . Abs ( options . MetadataFile )
2023-02-01 09:27:43 +08:00
if err != nil {
return nil , err
}
}
return options , nil
}