Skip to content
This repository was archived by the owner on Oct 13, 2023. It is now read-only.

Commit 13f10d4

Browse files
Merge pull request #2457 from thaJeztah/19.03_backport_lazy_feature_detection
[19.03 backport] cli: perform feature detection lazily Upstream-commit: 39299333681b5c4ad47935b2b3e3717e80ef5495 Component: cli
2 parents 7068179 + 5d14c92 commit 13f10d4

4 files changed

Lines changed: 149 additions & 96 deletions

File tree

components/cli/cli/command/cli.go

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import (
88
"path/filepath"
99
"runtime"
1010
"strconv"
11+
"strings"
12+
"sync"
13+
"time"
1114

1215
"github.com/docker/cli/cli/config"
1316
cliconfig "github.com/docker/cli/cli/config"
@@ -25,6 +28,7 @@ import (
2528
"github.com/docker/cli/internal/containerizedengine"
2629
dopts "github.com/docker/cli/opts"
2730
clitypes "github.com/docker/cli/types"
31+
"github.com/docker/docker/api"
2832
"github.com/docker/docker/api/types"
2933
registrytypes "github.com/docker/docker/api/types/registry"
3034
"github.com/docker/docker/client"
@@ -76,7 +80,7 @@ type DockerCli struct {
7680
err io.Writer
7781
client client.APIClient
7882
serverInfo ServerInfo
79-
clientInfo ClientInfo
83+
clientInfo *ClientInfo
8084
contentTrust bool
8185
newContainerizeClient func(string) (clitypes.ContainerizedClient, error)
8286
contextStore store.Store
@@ -87,7 +91,7 @@ type DockerCli struct {
8791

8892
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
8993
func (cli *DockerCli) DefaultVersion() string {
90-
return cli.clientInfo.DefaultVersion
94+
return cli.ClientInfo().DefaultVersion
9195
}
9296

9397
// Client returns the APIClient
@@ -126,18 +130,55 @@ func ShowHelp(err io.Writer) func(*cobra.Command, []string) error {
126130

127131
// ConfigFile returns the ConfigFile
128132
func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
133+
if cli.configFile == nil {
134+
cli.loadConfigFile()
135+
}
129136
return cli.configFile
130137
}
131138

139+
func (cli *DockerCli) loadConfigFile() {
140+
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
141+
}
142+
143+
var fetchServerInfo sync.Once
144+
132145
// ServerInfo returns the server version details for the host this client is
133146
// connected to
134147
func (cli *DockerCli) ServerInfo() ServerInfo {
148+
fetchServerInfo.Do(cli.initializeFromClient)
135149
return cli.serverInfo
136150
}
137151

138152
// ClientInfo returns the client details for the cli
139153
func (cli *DockerCli) ClientInfo() ClientInfo {
140-
return cli.clientInfo
154+
if cli.clientInfo == nil {
155+
_ = cli.loadClientInfo()
156+
}
157+
return *cli.clientInfo
158+
}
159+
160+
func (cli *DockerCli) loadClientInfo() error {
161+
var experimentalValue string
162+
// Environment variable always overrides configuration
163+
if experimentalValue = os.Getenv("DOCKER_CLI_EXPERIMENTAL"); experimentalValue == "" {
164+
experimentalValue = cli.ConfigFile().Experimental
165+
}
166+
hasExperimental, err := isEnabled(experimentalValue)
167+
if err != nil {
168+
return errors.Wrap(err, "Experimental field")
169+
}
170+
171+
var v string
172+
if cli.client != nil {
173+
v = cli.client.ClientVersion()
174+
} else {
175+
v = api.DefaultVersion
176+
}
177+
cli.clientInfo = &ClientInfo{
178+
DefaultVersion: v,
179+
HasExperimental: hasExperimental,
180+
}
181+
return nil
141182
}
142183

143184
// ContentTrustEnabled returns whether content trust has been enabled by an
@@ -207,7 +248,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize
207248
debug.Enable()
208249
}
209250

210-
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
251+
cli.loadConfigFile()
211252

212253
baseContextStore := store.New(cliconfig.ContextStoreDir(), cli.contextStoreConfig)
213254
cli.contextStore = &ContextStoreWithDefault{
@@ -239,20 +280,6 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize
239280
return err
240281
}
241282
}
242-
var experimentalValue string
243-
// Environment variable always overrides configuration
244-
if experimentalValue = os.Getenv("DOCKER_CLI_EXPERIMENTAL"); experimentalValue == "" {
245-
experimentalValue = cli.configFile.Experimental
246-
}
247-
hasExperimental, err := isEnabled(experimentalValue)
248-
if err != nil {
249-
return errors.Wrap(err, "Experimental field")
250-
}
251-
cli.clientInfo = ClientInfo{
252-
DefaultVersion: cli.client.ClientVersion(),
253-
HasExperimental: hasExperimental,
254-
}
255-
cli.initializeFromClient()
256283
return nil
257284
}
258285

@@ -343,7 +370,16 @@ func isEnabled(value string) (bool, error) {
343370
}
344371

345372
func (cli *DockerCli) initializeFromClient() {
346-
ping, err := cli.client.Ping(context.Background())
373+
ctx := context.Background()
374+
if strings.HasPrefix(cli.DockerEndpoint().Host, "tcp://") {
375+
// @FIXME context.WithTimeout doesn't work with connhelper / ssh connections
376+
// time="2020-04-10T10:16:26Z" level=warning msg="commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
377+
var cancel func()
378+
ctx, cancel = context.WithTimeout(ctx, 2*time.Second)
379+
defer cancel()
380+
}
381+
382+
ping, err := cli.client.Ping(ctx)
347383
if err != nil {
348384
// Default to true if we fail to connect to daemon
349385
cli.serverInfo = ServerInfo{HasExperimental: true}

components/cli/cli/command/image/build.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,25 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
115115
},
116116
}
117117

118+
// Wrap the global pre-run to handle non-BuildKit use of the --platform flag.
119+
//
120+
// We're doing it here so that we're only contacting the daemon when actually
121+
// running the command, and not during initialization.
122+
// TODO remove this hack once we no longer support the experimental use of --platform
123+
rootFn := cmd.Root().PersistentPreRunE
124+
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
125+
if ok, _ := command.BuildKitEnabled(dockerCli.ServerInfo()); !ok {
126+
f := cmd.Flag("platform")
127+
delete(f.Annotations, "buildkit")
128+
f.Annotations["version"] = []string{"1.32"}
129+
f.Annotations["experimental"] = nil
130+
}
131+
if rootFn != nil {
132+
return rootFn(cmd, args)
133+
}
134+
return nil
135+
}
136+
118137
flags := cmd.Flags()
119138

