Repository: docker/compose-cli
Pull request #2104 (2021-11-16 11:19:17)

Build metrics
This PR handles metrics for build commands. It also adds a new field `Metadata` to the `Command` struct for extensibility. It could be used for other commands in the future.

Detecting build backend used by buildx is a bit tedious in the current release because no formatter is available to handle a proper output with `buildx ls`. The `ls` command locks the buildx store and that _can_ take some time (top 20s) to complete because it needs to check the liveliness of the nodes so user cannot use buildx during this time (https://github.com/docker/buildx/issues/764). That will cause some bad user experience if we rely on this command for metrics. I opened a pull request to start make things easier for us in the future (https://github.com/docker/buildx/pull/830).

So for our purpose we directly read from the store without using an actual command which is more effective:

```
$ cat ~/.docker/buildx/current
{"Key":"unix:///var/run/docker.sock","Name":"builder","Global":false}
# "builder" is the current instance used. read the instance info and retrieve the current driver:
$ cat ~/.docker/buildx/instances/builder
{"Name":"builder","Driver":"docker-container","Nodes":[{"Name":"builder0","Endpoint":"unix:///var/run/docker.sock","Platforms":null,"Flags":["--allow-insecure-entitlement","security.insecure","--allow-insecure-entitlement","network.host"],"DriverOpts":{"env.JAEGER_TRACE":"localhost:6831","image":"moby/buildkit:latest","network":"host"},"Files":null}],"Dynamic":false}
# "docker-container" is the current driver
```

The step above is valid only if user doesn't provide the `--builder` flag to the build command (or `BUILDX_BUILDER` env var). If the builder is provided, we only have to read from the store in `~/.docker/buildx/instances/<builder>`.

For the `docker build` command it's easier. We just need to read the cli config and check `DOCKER_BUILDKIT` env var as well as daemon config:

```json
{
  "features": {
    "buildkit": true
  }
}
```

There is also a special case if a builder alias is being used:

```
$ cat ~/.docker/config.json
{
        "credsStore": "desktop.exe",
        "aliases": {
                "builder": "buildx"
        }
}
```

The following metadata will be send along the command:

```json
{
        "build": {
                "cli": "docker | buildx | <alias>",
                "builder": "legacy | buildkit | <driver>"
        }
}
```

* `build.cli.docker`: user type `docker build ...`.
* `build.cli.buildx`: user type `docker buildx build ...` or `docker buildx bake ...`.
* `build.cli.<alias>` : user type `docker build ...` but an alias is used in `~/.docker/config.json` so set this one instead.
* `build.builder.legacy`: legacy builder is used with `docker build` command.
* `build.server.buildkit`: backend buildkit builder is used with `docker build` command.
* `build.server.<driver>`: a driver is used (can be `docker`, `docker-container` or `kubernetes` atm). Only for buildx.

Metrics commands have also been regenerated and sorted in separate commits.

cc @chris-crone @tonistiigi @mat007 @gtardif 


Patch:
diff --git a/cli/metrics/commands.go b/cli/metrics/commands.go
index e021897f..0fef9f54 100644
--- a/cli/metrics/commands.go
+++ b/cli/metrics/commands.go
@@ -19,131 +19,133 @@ package metrics
 var commandFlags = []string{
 	//added to catch scan details
 	"--version", "--login",
+	// added for build
+	"--builder", "--platforms",
 }
 
 // Generated with generatecommands/main.go
 var managementCommands = []string{
 	"help",
-	"ecs",
-	"scan",
 	"app",
 	"builder",
-	"imagetools",
 	"buildx",
 	"checkpoint",
+	"compose",
 	"config",
 	"container",
 	"context",
 	"create",
+	"ecs",
 	"image",
+	"imagetools",
+	"key",
+	"login",
+	"logout",
 	"manifest",
 	"network",
 	"node",
 	"plugin",
+	"scan",
 	"secret",
 	"service",
+	"signer",
 	"stack",
 	"swarm",
 	"system",
-	"key",
-	"signer",
 	"trust",
 	"volume",
-	"login",
-	"logout",
-	"compose",
 }
 
 var commands = []string{
+	"aci",
+	"add",
+	"annotate",
+	"attach",
+	"azure",
+	"b",
+	"bake",
+	"build",
 	"bundle",
+	"ca",
+	"commit",
 	"completion",
-	"init",
-	"inspect",
-	"install",
-	"deploy",
-	"list",
-	"ls",
+	"config",
+	"connect",
+	"convert",
 	"cp",
-	"merge",
-	"pull",
-	"push",
-	"render",
-	"split",
-	"status",
-	"uninstall",
-	"upgrade",
-	"validate",
-	"version",
-	"build",
-	"prune",
 	"create",
-	"bake",
-	"f",
-	"b",
-	"du",
-	"rm",
-	"stop",
-	"use",
-	"remove",
-	"attach",
-	"commit",
-	"cp",
+	"demote",
+	"deploy",
+	"df",
 	"diff",
+	"disable",
+	"disconnect",
+	"down",
+	"du",
+	"ecs",
+	"enable",
+	"events",
 	"exec",
 	"export",
+	"f",
+	"generate",
+	"history",
+	"images",
+	"import",
+	"info",
+	"init",
+	"inspect",
+	"install",
+	"join",
+	"join-token",
 	"kill",
+	"leave",
+	"list",
+	"load",
+	"login",
+	"logout",
 	"logs",
-	"ps",
+	"ls",
+	"merge",
 	"pause",
 	"port",
+	"promote",
+	"prune",
+	"ps",
+	"pull",
+	"push",
+	"remove",
 	"rename",
+	"render",
 	"restart",
+	"revoke",
+	"rm",
+	"rmi",
+	"rollback",
 	"run",
+	"save",
+	"scale",
+	"search",
+	"services",
+	"set",
+	"show",
+	"sign",
+	"split",
 	"start",
 	"stats",
+	"status",
+	"stop",
+	"tag",
 	"top",
+	"uninstall",
+	"unlock",
+	"unlock-key",
 	"unpause",
+	"up",
 	"update",
+	"upgrade",
+	"use",
+	"validate",
+	"version",
 	"wait",
-	"aci",
-	"ecs",
-	"show",
-	"history",
-	"import",
-	"load",
-	"images",
-	"rmi",
-	"save",
-	"tag",
-	"annotate",
-	"connect",
-	"disconnect",
-	"demote",
-	"promote",
-	"disable",
-	"enable",
-	"set",
-	"rollback",
-	"scale",
-	"up",
-	"down",
-	"services",
-	"ca",
-	"join",
-	"join-token",
-	"leave",
-	"unlock",
-	"unlock-key",
-	"df",
-	"events",
-	"info",
-	"generate",
-	"add",
-	"revoke",
-	"sign",
-	"login",
-	"azure",
-	"logout",
-	"search",
-	"convert",
 }
diff --git a/cli/metrics/generatecommands/main.go b/cli/metrics/generatecommands/main.go
index 681728cf..8fe8ec78 100644
--- a/cli/metrics/generatecommands/main.go
+++ b/cli/metrics/generatecommands/main.go
@@ -19,20 +19,26 @@ package main
 import (
 	"fmt"
 	"os/exec"
+	"sort"
 	"strings"
 
 	"github.com/docker/compose/v2/pkg/utils"
 )
 
-var managementCommands = []string{"ecs", "scan"}
-
-var commands = []string{}
+var (
+	managementCommands = []string{"ecs", "scan"}
+	commands           = []string{"bundle", "completion", "install", "merge", "render", "split", "status", "uninstall", "validate"}
+)
 
 func main() {
 	fmt.Println("Walking through docker help to list commands...")
 	getCommands()
+	getCommands("buildx")
 	getCommands("compose")
 
+	sort.Strings(managementCommands)
+	sort.Strings(commands)
+
 	fmt.Printf(`
 var managementCommands = []string{
 	"help",
@@ -71,7 +77,9 @@ func getCommands(execCommands ...string) {
 			section = commandsSection
 			if len(execCommands) > 0 {
 				command := execCommands[len(execCommands)-1]
-				managementCommands = append(managementCommands, command)
+				if !utils.StringContains(managementCommands, command) {
+					managementCommands = append(managementCommands, command)
+				}
 			}
 			continue
 		}
diff --git a/cli/metrics/metadata/build.go b/cli/metrics/metadata/build.go
new file mode 100644
index 00000000..49861821
--- /dev/null
+++ b/cli/metrics/metadata/build.go
@@ -0,0 +1,189 @@
+/*
+   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
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"strconv"
+
+	"github.com/docker/cli/cli/config"
+	"github.com/docker/cli/cli/config/configfile"
+	"github.com/docker/docker/api/types"
+	dockerclient "github.com/docker/docker/client"
+	"github.com/spf13/pflag"
+)
+
+// getBuildMetadata returns build metadata for this command
+func getBuildMetadata(cliSource string, command string, args []string) string {
+	var cli, builder string
+	dockercfg := config.LoadDefaultConfigFile(io.Discard)
+	if alias, ok := dockercfg.Aliases["builder"]; ok {
+		command = alias
+	}
+	if command == "build" {
+		cli = "docker"
+		builder = "buildkit"
+		if enabled, _ := isBuildKitEnabled(); !enabled {
+			builder = "legacy"
+		}
+	} else if command == "buildx" {
+		cli = "buildx"
+		builder = buildxDriver(dockercfg, args)
+	}
+	return fmt.Sprintf("%s-%s;%s", cliSource, cli, builder)
+}
+
+// isBuildKitEnabled returns whether buildkit is enabled either through a
+// daemon setting or otherwise the client-side DOCKER_BUILDKIT environment
+// variable
+func isBuildKitEnabled() (bool, error) {
+	if buildkitEnv := os.Getenv("DOCKER_BUILDKIT"); len(buildkitEnv) > 0 {
+		return strconv.ParseBool(buildkitEnv)
+	}
+	apiClient, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation())
+	if err != nil {
+		return false, err
+	}
+	defer apiClient.Close() //nolint:errcheck
+	ping, err := apiClient.Ping(context.Background())
+	if err != nil {
+		return false, err
+	}
+	return ping.BuilderVersion == types.BuilderBuildKit, nil
+}
+
+// buildxConfigDir will look for correct configuration store path;
+// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
+// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
+func buildxConfigDir(dockercfg *configfile.ConfigFile) string {
+	if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
+		return buildxConfig
+	}
+	return filepath.Join(filepath.Dir(dockercfg.Filename), "buildx")
+}
+
+// buildxDriver returns the build driver being used for the build command
+func buildxDriver(dockercfg *configfile.ConfigFile, buildArgs []string) string {
+	driver := "error"
+	configDir := buildxConfigDir(dockercfg)
+	if _, err := os.Stat(configDir); err != nil {
+		return driver
+	}
+	builder := buildxBuilder(buildArgs)
+	if len(builder) == 0 {
+		// if builder not defined in command, seek current in buildx store
+		// `${DOCKER_CONFIG}/buildx/current`
+		fileCurrent := path.Join(configDir, "current")
+		if _, err := os.Stat(fileCurrent); err != nil {
+			return driver
+		}
+		// content looks like
+		// {
+		//   "Key": "unix:///var/run/docker.sock",
+		//   "Name": "builder",
+		//   "Global": false
+		// }
+		rawCurrent, err := ioutil.ReadFile(fileCurrent)
+		if err != nil {
+			return driver
+		}
+		// unmarshal and returns `Name`
+		var obj map[string]interface{}
+		if err = json.Unmarshal(rawCurrent, &obj); err != nil {
+			return driver
+		}
+		if n, ok := obj["Name"]; ok {
+			builder = n.(string)
+			// `Name` will be empty if `default` builder is used
+			// {
+			//   "Key": "unix:///var/run/docker.sock",
+			//   "Name": "",
+			//   "Global": false
+			// }
+			if len(builder) == 0 {
+				builder = "default"
+			}
+		} else {
+			return driver
+		}
+	}
+
+	// if default builder return docker
+	if builder == "default" {
+		return "docker"
+	}
+
+	// read builder info and retrieve the current driver
+	// `${DOCKER_CONFIG}/buildx/instances/<builder>`
+	fileBuilder := path.Join(configDir, "instances", builder)
+	if _, err := os.Stat(fileBuilder); err != nil {
+		return driver
+	}
+	// content looks like
+	// {
+	//   "Name": "builder",
+	//   "Driver": "docker-container",
+	//   "Nodes": [
+	//     {
+	//       "Name": "builder0",
+	//       "Endpoint": "unix:///var/run/docker.sock",
+	//       "Platforms": null,
+	//       "Flags": null,
+	//       "ConfigFile": "",
+	//       "DriverOpts": null
+	//     }
+	//   ],
+	//   "Dynamic": false
+	// }
+	rawBuilder, err := ioutil.ReadFile(fileBuilder)
+	if err != nil {
+		return driver
+	}
+	// unmarshal and returns `Driver`
+	var obj map[string]interface{}
+	if err = json.Unmarshal(rawBuilder, &obj); err != nil {
+		return driver
+	}
+	if d, ok := obj["Driver"]; ok {
+		driver = d.(string)
+	}
+	return driver
+}
+
+// buildxBuilder returns the builder being used in the build command
+func buildxBuilder(buildArgs []string) string {
+	var builder string
+	fset := pflag.NewFlagSet("buildx", pflag.ContinueOnError)
+	fset.String("builder", "", "")
+	_ = fset.ParseAll(buildArgs, func(flag *pflag.Flag, value string) error {
+		if flag.Name == "builder" {
+			builder = value
+		}
+		return nil
+	})
+	if len(builder) == 0 {
+		builder = os.Getenv("BUILDX_BUILDER")
+	}
+	return builder
+}
diff --git a/cli/metrics/metadata/metadata.go b/cli/metrics/metadata/metadata.go
new file mode 100644
index 00000000..7d55ab8b
--- /dev/null
+++ b/cli/metrics/metadata/metadata.go
@@ -0,0 +1,29 @@
+/*
+   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
+}
diff --git a/cli/metrics/metrics.go b/cli/metrics/metrics.go
index 7840892c..11cbfc2e 100644
--- a/cli/metrics/metrics.go
+++ b/cli/metrics/metrics.go
@@ -20,6 +20,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/docker/compose-cli/cli/metrics/metadata"
 	"github.com/docker/compose/v2/pkg/utils"
 )
 
@@ -34,7 +35,7 @@ func Track(context string, args []string, status string) {
 		c.Send(Command{
 			Command: command,
 			Context: context,
-			Source:  CLISource,
+			Source:  metadata.Get(CLISource, args),
 			Status:  status,
 		})
 	}
diff --git a/docs/aci-container-features.md b/docs/aci-container-features.md
index 7d87e2db..19a9e94f 100644
--- a/docs/aci-container-features.md
+++ b/docs/aci-container-features.md
@@ -3,7 +3,7 @@ title: ACI integration container features
 description: Reference list of container ACI features
 keywords: Docker, Azure, Integration, ACI, container, cli, deploy, cloud
 ---
-# Azure Container Instances: running single containers
+# Azure Container Instances: running single containerrs
 
 Single containers can be executed on ACI with the `docker run` command.
 A single container is executed in its own ACI container group, that will container only one container.
diff --git a/ecs/compatibility.go b/ecs/compatibility.go
index 146d01b0..814c239b 100644
--- a/ecs/compatibility.go
+++ b/ecs/compatibility.go
@@ -84,7 +84,6 @@ var compatibleComposeAttributes = []string{
 	"services.healthcheck.timeout",
 	"services.image",
 	"services.init",
-	"services.labels",
 	"services.logging",
 	"services.logging.options",
 	"services.networks",
diff --git a/ecs/convert.go b/ecs/convert.go
index b24420b0..fc23a961 100644
--- a/ecs/convert.go
+++ b/ecs/convert.go
@@ -115,7 +115,6 @@ func (b *ecsAPIService) createTaskDefinition(project *types.Project, service typ
 		DependsOnProp:          dependencies,
 		DnsSearchDomains:       service.DNSSearch,
 		DnsServers:             service.DNS,
-		DockerLabels:           service.Labels,
 		DockerSecurityOptions:  service.SecurityOpt,
 		EntryPoint:             service.Entrypoint,
 		Environment:            pairs,
diff --git a/go.mod b/go.mod
index 5d190e84..fe6f7d82 100644
--- a/go.mod
+++ b/go.mod
@@ -21,10 +21,10 @@ require (
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
 	github.com/compose-spec/compose-go v1.0.5
 	github.com/containerd/console v1.0.2
-	github.com/containerd/containerd v1.5.8
+	github.com/containerd/containerd v1.5.7
 	github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
 	github.com/docker/cli v20.10.7+incompatible
-	github.com/docker/compose/v2 v2.1.1
+	github.com/docker/compose/v2 v2.1.0
 	github.com/docker/docker v20.10.7+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/docker/go-units v0.4.0
@@ -44,7 +44,7 @@ require (
 	github.com/mattn/go-ieproxy v0.0.1 // indirect
 	github.com/morikuni/aec v1.0.0
 	github.com/opencontainers/go-digest v1.0.0
-	github.com/opencontainers/image-spec v1.0.2
+	github.com/opencontainers/image-spec v1.0.1
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/tsdb v0.10.0
 	github.com/rogpeppe/go-internal v1.5.2 // indirect
@@ -59,7 +59,7 @@ require (
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
 	google.golang.org/grpc v1.38.0
-	google.golang.org/protobuf v1.27.1
+	google.golang.org/protobuf v1.26.0
 	gopkg.in/ini.v1 v1.62.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 	gotest.tools v2.2.0+incompatible
diff --git a/go.sum b/go.sum
index 7e5fd9ec..c4b5e0b1 100644
--- a/go.sum
+++ b/go.sum
@@ -130,8 +130,8 @@ github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2
 github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
 github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
 github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
-github.com/Microsoft/hcsshim v0.8.23 h1:47MSwtKGXet80aIn+7h4YI6fwPmwIghAnsx2aOUrG2M=
-github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg=
+github.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM=
+github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
 github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
 github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@@ -224,7 +224,6 @@ github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n
 github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
-github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@@ -296,7 +295,6 @@ github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
 github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.5.0-beta.0.0.20210122062454-5a66c2ae5cec/go.mod h1:p9z+oqCID32tZ7LKgej316N9pJf1iIkQ/7nK1VvEFZE=
 github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
 github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
@@ -305,8 +303,8 @@ github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoT
 github.com/containerd/containerd v1.5.0-rc.3/go.mod h1:kYiJ+LvywDUKzyax6+UKCk5xwQNCfcGR6KsSdShdg5U=
 github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
 github.com/containerd/containerd v1.5.5/go.mod h1:oSTh0QpT1w6jYcGmbiSbxv9OSQYaa88mPyWIuU79zyo=
-github.com/containerd/containerd v1.5.8 h1:NmkCC1/QxyZFBny8JogwLpOy2f+VEbO/f6bV2Mqtwuw=
-github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s=
+github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM=
+github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
 github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
 github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
 github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@@ -345,9 +343,8 @@ github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDG
 github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
 github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
 github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
+github.com/containerd/ttrpc v1.0.2 h1:2/O3oTZN36q2xRolk0a2WWGgh7/Vf/liElg5hFYLX9U=
 github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
-github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lWI=
-github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ=
 github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
 github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
 github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
@@ -426,8 +423,8 @@ github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHv
 github.com/docker/cli-docs-tool v0.1.1/go.mod h1:oMzPNt1wC3TcxuY22GMnOODNOxkwGH51gV3AhqAjFQ4=
 github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE=
 github.com/docker/compose-switch v1.0.2/go.mod h1:uyPj8S3oH1O9rSZ5QVozw28OIjdNIflSSYElC2P0plQ=
-github.com/docker/compose/v2 v2.1.1 h1:Cv2Hlu1EyAp/oo8uaReoGUSf8yX9PBLsa32gQGPd4co=
-github.com/docker/compose/v2 v2.1.1/go.mod h1:95eVFqu5FS0X7KeBqFF71gtwKJPW+28UW0c2JlGaTbQ=
+github.com/docker/compose/v2 v2.1.0 h1:DSn1Rg6/+MHXesty9th8G4tq8mMbjOOfxwx6nX+Jd58=
+github.com/docker/compose/v2 v2.1.0/go.mod h1:ZjLheSkXKciD84MYJqJu5FUOqvv3110tFWvzpBJ56bo=
 github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
 github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
@@ -1080,9 +1077,8 @@ github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
-github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
-github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
@@ -1873,9 +1869,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=


Test patch:
diff --git a/cli/metrics/metadata/build_test.go b/cli/metrics/metadata/build_test.go
new file mode 100644
index 00000000..32a3a355
--- /dev/null
+++ b/cli/metrics/metadata/build_test.go
@@ -0,0 +1,87 @@
+/*
+   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
+
+import (
+	"io"
+	"os"
+	"testing"
+
+	"github.com/docker/cli/cli/config"
+	"gotest.tools/v3/assert"
+)
+
+func TestBuildxBuilder(t *testing.T) {
+	tts := []struct {
+		name     string
+		args     []string
+		expected string
+	}{
+		{
+			name:     "without builder",
+			args:     []string{"buildx", "build", "-t", "foo:bar", "."},
+			expected: "",
+		},
+		{
+			name:     "with builder",
+			args:     []string{"--builder", "foo", "buildx", "build", "."},
+			expected: "foo",
+		},
+	}
+	for _, tt := range tts {
+		t.Run(tt.name, func(t *testing.T) {
+			result := buildxBuilder(tt.args)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestBuildxDriver(t *testing.T) {
+	tts := []struct {
+		name     string
+		cfg      string
+		args     []string
+		expected string
+	}{
+		{
+			name:     "no flag and default builder",
+			cfg:      "./testdata/buildx-default",
+			args:     []string{"buildx", "build", "-t", "foo:bar", "."},
+			expected: "docker",
+		},
+		{
+			name:     "no flag and current builder",
+			cfg:      "./testdata/buildx-container",
+			args:     []string{"buildx", "build", "-t", "foo:bar", "."},
+			expected: "docker-container",
+		},
+		{
+			name:     "builder flag",
+			cfg:      "./testdata/buildx-default",
+			args:     []string{"--builder", "graviton2", "buildx", "build", "."},
+			expected: "docker-container",
+		},
+	}
+
+	for _, tt := range tts {
+		t.Run(tt.name, func(t *testing.T) {
+			_ = os.Setenv("BUILDX_CONFIG", tt.cfg)
+			result := buildxDriver(config.LoadDefaultConfigFile(io.Discard), tt.args)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
diff --git a/cli/metrics/metadata/testdata/buildx-container/current b/cli/metrics/metadata/testdata/buildx-container/current
new file mode 100644
index 00000000..1ae8f69d
--- /dev/null
+++ b/cli/metrics/metadata/testdata/buildx-container/current
@@ -0,0 +1,1 @@
+{"Key":"unix:///var/run/docker.sock","Name":"builder","Global":false}
\ No newline at end of file
diff --git a/cli/metrics/metadata/testdata/buildx-container/instances/builder b/cli/metrics/metadata/testdata/buildx-container/instances/builder
new file mode 100644
index 00000000..f12c83a8
--- /dev/null
+++ b/cli/metrics/metadata/testdata/buildx-container/instances/builder
@@ -0,0 +1,1 @@
+{"Name":"builder","Driver":"docker-container","Nodes":[{"Name":"builder0","Endpoint":"unix:///var/run/docker.sock","Platforms":null,"Flags":["--allow-insecure-entitlement","security.insecure","--allow-insecure-entitlement","network.host"],"DriverOpts":{"env.JAEGER_TRACE":"localhost:6831","image":"moby/buildkit:latest","network":"host"},"Files":null}],"Dynamic":false}
\ No newline at end of file
diff --git a/cli/metrics/metadata/testdata/buildx-default/current b/cli/metrics/metadata/testdata/buildx-default/current
new file mode 100644
index 00000000..f34f695d
--- /dev/null
+++ b/cli/metrics/metadata/testdata/buildx-default/current
@@ -0,0 +1,1 @@
+{"Key":"unix:///var/run/docker.sock","Name":"","Global":false}
\ No newline at end of file
diff --git a/cli/metrics/metadata/testdata/buildx-default/instances/graviton2 b/cli/metrics/metadata/testdata/buildx-default/instances/graviton2
new file mode 100644
index 00000000..ab45e1c0
--- /dev/null
+++ b/cli/metrics/metadata/testdata/buildx-default/instances/graviton2
@@ -0,0 +1,1 @@
+{"Name":"graviton2","Driver":"docker-container","Nodes":[{"Name":"node1","Endpoint":"ssh://ubuntu@18.246.77.142","Platforms":[{"architecture":"arm64","os":"linux"}],"Flags":null,"ConfigFile":"","DriverOpts":{}}],"Dynamic":false}
\ No newline at end of file
diff --git a/cli/metrics/metrics_test.go b/cli/metrics/metrics_test.go
index 8711a07b..5c4a7175 100644
--- a/cli/metrics/metrics_test.go
+++ b/cli/metrics/metrics_test.go
@@ -335,3 +335,49 @@ func TestScan(t *testing.T) {
 		})
 	}
 }
+
+func TestBuild(t *testing.T) {
+	testCases := []struct {
+		name     string
+		args     []string
+		expected string
+	}{
+		{
+			name:     "build",
+			args:     []string{"build", "."},
+			expected: "build",
+		},
+		{
+			name:     "build with flags",
+			args:     []string{"build", "--file", "./Dockerfile", "--tag", "myimage:latest", "."},
+			expected: "build",
+		},
+		{
+			name:     "buildx build",
+			args:     []string{"buildx", "build", "."},
+			expected: "buildx build",
+		},
+		{
+			name:     "buildx build with flags",
+			args:     []string{"buildx", "build", "--file", "./Dockerfile", "--tag", "myimage:latest", "."},
+			expected: "buildx build",
+		},
+		{
+			name:     "buildx build with flags and builder",
+			args:     []string{"buildx", "--builder", "foo", "build", "--file", "./Dockerfile", "--tag", "myimage:latest", "."},
+			expected: "buildx --builder build",
+		},
+		{
+			name:     "buildx version",
+			args:     []string{"buildx", "version"},
+			expected: "buildx version",
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			result := GetCommand(testCase.args)
+			assert.Equal(t, testCase.expected, result)
+		})
+	}
+}
diff --git a/local/e2e/cli-only/e2e_test.go b/local/e2e/cli-only/e2e_test.go
index 896387e2..fb430e9f 100644
--- a/local/e2e/cli-only/e2e_test.go
+++ b/local/e2e/cli-only/e2e_test.go
@@ -210,6 +210,22 @@ func TestContextMetrics(t *testing.T) {
 			`{"command":"context ls","context":"moby","source":"cli","status":"success"}`,
 		}, usage)
 	})
+
+	t.Run("metrics on build", func(t *testing.T) {
+		s.ResetUsage()
+
+		_ = os.Setenv("BUILDX_CONFIG", "./testdata/buildx-config")
+		c.RunDockerOrExitError("build", "-t", "foo:bar", ".")
+		c.RunDockerOrExitError("buildx", "build", "-t", "foo:bar", ".")
+		c.RunDockerOrExitError("buildx", "--builder", "graviton2", "build", "-t", "foo:bar", ".")
+
+		usage := s.GetUsage()
+		assert.DeepEqual(t, []string{
+			`{"command":"build","context":"moby","source":"cli-docker;buildkit","status":"failure"}`,
+			`{"command":"buildx build","context":"moby","source":"cli-buildx;docker-container","status":"failure"}`,
+			`{"command":"buildx --builder build","context":"moby","source":"cli-buildx;docker-container","status":"failure"}`,
+		}, usage)
+	})
 }
 
 func TestContextDuplicateACI(t *testing.T) {
diff --git a/local/e2e/cli-only/testdata/buildx-config/current b/local/e2e/cli-only/testdata/buildx-config/current
new file mode 100644
index 00000000..1ae8f69d
--- /dev/null
+++ b/local/e2e/cli-only/testdata/buildx-config/current
@@ -0,0 +1,1 @@
+{"Key":"unix:///var/run/docker.sock","Name":"builder","Global":false}
\ No newline at end of file
diff --git a/local/e2e/cli-only/testdata/buildx-config/instances/builder b/local/e2e/cli-only/testdata/buildx-config/instances/builder
new file mode 100644
index 00000000..f12c83a8
--- /dev/null
+++ b/local/e2e/cli-only/testdata/buildx-config/instances/builder
@@ -0,0 +1,1 @@
+{"Name":"builder","Driver":"docker-container","Nodes":[{"Name":"builder0","Endpoint":"unix:///var/run/docker.sock","Platforms":null,"Flags":["--allow-insecure-entitlement","security.insecure","--allow-insecure-entitlement","network.host"],"DriverOpts":{"env.JAEGER_TRACE":"localhost:6831","image":"moby/buildkit:latest","network":"host"},"Files":null}],"Dynamic":false}
\ No newline at end of file
diff --git a/local/e2e/cli-only/testdata/buildx-config/instances/graviton2 b/local/e2e/cli-only/testdata/buildx-config/instances/graviton2
new file mode 100644
index 00000000..ab45e1c0
--- /dev/null
+++ b/local/e2e/cli-only/testdata/buildx-config/instances/graviton2
@@ -0,0 +1,1 @@
+{"Name":"graviton2","Driver":"docker-container","Nodes":[{"Name":"node1","Endpoint":"ssh://ubuntu@18.246.77.142","Platforms":[{"architecture":"arm64","os":"linux"}],"Flags":null,"ConfigFile":"","DriverOpts":{}}],"Dynamic":false}
\ No newline at end of file