// Copyright 2024, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package internal import ( "context" "fmt" "path/filepath" "sync" "time" "github.com/docker/buildx/builder" "github.com/docker/buildx/store/storeutil" "github.com/docker/cli/cli/command" cfgtypes "github.com/docker/cli/cli/config/types" ) // host contains a host-level Docker CLI as well as a cache of initialized // builders. Operations on the host are serialized. type host struct { mu sync.Mutex cli command.Cli config *Config builders map[string]*cachedBuilder auths map[string]cfgtypes.AuthConfig } func newHost(config *Config) (*host, error) { docker, err := newDockerCLI(config) if err != nil { return nil, err } // Load existing credentials into memory. auths, err := docker.ConfigFile().GetAllCredentials() if err != nil { return nil, err } h := &host{ cli: docker, config: config, builders: map[string]*cachedBuilder{}, auths: auths, } return h, err } // builderFor ensures a builder is available and running. This is guarded by a // mutex to ensure other resources don't attempt to use the builder until it's // ready. // // If the build doesn't specify a builder by name, we will iterate through all // available builders until we find one that we can connect to. func (h *host) builderFor(build Build) (*cachedBuilder, error) { h.mu.Lock() defer h.mu.Unlock() opts := build.BuildOptions() if b, ok := h.builders[opts.Builder]; ok { return b, nil } txn, release, err := storeutil.GetStore(h.cli) if err != nil { return nil, fmt.Errorf("getting store: %w", err) } defer release() contextPathHash := opts.ContextPath if absContextPath, err := filepath.Abs(contextPathHash); err == nil { contextPathHash = absContextPath } b, err := builder.New(h.cli, builder.WithName(opts.Builder), builder.WithContextPathHash(contextPathHash), builder.WithStore(txn), ) if err != nil { return nil, fmt.Errorf("new builder: %w", err) } // If we didn't request a particular builder, and we loaded a default // builder with an unsupported (docker) driver, then look for a builder we // do support. if b.Driver == "" && opts.Builder == "" { builders, err := builder.GetBuilders(h.cli, txn) if err != nil { return nil, fmt.Errorf("getting builders: %w", err) } nextbuilder: for _, bb := range builders { if bb.Driver == "" { continue } if err := bb.Validate(); err != nil { continue } if bb.Err() != nil { continue } nodes, err := bb.LoadNodes(context.Background()) if err != nil { continue } for idx := range nodes { n := nodes[idx] if n.Driver == nil { continue nextbuilder } if _, err := n.Driver.Dial(context.Background()); err != nil { continue nextbuilder } // TODO: Confirm the builder supports the requested platforms. } b = bb break } } if b.Driver == "" && opts.Builder == "" { // If we STILL don't have a builder, create a docker-container instance. b, err = builder.Create( context.Background(), txn, h.cli, builder.CreateOpts{Driver: "docker-container"}, ) if err != nil { return nil, fmt.Errorf("creating builder: %w", err) } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if _, err := b.Boot(ctx); err != nil { return nil, fmt.Errorf("booting builder: %w", err) } } // Attempt to load nodes in order to determine the builder's driver. Ignore // errors for "exec" builds because it's possible to request builders with // drivers that are unknown to us. nodes, err := b.LoadNodes(context.Background()) if err != nil && !build.ShouldExec() { return nil, fmt.Errorf("loading nodes: %w", err) } cached := &cachedBuilder{name: b.Name, driver: b.Driver, nodes: nodes} h.builders[opts.Builder] = cached return cached, nil } // cachedBuilder caches the builders we've loaded. Repeatedly fetching them can // sometimes result in EOF errors from the daemon, especially when under load. type cachedBuilder struct { name string driver string nodes []builder.Node }