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 }