120139
flags.VarP(&options.tags, "tag", "t", "Name and optionally a tag in the 'name:tag' format")
@@ -163,14 +182,8 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
163182
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
164183

165184
flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
166-
// Platform is not experimental when BuildKit is used
167-
buildkitEnabled, err := command.BuildKitEnabled(dockerCli.ServerInfo())
168-
if err == nil && buildkitEnabled {
169-
flags.SetAnnotation("platform", "version", []string{"1.38"})
170-
} else {
171-
flags.SetAnnotation("platform", "version", []string{"1.32"})
172-
flags.SetAnnotation("platform", "experimental", nil)
173-
}
185+
flags.SetAnnotation("platform", "version", []string{"1.38"})
186+
flags.SetAnnotation("platform", "buildkit", nil)
174187

175188
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
176189
flags.SetAnnotation("squash", "experimental", nil)

components/cli/cli/command/image/build_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import (
2020
"github.com/google/go-cmp/cmp"
2121
"github.com/moby/buildkit/session/secrets/secretsprovider"
2222
"gotest.tools/v3/assert"
23+
"gotest.tools/v3/env"
2324
"gotest.tools/v3/fs"
2425
"gotest.tools/v3/skip"
2526
)
2627

2728
func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
29+
defer env.Patch(t, "DOCKER_BUILDKIT", "0")()
2830
buffer := new(bytes.Buffer)
2931
fakeBuild := newFakeBuild()
3032
fakeImageBuild := func(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
@@ -60,6 +62,7 @@ func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
6062
}
6163

6264
func TestRunBuildResetsUidAndGidInContext(t *testing.T) {
65+
defer env.Patch(t, "DOCKER_BUILDKIT", "0")()
6366
skip.If(t, os.Getuid() != 0, "root is required to chown files")
6467
fakeBuild := newFakeBuild()
6568
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeBuild.build})
@@ -90,6 +93,7 @@ func TestRunBuildResetsUidAndGidInContext(t *testing.T) {
9093
}
9194

9295
func TestRunBuildDockerfileOutsideContext(t *testing.T) {
96+
defer env.Patch(t, "DOCKER_BUILDKIT", "0")()
9397
dir := fs.NewDir(t, t.Name(),
9498
fs.WithFile("data", "data file"))
9599
defer dir.Remove()
@@ -122,7 +126,8 @@ COPY data /data
122126
// TODO: test "context selection" logic directly when runBuild is refactored
123127
// to support testing (ex: docker/cli#294)
124128
func TestRunBuildFromGitHubSpecialCase(t *testing.T) {
125-
cmd := NewBuildCommand(test.NewFakeCli(nil))
129+
defer env.Patch(t, "DOCKER_BUILDKIT", "0")()
130+
cmd := NewBuildCommand(test.NewFakeCli(&fakeClient{}))
126131
// Clone a small repo that exists so git doesn't prompt for credentials
127132
cmd.SetArgs([]string{"github.com/docker/for-win"})
128133
cmd.SetOutput(ioutil.Discard)
@@ -135,6 +140,7 @@ func TestRunBuildFromGitHubSpecialCase(t *testing.T) {
135140
// starting with `github.com` takes precedence over the `github.com` special
136141
// case.
137142
func TestRunBuildFromLocalGitHubDir(t *testing.T) {
143+
defer env.Patch(t, "DOCKER_BUILDKIT", "0")()
138144
tmpDir, err := ioutil.TempDir("", "docker-build-from-local-dir-")
139145
assert.NilError(t, err)
140146
defer os.RemoveAll(tmpDir)
@@ -154,6 +160,7 @@ func TestRunBuildFromLocalGitHubDir(t *testing.T) {
154160
}
155161

156162
func TestRunBuildWithSymlinkedContext(t *testing.T) {
163+
defer env.Patch(t, "DOCKER_BUILDKIT", "0")()
157164
dockerfile := `
158165
FROM alpine:3.6
159166
RUN echo hello world

0 commit comments

Comments
 (0)