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"
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"
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"
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-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"
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"
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"
"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"
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"
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"
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-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
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 ,
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 ,
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 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-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
}
2023-04-21 18:13:56 +08:00
func runBuild ( dockerCli command . Cli , options 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 )
} ( )
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
}
_ , err = b . LoadNodes ( ctx , false )
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
}
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-04-21 18:13:56 +08:00
progressMode , err := options . toProgress ( )
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
printer , err = progress . NewPrinter ( ctx2 , os . Stderr , os . Stderr , progressMode ,
progress . WithDesc (
fmt . Sprintf ( "building with %q instance using %s driver" , b . Name , b . Driver ) ,
fmt . Sprintf ( "%s:%s" , b . Driver , b . Name ) ,
) ,
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
var resp * client . SolveResponse
var retErr error
if 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
}
if retErr != nil {
return retErr
}
2023-05-26 16:30:58 +08:00
if progressMode != progress . PrinterModeQuiet {
desktop . PrintBuildDetails ( os . Stderr , printer . BuildRefs ( ) , term )
} else {
2023-05-26 15:20:29 +08:00
fmt . Println ( getImageID ( resp . ExporterResponse ) )
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-04-21 18:13:56 +08:00
if options . invoke != nil && ( options . dockerfileName == "-" || options . contextPath == "-" ) {
// 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
f := ioset . NewSingleForwarder ( )
2023-04-04 23:20:19 +08:00
f . SetReader ( dockerCli . In ( ) )
2023-04-21 18:13:56 +08:00
if ! options . noBuild {
pr , pw := io . Pipe ( )
f . SetWriter ( pw , func ( ) io . WriteCloser {
pw . Close ( ) // propagate EOF
logrus . Debug ( "propagating stdin close" )
return nil
} )
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
ref , resp , err = c . Build ( ctx , * opts , pr , printer )
2023-04-21 18:13:56 +08:00
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" )
}
}
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" )
}
}
// post-build operations
if options . invoke != nil && options . invoke . needsMonitor ( retErr ) {
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 nil , errors . Errorf ( "failed to configure terminal: %v" , 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
err = monitor . RunMonitor ( ctx , ref , opts , options . invoke . InvokeConfig , c , pr2 , os . Stdout , os . Stderr , printer )
2023-04-21 18:13:56 +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 resp , retErr
}
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 )
2023-04-21 18:13:56 +08:00
if invokeFlag != "" {
invoke , err := parseInvokeConfig ( invokeFlag )
if err != nil {
return err
2023-04-10 07:49:48 +08:00
}
2023-04-21 18:13:56 +08:00
options . invoke = & invoke
options . noBuild = invokeFlag == "debug-shell"
2022-08-29 13:14:14 +08:00
}
2020-05-01 04:01:45 +08:00
return runBuild ( dockerCli , options )
} ,
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") ` )
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-06-20 17:42:02 +08:00
flags . StringVar ( & options . printFunc , "print" , "" , "Print result of information request (e.g., outline, targets)" )
flags . SetAnnotation ( "print" , "experimentalCLI" , nil )
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" ` )
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
2022-05-05 09:48:50 +08:00
if isExperimental ( ) {
2023-06-20 17:42:02 +08:00
flags . StringVar ( & invokeFlag , "invoke" , "" , "Invoke a command after the build" )
flags . SetAnnotation ( "invoke" , "experimentalCLI" , nil )
flags . StringVar ( & options . Root , "root" , "" , "Specify root directory of server to connect" )
flags . SetAnnotation ( "root" , "experimentalCLI" , nil )
flags . BoolVar ( & options . Detach , "detach" , false , "Detach buildx server (supported only on linux)" )
flags . SetAnnotation ( "detach" , "experimentalCLI" , nil )
flags . StringVar ( & options . ServerConfig , "server-config" , "" , "Specify buildx server config file (used only when launching new server)" )
flags . SetAnnotation ( "server-config" , "experimentalCLI" , nil )
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." } )
2023-06-20 17:42:02 +08:00
flags . SetAnnotation ( "squash" , "experimentalCLI" , nil )
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
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 ) )
2023-05-24 09:43:27 +08:00
csvReader . LazyQuotes = true
2022-08-29 13:14:14 +08:00
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" :
2023-05-24 09:43:27 +08:00
cfg . Cmd = append ( cfg . Cmd , maybeJSONArray ( value ) ... )
2022-08-29 13:14:14 +08:00
case "entrypoint" :
2023-05-24 09:43:27 +08:00
cfg . Entrypoint = append ( cfg . Entrypoint , maybeJSONArray ( value ) ... )
2023-06-08 07:33:10 +08:00
if cfg . Cmd == nil {
cfg . Cmd = [ ] string { }
}
2022-08-29 13:14:14 +08:00
case "env" :
2023-05-24 09:43:27 +08:00
cfg . Env = append ( cfg . Env , maybeJSONArray ( value ) ... )
2022-08-29 13:14:14 +08:00
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-05-24 09:43:27 +08:00
func maybeJSONArray ( v string ) [ ] string {
var list [ ] string
if err := json . Unmarshal ( [ ] byte ( v ) , & list ) ; err == nil {
return list
}
return [ ] string { v }
}
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
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
func printWarnings ( w io . Writer , warnings [ ] client . VertexWarning , mode string ) {
if len ( warnings ) == 0 || mode == progress . PrinterModeQuiet {
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 )
default :
if dt , ok := res [ "result.txt" ] ; ok {
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 )
}