Skip to content

Commit 5f07d7d

Browse files
Merge pull request #3179 from thaJeztah/optimize_info
docker info: skip API connection if possible
2 parents d7a311b + d738e7c commit 5f07d7d

2 files changed

Lines changed: 102 additions & 7 deletions

File tree

cli/command/system/info.go

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"io/ioutil"
8+
"regexp"
79
"sort"
810
"strings"
911

@@ -64,13 +66,6 @@ func NewInfoCommand(dockerCli command.Cli) *cobra.Command {
6466
func runInfo(cmd *cobra.Command, dockerCli command.Cli, opts *infoOptions) error {
6567
var info info
6668

67-
ctx := context.Background()
68-
if dinfo, err := dockerCli.Client().Info(ctx); err == nil {
69-
info.Info = &dinfo
70-
} else {
71-
info.ServerErrors = append(info.ServerErrors, err.Error())
72-
}
73-
7469
info.ClientInfo = &clientInfo{
7570
Context: dockerCli.CurrentContext(),
7671
Debug: debug.IsEnabled(),
@@ -81,12 +76,60 @@ func runInfo(cmd *cobra.Command, dockerCli command.Cli, opts *infoOptions) error
8176
info.ClientErrors = append(info.ClientErrors, err.Error())
8277
}
8378

79+
if needsServerInfo(opts.format, info) {
80+
ctx := context.Background()
81+
if dinfo, err := dockerCli.Client().Info(ctx); err == nil {
82+
info.Info = &dinfo
83+
} else {
84+
info.ServerErrors = append(info.ServerErrors, err.Error())
85+
}
86+
}
87+
8488
if opts.format == "" {
8589
return prettyPrintInfo(dockerCli, info)
8690
}
8791
return formatInfo(dockerCli, info, opts.format)
8892
}
8993

94+
// placeHolders does a rudimentary match for possible placeholders in a
95+
// template, matching a '.', followed by an letter (a-z/A-Z).
96+
var placeHolders = regexp.MustCompile(`\.[a-zA-Z]`)
97+
98+
// needsServerInfo detects if the given template uses any server information.
99+
// If only client-side information is used in the template, we can skip
100+
// connecting to the daemon. This allows (e.g.) to only get cli-plugin
101+
// information, without also making a (potentially expensive) API call.
102+
func needsServerInfo(template string, info info) bool {
103+
if len(template) == 0 || placeHolders.FindString(template) == "" {
104+
// The template is empty, or does not contain formatting fields
105+
// (e.g. `table` or `raw` or `{{ json .}}`). Assume we need server-side
106+
// information to render it.
107+
return true
108+
}
109+
110+
// A template is provided and has at least one field set.
111+
tmpl, err := templates.NewParse("", template)
112+
if err != nil {
113+
// ignore parsing errors here, and let regular code handle them
114+
return true
115+
}
116+
117+
type sparseInfo struct {
118+
ClientInfo *clientInfo `json:",omitempty"`
119+
ClientErrors []string `json:",omitempty"`
120+
}
121+
122+
// This constructs an "info" object that only has the client-side fields.
123+
err = tmpl.Execute(ioutil.Discard, sparseInfo{
124+
ClientInfo: info.ClientInfo,
125+
ClientErrors: info.ClientErrors,
126+
})
127+
// If executing the template failed, it means the template needs
128+
// server-side information as well. If it succeeded without server-side
129+
// information, we don't need to make API calls to collect that information.
130+
return err != nil
131+
}
132+
90133
func prettyPrintInfo(dockerCli command.Cli, info info) error {
91134
fmt.Fprintln(dockerCli.Out(), "Client:")
92135
if info.ClientInfo != nil {

cli/command/system/info_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,55 @@ func TestFormatInfo(t *testing.T) {
420420
})
421421
}
422422
}
423+
424+
func TestNeedsServerInfo(t *testing.T) {
425+
tests := []struct {
426+
doc string
427+
template string
428+
expected bool
429+
}{
430+
{
431+
doc: "no template",
432+
template: "",
433+
expected: true,
434+
},
435+
{
436+
doc: "JSON",
437+
template: "json",
438+
expected: true,
439+
},
440+
{
441+
doc: "JSON (all fields)",
442+
template: "{{json .}}",
443+
expected: true,
444+
},
445+
{
446+
doc: "JSON (Server ID)",
447+
template: "{{json .ID}}",
448+
expected: true,
449+
},
450+
{
451+
doc: "ClientInfo",
452+
template: "{{json .ClientInfo}}",
453+
expected: false,
454+
},
455+
{
456+
doc: "JSON ClientInfo",
457+
template: "{{json .ClientInfo}}",
458+
expected: false,
459+
},
460+
{
461+
doc: "JSON (Active context)",
462+
template: "{{json .ClientInfo.Context}}",
463+
expected: false,
464+
},
465+
}
466+
467+
inf := info{ClientInfo: &clientInfo{}}
468+
for _, tc := range tests {
469+
tc := tc
470+
t.Run(tc.doc, func(t *testing.T) {
471+
assert.Equal(t, needsServerInfo(tc.template, inf), tc.expected)
472+
})
473+
}
474+
}

0 commit comments

Comments
 (0)