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

Commit 69495d8

Browse files
committed
build metrics compatibility for next 22.06
Signed-off-by: CrazyMax <[email protected]>
1 parent 2d878f7 commit 69495d8

File tree

10 files changed

+164
-57
lines changed

10 files changed

+164
-57
lines changed

cli/main.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import (
6161
)
6262

6363
var (
64+
metricsClient metrics.Client
6465
contextAgnosticCommands = map[string]struct{}{
6566
"context": {},
6667
"login": {},
@@ -86,6 +87,12 @@ func init() {
8687
if err := os.Setenv("PATH", appendPaths(os.Getenv("PATH"), path)); err != nil {
8788
panic(err)
8889
}
90+
91+
metricsClient = metrics.NewClient()
92+
metricsClient.WithCliVersionFunc(func() string {
93+
return mobycli.CliVersion()
94+
})
95+
8996
// Seed random
9097
rand.Seed(time.Now().UnixNano())
9198
}
@@ -249,7 +256,7 @@ func main() {
249256
if err = root.ExecuteContext(ctx); err != nil {
250257
handleError(ctx, err, ctype, currentContext, cc, root)
251258
}
252-
metrics.Track(ctype, os.Args[1:], compose.SuccessStatus)
259+
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
253260
}
254261

255262
func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
@@ -271,7 +278,7 @@ func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
271278
func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) {
272279
// if user canceled request, simply exit without any error message
273280
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
274-
metrics.Track(ctype, os.Args[1:], compose.CanceledStatus)
281+
metricsClient.Track(ctype, os.Args[1:], compose.CanceledStatus)
275282
os.Exit(130)
276283
}
277284
if ctype == store.AwsContextType {
@@ -293,7 +300,7 @@ $ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
293300

294301
func exit(ctx string, err error, ctype string) {
295302
if exit, ok := err.(cli.StatusError); ok {
296-
metrics.Track(ctype, os.Args[1:], compose.SuccessStatus)
303+
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
297304
os.Exit(exit.StatusCode)
298305
}
299306

@@ -308,7 +315,7 @@ func exit(ctx string, err error, ctype string) {
308315
metricsStatus = compose.CommandSyntaxFailure.MetricsStatus
309316
exitCode = compose.CommandSyntaxFailure.ExitCode
310317
}
311-
metrics.Track(ctype, os.Args[1:], metricsStatus)
318+
metricsClient.Track(ctype, os.Args[1:], metricsStatus)
312319

313320
if errors.Is(err, api.ErrLoginRequired) {
314321
fmt.Fprintln(os.Stderr, err)
@@ -343,7 +350,7 @@ func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string
343350

344351
if mobycli.IsDefaultContextCommand(dockerCommand) {
345352
fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s), you can use the \"default\" context to run this command\n", dockerCommand, currentContext)
346-
metrics.Track(contextType, os.Args[1:], compose.FailureStatus)
353+
metricsClient.Track(contextType, os.Args[1:], compose.FailureStatus)
347354
os.Exit(1)
348355
}
349356
}

cli/metrics/client.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ import (
2727
)
2828

2929
type client struct {
30+
cliversion *cliversion
3031
httpClient *http.Client
3132
}
3233

34+
type cliversion struct {
35+
version string
36+
f func() string
37+
}
38+
3339
// Command is a command
3440
type Command struct {
3541
Command string `json:"command"`
@@ -47,17 +53,23 @@ func init() {
4753
}
4854
}
4955

50-
// Client sends metrics to Docker Desktopn
56+
// Client sends metrics to Docker Desktop
5157
type Client interface {
58+
// WithCliVersionFunc sets the docker cli version func
59+
// that returns the docker cli version (com.docker.cli)
60+
WithCliVersionFunc(f func() string)
5261
// Send sends the command to Docker Desktop. Note that the function doesn't
5362
// return anything, not even an error, this is because we don't really care
5463
// if the metrics were sent or not. We only fire and forget.
5564
Send(Command)
65+
// Track sends the tracking analytics to Docker Desktop
66+
Track(context string, args []string, status string)
5667
}
5768

5869
// NewClient returns a new metrics client
5970
func NewClient() Client {
6071
return &client{
72+
cliversion: &cliversion{},
6173
httpClient: &http.Client{
6274
Transport: &http.Transport{
6375
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
@@ -68,6 +80,10 @@ func NewClient() Client {
6880
}
6981
}
7082

83+
func (c *client) WithCliVersionFunc(f func() string) {
84+
c.cliversion.f = f
85+
}
86+
7187
func (c *client) Send(command Command) {
7288
result := make(chan bool, 1)
7389
go func() {

cli/metrics/metadata/build.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,34 @@ import (
3131
"github.com/docker/cli/cli/config/configfile"
3232
"github.com/docker/docker/api/types"
3333
dockerclient "github.com/docker/docker/client"
34+
"github.com/hashicorp/go-version"
3435
"github.com/spf13/pflag"
3536
)
3637

37-
// getBuildMetadata returns build metadata for this command
38-
func getBuildMetadata(cliSource string, command string, args []string) string {
38+
// BuildMetadata returns build metadata for this command
39+
func BuildMetadata(cliSource, cliVersion, command string, args []string) string {
3940
var cli, builder string
4041
dockercfg := config.LoadDefaultConfigFile(io.Discard)
4142
if alias, ok := dockercfg.Aliases["builder"]; ok {
43+
if alias != "buildx" {
44+
return cliSource
45+
}
4246
command = alias
4347
}
4448
if command == "build" {
45-
cli = "docker"
46-
builder = "buildkit"
47-
if enabled, _ := isBuildKitEnabled(); !enabled {
48-
builder = "legacy"
49+
buildkitEnabled, _ := isBuildKitEnabled()
50+
if buildkitEnabled && isBuildxDefault(cliVersion) {
51+
command = "buildx"
52+
args = append([]string{"build"}, args...)
53+
} else {
54+
cli = "docker"
55+
builder = "buildkit"
56+
if !buildkitEnabled {
57+
builder = "legacy"
58+
}
4959
}
50-
} else if command == "buildx" {
60+
}
61+
if command == "buildx" {
5162
cli = "buildx"
5263
builder = buildxDriver(dockercfg, args)
5364
}
@@ -183,3 +194,24 @@ func buildxBuilder(buildArgs []string) string {
183194
}
184195
return builder
185196
}
197+
198+
// isBuildxDefault returns true if buildx by default is used
199+
// through "docker build" command which is already an alias to
200+
// "docker buildx build" in docker cli.
201+
// more info: https://github.com/docker/cli/pull/3314
202+
func isBuildxDefault(cliVersion string) bool {
203+
if cliVersion == "" {
204+
// empty means DWARF symbol table is stripped from cli binary
205+
// which is the case with docker cli < 22.06
206+
return false
207+
}
208+
verCurrent, err := version.NewVersion(cliVersion)
209+
if err != nil {
210+
return false
211+
}
212+
// 21.0.0 is an arbitrary version number because next major is not
213+
// intended to be 21 but 22 and buildx by default will never be part
214+
// of a 20 release version anyway.
215+
verBuildxDefault, _ := version.NewVersion("21.0.0")
216+
return verCurrent.GreaterThanOrEqual(verBuildxDefault)
217+
}

cli/metrics/metadata/build_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,32 @@ func TestBuildxDriver(t *testing.T) {
8585
})
8686
}
8787
}
88+
89+
func TestIsBuildxDefault(t *testing.T) {
90+
tts := []struct {
91+
cliVersion string
92+
expected bool
93+
}{
94+
{
95+
cliVersion: "",
96+
expected: false,
97+
},
98+
{
99+
cliVersion: "20.10.15",
100+
expected: false,
101+
},
102+
{
103+
cliVersion: "20.10.2-575-g22edabb584.m",
104+
expected: false,
105+
},
106+
{
107+
cliVersion: "22.05.0",
108+
expected: true,
109+
},
110+
}
111+
for _, tt := range tts {
112+
t.Run(tt.cliVersion, func(t *testing.T) {
113+
assert.Equal(t, tt.expected, isBuildxDefault(tt.cliVersion))
114+
})
115+
}
116+
}

cli/metrics/metadata/metadata.go

Lines changed: 0 additions & 29 deletions
This file was deleted.

cli/metrics/metrics.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,32 @@ import (
2424
"github.com/docker/compose/v2/pkg/utils"
2525
)
2626

27-
// Track sends the tracking analytics to Docker Desktop
28-
func Track(context string, args []string, status string) {
27+
func (c *client) Track(context string, args []string, status string) {
2928
if isInvokedAsCliBackend() {
3029
return
3130
}
3231
command := GetCommand(args)
3332
if command != "" {
34-
c := NewClient()
3533
c.Send(Command{
3634
Command: command,
3735
Context: context,
38-
Source: metadata.Get(CLISource, args),
36+
Source: c.getMetadata(CLISource, args),
3937
Status: status,
4038
})
4139
}
4240
}
4341

42+
func (c *client) getMetadata(cliSource string, args []string) string {
43+
if len(args) == 0 {
44+
return cliSource
45+
}
46+
switch args[0] {
47+
case "build", "buildx":
48+
cliSource = metadata.BuildMetadata(cliSource, c.cliversion.f(), args[0], args[1:])
49+
}
50+
return cliSource
51+
}
52+
4453
func isInvokedAsCliBackend() bool {
4554
executable := os.Args[0]
4655
return strings.HasSuffix(executable, "-backend")

cli/mobycli/exec.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,23 @@ package mobycli
1818

1919
import (
2020
"context"
21+
"debug/buildinfo"
2122
"fmt"
2223
"os"
2324
"os/exec"
2425
"os/signal"
2526
"path/filepath"
2627
"regexp"
27-
28-
"github.com/docker/compose/v2/pkg/compose"
29-
"github.com/docker/compose/v2/pkg/utils"
30-
"github.com/spf13/cobra"
28+
"strings"
3129

3230
apicontext "github.com/docker/compose-cli/api/context"
3331
"github.com/docker/compose-cli/api/context/store"
3432
"github.com/docker/compose-cli/cli/metrics"
3533
"github.com/docker/compose-cli/cli/mobycli/resolvepath"
34+
"github.com/docker/compose/v2/pkg/compose"
35+
"github.com/docker/compose/v2/pkg/utils"
36+
"github.com/google/shlex"
37+
"github.com/spf13/cobra"
3638
)
3739

3840
var delegatedContextTypes = []string{store.DefaultContextType}
@@ -64,16 +66,20 @@ func mustDelegateToMoby(ctxType string) bool {
6466

6567
// Exec delegates to com.docker.cli if on moby context
6668
func Exec(root *cobra.Command) {
69+
metricsClient := metrics.NewClient()
70+
metricsClient.WithCliVersionFunc(func() string {
71+
return CliVersion()
72+
})
6773
childExit := make(chan bool)
6874
err := RunDocker(childExit, os.Args[1:]...)
6975
childExit <- true
7076
if err != nil {
7177
if exiterr, ok := err.(*exec.ExitError); ok {
7278
exitCode := exiterr.ExitCode()
73-
metrics.Track(store.DefaultContextType, os.Args[1:], compose.ByExitCode(exitCode).MetricsStatus)
79+
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.ByExitCode(exitCode).MetricsStatus)
7480
os.Exit(exitCode)
7581
}
76-
metrics.Track(store.DefaultContextType, os.Args[1:], compose.FailureStatus)
82+
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.FailureStatus)
7783
fmt.Fprintln(os.Stderr, err)
7884
os.Exit(1)
7985
}
@@ -85,7 +91,7 @@ func Exec(root *cobra.Command) {
8591
if command == "login" && !metrics.HasQuietFlag(commandArgs) {
8692
displayPATSuggestMsg(commandArgs)
8793
}
88-
metrics.Track(store.DefaultContextType, os.Args[1:], compose.SuccessStatus)
94+
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.SuccessStatus)
8995

9096
os.Exit(0)
9197
}
@@ -157,6 +163,35 @@ func IsDefaultContextCommand(dockerCommand string) bool {
157163
return regexp.MustCompile("Usage:\\s*docker\\s*" + dockerCommand).Match(b)
158164
}
159165

166+
// CliVersion returns the docker cli version
167+
func CliVersion() string {
168+
info, err := buildinfo.ReadFile(ComDockerCli)
169+
if err != nil {
170+
return ""
171+
}
172+
for _, s := range info.Settings {
173+
if s.Key != "-ldflags" {
174+
continue
175+
}
176+
args, err := shlex.Split(s.Value)
177+
if err != nil {
178+
return ""
179+
}
180+
for _, a := range args {
181+
// https://github.com/docker/cli/blob/f1615facb1ca44e4336ab20e621315fc2cfb845a/scripts/build/.variables#L77
182+
if !strings.HasPrefix(a, "github.com/docker/cli/cli/version.Version") {
183+
continue
184+
}
185+
parts := strings.Split(a, "=")
186+
if len(parts) != 2 {
187+
return ""
188+
}
189+
return parts[1]
190+
}
191+
}
192+
return ""
193+
}
194+
160195
// ExecSilent executes a command and do redirect output to stdOut, return output
161196
func ExecSilent(ctx context.Context, args ...string) ([]byte, error) {
162197
if len(args) == 0 {

cli/server/metrics_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ type mockMetricsClient struct {
122122
mock.Mock
123123
}
124124

125+
func (s *mockMetricsClient) WithCliVersionFunc(f func() string) {
126+
s.Called(f)
127+
}
128+
125129
func (s *mockMetricsClient) Send(command metrics.Command) {
126130
s.Called(command)
127131
}
132+
133+
func (s *mockMetricsClient) Track(context string, args []string, status string) {
134+
s.Called(context, args, status)
135+
}

0 commit comments

Comments
 (0)