| package main |
| |
| import ( |
| "errors" |
| "fmt" |
| "io" |
| "os" |
| "strconv" |
| "strings" |
| |
| "github.com/containerd/errdefs" |
| pluginmanager "github.com/docker/cli/cli-plugins/manager" |
| "github.com/docker/cli/cli-plugins/metadata" |
| "github.com/docker/cli/cli/command" |
| "github.com/moby/moby/api/types/build" |
| "github.com/spf13/cobra" |
| "github.com/spf13/pflag" |
| ) |
| |
| const ( |
| builderDefaultPlugin = "buildx" |
| buildxMissingWarning = `DEPRECATED: The legacy builder is deprecated and will be removed in a future release. |
| Install the buildx component to build images with BuildKit: |
| https://docs.docker.com/go/buildx/` |
| |
| buildkitDisabledWarning = `DEPRECATED: The legacy builder is deprecated and will be removed in a future release. |
| BuildKit is currently disabled; enable it by removing the DOCKER_BUILDKIT=0 |
| environment-variable.` |
| |
| buildxMissingError = `ERROR: BuildKit is enabled but the buildx component is missing or broken. |
| Install the buildx component to build images with BuildKit: |
| https://docs.docker.com/go/buildx/` |
| |
| bakeMissingError = `ERROR: docker bake requires the buildx component but it is missing or broken. |
| Install the buildx component to use bake: |
| https://docs.docker.com/go/buildx/` |
| ) |
| |
| func newBuilderError(errorMsg string, pluginLoadErr error) error { |
| if errdefs.IsNotFound(pluginLoadErr) { |
| return errors.New(errorMsg) |
| } |
| if pluginLoadErr != nil { |
| return fmt.Errorf("%w\n\n%s", pluginLoadErr, errorMsg) |
| } |
| return errors.New(errorMsg) |
| } |
| |
| //nolint:gocyclo |
| func processBuilder(dockerCli command.Cli, cmd *cobra.Command, args, osargs []string) ([]string, []string, []string, error) { |
| var buildKitDisabled, useBuilder, useAlias bool |
| var envs []string |
| |
| // check DOCKER_BUILDKIT env var is not empty |
| // if it is assume we want to use the builder component |
| if v := os.Getenv("DOCKER_BUILDKIT"); v != "" { |
| enabled, err := strconv.ParseBool(v) |
| if err != nil { |
| return args, osargs, nil, fmt.Errorf("DOCKER_BUILDKIT environment variable expects boolean value: %w", err) |
| } |
| if !enabled { |
| buildKitDisabled = true |
| } else { |
| useBuilder = true |
| } |
| } |
| // docker bake always requires buildkit; ignore "DOCKER_BUILDKIT=0". |
| if buildKitDisabled && len(args) > 0 && args[0] == "bake" { |
| buildKitDisabled = false |
| } |
| |
| // if a builder alias is defined, use it instead |
| // of the default one |
| builderAlias := builderDefaultPlugin |
| aliasMap := dockerCli.ConfigFile().Aliases |
| if v, ok := aliasMap[keyBuilderAlias]; ok { |
| useBuilder = true |
| useAlias = true |
| builderAlias = v |
| } |
| |
| // is this a build that should be forwarded to the builder? |
| fwargs, fwosargs, fwcmdpath, forwarded := forwardBuilder(builderAlias, args, osargs) |
| if !forwarded { |
| return args, osargs, nil, nil |
| } |
| |
| if !useBuilder { |
| // Builder is not explicitly configured as an alias for buildx. |
| // Detect whether we should use BuildKit, or fallback to the |
| // legacy builder. |
| if si := dockerCli.ServerInfo(); si.BuildkitVersion != build.BuilderBuildKit && si.OSType == "windows" { |
| // The daemon didn't advertise BuildKit as the preferred builder, |
| // so use the legacy builder, which is still the default for |
| // Windows / WCOW. |
| return args, osargs, nil, nil |
| } |
| } |
| |
| if buildKitDisabled { |
| // When using a Linux daemon, print a warning that the legacy builder |
| // is deprecated. For Windows / WCOW, BuildKit is still experimental, |
| // so we don't print this warning, even if the daemon advertised that |
| // it supports BuildKit. |
| if dockerCli.ServerInfo().OSType != "windows" { |
| _, _ = fmt.Fprintf(dockerCli.Err(), "%s\n\n", buildkitDisabledWarning) |
| } |
| return args, osargs, nil, nil |
| } |
| |
| // check plugin is available if cmd forwarded |
| plugin, perr := pluginmanager.GetPlugin(builderAlias, dockerCli, cmd.Root()) |
| if perr == nil && plugin != nil { |
| perr = plugin.Err |
| } |
| if perr != nil { |
| // Using bake without buildx installed is always an error. |
| if len(args) > 0 && args[0] == "bake" { |
| return args, osargs, nil, newBuilderError(bakeMissingError, perr) |
| } |
| // if builder is enforced with DOCKER_BUILDKIT=1, cmd must fail |
| // if the plugin is missing or broken. |
| if useBuilder { |
| return args, osargs, nil, newBuilderError(buildxMissingError, perr) |
| } |
| // otherwise, display warning and continue |
| _, _ = fmt.Fprintf(dockerCli.Err(), "%s\n\n", newBuilderError(buildxMissingWarning, perr)) |
| return args, osargs, nil, nil |
| } |
| |
| // If build subcommand is forwarded, user would expect "docker build" to |
| // always create a local docker image (default context builder). This is |
| // for better backward compatibility in case where a user could switch to |
| // a docker container builder with "docker buildx --use foo" which does |
| // not --load by default. Also makes sure that an arbitrary builder name |
| // is not being set in the command line or in the environment before |
| // setting the default context and keep "buildx install" behavior if being |
| // set (builder alias). |
| if forwarded && !useAlias && !hasBuilderName(args, os.Environ()) { |
| envs = append([]string{"BUILDX_BUILDER=" + dockerCli.CurrentContext()}, envs...) |
| } |
| |
| // overwrite the command path for this plugin using the alias name. |
| cmd.Annotations[metadata.CommandAnnotationPluginCommandPath] = strings.Join(append([]string{cmd.CommandPath()}, fwcmdpath...), " ") |
| |
| return fwargs, fwosargs, envs, nil |
| } |
| |
| func forwardBuilder(alias string, args, osargs []string) ([]string, []string, []string, bool) { |
| aliases := [][3][]string{ |
| { |
| {"bake"}, |
| {alias, "bake"}, |
| {}, |
| }, |
| { |
| {"builder"}, |
| {alias}, |
| {"builder"}, |
| }, |
| { |
| {"build"}, |
| {alias, "build"}, |
| {}, |
| }, |
| { |
| {"image", "build"}, |
| {alias, "build"}, |
| {"image"}, |
| }, |
| } |
| for _, al := range aliases { |
| if fwargs, changed := stringSliceReplaceAt(args, al[0], al[1], 0); changed { |
| fwosargs, _ := stringSliceReplaceAt(osargs, al[0], al[1], -1) |
| fwcmdpath := al[2] |
| return fwargs, fwosargs, fwcmdpath, true |
| } |
| } |
| return args, osargs, nil, false |
| } |
| |
| // hasBuilderName checks if a builder name is defined in args or env vars |
| func hasBuilderName(args []string, envs []string) bool { |
| var builder string |
| flagset := pflag.NewFlagSet("buildx", pflag.ContinueOnError) |
| flagset.Usage = func() {} |
| flagset.SetOutput(io.Discard) |
| flagset.StringVar(&builder, "builder", "", "") |
| _ = flagset.Parse(args) |
| if builder != "" { |
| return true |
| } |
| for _, e := range envs { |
| if strings.HasPrefix(e, "BUILDX_BUILDER=") && e != "BUILDX_BUILDER=" { |
| return true |
| } |
| } |
| return false |
| } |