commands: add implementations for create, use, rm, stop

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2019-04-12 16:39:06 -07:00
parent 0e72bf0049
commit bd3d5cd19e
20 changed files with 1695 additions and 101 deletions

View File

@ -12,7 +12,6 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/tonistiigi/buildx/build"
"github.com/tonistiigi/buildx/driver"
"github.com/tonistiigi/buildx/util/progress"
)
@ -103,7 +102,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
}
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode string) error {
d, err := driver.GetDriver(ctx, "buildx-buildkit-default", nil, dockerCli.Client())
dis, err := getDefaultDrivers(ctx, dockerCli)
if err != nil {
return err
}
@ -112,7 +111,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]bu
defer cancel()
pw := progress.NewPrinter(ctx2, os.Stderr, progressMode)
_, err = build.Build(ctx, []driver.Driver{d}, opts, pw)
_, err = build.Build(ctx, dis, opts, pw)
return err
}

View File

@ -2,10 +2,16 @@ package commands
import (
"fmt"
"os"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/util/appcontext"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/tonistiigi/buildx/driver"
"github.com/tonistiigi/buildx/store"
)
type createOptions struct {
@ -15,11 +21,119 @@ type createOptions struct {
platform []string
actionAppend bool
actionLeave bool
use bool
// upgrade bool // perform upgrade of the driver
}
func runCreate(dockerCli command.Cli, in createOptions) error {
fmt.Printf("%+v\n", in)
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
ctx := appcontext.Context()
if in.name == "default" {
return errors.Errorf("default is a reserved name and can't be used to identify builder instance")
}
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)
}
txn, release, err := getStore(dockerCli)
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
}
}
if ng == nil {
ng = &store.NodeGroup{
Name: name,
}
}
if ng.Driver == "" || in.driver != "" {
ng.Driver = driverName
}
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 {
ep, err = getCurrentEndpoint(dockerCli)
if err != nil {
return err
}
}
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend); err != nil {
return err
}
}
if err := txn.Save(ng); err != nil {
return err
}
if in.use && ep != "" {
if err := txn.SetCurrent(ep, ng.Name, false, false); err != nil {
return err
}
}
fmt.Printf("%s\n", ng.Name)
return nil
}
@ -31,7 +145,7 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
Short: "Create a new builder instance",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runCreate(dockerCli, options)
return runCreate(dockerCli, options, args)
},
}
@ -44,6 +158,7 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
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")
flags.BoolVar(&options.use, "use", false, "Set the current builder instance")
_ = flags

View File

@ -1,19 +1,53 @@
package commands
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/tonistiigi/buildx/store"
)
type inspectOptions struct {
bootstrap bool
}
func runInspect(dockerCli command.Cli, in inspectOptions) error {
fmt.Printf("%+v\n", in)
func runInspect(dockerCli command.Cli, in inspectOptions, args []string) error {
txn, release, err := getStore(dockerCli)
if err != nil {
return err
}
defer release()
var ng *store.NodeGroup
if len(args) > 0 {
ng, err = txn.NodeGroupByName(args[0])
if err != nil {
return err
}
} else {
ng, err = getCurrentInstance(txn, dockerCli)
if err != nil {
return err
}
}
if ng == nil {
ep, err := getCurrentEndpoint(dockerCli)
if err != nil {
return err
}
ng = &store.NodeGroup{
Name: "default",
Nodes: []store.Node{
{
Name: "default",
Endpoint: ep,
},
},
}
}
return nil
}
@ -25,7 +59,7 @@ func inspectCmd(dockerCli command.Cli) *cobra.Command {
Short: "Inspect current builder instance",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runInspect(dockerCli, options)
return runInspect(dockerCli, options, args)
},
}

View File

