2019-04-12 08:24:24 +08:00
package commands
import (
2021-09-29 21:33:19 +08:00
"bytes"
2021-08-12 14:45:40 +08:00
"context"
2019-08-01 08:25:25 +08:00
"encoding/csv"
2019-04-12 08:24:24 +08:00
"fmt"
2020-12-30 13:59:51 +08:00
"net/url"
2019-04-13 07:39:06 +08:00
"os"
2019-08-01 08:25:25 +08:00
"strings"
2021-08-12 14:45:40 +08:00
"time"
2019-04-12 08:24:24 +08:00
2019-04-25 10:29:56 +08:00
"github.com/docker/buildx/driver"
"github.com/docker/buildx/store"
2021-11-04 12:17:27 +08:00
"github.com/docker/buildx/store/storeutil"
2019-04-12 08:24:24 +08:00
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
2019-07-09 06:06:07 +08:00
"github.com/google/shlex"
2019-04-13 07:39:06 +08:00
"github.com/moby/buildkit/util/appcontext"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
2019-04-12 08:24:24 +08:00
"github.com/spf13/cobra"
)
type createOptions struct {
name string
driver string
nodeName string
platform [ ] string
actionAppend bool
actionLeave bool
2019-04-13 07:39:06 +08:00
use bool
2019-07-09 06:06:07 +08:00
flags string
2019-08-01 01:54:20 +08:00
configFile string
2019-08-01 08:25:25 +08:00
driverOpts [ ] string
2021-08-12 14:45:40 +08:00
bootstrap bool
2019-04-12 08:24:24 +08:00
// upgrade bool // perform upgrade of the driver
}
2019-04-13 07:39:06 +08:00
func runCreate ( dockerCli command . Cli , in createOptions , args [ ] string ) error {
ctx := appcontext . Context ( )
if in . name == "default" {
2019-04-16 08:01:00 +08:00
return errors . Errorf ( "default is a reserved name and cannot be used to identify builder instance" )
2019-04-13 07:39:06 +08:00
}
if in . actionLeave {
if in . name == "" {
return errors . Errorf ( "leave requires instance name" )
}
if in . nodeName == "" {
return errors . Errorf ( "leave requires node name but --node not set" )
}
}
if in . actionAppend {
if in . name == "" {
logrus . Warnf ( "append used without name, creating a new instance instead" )
}
}
driverName := in . driver
if driverName == "" {
f , err := driver . GetDefaultFactory ( ctx , dockerCli . Client ( ) , true )
if err != nil {
return err
}
if f == nil {
return errors . Errorf ( "no valid drivers found" )
}
driverName = f . Name ( )
}
if driver . GetFactory ( driverName , true ) == nil {
return errors . Errorf ( "failed to find driver %q" , in . driver )
}
2021-11-04 12:17:27 +08:00
txn , release , err := storeutil . GetStore ( dockerCli )
2019-04-13 07:39:06 +08:00
if err != nil {
return err
}
defer release ( )
name := in . name
if name == "" {
name , err = store . GenerateName ( txn )
if err != nil {
return err
}
}
ng , err := txn . NodeGroupByName ( name )
if err != nil {
if os . IsNotExist ( errors . Cause ( err ) ) {
if in . actionAppend && in . name != "" {
logrus . Warnf ( "failed to find %q for append, creating a new instance instead" , in . name )
}
if in . actionLeave {
return errors . Errorf ( "failed to find instance %q for leave" , name )
}
} else {
return err
}
}
2019-04-16 08:01:00 +08:00
if ng != nil {
if in . nodeName == "" && ! in . actionAppend {
return errors . Errorf ( "existing instance for %s but no append mode, specify --node to make changes for existing instances" , name )
}
}
2019-04-13 07:39:06 +08:00
if ng == nil {
ng = & store . NodeGroup {
Name : name ,
}
}
if ng . Driver == "" || in . driver != "" {
ng . Driver = driverName
}
2019-07-09 06:06:07 +08:00
var flags [ ] string
if in . flags != "" {
flags , err = shlex . Split ( in . flags )
if err != nil {
return errors . Wrap ( err , "failed to parse buildkit flags" )
}
}
2019-04-13 07:39:06 +08:00
var ep string
if in . actionLeave {
if err := ng . Leave ( in . nodeName ) ; err != nil {
return err
}
} else {
if len ( args ) > 0 {
ep , err = validateEndpoint ( dockerCli , args [ 0 ] )
if err != nil {
return err
}
} else {
2019-04-23 05:26:18 +08:00
if dockerCli . CurrentContext ( ) == "default" && dockerCli . DockerEndpoint ( ) . TLSData != nil {
return errors . Errorf ( "could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with `docker buildx create <context-name>`" )
}
2021-11-04 12:17:27 +08:00
ep , err = storeutil . GetCurrentEndpoint ( dockerCli )
2019-04-13 07:39:06 +08:00
if err != nil {
return err
}
}
2020-08-28 14:51:58 +08:00
if in . driver == "kubernetes" {
// naming endpoint to make --append works
2020-12-30 13:59:51 +08:00
ep = ( & url . URL {
Scheme : in . driver ,
Path : "/" + in . name ,
RawQuery : ( & url . Values {
"deployment" : { in . nodeName } ,
"kubeconfig" : { os . Getenv ( "KUBECONFIG" ) } ,
} ) . Encode ( ) ,
} ) . String ( )
2020-08-28 14:51:58 +08:00
}
2019-08-01 08:25:25 +08:00
m , err := csvToMap ( in . driverOpts )
if err != nil {
return err
}
if err := ng . Update ( in . nodeName , ep , in . platform , len ( args ) > 0 , in . actionAppend , flags , in . configFile , m ) ; err != nil {
2019-04-13 07:39:06 +08:00
return err
}
}
if err := txn . Save ( ng ) ; err != nil {
return err
}
if in . use && ep != "" {
2021-11-04 12:17:27 +08:00
current , err := storeutil . GetCurrentEndpoint ( dockerCli )
2019-04-16 01:21:09 +08:00
if err != nil {
return err
}
if err := txn . SetCurrent ( current , ng . Name , false , false ) ; err != nil {
2019-04-13 07:39:06 +08:00
return err
}
}
2021-08-12 14:45:40 +08:00
ngi := & nginfo { ng : ng }
timeoutCtx , cancel := context . WithTimeout ( ctx , 20 * time . Second )
defer cancel ( )
if err = loadNodeGroupData ( timeoutCtx , dockerCli , ngi ) ; err != nil {
return err
}
if in . bootstrap {
if _ , err = boot ( ctx , ngi ) ; err != nil {
return err
}
}
2019-04-13 07:39:06 +08:00
fmt . Printf ( "%s\n" , ng . Name )
2019-04-12 08:24:24 +08:00
return nil
}
func createCmd ( dockerCli command . Cli ) * cobra . Command {
var options createOptions
2021-09-29 21:33:19 +08:00
var drivers bytes . Buffer
for _ , d := range driver . GetFactories ( ) {
if len ( drivers . String ( ) ) > 0 {
drivers . WriteString ( ", " )
}
drivers . WriteString ( fmt . Sprintf ( "`%s`" , d . Name ( ) ) )
2019-05-26 06:45:20 +08:00
}
2019-04-12 08:24:24 +08:00
cmd := & cobra . Command {
Use : "create [OPTIONS] [CONTEXT|ENDPOINT]" ,
Short : "Create a new builder instance" ,
Args : cli . RequiresMaxArgs ( 1 ) ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2019-04-13 07:39:06 +08:00
return runCreate ( dockerCli , options , args )
2019-04-12 08:24:24 +08:00
} ,
}
flags := cmd . Flags ( )
flags . StringVar ( & options . name , "name" , "" , "Builder instance name" )
2021-09-29 21:33:19 +08:00
flags . StringVar ( & options . driver , "driver" , "" , fmt . Sprintf ( "Driver to use (available: %s)" , drivers . String ( ) ) )
2019-04-12 08:24:24 +08:00
flags . StringVar ( & options . nodeName , "node" , "" , "Create/modify node with given name" )
2019-07-09 06:06:07 +08:00
flags . StringVar ( & options . flags , "buildkitd-flags" , "" , "Flags for buildkitd daemon" )
2019-08-01 01:54:20 +08:00
flags . StringVar ( & options . configFile , "config" , "" , "BuildKit config file" )
2019-04-12 08:24:24 +08:00
flags . StringArrayVar ( & options . platform , "platform" , [ ] string { } , "Fixed platforms for current node" )
2019-08-01 08:25:25 +08:00
flags . StringArrayVar ( & options . driverOpts , "driver-opt" , [ ] string { } , "Options for the driver" )
2021-08-12 14:45:40 +08:00
flags . BoolVar ( & options . bootstrap , "bootstrap" , false , "Boot builder after creation" )
2019-04-12 08:24:24 +08:00
flags . BoolVar ( & options . actionAppend , "append" , false , "Append a node to builder instead of changing it" )
flags . BoolVar ( & options . actionLeave , "leave" , false , "Remove a node from builder instead of changing it" )
2019-04-13 07:39:06 +08:00
flags . BoolVar ( & options . use , "use" , false , "Set the current builder instance" )
2019-04-12 08:24:24 +08:00
_ = flags
return cmd
}
2019-08-01 08:25:25 +08:00
func csvToMap ( in [ ] string ) ( map [ string ] string , error ) {
m := make ( map [ string ] string , len ( in ) )
for _ , s := range in {
csvReader := csv . NewReader ( strings . NewReader ( s ) )
fields , err := csvReader . Read ( )
if err != nil {
return nil , err
}
for _ , v := range fields {
p := strings . SplitN ( v , "=" , 2 )
if len ( p ) != 2 {
return nil , errors . Errorf ( "invalid value %q, expecting k=v" , v )
}
m [ p [ 0 ] ] = p [ 1 ]
}
}
return m , nil
}