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

Commit 58700a7

Browse files
committed
build metrics compatibility for next 22.06
Signed-off-by: CrazyMax <[email protected]>
1 parent 84ba23e commit 58700a7

File tree

9 files changed

+120
-22
lines changed

9 files changed

+120
-22
lines changed

cli/main.go

Lines changed: 13 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,13 @@ func init() {
8687
if err := os.Setenv("PATH", appendPaths(os.Getenv("PATH"), path)); err != nil {
8788
panic(err)
8889
}
90+
91+
// init metrics client
92+
metricsClient = metrics.NewClient()
93+
metricsClient.WithCliVersionFunc(func() string {
94+
return mobycli.CliVersion()
95+
})
96+
8997
// Seed random
9098
rand.Seed(time.Now().UnixNano())
9199
}
@@ -249,7 +257,7 @@ func main() {
249257
if err = root.ExecuteContext(ctx); err != nil {
250258
handleError(ctx, err, ctype, currentContext, cc, root)
251259
}
252-
metrics.Track(ctype, os.Args[1:], compose.SuccessStatus)
260+
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
253261
}
254262

255263
func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
@@ -271,7 +279,7 @@ func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
271279
func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) {
272280
// if user canceled request, simply exit without any error message
273281
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
274-
metrics.Track(ctype, os.Args[1:], compose.CanceledStatus)
282+
metricsClient.Track(ctype, os.Args[1:], compose.CanceledStatus)
275283
os.Exit(130)
276284
}
277285
if ctype == store.AwsContextType {
@@ -293,7 +301,7 @@ $ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
293301

294302
func exit(ctx string, err error, ctype string) {
295303
if exit, ok := err.(cli.StatusError); ok {
296-
metrics.Track(ctype, os.Args[1:], compose.SuccessStatus)
304+
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
297305
os.Exit(exit.StatusCode)
298306
}
299307

@@ -308,7 +316,7 @@ func exit(ctx string, err error, ctype string) {
308316
metricsStatus = compose.CommandSyntaxFailure.MetricsStatus
309317
exitCode = compose.CommandSyntaxFailure.ExitCode
310318
}
311-
metrics.Track(ctype, os.Args[1:], metricsStatus)
319+
metricsClient.Track(ctype, os.Args[1:], metricsStatus)
312320

313321
if errors.Is(err, api.ErrLoginRequired) {
314322
fmt.Fprintln(os.Stderr, err)
@@ -343,7 +351,7 @@ func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string
343351

344352
if mobycli.IsDefaultContextCommand(dockerCommand) {
345353
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)
354+
metricsClient.Track(contextType, os.Args[1:], compose.FailureStatus)
347355
os.Exit(1)
348356
}
349357
}

cli/metrics/client.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,21 @@ import (
2323
"net"
2424
"net/http"
2525
"os"
26+
"sync"
2627
"time"
2728
)
2829

2930
type client struct {
31+
cliversion *cliversion
3032
httpClient *http.Client
3133
}
3234

35+
type cliversion struct {
36+
version string
37+
f func() string
38+
once sync.Once
39+
}
40+
3341
// Command is a command
3442
type Command struct {
3543
Command string `json:"command"`
@@ -47,17 +55,22 @@ func init() {
4755
}
4856
}
4957

50-
// Client sends metrics to Docker Desktopn
58+
// Client sends metrics to Docker Desktop
5159
type Client interface {
60+
// WithCliVersionFunc sets the docker cli version func
61+
WithCliVersionFunc(f func() string)
5262
// Send sends the command to Docker Desktop. Note that the function doesn't
5363
// return anything, not even an error, this is because we don't really care
5464
// if the metrics were sent or not. We only fire and forget.
5565
Send(Command)
66+
// Track sends the tracking analytics to Docker Desktop
67+
Track(context string, args []string, status string)
5668
}
5769

5870
// NewClient returns a new metrics client
5971
func NewClient() Client {
6072
return &client{
73+
cliversion: &cliversion{},
6174
httpClient: &http.Client{
6275
Transport: &http.Transport{
6376
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
@@ -68,6 +81,19 @@ func NewClient() Client {
6881
}
6982
}
7083

84+
func (c *client) WithCliVersionFunc(f func() string) {
85+
c.cliversion.f = f
86+
}
87+
88+
func (c *client) cliVersion() string {
89+
c.cliversion.once.Do(func() {
90+
if c.cliversion.f != nil {
91+
c.cliversion.version = c.cliversion.f()
92+
}
93+
})
94+
return c.cliversion.version
95+
}
96+
7197
func (c *client) Send(command Command) {
7298
result := make(chan bool, 1)
7399
go func() {

cli/metrics/metadata/build.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,31 @@ 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

3738
// getBuildMetadata returns build metadata for this command
38-
func getBuildMetadata(cliSource string, command string, args []string) string {
39+
func getBuildMetadata(cliSource string, cliVersion string, command string, args []string) string {
3940
var cli, builder string
4041
dockercfg := config.LoadDefaultConfigFile(io.Discard)
4142
if alias, ok := dockercfg.Aliases["builder"]; ok {
4243
command = alias
4344
}
4445
if command == "build" {
45-
cli = "docker"
46-
builder = "buildkit"
47-
if enabled, _ := isBuildKitEnabled(); !enabled {
48-
builder = "legacy"
46+
buildkitEnabled, _ := isBuildKitEnabled()
47+
if buildkitEnabled && isBuildxDefault(cliVersion) {
48+
command = "buildx"
49+
args = append([]string{"build"}, args...)
50+
} else {
51+
cli = "docker"
52+
builder = "buildkit"
53+
if !buildkitEnabled {
54+
builder = "legacy"
55+
}
4956
}
50-
} else if command == "buildx" {
57+
}
58+
if command == "buildx" {
5159
cli = "buildx"
5260
builder = buildxDriver(dockercfg, args)
5361
}
@@ -187,3 +195,19 @@ func buildxBuilder(buildArgs []string) string {
187195
}
188196
return builder
189197
}
198+
199+
// isBuildxDefault returns true if buildx by default is used
200+
// through "docker build" command which is already an alias to
201+
// "docker buildx build" in docker cli.
202+
// more info: https://github.com/docker/cli/pull/3314
203+
func isBuildxDefault(cliVersion string) bool {
204+
verCurrent, err := version.NewVersion(cliVersion)
205+
if err != nil {
206+
return false
207+
}
208+
// 21.0.0 is an arbitrary version number because next major is not
209+
// intended to be 21 but 22 and buildx by default will never be part
210+
// of a 20 release version anyway.
211+
verBuildxDefault, _ := version.NewVersion("21.0.0")
212+
return verCurrent.GreaterThanOrEqual(verBuildxDefault)
213+
}

cli/metrics/metadata/build_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,28 @@ 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: "20.10.15",
96+
expected: false,
97+
},
98+
{
99+
cliVersion: "20.10.2-575-g22edabb584.m",
100+
expected: false,
101+
},
102+
{
103+
cliVersion: "22.05.0",
104+
expected: true,
105+
},
106+
}
107+
for _, tt := range tts {
108+
t.Run(tt.cliVersion, func(t *testing.T) {
109+
assert.Equal(t, tt.expected, isBuildxDefault(tt.cliVersion))
110+
})
111+
}
112+
}

cli/metrics/metadata/metadata.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
package metadata
1818

1919
// Get returns the JSON metadata linked to the invoked command
20-
func Get(cliSource string, args []string) string {
20+
func Get(cliSource string, cliVersion string, args []string) string {
2121
if len(args) == 0 {
2222
return cliSource
2323
}
2424
switch args[0] {
2525
case "build", "buildx":
26-
cliSource = getBuildMetadata(cliSource, args[0], args[1:])
26+
cliSource = getBuildMetadata(cliSource, cliVersion, args[0], args[1:])
2727
}
2828
return cliSource
2929
}

cli/metrics/metrics.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,16 @@ 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: metadata.Get(CLISource, c.cliVersion(), args),
3937
Status: status,
4038
})
4139
}

cli/mobycli/exec.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
package mobycli
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"fmt"
2223
"os"
2324
"os/exec"
2425
"os/signal"
2526
"path/filepath"
2627
"regexp"
28+
"strings"
2729

2830
"github.com/docker/compose/v2/pkg/compose"
2931
"github.com/docker/compose/v2/pkg/utils"
@@ -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,15 @@ 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+
cmd := exec.Command(ComDockerCli, "version", "--format", "{{.Client.Version}}")
169+
var stdout bytes.Buffer
170+
cmd.Stdout = &stdout
171+
_ = cmd.Run() // we don't need to check for error as we only want the client version
172+
return strings.TrimSpace(stdout.String())
173+
}
174+
160175
// ExecSilent executes a command and do redirect output to stdOut, return output
161176
func ExecSilent(ctx context.Context, args ...string) ([]byte, error) {
162177
if len(args) == 0 {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
github.com/google/go-cmp v0.5.5
3838
github.com/hashicorp/go-multierror v1.1.0
3939
github.com/hashicorp/go-uuid v1.0.2
40+
github.com/hashicorp/go-version v1.4.0
4041
github.com/iancoleman/strcase v0.1.2
4142
github.com/joho/godotenv v1.3.0
4243
github.com/labstack/echo v3.3.10+incompatible

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,8 +790,9 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I
790790
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
791791
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
792792
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
793-
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
794793
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
794+
github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4=
795+
github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
795796
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
796797
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
797798
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=

0 commit comments

Comments
 (0)