@ -12,8 +12,11 @@ type lsOptions struct {
}
func runLs(dockerCli command.Cli, in lsOptions) error {
fmt.Printf("current config file: %+v\n", dockerCli.ConfigFile().Filename)
fmt.Printf("current context: %+v\n", dockerCli.CurrentContext())
ep, err := getCurrentEndpoint(dockerCli)
fmt.Printf("current endpoint: %+v %v\n", ep, err)
fmt.Printf("current config file: %+v\n", dockerCli.ConfigFile())
fmt.Printf("current config file: %+v\n", dockerCli)
list, err := dockerCli.ContextStore().ListContexts()
if err != nil {
@ -23,6 +26,20 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
fmt.Printf("context%d: %+v\n", i, l)
}
txn, release, err := getStore(dockerCli)
if err != nil {
return err
}
defer release()
ll, err := txn.List()
if err != nil {
return err
}
for i, l := range ll {
fmt.Printf("store %d: %+v\n", i, l)
}
return nil
}

View File

@ -1,14 +1,49 @@
package commands
import (
"context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/util/appcontext"
"github.com/spf13/cobra"
"github.com/tonistiigi/buildx/store"
)
type rmOptions struct {
}
func runRm(dockerCli command.Cli, in rmOptions) error {
func runRm(dockerCli command.Cli, in rmOptions, args []string) error {
ctx := appcontext.Context()
txn, release, err := getStore(dockerCli)
if err != nil {
return err
}
defer release()
if len(args) > 0 {
ng, err := getNodeGroup(txn, dockerCli, args[0])
if err != nil {
return err
}
if err := stop(ctx, dockerCli, ng, true); err != nil {
return err
}
return txn.Remove(ng.Name)
}
ng, err := getCurrentInstance(txn, dockerCli)
if err != nil {
return err
}
if ng != nil {
if err := stop(ctx, dockerCli, ng, true); err != nil {
return err
}
return txn.Remove(ng.Name)
}
return nil
}
@ -18,17 +53,57 @@ func rmCmd(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "rm [NAME]",
Short: "Remove a builder instance",
// Args: cli.ExactArgs(1),
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runRm(dockerCli, options)
return runRm(dockerCli, options, args)
},
}
flags := cmd.Flags()
// flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
_ = flags
return cmd
}
func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, rm bool) error {
dis, err := driversForNodeGroup(ctx, dockerCli, ng)
if err != nil {
return err
}
for _, di := range dis {
if di.Driver != nil {
if err := di.Driver.Stop(ctx, true); err != nil {
return err
}
if rm {
if err := di.Driver.Rm(ctx, true); err != nil {
return err
}
}
}
if di.Err != nil {
err = di.Err
}
}
return err
}
func stopCurrent(ctx context.Context, dockerCli command.Cli, rm bool) error {
dis, err := getDefaultDrivers(ctx, dockerCli)
if err != nil {
return err
}
for _, di := range dis {
if di.Driver != nil {
if err := di.Driver.Stop(ctx, true); err != nil {
return err
}
if rm {
if err := di.Driver.Rm(ctx, true); err != nil {
return err
}
}
}
if di.Err != nil {
err = di.Err
}
}
return err
}

View File

@ -30,6 +30,6 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
lsCmd(dockerCli),
useCmd(dockerCli),
inspectCmd(dockerCli),
viewCmd(dockerCli),
stopCmd(dockerCli),
)
}

63
commands/stop.go Normal file
View File

@ -0,0 +1,63 @@
package commands
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/util/appcontext"
"github.com/spf13/cobra"
)
type stopOptions struct {
}
func runStop(dockerCli command.Cli, in stopOptions, args []string) error {
ctx := appcontext.Context()
txn, release, err := getStore(dockerCli)
if err != nil {
return err
}
defer release()
if len(args) > 0 {
ng, err := getNodeGroup(txn, dockerCli, args[0])
if err != nil {
return err
}
if err := stop(ctx, dockerCli, ng, false); err != nil {
return err
}
return nil
}
ng, err := getCurrentInstance(txn, dockerCli)
if err != nil {
return err
}
if ng != nil {
return stop(ctx, dockerCli, ng, false)
}
return stopCurrent(ctx, dockerCli, false)
}
func stopCmd(dockerCli command.Cli) *cobra.Command {
var options stopOptions
cmd := &cobra.Command{
Use: "stop [NAME]",
Short: "Stop builder instance",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runStop(dockerCli, options, args)
},
}
flags := cmd.Flags()
// flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
_ = flags
return cmd
}

