2019-03-24 12:30:29 +08:00
package commands
import (
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
"bytes"
2019-03-24 12:30:29 +08:00
"context"
2024-02-01 02:59:26 +08:00
"crypto/sha256"
2022-02-11 15:15:47 +08:00
"encoding/base64"
2022-05-24 19:00:55 +08:00
"encoding/csv"
2024-02-01 02:59:26 +08:00
"encoding/hex"
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"
2023-04-24 17:41:10 +08:00
"log"
2019-03-24 12:30:29 +08:00
"os"
2023-02-01 09:27:43 +08:00
"path/filepath"
2022-05-24 19:00:55 +08:00
"strconv"
2019-03-24 12:30:29 +08:00
"strings"
2024-02-01 02:59:26 +08:00
"sync"
"time"
2019-03-24 12:30:29 +08:00
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"
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
"github.com/docker/buildx/builder"
2023-08-15 15:37:42 +08:00
"github.com/docker/buildx/commands/debug"
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"
2024-01-12 10:49:16 +08:00
"github.com/docker/buildx/util/cobrautil"
2024-02-01 02:59:26 +08:00
"github.com/docker/buildx/util/confutil"
2023-05-26 16:30:58 +08:00
"github.com/docker/buildx/util/desktop"
2022-08-29 13:14:14 +08:00
"github.com/docker/buildx/util/ioset"
2024-01-31 03:23:30 +08:00
"github.com/docker/buildx/util/metricutil"
2024-02-01 02:59:26 +08:00
"github.com/docker/buildx/util/osutil"
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-24 17:41:10 +08:00
"github.com/docker/docker/api/types/versions"
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"
2023-04-24 17:41:10 +08:00
"github.com/moby/buildkit/frontend/subrequests"
2024-04-11 22:52:07 +08:00
"github.com/moby/buildkit/frontend/subrequests/lint"
2023-04-24 17:41:10 +08:00
"github.com/moby/buildkit/frontend/subrequests/outline"
"github.com/moby/buildkit/frontend/subrequests/targets"
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
"github.com/moby/buildkit/solver/errdefs"
2022-01-05 14:34:30 +08:00
"github.com/moby/buildkit/util/grpcerrors"
2023-09-07 19:13:54 +08:00
"github.com/moby/buildkit/util/progress/progressui"
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
"github.com/morikuni/aec"
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"
2024-02-01 02:59:26 +08:00
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
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
2023-08-24 20:55:21 +08:00
annotations [ ] string
2023-02-09 20:03:58 +08:00
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-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
2023-08-15 15:37:42 +08:00
invokeConfig * invokeConfig
2019-04-05 15:04:19 +08:00
}
2023-04-21 18:13:56 +08:00
func ( o * buildOptions ) toControllerOptions ( ) ( * controllerapi . BuildOptions , error ) {
2023-02-09 20:03:58 +08:00
var err error
2023-09-11 22:35:57 +08:00
buildArgs , err := listToMap ( o . buildArgs , true )
if err != nil {
return nil , err
}
labels , err := listToMap ( o . labels , false )
if err != nil {
return nil , err
}
2023-02-09 20:03:58 +08:00
opts := controllerapi . BuildOptions {
2024-03-15 21:38:00 +08:00
Allow : o . allow ,
Annotations : o . annotations ,
BuildArgs : buildArgs ,
CgroupParent : o . cgroupParent ,
ContextPath : o . contextPath ,
DockerfileName : o . dockerfileName ,
ExtraHosts : o . extraHosts ,
Labels : labels ,
NetworkMode : o . networkMode ,
NoCacheFilter : o . noCacheFilter ,
Platforms : o . platforms ,
ShmSize : int64 ( o . shmSize ) ,
Tags : o . tags ,
Target : o . target ,
Ulimits : dockerUlimitToControllerUlimit ( o . ulimits ) ,
Builder : o . builder ,
NoCache : o . noCache ,
Pull : o . pull ,
ExportPush : o . exportPush ,
ExportLoad : o . exportLoad ,
WithProvenanceResponse : len ( o . metadataFile ) > 0 ,
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 08:37:26 +08:00
opts . SourcePolicy , err = build . ReadSourcePolicy ( )
if err != nil {
return nil , err
}
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 {
2023-04-21 18:13:56 +08:00
return nil , err
2023-02-15 22:24:09 +08:00
}
2023-02-09 20:03:58 +08:00
opts . NamedContexts , err = buildflags . ParseContextNames ( o . contexts )
if err != nil {
2023-04-21 18:13:56 +08:00
return nil , err
2023-02-09 20:03:58 +08:00
}
opts . Exports , err = buildflags . ParseExports ( o . outputs )
if err != nil {
2023-04-21 18:13:56 +08:00
return nil , err
2023-02-09 20:03:58 +08:00
}
2023-02-15 20:14:24 +08:00
for _ , e := range opts . Exports {
if ( e . Type == client . ExporterLocal || e . Type == client . ExporterTar ) && o . imageIDFile != "" {
2023-04-21 18:13:56 +08:00
return nil , errors . Errorf ( "local and tar exporters are incompatible with image ID file" )
2023-02-15 20:14:24 +08:00
}
}
2023-02-09 20:03:58 +08:00
opts . CacheFrom , err = buildflags . ParseCacheEntry ( o . cacheFrom )
if err != nil {
2023-04-21 18:13:56 +08:00
return nil , err
2023-02-09 20:03:58 +08:00
}
opts . CacheTo , err = buildflags . ParseCacheEntry ( o . cacheTo )
if err != nil {
2023-04-21 18:13:56 +08:00
return nil , err
2023-02-09 20:03:58 +08:00
}
opts . Secrets , err = buildflags . ParseSecretSpecs ( o . secrets )
if err != nil {
2023-04-21 18:13:56 +08:00
return nil , err
2023-02-09 20:03:58 +08:00
}
opts . SSH , err = buildflags . ParseSSHSpecs ( o . ssh )
if err != nil {
2023-04-21 18:13:56 +08:00
return nil , err
2023-02-09 20:03:58 +08:00
}
2023-04-24 17:41:10 +08:00
opts . PrintFunc , err = buildflags . ParsePrintFunc ( o . printFunc )
if err != nil {
return nil , err
}
2023-04-21 18:13:56 +08:00
return & opts , nil
2023-02-09 20:03:58 +08:00
}
2023-09-07 19:13:54 +08:00
func ( o * buildOptions ) toDisplayMode ( ) ( progressui . DisplayMode , error ) {
progress := progressui . DisplayMode ( o . progress )
2023-02-14 19:00:24 +08:00
if o . quiet {
2023-09-07 19:13:54 +08:00
if progress != progressui . AutoMode && progress != progressui . QuietMode {
2023-02-14 19:00:24 +08:00
return "" , errors . Errorf ( "progress=%s and quiet cannot be used together" , o . progress )
}
2023-09-07 19:13:54 +08:00
return progressui . QuietMode , nil
2023-02-14 19:00:24 +08:00
}
2023-09-07 19:13:54 +08:00
return progress , nil
2023-02-14 19:00:24 +08:00
}
2024-02-01 02:59:26 +08:00
func buildMetricAttributes ( dockerCli command . Cli , b * builder . Builder , options * buildOptions ) attribute . Set {
return attribute . NewSet (
attribute . String ( "command.name" , "build" ) ,
attribute . Stringer ( "command.options.hash" , & buildOptionsHash {
buildOptions : options ,
configDir : confutil . ConfigDir ( dockerCli ) ,
} ) ,
attribute . String ( "driver.name" , options . builder ) ,
attribute . String ( "driver.type" , b . Driver ) ,
)
}
// buildOptionsHash computes a hash for the buildOptions when the String method is invoked.
// This is done so we can delay the computation of the hash until needed by OTEL using
// the fmt.Stringer interface.
type buildOptionsHash struct {
* buildOptions
configDir string
result string
resultOnce sync . Once
}
func ( o * buildOptionsHash ) String ( ) string {
o . resultOnce . Do ( func ( ) {
target := o . target
contextPath := o . contextPath
dockerfile := o . dockerfileName
if dockerfile == "" {
dockerfile = "Dockerfile"
}
if contextPath != "-" && osutil . IsLocalDir ( contextPath ) {
contextPath = osutil . ToAbs ( contextPath )
}
salt := confutil . TryNodeIdentifier ( o . configDir )
h := sha256 . New ( )
for _ , s := range [ ] string { target , contextPath , dockerfile , salt } {
_ , _ = io . WriteString ( h , s )
h . Write ( [ ] byte { 0 } )
}
o . result = hex . EncodeToString ( h . Sum ( nil ) )
} )
return o . result
}
2024-01-05 20:11:53 +08:00
func runBuild ( ctx context . Context , dockerCli command . Cli , options buildOptions ) ( err error ) {
2024-03-26 05:10:04 +08:00
mp := dockerCli . MeterProvider ( ctx )
defer metricutil . Shutdown ( ctx , mp )
2023-12-13 06:27:49 +08:00
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-04-24 17:41:10 +08:00
opts , err := options . toControllerOptions ( )
if err != nil {
return err
}
2023-02-15 20:14:24 +08:00
// Avoid leaving a stale file if we eventually fail
2023-04-21 18:13:56 +08:00
if options . imageIDFile != "" {
if err := os . Remove ( options . imageIDFile ) ; err != nil && ! os . IsNotExist ( err ) {
2023-02-15 20:14:24 +08:00
return errors . Wrap ( err , "removing image ID file" )
}
}
2023-04-21 18:13:56 +08:00
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
contextPathHash := options . contextPath
if absContextPath , err := filepath . Abs ( contextPathHash ) ; err == nil {
contextPathHash = absContextPath
}
b , err := builder . New ( dockerCli ,
builder . WithName ( options . builder ) ,
builder . WithContextPathHash ( contextPathHash ) ,
)
if err != nil {
return err
2023-05-23 17:37:22 +08:00
}
2023-08-23 04:11:50 +08:00
_ , err = b . LoadNodes ( ctx )
2023-05-23 17:37:22 +08:00
if err != nil {
return err
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
}
2023-05-26 16:30:58 +08:00
var term bool
if _ , err := console . ConsoleFromFile ( os . Stderr ) ; err == nil {
term = true
}
2024-01-25 01:48:14 +08:00
attributes := buildMetricAttributes ( dockerCli , b , & options )
2023-05-26 16:30:58 +08:00
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
ctx2 , cancel := context . WithCancel ( context . TODO ( ) )
defer cancel ( )
2023-09-07 19:13:54 +08:00
progressMode , err := options . toDisplayMode ( )
2023-02-14 19:00:24 +08:00
if err != nil {
return err
}
2023-04-24 16:58:02 +08:00
var printer * progress . Printer
2023-09-07 19:13:54 +08:00
printer , err = progress . NewPrinter ( ctx2 , os . Stderr , progressMode ,
2023-04-24 16:58:02 +08:00
progress . WithDesc (
fmt . Sprintf ( "building with %q instance using %s driver" , b . Name , b . Driver ) ,
fmt . Sprintf ( "%s:%s" , b . Driver , b . Name ) ,
) ,
2024-01-25 01:48:14 +08:00
progress . WithMetrics ( mp , attributes ) ,
2023-04-24 16:58:02 +08:00
progress . WithOnClose ( func ( ) {
printWarnings ( os . Stderr , printer . Warnings ( ) , progressMode )
} ) ,
)
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
if err != nil {
return err
}
2023-04-21 18:13:56 +08:00
2024-02-01 02:59:26 +08:00
done := timeBuildCommand ( mp , attributes )
2023-04-21 18:13:56 +08:00
var resp * client . SolveResponse
var retErr error
2024-03-15 18:52:11 +08:00
if confutil . IsExperimental ( ) {
2023-04-24 17:41:10 +08:00
resp , retErr = runControllerBuild ( ctx , dockerCli , opts , options , printer )
2023-04-21 18:13:56 +08:00
} else {
2023-04-24 17:41:10 +08:00
resp , retErr = runBasicBuild ( ctx , dockerCli , opts , options , printer )
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
}
if err := printer . Wait ( ) ; retErr == nil {
retErr = err
2023-04-21 18:13:56 +08:00
}
2024-02-01 02:59:26 +08:00
done ( retErr )
2023-04-21 18:13:56 +08:00
if retErr != nil {
return retErr
}
2024-02-09 19:37:27 +08:00
switch progressMode {
case progressui . RawJSONMode :
// no additional display
case progressui . QuietMode :
2023-05-26 15:20:29 +08:00
fmt . Println ( getImageID ( resp . ExporterResponse ) )
2024-02-09 19:37:27 +08:00
default :
desktop . PrintBuildDetails ( os . Stderr , printer . BuildRefs ( ) , term )
2023-02-14 19:00:24 +08:00
}
2023-04-21 18:13:56 +08:00
if options . imageIDFile != "" {
2023-05-26 15:20:29 +08:00
if err := os . WriteFile ( options . imageIDFile , [ ] byte ( getImageID ( resp . ExporterResponse ) ) , 0644 ) ; err != nil {
return errors . Wrap ( err , "writing image ID file" )
2023-02-15 20:14:24 +08:00
}
}
2023-04-24 17:21:02 +08:00
if options . metadataFile != "" {
if err := writeMetadataFile ( options . metadataFile , decodeExporterResponse ( resp . ExporterResponse ) ) ; err != nil {
return err
}
}
2023-04-24 17:41:10 +08:00
if opts . PrintFunc != nil {
if err := printResult ( opts . PrintFunc , resp . ExporterResponse ) ; err != nil {
return err
}
}
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
2023-05-26 15:20:29 +08:00
// getImageID returns the image ID - the digest of the image config
func getImageID ( resp map [ string ] string ) string {
dgst := resp [ exptypes . ExporterImageDigestKey ]
if v , ok := resp [ exptypes . ExporterImageConfigDigestKey ] ; ok {
dgst = v
}
return dgst
}
2023-04-24 17:41:10 +08:00
func runBasicBuild ( ctx context . Context , dockerCli command . Cli , opts * controllerapi . BuildOptions , options buildOptions , printer * progress . Printer ) ( * client . SolveResponse , error ) {
2023-04-04 23:20:19 +08:00
resp , res , err := cbuild . RunBuild ( ctx , dockerCli , * opts , dockerCli . In ( ) , printer , false )
2023-05-31 22:13:00 +08:00
if res != nil {
res . Done ( )
}
2023-04-21 18:13:56 +08:00
return resp , err
}
2023-04-24 17:41:10 +08:00
func runControllerBuild ( ctx context . Context , dockerCli command . Cli , opts * controllerapi . BuildOptions , options buildOptions , printer * progress . Printer ) ( * client . SolveResponse , error ) {
2023-08-15 15:37:42 +08:00
if options . invokeConfig != nil && ( options . dockerfileName == "-" || options . contextPath == "-" ) {
2023-04-21 18:13:56 +08:00
// stdin must be usable for monitor
return nil , errors . Errorf ( "Dockerfile or context from stdin is not supported with invoke" )
}
2023-04-21 19:04:11 +08:00
c , err := controller . NewController ( ctx , options . ControlOptions , dockerCli , printer )
2023-04-21 18:13:56 +08:00
if err != nil {
return nil , err
}
defer func ( ) {
if err := c . Close ( ) ; err != nil {
logrus . Warnf ( "failed to close server connection %v" , err )
}
} ( )
// NOTE: buildx server has the current working directory different from the client
// so we need to resolve paths to abosolute ones in the client.
2023-04-24 18:14:32 +08:00
opts , err = controllerapi . ResolveOptionPaths ( opts )
2023-04-21 18:13:56 +08:00
if err != nil {
return nil , err
}
var ref string
var retErr error
var resp * client . SolveResponse
2024-04-09 20:35:58 +08:00
var f * ioset . SingleForwarder
var pr io . ReadCloser
var pw io . WriteCloser
if options . invokeConfig == nil {
pr = dockerCli . In ( )
} else {
f = ioset . NewSingleForwarder ( )
f . SetReader ( dockerCli . In ( ) )
pr , pw = io . Pipe ( )
f . SetWriter ( pw , func ( ) io . WriteCloser {
pw . Close ( ) // propagate EOF
logrus . Debug ( "propagating stdin close" )
return nil
} )
}
2023-04-21 18:13:56 +08:00
2023-08-15 15:37:42 +08:00
ref , resp , err = c . Build ( ctx , * opts , pr , printer )
if err != nil {
var be * controllererrors . BuildError
if errors . As ( err , & be ) {
ref = be . Ref
retErr = err
// We can proceed to monitor
} else {
return nil , errors . Wrapf ( err , "failed to build" )
2023-04-21 18:13:56 +08:00
}
2023-08-15 15:37:42 +08:00
}
2023-04-21 18:13:56 +08:00
2024-04-09 20:35:58 +08:00
if options . invokeConfig != nil {
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" )
}
2023-04-21 18:13:56 +08:00
}
2023-08-15 15:37:42 +08:00
if options . invokeConfig != nil && options . invokeConfig . needsDebug ( retErr ) {
2023-10-13 10:36:14 +08:00
// Print errors before launching monitor
if err := printError ( retErr , printer ) ; err != nil {
logrus . Warnf ( "failed to print error information: %v" , err )
}
2023-04-21 18:13:56 +08:00
pr2 , pw2 := io . Pipe ( )
f . SetWriter ( pw2 , func ( ) io . WriteCloser {
pw2 . Close ( ) // propagate EOF
return nil
} )
2023-10-18 10:58:36 +08:00
monitorBuildResult , err := options . invokeConfig . runDebug ( ctx , ref , opts , c , pr2 , os . Stdout , os . Stderr , printer )
2023-04-21 18:13:56 +08:00
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 )
}
2023-10-18 10:58:36 +08:00
if monitorBuildResult != nil {
// Update return values with the last build result from monitor
resp , retErr = monitorBuildResult . Resp , monitorBuildResult . Err
}
2023-04-21 18:13:56 +08:00
} else {
if err := c . Disconnect ( ctx , ref ) ; err != nil {
logrus . Warnf ( "disconnect error: %v" , err )
}
}
return resp , retErr
}
2023-10-13 10:36:14 +08:00
func printError ( err error , printer * progress . Printer ) error {
2023-10-18 11:17:28 +08:00
if err == nil {
return nil
}
2023-10-13 10:36:14 +08:00
if err := printer . Pause ( ) ; err != nil {
return err
}
defer printer . Unpause ( )
for _ , s := range errdefs . Sources ( err ) {
s . Print ( os . Stderr )
}
fmt . Fprintf ( os . Stderr , "ERROR: %v\n" , err )
return nil
}
2023-08-15 15:37:42 +08:00
func newDebuggableBuild ( dockerCli command . Cli , rootOpts * rootOptions ) debug . DebuggableCmd {
return & debuggableBuild { dockerCli : dockerCli , rootOpts : rootOpts }
}
type debuggableBuild struct {
dockerCli command . Cli
rootOpts * rootOptions
}
func ( b * debuggableBuild ) NewDebugger ( cfg * debug . DebugConfig ) * cobra . Command {
return buildCmd ( b . dockerCli , b . rootOpts , cfg )
}
func buildCmd ( dockerCli command . Cli , rootOpts * rootOptions , debugConfig * debug . DebugConfig ) * cobra . Command {
2022-08-29 13:14:14 +08:00
cFlags := & commonFlags { }
2023-08-15 15:37:42 +08:00
options := & buildOptions { }
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 ) ,
2024-01-20 08:44:30 +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 )
2023-04-21 18:13:56 +08:00
2023-08-15 15:37:42 +08:00
if debugConfig != nil && ( debugConfig . InvokeFlag != "" || debugConfig . OnFlag != "" ) {
iConfig := new ( invokeConfig )
if err := iConfig . parseInvokeConfig ( debugConfig . InvokeFlag , debugConfig . OnFlag ) ; err != nil {
2023-04-21 18:13:56 +08:00
return err
2023-04-10 07:49:48 +08:00
}
2023-08-15 15:37:42 +08:00
options . invokeConfig = iConfig
2022-08-29 13:14:14 +08:00
}
2023-08-15 15:37:42 +08:00
2024-01-05 20:11:53 +08:00
return runBuild ( cmd . Context ( ) , dockerCli , * options )
2024-01-20 08:44:30 +08:00
} ,
2023-04-11 17:35:50 +08:00
ValidArgsFunction : func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return nil , cobra . ShellCompDirectiveFilterDirs
} ,
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") ` )
2024-02-09 21:35:18 +08:00
flags . SetAnnotation ( "add-host" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/reference/cli/docker/image/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-08-24 20:55:21 +08:00
flags . StringArrayVarP ( & options . annotations , "annotation" , "" , [ ] string { } , "Add annotation to the image" )
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-06-23 16:28:50 +08:00
flags . StringVar ( & options . cgroupParent , "cgroup-parent" , "" , ` Set the parent cgroup for the "RUN" instructions during build ` )
2024-02-09 21:35:18 +08:00
flags . SetAnnotation ( "cgroup-parent" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/reference/cli/docker/image/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") ` )
2024-02-09 21:35:18 +08:00
flags . SetAnnotation ( "file" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/reference/cli/docker/image/build/#file" } )
2019-04-17 01:55:13 +08:00
2024-03-15 21:38:00 +08:00
flags . StringVar ( & options . imageIDFile , "iidfile" , "" , "Write the image ID to a 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
2024-03-15 18:52:11 +08:00
if confutil . IsExperimental ( ) {
2023-06-20 17:42:02 +08:00
flags . StringVar ( & options . printFunc , "print" , "" , "Print result of information request (e.g., outline, targets)" )
2024-01-12 10:49:16 +08:00
cobrautil . MarkFlagsExperimental ( flags , "print" )
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
2024-02-20 18:28:09 +08:00
flags . Var ( & options . shmSize , "shm-size" , ` Shared memory size for build containers ` )
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") ` )
2024-02-09 21:35:18 +08:00
flags . SetAnnotation ( "tag" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/reference/cli/docker/image/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" )
2024-02-09 21:35:18 +08:00
flags . SetAnnotation ( "target" , annotation . ExternalURL , [ ] string { "https://docs.docker.com/reference/cli/docker/image/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" ` )
2023-05-31 19:40:58 +08:00
flags . StringVar ( & options . provenance , "provenance" , "" , ` Shorthand for "--attest=type=provenance" ` )
2022-12-08 02:44:11 +08:00
2024-03-15 18:52:11 +08:00
if confutil . IsExperimental ( ) {
2023-08-15 15:37:42 +08:00
// TODO: move this to debug command if needed
2023-06-20 17:42:02 +08:00
flags . StringVar ( & options . Root , "root" , "" , "Specify root directory of server to connect" )
flags . BoolVar ( & options . Detach , "detach" , false , "Detach buildx server (supported only on linux)" )
flags . StringVar ( & options . ServerConfig , "server-config" , "" , "Specify buildx server config file (used only when launching new server)" )
2024-01-12 10:49:16 +08:00
cobrautil . MarkFlagsExperimental ( flags , "root" , "detach" , "server-config" )
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." } )
2024-01-12 10:49:16 +08:00
cobrautil . MarkFlagsExperimental ( flags , "squash" )
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" )
2024-03-15 21:38:00 +08:00
flags . StringVar ( & options . metadataFile , "metadata-file" , "" , "Write build result metadata to a 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
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
2023-09-11 22:35:57 +08:00
func listToMap ( values [ ] string , defaultEnv bool ) ( map [ string ] string , error ) {
2023-02-09 20:03:58 +08:00
result := make ( map [ string ] string , len ( values ) )
for _ , value := range values {
2023-09-11 22:35:57 +08:00
k , v , hasValue := strings . Cut ( value , "=" )
if k == "" {
return nil , errors . Errorf ( "invalid key-value pair %q: empty key" , value )
}
if hasValue {
result [ k ] = v
} else if defaultEnv {
if envVal , ok := os . LookupEnv ( k ) ; ok {
result [ k ] = envVal
2023-02-09 20:03:58 +08:00
}
} else {
2023-09-11 22:35:57 +08:00
result [ k ] = ""
2023-02-09 20:03:58 +08:00
}
2022-08-29 13:14:14 +08:00
}
2023-09-11 22:35:57 +08:00
return result , nil
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
2023-09-07 19:13:54 +08:00
func printWarnings ( w io . Writer , warnings [ ] client . VertexWarning , mode progressui . DisplayMode ) {
2024-02-09 19:37:27 +08:00
if len ( warnings ) == 0 || mode == progressui . QuietMode || mode == progressui . RawJSONMode {
controller: refactor progress api
Refactor the progress printer creation to the caller-side of the
controller api. Then, instead of passing around status channels (and
progressMode strings), we can simply pass around the higher level
interface progress.Writer.
This has a couple of benefits:
- A simplified interface to the controller
- Allows us to correctly extract warnings out of the controller, so that
they can be displayed correctly from the client side.
Some extra work is required to make sure that we can pass a
progress.Printer into the debug monitor. If we want to keep it
persistent, then we need a way to temporarily suspend output from it,
otherwise it will continue printing as the monitor is prompting for
input from the user, and forwarding output from debug containers.
To handle this, we add two methods to the printer, `Pause` and
`Unpause`. `Pause` acts similarly to `Wait`, closing the printer, and
cleanly shutting down the display - however, the printer does not
terminate, and can later be resumed by a call to `Unpause`. This
provides a neater interface to the caller, instead of needing to
continually reconstruct printers for every single time we want to
produce progress output.
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-04-21 18:17:43 +08:00
return
}
fmt . Fprintf ( w , "\n " )
sb := & bytes . Buffer { }
if len ( warnings ) == 1 {
fmt . Fprintf ( sb , "1 warning found" )
} else {
fmt . Fprintf ( sb , "%d warnings found" , len ( warnings ) )
}
if logrus . GetLevel ( ) < logrus . DebugLevel {
fmt . Fprintf ( sb , " (use --debug to expand)" )
}
fmt . Fprintf ( sb , ":\n" )
fmt . Fprint ( w , aec . Apply ( sb . String ( ) , aec . YellowF ) )
for _ , warn := range warnings {
fmt . Fprintf ( w , " - %s\n" , warn . Short )
if logrus . GetLevel ( ) < logrus . DebugLevel {
continue
}
for _ , d := range warn . Detail {
fmt . Fprintf ( w , "%s\n" , d )
}
if warn . URL != "" {
fmt . Fprintf ( w , "More info: %s\n" , warn . URL )
}
if warn . SourceInfo != nil && warn . Range != nil {
src := errdefs . Source {
Info : warn . SourceInfo ,
Ranges : warn . Range ,
}
src . Print ( w )
}
fmt . Fprintf ( w , "\n" )
}
}
2023-04-24 17:41:10 +08:00
func printResult ( f * controllerapi . PrintFunc , res map [ string ] string ) error {
switch f . Name {
case "outline" :
return printValue ( outline . PrintOutline , outline . SubrequestsOutlineDefinition . Version , f . Format , res )
case "targets" :
return printValue ( targets . PrintTargets , targets . SubrequestsTargetsDefinition . Version , f . Format , res )
case "subrequests.describe" :
return printValue ( subrequests . PrintDescribe , subrequests . SubrequestsDescribeDefinition . Version , f . Format , res )
2024-04-11 22:52:07 +08:00
case "lint" :
return printValue ( lint . PrintLintViolations , lint . SubrequestLintDefinition . Version , f . Format , res )
2023-04-24 17:41:10 +08:00
default :
2024-04-02 07:46:04 +08:00
if dt , ok := res [ "result.json" ] ; ok && f . Format == "json" {
fmt . Println ( dt )
} else if dt , ok := res [ "result.txt" ] ; ok {
2023-04-24 17:41:10 +08:00
fmt . Print ( dt )
} else {
log . Printf ( "%s %+v" , f , res )
}
}
return nil
}
type printFunc func ( [ ] byte , io . Writer ) error
func printValue ( printer printFunc , version string , format string , res map [ string ] string ) error {
if format == "json" {
fmt . Fprintln ( os . Stdout , res [ "result.json" ] )
return nil
}
if res [ "version" ] != "" && versions . LessThan ( version , res [ "version" ] ) && res [ "result.txt" ] != "" {
// structure is too new and we don't know how to print it
fmt . Fprint ( os . Stdout , res [ "result.txt" ] )
return nil
}
return printer ( [ ] byte ( res [ "result.json" ] ) , os . Stdout )
}
2023-08-15 15:37:42 +08:00
type invokeConfig struct {
controllerapi . InvokeConfig
onFlag string
invokeFlag string
}
func ( cfg * invokeConfig ) needsDebug ( retErr error ) bool {
switch cfg . onFlag {
case "always" :
return true
case "error" :
return retErr != nil
default :
return cfg . invokeFlag != ""
}
}
2023-10-18 10:58:36 +08:00
func ( cfg * invokeConfig ) runDebug ( ctx context . Context , ref string , options * controllerapi . BuildOptions , c control . BuildxController , stdin io . ReadCloser , stdout io . WriteCloser , stderr console . File , progress * progress . Printer ) ( * monitor . MonitorBuildResult , error ) {
2023-08-15 15:37:42 +08:00
con := console . Current ( )
if err := con . SetRaw ( ) ; err != nil {
// TODO: run disconnect in build command (on error case)
if err := c . Disconnect ( ctx , ref ) ; err != nil {
logrus . Warnf ( "disconnect error: %v" , err )
}
2023-10-18 10:58:36 +08:00
return nil , errors . Errorf ( "failed to configure terminal: %v" , err )
2023-08-15 15:37:42 +08:00
}
defer con . Reset ( )
return monitor . RunMonitor ( ctx , ref , options , cfg . InvokeConfig , c , stdin , stdout , stderr , progress )
}
func ( cfg * invokeConfig ) parseInvokeConfig ( invoke , on string ) error {
cfg . onFlag = on
cfg . invokeFlag = invoke
cfg . Tty = true
cfg . NoCmd = true
switch invoke {
case "default" , "" :
return 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" }
cfg . NoCmd = false
return nil
}
csvReader := csv . NewReader ( strings . NewReader ( invoke ) )
csvReader . LazyQuotes = true
fields , err := csvReader . Read ( )
if err != nil {
return err
}
if len ( fields ) == 1 && ! strings . Contains ( fields [ 0 ] , "=" ) {
cfg . Cmd = [ ] string { fields [ 0 ] }
cfg . NoCmd = false
return nil
}
cfg . NoUser = true
cfg . NoCwd = true
for _ , field := range fields {
parts := strings . SplitN ( field , "=" , 2 )
if len ( parts ) != 2 {
return errors . Errorf ( "invalid value %s" , field )
}
key := strings . ToLower ( parts [ 0 ] )
value := parts [ 1 ]
switch key {
case "args" :
cfg . Cmd = append ( cfg . Cmd , maybeJSONArray ( value ) ... )
cfg . NoCmd = false
case "entrypoint" :
cfg . Entrypoint = append ( cfg . Entrypoint , maybeJSONArray ( value ) ... )
if cfg . Cmd == nil {
cfg . Cmd = [ ] string { }
cfg . NoCmd = false
}
case "env" :
cfg . Env = append ( cfg . Env , maybeJSONArray ( 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 errors . Errorf ( "failed to parse tty: %v" , err )
}
default :
return errors . Errorf ( "unknown key %q" , key )
}
}
return nil
}
func maybeJSONArray ( v string ) [ ] string {
var list [ ] string
if err := json . Unmarshal ( [ ] byte ( v ) , & list ) ; err == nil {
return list
}
return [ ] string { v }
}
2024-02-01 02:59:26 +08:00
// timeBuildCommand will start a timer for timing the build command. It records the time when the returned
// function is invoked into a metric.
func timeBuildCommand ( mp metric . MeterProvider , attrs attribute . Set ) func ( err error ) {
meter := metricutil . Meter ( mp )
counter , _ := meter . Float64Counter ( "command.time" ,
metric . WithDescription ( "Measures the duration of the build command." ) ,
metric . WithUnit ( "ms" ) ,
)
start := time . Now ( )
return func ( err error ) {
dur := float64 ( time . Since ( start ) ) / float64 ( time . Millisecond )
extraAttrs := attribute . NewSet ( )
if err != nil {
extraAttrs = attribute . NewSet (
attribute . String ( "error.type" , otelErrorType ( err ) ) ,
)
}
counter . Add ( context . Background ( ) , dur ,
metric . WithAttributeSet ( attrs ) ,
metric . WithAttributeSet ( extraAttrs ) ,
)
}
}
// otelErrorType returns an attribute for the error type based on the error category.
// If nil, this function returns an invalid attribute.
func otelErrorType ( err error ) string {
name := "generic"
if errors . Is ( err , context . Canceled ) {
name = "canceled"
}
return name
}