зеркало из https://github.com/docker/compose-cli.git
Merge pull request #2151 from crazy-max/build-metrics-futureproof
build metrics compatibility for next 22.06
This commit is contained in:
Коммит
6135c5edf1
17
cli/main.go
17
cli/main.go
|
@ -61,6 +61,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
metricsClient metrics.Client
|
||||
contextAgnosticCommands = map[string]struct{}{
|
||||
"context": {},
|
||||
"login": {},
|
||||
|
@ -86,6 +87,12 @@ func init() {
|
|||
if err := os.Setenv("PATH", appendPaths(os.Getenv("PATH"), path)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metricsClient = metrics.NewClient()
|
||||
metricsClient.WithCliVersionFunc(func() string {
|
||||
return mobycli.CliVersion()
|
||||
})
|
||||
|
||||
// Seed random
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
@ -249,7 +256,7 @@ func main() {
|
|||
if err = root.ExecuteContext(ctx); err != nil {
|
||||
handleError(ctx, err, ctype, currentContext, cc, root)
|
||||
}
|
||||
metrics.Track(ctype, os.Args[1:], compose.SuccessStatus)
|
||||
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
|
||||
}
|
||||
|
||||
func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
|
||||
|
@ -271,7 +278,7 @@ func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
|
|||
func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) {
|
||||
// if user canceled request, simply exit without any error message
|
||||
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
|
||||
metrics.Track(ctype, os.Args[1:], compose.CanceledStatus)
|
||||
metricsClient.Track(ctype, os.Args[1:], compose.CanceledStatus)
|
||||
os.Exit(130)
|
||||
}
|
||||
if ctype == store.AwsContextType {
|
||||
|
@ -293,7 +300,7 @@ $ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
|
|||
|
||||
func exit(ctx string, err error, ctype string) {
|
||||
if exit, ok := err.(cli.StatusError); ok {
|
||||
metrics.Track(ctype, os.Args[1:], compose.SuccessStatus)
|
||||
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
|
||||
os.Exit(exit.StatusCode)
|
||||
}
|
||||
|
||||
|
@ -308,7 +315,7 @@ func exit(ctx string, err error, ctype string) {
|
|||
metricsStatus = compose.CommandSyntaxFailure.MetricsStatus
|
||||
exitCode = compose.CommandSyntaxFailure.ExitCode
|
||||
}
|
||||
metrics.Track(ctype, os.Args[1:], metricsStatus)
|
||||
metricsClient.Track(ctype, os.Args[1:], metricsStatus)
|
||||
|
||||
if errors.Is(err, api.ErrLoginRequired) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
|
@ -343,7 +350,7 @@ func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string
|
|||
|
||||
if mobycli.IsDefaultContextCommand(dockerCommand) {
|
||||
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)
|
||||
metrics.Track(contextType, os.Args[1:], compose.FailureStatus)
|
||||
metricsClient.Track(contextType, os.Args[1:], compose.FailureStatus)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,15 @@ import (
|
|||
)
|
||||
|
||||
type client struct {
|
||||
cliversion *cliversion
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type cliversion struct {
|
||||
version string
|
||||
f func() string
|
||||
}
|
||||
|
||||
// Command is a command
|
||||
type Command struct {
|
||||
Command string `json:"command"`
|
||||
|
@ -47,17 +53,23 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// Client sends metrics to Docker Desktopn
|
||||
// Client sends metrics to Docker Desktop
|
||||
type Client interface {
|
||||
// WithCliVersionFunc sets the docker cli version func
|
||||
// that returns the docker cli version (com.docker.cli)
|
||||
WithCliVersionFunc(f func() string)
|
||||
// Send sends the command to Docker Desktop. Note that the function doesn't
|
||||
// return anything, not even an error, this is because we don't really care
|
||||
// if the metrics were sent or not. We only fire and forget.
|
||||
Send(Command)
|
||||
// Track sends the tracking analytics to Docker Desktop
|
||||
Track(context string, args []string, status string)
|
||||
}
|
||||
|
||||
// NewClient returns a new metrics client
|
||||
func NewClient() Client {
|
||||
return &client{
|
||||
cliversion: &cliversion{},
|
||||
httpClient: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||
|
@ -68,6 +80,10 @@ func NewClient() Client {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *client) WithCliVersionFunc(f func() string) {
|
||||
c.cliversion.f = f
|
||||
}
|
||||
|
||||
func (c *client) Send(command Command) {
|
||||
result := make(chan bool, 1)
|
||||
go func() {
|
||||
|
|
|
@ -31,23 +31,34 @@ import (
|
|||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/docker/api/types"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// getBuildMetadata returns build metadata for this command
|
||||
func getBuildMetadata(cliSource string, command string, args []string) string {
|
||||
// BuildMetadata returns build metadata for this command
|
||||
func BuildMetadata(cliSource, cliVersion, command string, args []string) string {
|
||||
var cli, builder string
|
||||
dockercfg := config.LoadDefaultConfigFile(io.Discard)
|
||||
if alias, ok := dockercfg.Aliases["builder"]; ok {
|
||||
if alias != "buildx" {
|
||||
return cliSource
|
||||
}
|
||||
command = alias
|
||||
}
|
||||
if command == "build" {
|
||||
cli = "docker"
|
||||
builder = "buildkit"
|
||||
if enabled, _ := isBuildKitEnabled(); !enabled {
|
||||
builder = "legacy"
|
||||
buildkitEnabled, _ := isBuildKitEnabled()
|
||||
if buildkitEnabled && isBuildxDefault(cliVersion) {
|
||||
command = "buildx"
|
||||
args = append([]string{"build"}, args...)
|
||||
} else {
|
||||
cli = "docker"
|
||||
builder = "buildkit"
|
||||
if !buildkitEnabled {
|
||||
builder = "legacy"
|
||||
}
|
||||
}
|
||||
} else if command == "buildx" {
|
||||
}
|
||||
if command == "buildx" {
|
||||
cli = "buildx"
|
||||
builder = buildxDriver(dockercfg, args)
|
||||
}
|
||||
|
@ -183,3 +194,24 @@ func buildxBuilder(buildArgs []string) string {
|
|||
}
|
||||
return builder
|
||||
}
|
||||
|
||||
// isBuildxDefault returns true if buildx by default is used
|
||||
// through "docker build" command which is already an alias to
|
||||
// "docker buildx build" in docker cli.
|
||||
// more info: https://github.com/docker/cli/pull/3314
|
||||
func isBuildxDefault(cliVersion string) bool {
|
||||
if cliVersion == "" {
|
||||
// empty means DWARF symbol table is stripped from cli binary
|
||||
// which is the case with docker cli < 22.06
|
||||
return false
|
||||
}
|
||||
verCurrent, err := version.NewVersion(cliVersion)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// 21.0.0 is an arbitrary version number because next major is not
|
||||
// intended to be 21 but 22 and buildx by default will never be part
|
||||
// of a 20 release version anyway.
|
||||
verBuildxDefault, _ := version.NewVersion("21.0.0")
|
||||
return verCurrent.GreaterThanOrEqual(verBuildxDefault)
|
||||
}
|
||||
|
|
|
@ -85,3 +85,32 @@ func TestBuildxDriver(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBuildxDefault(t *testing.T) {
|
||||
tts := []struct {
|
||||
cliVersion string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
cliVersion: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
cliVersion: "20.10.15",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
cliVersion: "20.10.2-575-g22edabb584.m",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
cliVersion: "22.05.0",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tts {
|
||||
t.Run(tt.cliVersion, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, isBuildxDefault(tt.cliVersion))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package metadata
|
||||
|
||||
// Get returns the JSON metadata linked to the invoked command
|
||||
func Get(cliSource string, args []string) string {
|
||||
if len(args) == 0 {
|
||||
return cliSource
|
||||
}
|
||||
switch args[0] {
|
||||
case "build", "buildx":
|
||||
cliSource = getBuildMetadata(cliSource, args[0], args[1:])
|
||||
}
|
||||
return cliSource
|
||||
}
|
|
@ -24,23 +24,32 @@ import (
|
|||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
// Track sends the tracking analytics to Docker Desktop
|
||||
func Track(context string, args []string, status string) {
|
||||
func (c *client) Track(context string, args []string, status string) {
|
||||
if isInvokedAsCliBackend() {
|
||||
return
|
||||
}
|
||||
command := GetCommand(args)
|
||||
if command != "" {
|
||||
c := NewClient()
|
||||
c.Send(Command{
|
||||
Command: command,
|
||||
Context: context,
|
||||
Source: metadata.Get(CLISource, args),
|
||||
Source: c.getMetadata(CLISource, args),
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) getMetadata(cliSource string, args []string) string {
|
||||
if len(args) == 0 {
|
||||
return cliSource
|
||||
}
|
||||
switch args[0] {
|
||||
case "build", "buildx":
|
||||
cliSource = metadata.BuildMetadata(cliSource, c.cliversion.f(), args[0], args[1:])
|
||||
}
|
||||
return cliSource
|
||||
}
|
||||
|
||||
func isInvokedAsCliBackend() bool {
|
||||
executable := os.Args[0]
|
||||
return strings.HasSuffix(executable, "-backend")
|
||||
|
|
|
@ -18,6 +18,7 @@ package mobycli
|
|||
|
||||
import (
|
||||
"context"
|
||||
"debug/buildinfo"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -25,15 +26,16 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"strings"
|
||||
|
||||
apicontext "github.com/docker/compose-cli/api/context"
|
||||
"github.com/docker/compose-cli/api/context/store"
|
||||
"github.com/docker/compose-cli/cli/metrics"
|
||||
"github.com/docker/compose-cli/cli/mobycli/resolvepath"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/google/shlex"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var delegatedContextTypes = []string{store.DefaultContextType}
|
||||
|
@ -71,16 +73,20 @@ func mustDelegateToMoby(ctxType string) bool {
|
|||
|
||||
// Exec delegates to com.docker.cli if on moby context
|
||||
func Exec(root *cobra.Command) {
|
||||
metricsClient := metrics.NewClient()
|
||||
metricsClient.WithCliVersionFunc(func() string {
|
||||
return CliVersion()
|
||||
})
|
||||
childExit := make(chan bool)
|
||||
err := RunDocker(childExit, os.Args[1:]...)
|
||||
childExit <- true
|
||||
if err != nil {
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
exitCode := exiterr.ExitCode()
|
||||
metrics.Track(store.DefaultContextType, os.Args[1:], compose.ByExitCode(exitCode).MetricsStatus)
|
||||
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.ByExitCode(exitCode).MetricsStatus)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
metrics.Track(store.DefaultContextType, os.Args[1:], compose.FailureStatus)
|
||||
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.FailureStatus)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -92,7 +98,7 @@ func Exec(root *cobra.Command) {
|
|||
if command == "login" && !metrics.HasQuietFlag(commandArgs) {
|
||||
displayPATSuggestMsg(commandArgs)
|
||||
}
|
||||
metrics.Track(store.DefaultContextType, os.Args[1:], compose.SuccessStatus)
|
||||
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.SuccessStatus)
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
@ -174,6 +180,35 @@ func IsDefaultContextCommand(dockerCommand string) bool {
|
|||
return regexp.MustCompile("Usage:\\s*docker\\s*" + dockerCommand).Match(b)
|
||||
}
|
||||
|
||||
// CliVersion returns the docker cli version
|
||||
func CliVersion() string {
|
||||
info, err := buildinfo.ReadFile(ComDockerCli)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, s := range info.Settings {
|
||||
if s.Key != "-ldflags" {
|
||||
continue
|
||||
}
|
||||
args, err := shlex.Split(s.Value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, a := range args {
|
||||
// https://github.com/docker/cli/blob/f1615facb1ca44e4336ab20e621315fc2cfb845a/scripts/build/.variables#L77
|
||||
if !strings.HasPrefix(a, "github.com/docker/cli/cli/version.Version") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(a, "=")
|
||||
if len(parts) != 2 {
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecSilent executes a command and do redirect output to stdOut, return output
|
||||
func ExecSilent(ctx context.Context, args ...string) ([]byte, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
|
@ -122,6 +122,14 @@ type mockMetricsClient struct {
|
|||
mock.Mock
|
||||
}
|
||||
|
||||
func (s *mockMetricsClient) WithCliVersionFunc(f func() string) {
|
||||
s.Called(f)
|
||||
}
|
||||
|
||||
func (s *mockMetricsClient) Send(command metrics.Command) {
|
||||
s.Called(command)
|
||||
}
|
||||
|
||||
func (s *mockMetricsClient) Track(context string, args []string, status string) {
|
||||
s.Called(context, args, status)
|
||||
}
|
||||
|
|
4
go.mod
4
go.mod
|
@ -30,8 +30,10 @@ require (
|
|||
github.com/golang/mock v1.5.0
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/hashicorp/go-version v1.4.0
|
||||
github.com/iancoleman/strcase v0.1.2
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/labstack/echo v3.3.10+incompatible
|
||||
|
@ -115,7 +117,6 @@ require (
|
|||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/googleapis/gnostic v0.4.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
|
@ -124,7 +125,6 @@ require (
|
|||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.3.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.3 // indirect
|
||||
github.com/huandu/xstrings v1.3.1 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -787,8 +787,8 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I
|
|||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
|
||||
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4=
|
||||
github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
|
Загрузка…
Ссылка в новой задаче