buildergo

pull/1499/head
timscharfenort8989 3 years ago
parent 8340c40647
commit 4c3c386dd4

@ -1,292 +1,292 @@
package builder package builder
import ( import (
"context" "context"
"os" "os"
"sort" "sort"
"sync" "sync"
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
"github.com/docker/buildx/store" "github.com/docker/buildx/store"
"github.com/docker/buildx/store/storeutil" "github.com/docker/buildx/store/storeutil"
"github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/imagetools" "github.com/docker/buildx/util/imagetools"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
// Builder represents an active builder object // Builder represents an active builder object
type Builder struct { type Builder struct {
*store.NodeGroup *store.NodeGroup
driverFactory driverFactory driverFactory driverFactory
nodes []Node nodes []Node
opts builderOpts opts builderOpts
err error err error
} }
type builderOpts struct { type builderOpts struct {
dockerCli command.Cli dockerCli command.Cli
name string name string
txn *store.Txn txn *store.Txn
contextPathHash string contextPathHash string
validate bool validate bool
} }
// Option provides a variadic option for configuring the builder. // Option provides a variadic option for configuring the builder.
type Option func(b *Builder) type Option func(b *Builder)
// WithName sets builder name. // WithName sets builder name.
func WithName(name string) Option { func WithName(name string) Option {
return func(b *Builder) { return func(b *Builder) {
b.opts.name = name b.opts.name = name
} }
} }
// WithStore sets a store instance used at init. // WithStore sets a store instance used at init.
func WithStore(txn *store.Txn) Option { func WithStore(txn *store.Txn) Option {
return func(b *Builder) { return func(b *Builder) {
b.opts.txn = txn b.opts.txn = txn
} }
} }
// WithContextPathHash is used for determining pods in k8s driver instance. // WithContextPathHash is used for determining pods in k8s driver instance.
func WithContextPathHash(contextPathHash string) Option { func WithContextPathHash(contextPathHash string) Option {
return func(b *Builder) { return func(b *Builder) {
b.opts.contextPathHash = contextPathHash b.opts.contextPathHash = contextPathHash
} }
} }
// WithSkippedValidation skips builder context validation. // WithSkippedValidation skips builder context validation.
func WithSkippedValidation() Option { func WithSkippedValidation() Option {
return func(b *Builder) { return func(b *Builder) {
b.opts.validate = false b.opts.validate = false
} }
} }
// New initializes a new builder client // New initializes a new builder client
func New(dockerCli command.Cli, opts ...Option) (_ *Builder, err error) { func New(dockerCli command.Cli, opts ...Option) (_ *Builder, err error) {
b := &Builder{ b := &Builder{
opts: builderOpts{ opts: builderOpts{
dockerCli: dockerCli, dockerCli: dockerCli,
validate: true, validate: true,
}, },
} }
for _, opt := range opts { for _, opt := range opts {
opt(b) opt(b)
} }
if b.opts.txn == nil { if b.opts.txn == nil {
// if store instance is nil we create a short-lived one using the // if store instance is nil we create a short-lived one using the
// default store and ensure we release it on completion // default store and ensure we release it on completion
var release func() var release func()
b.opts.txn, release, err = storeutil.GetStore(dockerCli) b.opts.txn, release, err = storeutil.GetStore(dockerCli)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer release() defer release()
} }
if b.opts.name != "" { if b.opts.name != "" {
if b.NodeGroup, err = storeutil.GetNodeGroup(b.opts.txn, dockerCli, b.opts.name); err != nil { if b.NodeGroup, err = storeutil.GetNodeGroup(b.opts.txn, dockerCli, b.opts.name); err != nil {
return nil, err return nil, err
} }
} else { } else {
if b.NodeGroup, err = storeutil.GetCurrentInstance(b.opts.txn, dockerCli); err != nil { if b.NodeGroup, err = storeutil.GetCurrentInstance(b.opts.txn, dockerCli); err != nil {
return nil, err return nil, err
} }
} }
if b.opts.validate { if b.opts.validate {
if err = b.Validate(); err != nil { if err = b.Validate(); err != nil {
return nil, err return nil, err
} }
} }
return b, nil return b, nil
} }
// Validate validates builder context // Validate validates builder context
func (b *Builder) Validate() error { func (b *Builder) Validate() error {
if b.NodeGroup.DockerContext { if b.NodeGroup.DockerContext {
list, err := b.opts.dockerCli.ContextStore().List() list, err := b.opts.dockerCli.ContextStore().List()
if err != nil { if err != nil {
return err return err
} }
currentContext := b.opts.dockerCli.CurrentContext() currentContext := b.opts.dockerCli.CurrentContext()
for _, l := range list { for _, l := range list {
if l.Name == b.Name && l.Name != currentContext { if l.Name == b.Name && l.Name != currentContext {
return errors.Errorf("use `docker --context=%s buildx` to switch to context %q", l.Name, l.Name) return errors.Errorf("use `docker --context=%s buildx` to switch to context %q", l.Name, l.Name)
} }
} }
} }
return nil return nil
} }
// ContextName returns builder context name if available. // ContextName returns builder context name if available.
func (b *Builder) ContextName() string { func (b *Builder) ContextName() string {
ctxbuilders, err := b.opts.dockerCli.ContextStore().List() ctxbuilders, err := b.opts.dockerCli.ContextStore().List()
if err != nil { if err != nil {
return "" return ""
} }
for _, cb := range ctxbuilders { for _, cb := range ctxbuilders {
if b.NodeGroup.Driver == "docker" && len(b.NodeGroup.Nodes) == 1 && b.NodeGroup.Nodes[0].Endpoint == cb.Name { if b.NodeGroup.Driver == "docker" && len(b.NodeGroup.Nodes) == 1 && b.NodeGroup.Nodes[0].Endpoint == cb.Name {
return cb.Name return cb.Name
} }
} }
return "" return ""
} }
// ImageOpt returns registry auth configuration // ImageOpt returns registry auth configuration
func (b *Builder) ImageOpt() (imagetools.Opt, error) { func (b *Builder) ImageOpt() (imagetools.Opt, error) {
return storeutil.GetImageConfig(b.opts.dockerCli, b.NodeGroup) return storeutil.GetImageConfig(b.opts.dockerCli, b.NodeGroup)
} }
// Boot bootstrap a builder // Boot bootstrap a builder
func (b *Builder) Boot(ctx context.Context) (bool, error) { func (b *Builder) Boot(ctx context.Context) (bool, error) {
toBoot := make([]int, 0, len(b.nodes)) toBoot := make([]int, 0, len(b.nodes))
for idx, d := range b.nodes { for idx, d := range b.nodes {
if d.Err != nil || d.Driver == nil || d.DriverInfo == nil { if d.Err != nil || d.Driver == nil || d.DriverInfo == nil {
continue continue
} }
if d.DriverInfo.Status != driver.Running { if d.DriverInfo.Status != driver.Running {
toBoot = append(toBoot, idx) toBoot = append(toBoot, idx)
} }
} }
if len(toBoot) == 0 { if len(toBoot) == 0 {
return false, nil return false, nil
} }
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, os.Stderr, progress.PrinterModeAuto) printer, err := progress.NewPrinter(context.TODO(), os.Stderr, os.Stderr, progress.PrinterModeAuto)
if err != nil { if err != nil {
return false, err return false, err
} }
baseCtx := ctx baseCtx := ctx
eg, _ := errgroup.WithContext(ctx) eg, _ := errgroup.WithContext(ctx)
for _, idx := range toBoot { for _, idx := range toBoot {
func(idx int) { func(idx int) {
eg.Go(func() error { eg.Go(func() error {
pw := progress.WithPrefix(printer, b.NodeGroup.Nodes[idx].Name, len(toBoot) > 1) pw := progress.WithPrefix(printer, b.NodeGroup.Nodes[idx].Name, len(toBoot) > 1)
_, err := driver.Boot(ctx, baseCtx, b.nodes[idx].Driver, pw) _, err := driver.Boot(ctx, baseCtx, b.nodes[idx].Driver, pw)
if err != nil { if err != nil {
b.nodes[idx].Err = err b.nodes[idx].Err = err
} }
return nil return nil
}) })
}(idx) }(idx)
} }
err = eg.Wait() err = eg.Wait()
err1 := printer.Wait() err1 := printer.Wait()
if err == nil { if err == nil {
err = err1 err = err1
} }
return true, err return true, err
} }
// Inactive checks if all nodes are inactive for this builder. // Inactive checks if all nodes are inactive for this builder.
func (b *Builder) Inactive() bool { func (b *Builder) Inactive() bool {
for _, d := range b.nodes { for _, d := range b.nodes {
if d.DriverInfo != nil && d.DriverInfo.Status == driver.Running { if d.DriverInfo != nil && d.DriverInfo.Status == driver.Running {
return false return false
} }
} }
return true return true
} }
// Err returns error if any. // Err returns error if any.
func (b *Builder) Err() error { func (b *Builder) Err() error {
return b.err return b.err
} }
type driverFactory struct { type driverFactory struct {
driver.Factory driver.Factory
once sync.Once once sync.Once
} }
// Factory returns the driver factory. // Factory returns the driver factory.
func (b *Builder) Factory(ctx context.Context) (_ driver.Factory, err error) { func (b *Builder) Factory(ctx context.Context) (_ driver.Factory, err error) {
b.driverFactory.once.Do(func() { b.driverFactory.once.Do(func() {
if b.Driver != "" { if b.Driver != "" {
b.driverFactory.Factory, err = driver.GetFactory(b.Driver, true) b.driverFactory.Factory, err = driver.GetFactory(b.Driver, true)
if err != nil { if err != nil {
return return
} }
} else { } else {
// empty driver means nodegroup was implicitly created as a default // empty driver means nodegroup was implicitly created as a default
// driver for a docker context and allows falling back to a // driver for a docker context and allows falling back to a
// docker-container driver for older daemon that doesn't support // docker-container driver for older daemon that doesn't support
// buildkit (< 18.06). // buildkit (< 18.06).
ep := b.NodeGroup.Nodes[0].Endpoint ep := b.NodeGroup.Nodes[0].Endpoint
var dockerapi *dockerutil.ClientAPI var dockerapi *dockerutil.ClientAPI
dockerapi, err = dockerutil.NewClientAPI(b.opts.dockerCli, b.NodeGroup.Nodes[0].Endpoint) dockerapi, err = dockerutil.NewClientAPI(b.opts.dockerCli, b.NodeGroup.Nodes[0].Endpoint)
if err != nil { if err != nil {
return return
} }
// check if endpoint is healthy is needed to determine the driver type. // check if endpoint is healthy is needed to determine the driver type.
// if this fails then can't continue with driver selection. // if this fails then can't continue with driver selection.
if _, err = dockerapi.Ping(ctx); err != nil { if _, err = dockerapi.Ping(ctx); err != nil {
return return
} }
b.driverFactory.Factory, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false) b.driverFactory.Factory, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
if err != nil { if err != nil {
return return
} }
b.Driver = b.driverFactory.Factory.Name() b.Driver = b.driverFactory.Factory.Name()
} }
}) })
return b.driverFactory.Factory, err return b.driverFactory.Factory, err
} }
// GetBuilders returns all builders // GetBuilders returns all builders
func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) { func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) {
storeng, err := txn.List() storeng, err := txn.List()
if err != nil { if err != nil {
return nil, err return nil, err
} }
builders := make([]*Builder, len(storeng)) builders := make([]*Builder, len(storeng))
seen := make(map[string]struct{}) seen := make(map[string]struct{})
for i, ng := range storeng { for i, ng := range storeng {
b, err := New(dockerCli, b, err := New(dockerCli,
WithName(ng.Name), WithName(ng.Name),
WithStore(txn), WithStore(txn),
WithSkippedValidation(), WithSkippedValidation(),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
builders[i] = b builders[i] = b
seen[b.NodeGroup.Name] = struct{}{} seen[b.NodeGroup.Name] = struct{}{}
} }
contexts, err := dockerCli.ContextStore().List() contexts, err := dockerCli.ContextStore().List()
if err != nil { if err != nil {
return nil, err return nil, err
} }
sort.Slice(contexts, func(i, j int) bool { sort.Slice(contexts, func(i, j int) bool {
return contexts[i].Name < contexts[j].Name return contexts[i].Name < contexts[j].Name
}) })
for _, c := range contexts { for _, c := range contexts {
// if a context has the same name as an instance from the store, do not // if a context has the same name as an instance from the store, do not
// add it to the builders list. An instance from the store takes // add it to the builders list. An instance from the store takes
// precedence over context builders. // precedence over context builders.
if _, ok := seen[c.Name]; ok { if _, ok := seen[c.Name]; ok {
continue continue
} }
b, err := New(dockerCli, b, err := New(dockerCli,
WithName(c.Name), WithName(c.Name),
WithStore(txn), WithStore(txn),
WithSkippedValidation(), WithSkippedValidation(),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
builders = append(builders, b) builders = append(builders, b)
} }
return builders, nil return builders, nil
} }

Loading…
Cancel
Save