buildergo
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…
Reference in New Issue