1
0
mirror of https://github.com/docker/compose-cli.git synced 2026-10-23 18:09:06 +03:00

Merge pull request #2104 from crazy-max/build-metrics

Build metrics
This commit is contained in:
Mathieu Champlon
2021-11-23 13:50:46 +01:00
committed by GitHub
15 changed files with 479 additions and 94 deletions

View File

@@ -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",
"config",
"connect",
"convert",
"cp",
"create",
"demote",
"deploy",
"df",
"diff",
"disable",
"disconnect",
"down",
"du",
"ecs",
"enable",
"events",
"exec",
"export",
"f",
"generate",
"history",
"images",
"import",
"info",
"init",
"inspect",
"install",
"deploy",
"list",
"ls",
"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",
"diff",
"exec",
"export",
"kill",
"logs",
"ps",
"pause",
"port",
"rename",
"restart",
"run",
"start",
"stats",
"top",
"unpause",
"update",
"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",
"kill",
"leave",
"list",
"load",
"login",
"logout",
"logs",
"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",
"df",
"events",
"info",
"generate",
"add",
"revoke",
"sign",
"login",
"azure",
"logout",
"search",
"convert",
"unpause",
"up",
"update",
"upgrade",
"use",
"validate",
"version",
"wait",
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
})
}
}

View File

@@ -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
}

View File

@@ -0,0 +1 @@
{"Key":"unix:///var/run/docker.sock","Name":"builder","Global":false}

View File

@@ -0,0 +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}

View File

@@ -0,0 +1 @@
{"Key":"unix:///var/run/docker.sock","Name":"","Global":false}

View File

@@ -0,0 +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}

View File

@@ -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,
})
}

View File

@@ -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)
})
}
}

View File

@@ -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) {

View File

@@ -0,0 +1 @@
{"Key":"unix:///var/run/docker.sock","Name":"builder","Global":false}

View File

@@ -0,0 +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}

View File

@@ -0,0 +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}