View File

@ -1,10 +1,11 @@
package commands
import (
"fmt"
"os"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -13,8 +14,47 @@ type useOptions struct {
isDefault bool
}
func runUse(dockerCli command.Cli, in useOptions) error {
fmt.Printf("%+v\n", in)
func runUse(dockerCli command.Cli, in useOptions, name string) error {
txn, release, err := getStore(dockerCli)
if err != nil {
return err
}
defer release()
if _, err := txn.NodeGroupByName(name); err != nil {
if os.IsNotExist(errors.Cause(err)) {
if name == "default" || name == dockerCli.CurrentContext() {
ep, err := getCurrentEndpoint(dockerCli)
if err != nil {
return err
}
if err := txn.SetCurrent(ep, "", false, false); err != nil {
return err
}
return nil
}
list, err := dockerCli.ContextStore().ListContexts()
if err != nil {
return err
}
for _, l := range list {
if l.Name == name {
return errors.Errorf("to switch to context %s use `docker context use %s`", name, name)
}
}
}
return errors.Wrapf(err, "failed to find instance %q", name)
}
ep, err := getCurrentEndpoint(dockerCli)
if err != nil {
return err
}
if err := txn.SetCurrent(ep, name, in.isGlobal, in.isDefault); err != nil {
return err
}
return nil
}
@ -26,7 +66,7 @@ func useCmd(dockerCli command.Cli) *cobra.Command {
Short: "Set the current builder instance",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runUse(dockerCli, options)
return runUse(dockerCli, options, args[0])
},
}

252
commands/util.go Normal file
View File

@ -0,0 +1,252 @@
package commands
import (
"context"
"os"
"path/filepath"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/context/docker"
dopts "github.com/docker/cli/opts"
dockerclient "github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/tonistiigi/buildx/build"
"github.com/tonistiigi/buildx/driver"
"github.com/tonistiigi/buildx/store"
"golang.org/x/sync/errgroup"
)
// getStore returns current builder instance store
func getStore(dockerCli command.Cli) (*store.Txn, func(), error) {
dir := filepath.Dir(dockerCli.ConfigFile().Filename)
s, err := store.New(dir)
if err != nil {
return nil, nil, err
}
return s.Txn()
}
// getCurrentEndpoint returns the current default endpoint value
func getCurrentEndpoint(dockerCli command.Cli) (string, error) {
name := dockerCli.CurrentContext()
if name != "default" {
return name, nil
}
de, err := getDockerEndpoint(dockerCli, name)
if err != nil {
return "", errors.Errorf("docker endpoint for %q not found", name)
}
return de, nil
}
// getDockerEndpoint returns docker endpoint string for given context
func getDockerEndpoint(dockerCli command.Cli, name string) (string, error) {
list, err := dockerCli.ContextStore().ListContexts()
if err != nil {
return "", err
}
for _, l := range list {
if l.Name == name {
ep, ok := l.Endpoints["docker"]
if !ok {
return "", errors.Errorf("context %q does not have a Docker endpoint", name)
}
typed, ok := ep.(docker.EndpointMeta)
if !ok {
return "", errors.Errorf("endpoint %q is not of type EndpointMeta, %T", ep, ep)
}
return typed.Host, nil
}
}
return "", nil
}
// validateEndpoint validates that endpoint is either a context or a docker host
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
de, err := getDockerEndpoint(dockerCli, ep)
if err == nil && de != "" {
if ep == "default" {
return de, nil
}
return ep, nil
}
h, err := dopts.ParseHost(true, ep)
if err != nil {
return "", errors.Wrapf(err, "failed to parse endpoint %s", ep)
}
return h, nil
}
// getCurrentInstance finds the current builder instance
func getCurrentInstance(txn *store.Txn, dockerCli command.Cli) (*store.NodeGroup, error) {
ep, err := getCurrentEndpoint(dockerCli)
if err != nil {
return nil, err
}
ng, err := txn.Current(ep)
if err != nil {
return nil, err
}
return ng, nil
}
// getNodeGroup returns nodegroup based on the name
func getNodeGroup(txn *store.Txn, dockerCli command.Cli, name string) (*store.NodeGroup, error) {
ng, err := txn.NodeGroupByName(name)
if err != nil {
if !os.IsNotExist(errors.Cause(err)) {
return nil, err
}
}
if ng != nil {
return ng, nil
}
if name == "default" {
name = dockerCli.CurrentContext()
}
list, err := dockerCli.ContextStore().ListContexts()
if err != nil {
return nil, err
}
for _, l := range list {
if l.Name == name {
return &store.NodeGroup{
Name: "default",
Nodes: []store.Node{
{
Name: "default",
Endpoint: name,
},
},
}, nil
}
}
return nil, errors.Errorf("no builder %q found", name)
}
// driversForNodeGroup returns drivers for a nodegroup instance
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup) ([]build.DriverInfo, error) {
eg, _ := errgroup.WithContext(ctx)
dis := make([]build.DriverInfo, len(ng.Nodes))
var f driver.Factory
if ng.Driver != "" {
f = driver.GetFactory(ng.Driver, true)
if f == nil {
return nil, errors.Errorf("failed to find driver %q", f)
}
} else {
dockerapi, err := clientForEndpoint(dockerCli, ng.Nodes[0].Endpoint)
if err != nil {
return nil, err
}
f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
if err != nil {
return nil, err
}
}
for i, n := range ng.Nodes {
func(i int, n store.Node) {
eg.Go(func() error {
di := build.DriverInfo{
Name: n.Name,
Platform: n.Platforms,
}
defer func() {
dis[i] = di
}()
dockerapi, err := clientForEndpoint(dockerCli, n.Endpoint)
if err != nil {
di.Err = err
return nil
}
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi)
if err != nil {
di.Err = err
return nil
}
di.Driver = d
return nil
})
}(i, n)
}
if err := eg.Wait(); err != nil {
return nil, err
}
return dis, nil
}
// clientForEndpoint returns a docker client for an endpoint
func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
list, err := dockerCli.ContextStore().ListContexts()
if err != nil {
return nil, err
}
for _, l := range list {
if l.Name == name {
dep, ok := l.Endpoints["docker"]
if !ok {
return nil, errors.Errorf("context %q does not have a Docker endpoint", name)
}
epm, ok := dep.(docker.EndpointMeta)
if !ok {
return nil, errors.Errorf("endpoint %q is not of type EndpointMeta, %T", dep, dep)
}
ep, err := docker.WithTLSData(dockerCli.ContextStore(), name, epm)
if err != nil {
return nil, err
}
clientOpts, err := ep.ClientOpts()
if err != nil {
return nil, err
}
return dockerclient.NewClientWithOpts(clientOpts...)
}
}
return dockerclient.NewClientWithOpts(dockerclient.WithHost(name))
}
// getDefaultDrivers returns drivers based on current cli config
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli) ([]build.DriverInfo, error) {
txn, release, err := getStore(dockerCli)
if err != nil {
return nil, err
}
defer release()
ng, err := getCurrentInstance(txn, dockerCli)
if err != nil {
return nil, err
}
if ng != nil {
return driversForNodeGroup(ctx, dockerCli, ng)
}
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client())
if err != nil {
return nil, err
}
return []build.DriverInfo{
{
Name: "default",
Driver: d,
},
}, nil
}
// type ngInfo struct {
// ng *store.NodeGroup
// }
//
// func (i *ngInfo) init(ctx context.Context, boot bool) {
//
// }

View File

@ -1,35 +0,0 @@
package commands
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)
type viewOptions struct {
}
func runView(dockerCli command.Cli, in viewOptions) error {
return nil
}
func viewCmd(dockerCli command.Cli) *cobra.Command {
var options viewOptions
cmd := &cobra.Command{
Use: "view REF",
Short: "Show metadata for image in registry",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runView(dockerCli, options)
},
}
flags := cmd.Flags()
// flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
_ = flags
return cmd
}