buildx/driver/manager.go
Justin Chadwell 17d4369866 create: improve interface when attempting to create docker driver
Previously, the help information for buildx indicated that users could
create a new instance of the docker driver - which is explicitly
something we don't support, driver of this form are automatically
derived from the available list of docker contexts.

This patch ensures that don't have AllowsInstance set will not appear in
the help text, and additionally provide a new more specific error
message instead of the generic "failed to find driver". This should help
point users in the correct direction.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2022-08-16 10:58:23 +01:00

161 lines
3.9 KiB
Go

package driver
import (
"context"
"os"
"sort"
"strings"
"sync"
"k8s.io/client-go/rest"
dockerclient "github.com/docker/docker/client"
"github.com/moby/buildkit/client"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type Factory interface {
Name() string
Usage() string
Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int
New(ctx context.Context, cfg InitConfig) (Driver, error)
AllowsInstances() bool
}
type BuildkitConfig struct {
// Entitlements []string
// Rootless bool
}
type KubeClientConfig interface {
ClientConfig() (*rest.Config, error)
Namespace() (string, bool, error)
}
type KubeClientConfigInCluster struct{}
func (k KubeClientConfigInCluster) ClientConfig() (*rest.Config, error) {
return rest.InClusterConfig()
}
func (k KubeClientConfigInCluster) Namespace() (string, bool, error) {
namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
return "", false, err
}
return strings.TrimSpace(string(namespace)), true, nil
}
type InitConfig struct {
// This object needs updates to be generic for different drivers
Name string
EndpointAddr string
DockerAPI dockerclient.APIClient
KubeClientConfig KubeClientConfig
BuildkitFlags []string
Files map[string][]byte
DriverOpts map[string]string
Auth Auth
Platforms []specs.Platform
// ContextPathHash can be used for determining pods in the driver instance
ContextPathHash string
}
var drivers map[string]Factory
func Register(f Factory) {
if drivers == nil {
drivers = map[string]Factory{}
}
drivers[f.Name()] = f
}
func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient, instanceRequired bool) (Factory, error) {
if len(drivers) == 0 {
return nil, errors.Errorf("no drivers available")
}
type p struct {
f Factory
priority int
}
dd := make([]p, 0, len(drivers))
for _, f := range drivers {
if instanceRequired && !f.AllowsInstances() {
continue
}
dd = append(dd, p{f: f, priority: f.Priority(ctx, ep, c)})
}
sort.Slice(dd, func(i, j int) bool {
return dd[i].priority < dd[j].priority
})
return dd[0].f, nil
}
func GetFactory(name string, instanceRequired bool) (Factory, error) {
for _, f := range drivers {
if f.Name() == name {
if instanceRequired && !f.AllowsInstances() {
return nil, errors.Errorf("additional instances of driver %q cannot be created", name)
}
return f, nil
}
}
return nil, errors.Errorf("failed to find driver %q", name)
}
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string) (Driver, error) {
ic := InitConfig{
EndpointAddr: endpointAddr,
DockerAPI: api,
KubeClientConfig: kcc,
Name: name,
BuildkitFlags: flags,
DriverOpts: do,
Auth: auth,
Platforms: platforms,
ContextPathHash: contextPathHash,
Files: files,
}
if f == nil {
var err error
f, err = GetDefaultFactory(ctx, endpointAddr, api, false)
if err != nil {
return nil, err
}
}
d, err := f.New(ctx, ic)
if err != nil {
return nil, err
}
return &cachedDriver{Driver: d}, nil
}
func GetFactories(instanceRequired bool) []Factory {
ds := make([]Factory, 0, len(drivers))
for _, d := range drivers {
if instanceRequired && !d.AllowsInstances() {
continue
}
ds = append(ds, d)
}
sort.Slice(ds, func(i, j int) bool {
return ds[i].Name() < ds[j].Name()
})
return ds
}
type cachedDriver struct {
Driver
client *client.Client
err error
once sync.Once
}
func (d *cachedDriver) Client(ctx context.Context) (*client.Client, error) {
d.once.Do(func() {
d.client, d.err = d.Driver.Client(ctx)
})
return d.client, d.err
}