github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cmd/docker/builder.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  
    10  	pluginmanager "github.com/docker/cli/cli-plugins/manager"
    11  	"github.com/docker/cli/cli/command"
    12  	"github.com/pkg/errors"
    13  	"github.com/spf13/cobra"
    14  	"github.com/spf13/pflag"
    15  )
    16  
    17  const (
    18  	builderDefaultPlugin = "buildx"
    19  	buildxMissingWarning = `DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
    20              Install the buildx component to build images with BuildKit:
    21              https://docs.docker.com/go/buildx/`
    22  
    23  	buildkitDisabledWarning = `DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
    24              BuildKit is currently disabled; enable it by removing the DOCKER_BUILDKIT=0
    25              environment-variable.`
    26  
    27  	buildxMissingError = `ERROR: BuildKit is enabled but the buildx component is missing or broken.
    28         Install the buildx component to build images with BuildKit:
    29         https://docs.docker.com/go/buildx/`
    30  )
    31  
    32  func newBuilderError(errorMsg string, pluginLoadErr error) error {
    33  	if pluginmanager.IsNotFound(pluginLoadErr) {
    34  		return errors.New(errorMsg)
    35  	}
    36  	if pluginLoadErr != nil {
    37  		return fmt.Errorf("%w\n\n%s", pluginLoadErr, errorMsg)
    38  	}
    39  	return errors.New(errorMsg)
    40  }
    41  
    42  //nolint:gocyclo
    43  func processBuilder(dockerCli command.Cli, cmd *cobra.Command, args, osargs []string) ([]string, []string, []string, error) {
    44  	var buildKitDisabled, useBuilder, useAlias bool
    45  	var envs []string
    46  
    47  	// check DOCKER_BUILDKIT env var is not empty
    48  	// if it is assume we want to use the builder component
    49  	if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
    50  		enabled, err := strconv.ParseBool(v)
    51  		if err != nil {
    52  			return args, osargs, nil, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
    53  		}
    54  		if !enabled {
    55  			buildKitDisabled = true
    56  		} else {
    57  			useBuilder = true
    58  		}
    59  	}
    60  
    61  	// if a builder alias is defined, use it instead
    62  	// of the default one
    63  	builderAlias := builderDefaultPlugin
    64  	aliasMap := dockerCli.ConfigFile().Aliases
    65  	if v, ok := aliasMap[keyBuilderAlias]; ok {
    66  		useBuilder = true
    67  		useAlias = true
    68  		builderAlias = v
    69  	}
    70  
    71  	// is this a build that should be forwarded to the builder?
    72  	fwargs, fwosargs, forwarded := forwardBuilder(builderAlias, args, osargs)
    73  	if !forwarded {
    74  		return args, osargs, nil, nil
    75  	}
    76  
    77  	// wcow build command must use the legacy builder
    78  	// if not opt-in through a builder component
    79  	if !useBuilder && dockerCli.ServerInfo().OSType == "windows" {
    80  		return args, osargs, nil, nil
    81  	}
    82  
    83  	if buildKitDisabled {
    84  		// display warning if not wcow and continue
    85  		if dockerCli.ServerInfo().OSType != "windows" {
    86  			_, _ = fmt.Fprintf(dockerCli.Err(), "%s\n\n", buildkitDisabledWarning)
    87  		}
    88  		return args, osargs, nil, nil
    89  	}
    90  
    91  	// check plugin is available if cmd forwarded
    92  	plugin, perr := pluginmanager.GetPlugin(builderAlias, dockerCli, cmd.Root())
    93  	if perr == nil && plugin != nil {
    94  		perr = plugin.Err
    95  	}
    96  	if perr != nil {
    97  		// if builder is enforced with DOCKER_BUILDKIT=1, cmd must fail
    98  		// if the plugin is missing or broken.
    99  		if useBuilder {
   100  			return args, osargs, nil, newBuilderError(buildxMissingError, perr)
   101  		}
   102  		// otherwise, display warning and continue
   103  		_, _ = fmt.Fprintf(dockerCli.Err(), "%s\n\n", newBuilderError(buildxMissingWarning, perr))
   104  		return args, osargs, nil, nil
   105  	}
   106  
   107  	// If build subcommand is forwarded, user would expect "docker build" to
   108  	// always create a local docker image (default context builder). This is
   109  	// for better backward compatibility in case where a user could switch to
   110  	// a docker container builder with "docker buildx --use foo" which does
   111  	// not --load by default. Also makes sure that an arbitrary builder name
   112  	// is not being set in the command line or in the environment before
   113  	// setting the default context and keep "buildx install" behavior if being
   114  	// set (builder alias).
   115  	if forwarded && !useAlias && !hasBuilderName(args, os.Environ()) {
   116  		envs = append([]string{"BUILDX_BUILDER=" + dockerCli.CurrentContext()}, envs...)
   117  	}
   118  
   119  	return fwargs, fwosargs, envs, nil
   120  }
   121  
   122  func forwardBuilder(alias string, args, osargs []string) ([]string, []string, bool) {
   123  	aliases := [][2][]string{
   124  		{
   125  			{"builder"},
   126  			{alias},
   127  		},
   128  		{
   129  			{"build"},
   130  			{alias, "build"},
   131  		},
   132  		{
   133  			{"image", "build"},
   134  			{alias, "build"},
   135  		},
   136  	}
   137  	for _, al := range aliases {
   138  		if fwargs, changed := command.StringSliceReplaceAt(args, al[0], al[1], 0); changed {
   139  			fwosargs, _ := command.StringSliceReplaceAt(osargs, al[0], al[1], -1)
   140  			return fwargs, fwosargs, true
   141  		}
   142  	}
   143  	return args, osargs, false
   144  }
   145  
   146  // hasBuilderName checks if a builder name is defined in args or env vars
   147  func hasBuilderName(args []string, envs []string) bool {
   148  	var builder string
   149  	flagset := pflag.NewFlagSet("buildx", pflag.ContinueOnError)
   150  	flagset.Usage = func() {}
   151  	flagset.SetOutput(io.Discard)
   152  	flagset.StringVar(&builder, "builder", "", "")
   153  	_ = flagset.Parse(args)
   154  	if builder != "" {
   155  		return true
   156  	}
   157  	for _, e := range envs {
   158  		if strings.HasPrefix(e, "BUILDX_BUILDER=") && e != "BUILDX_BUILDER=" {
   159  			return true
   160  		}
   161  	}
   162  	return false
   163  }