Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 535fb93

Browse files
committed
add build metrics
Signed-off-by: CrazyMax <[email protected]>
1 parent a4ae60a commit 535fb93

File tree

14 files changed

+378
-1
lines changed

14 files changed

+378
-1
lines changed

cli/metrics/commands.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package metrics
1919
var commandFlags = []string{
2020
//added to catch scan details
2121
"--version", "--login",
22+
// added for build
23+
"--builder", "--platforms",
2224
}
2325

2426
// Generated with generatecommands/main.go

cli/metrics/metadata/build.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metadata
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"fmt"
23+
"io"
24+
"io/ioutil"
25+
"os"
26+
"path"
27+
"path/filepath"
28+
"strconv"
29+
30+
"github.com/docker/cli/cli/config"
31+
"github.com/docker/cli/cli/config/configfile"
32+
"github.com/docker/docker/api/types"
33+
dockerclient "github.com/docker/docker/client"
34+
"github.com/spf13/pflag"
35+
)
36+
37+
// getBuildMetadata returns build metadata for this command
38+
func getBuildMetadata(cliSource string, command string, args []string) string {
39+
var cli, builder string
40+
dockercfg := config.LoadDefaultConfigFile(io.Discard)
41+
if alias, ok := dockercfg.Aliases["builder"]; ok {
42+
command = alias
43+
}
44+
if command == "build" {
45+
cli = "docker"
46+
builder = "buildkit"
47+
if enabled, _ := isBuildKitEnabled(); !enabled {
48+
builder = "legacy"
49+
}
50+
} else if command == "buildx" {
51+
cli = "buildx"
52+
builder = buildxDriver(dockercfg, args)
53+
}
54+
return fmt.Sprintf("%s-%s;%s", cliSource, cli, builder)
55+
}
56+
57+
// isBuildKitEnabled returns whether buildkit is enabled either through a
58+
// daemon setting or otherwise the client-side DOCKER_BUILDKIT environment
59+
// variable
60+
func isBuildKitEnabled() (bool, error) {
61+
if buildkitEnv := os.Getenv("DOCKER_BUILDKIT"); len(buildkitEnv) > 0 {
62+
return strconv.ParseBool(buildkitEnv)
63+
}
64+
apiClient, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation())
65+
if err != nil {
66+
return false, err
67+
}
68+
defer apiClient.Close() //nolint:errcheck
69+
ping, err := apiClient.Ping(context.Background())
70+
if err != nil {
71+
return false, err
72+
}
73+
return ping.BuilderVersion == types.BuilderBuildKit, nil
74+
}
75+
76+
// buildxConfigDir will look for correct configuration store path;
77+
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
78+
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
79+
func buildxConfigDir(dockercfg *configfile.ConfigFile) string {
80+
if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
81+
return buildxConfig
82+
}
83+
return filepath.Join(filepath.Dir(dockercfg.Filename), "buildx")
84+
}
85+
86+
// buildxDriver returns the build driver being used for the build command
87+
func buildxDriver(dockercfg *configfile.ConfigFile, buildArgs []string) string {
88+
driver := "error"
89+
configDir := buildxConfigDir(dockercfg)
90+
if _, err := os.Stat(configDir); err != nil {
91+
return driver
92+
}
93+
builder := buildxBuilder(buildArgs)
94+
if len(builder) == 0 {
95+
// if builder not defined in command, seek current in buildx store
96+
// `${DOCKER_CONFIG}/buildx/current`
97+
fileCurrent := path.Join(configDir, "current")
98+
if _, err := os.Stat(fileCurrent); err != nil {
99+
return driver
100+
}
101+
// content looks like
102+
// {
103+
// "Key": "unix:///var/run/docker.sock",
104+
// "Name": "builder",
105+
// "Global": false
106+
// }
107+
rawCurrent, err := ioutil.ReadFile(fileCurrent)
108+
if err != nil {
109+
return driver
110+
}
111+
// unmarshal and returns `Name`
112+
var obj map[string]interface{}
113+
if err = json.Unmarshal(rawCurrent, &obj); err != nil {
114+
return driver
115+
}
116+
if n, ok := obj["Name"]; ok {
117+
builder = n.(string)
118+
// `Name` will be empty if `default` builder is used
119+
// {
120+
// "Key": "unix:///var/run/docker.sock",
121+
// "Name": "",
122+
// "Global": false
123+
// }
124+
if len(builder) == 0 {
125+
builder = "default"
126+
}
127+
} else {
128+
return driver
129+
}
130+
}
131+
132+
// if default builder return docker
133+
if builder == "default" {
134+
return "docker"
135+
}
136+
137+
// read builder info and retrieve the current driver
138+
// `${DOCKER_CONFIG}/buildx/instances/<builder>`
139+
fileBuilder := path.Join(configDir, "instances", builder)
140+
if _, err := os.Stat(fileBuilder); err != nil {
141+
return driver
142+
}
143+
// content looks like
144+
// {
145+
// "Name": "builder",
146+
// "Driver": "docker-container",
147+
// "Nodes": [
148+
// {
149+
// "Name": "builder0",
150+
// "Endpoint": "unix:///var/run/docker.sock",
151+
// "Platforms": null,
152+
// "Flags": null,
153+
// "ConfigFile": "",
154+
// "DriverOpts": null
155+
// }
156+
// ],
157+
// "Dynamic": false
158+
// }
159+
rawBuilder, err := ioutil.ReadFile(fileBuilder)
160+
if err != nil {
161+
return driver
162+
}
163+
// unmarshal and returns `Driver`
164+
var obj map[string]interface{}
165+
if err = json.Unmarshal(rawBuilder, &obj); err != nil {
166+
return driver
167+
}
168+
if d, ok := obj["Driver"]; ok {
169+
driver = d.(string)
170+
}
171+
return driver
172+
}
173+
174+
// buildxBuilder returns the builder being used in the build command
175+
func buildxBuilder(buildArgs []string) string {
176+
var builder string
177+
fset := pflag.NewFlagSet("buildx", pflag.ContinueOnError)
178+
fset.String("builder", "", "")
179+
_ = fset.ParseAll(buildArgs, func(flag *pflag.Flag, value string) error {
180+
if flag.Name == "builder" {
181+
builder = value
182+
}
183+
return nil
184+
})
185+
if len(builder) == 0 {
186+
builder = os.Getenv("BUILDX_BUILDER")
187+
}
188+
return builder
189+
}

cli/metrics/metadata/build_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metadata
18+
19+
import (
20+
"io"
21+
"os"
22+
"testing"
23+
24+
"github.com/docker/cli/cli/config"
25+
"gotest.tools/v3/assert"
26+
)
27+
28+
func TestBuildxBuilder(t *testing.T) {
29+
tts := []struct {
30+
name string
31+
args []string
32+
expected string
33+
}{
34+
{
35+
name: "without builder",
36+
args: []string{"buildx", "build", "-t", "foo:bar", "."},
37+
expected: "",
38+
},
39+
{
40+
name: "with builder",
41+
args: []string{"--builder", "foo", "buildx", "build", "."},
42+
expected: "foo",
43+
},
44+
}
45+
for _, tt := range tts {
46+
t.Run(tt.name, func(t *testing.T) {
47+
result := buildxBuilder(tt.args)
48+
assert.Equal(t, tt.expected, result)
49+
})
50+
}
51+
}
52+
53+
func TestBuildxDriver(t *testing.T) {
54+
tts := []struct {
55+
name string
56+
cfg string
57+
args []string
58+
expected string
59+
}{
60+
{
61+
name: "no flag and default builder",
62+
cfg: "./testdata/buildx-default",
63+
args: []string{"buildx", "build", "-t", "foo:bar", "."},
64+
expected: "docker",
65+
},
66+
{
67+
name: "no flag and current builder",
68+
cfg: "./testdata/buildx-container",
69+
args: []string{"buildx", "build", "-t", "foo:bar", "."},
70+
expected: "docker-container",
71+
},
72+
{
73+
name: "builder flag",
74+
cfg: "./testdata/buildx-default",
75+
args: []string{"--builder", "graviton2", "buildx", "build", "."},
76+
expected: "docker-container",
77+
},
78+
}
79+
80+
for _, tt := range tts {
81+
t.Run(tt.name, func(t *testing.T) {
82+
_ = os.Setenv("BUILDX_CONFIG", tt.cfg)
83+
result := buildxDriver(config.LoadDefaultConfigFile(io.Discard), tt.args)
84+
assert.Equal(t, tt.expected, result)
85+
})
86+
}
87+
}

cli/metrics/metadata/metadata.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metadata
18+
19+
// Get returns the JSON metadata linked to the invoked command
20+
func Get(cliSource string, args []string) string {
21+
if len(args) == 0 {
22+
return cliSource
23+
}
24+
switch args[0] {
25+
case "build", "buildx":
26+
cliSource = getBuildMetadata(cliSource, args[0], args[1:])
27+
}
28+
return cliSource
29+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"Key":"unix:///var/run/docker.sock","Name":"builder","Global":false}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"Key":"unix:///var/run/docker.sock","Name":"","Global":false}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"Name":"graviton2","Driver":"docker-container","Nodes":[{"Name":"node1","Endpoint":"ssh://[email protected]","Platforms":[{"architecture":"arm64","os":"linux"}],"Flags":null,"ConfigFile":"","DriverOpts":{}}],"Dynamic":false}

cli/metrics/metrics.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"os"
2121
"strings"
2222

23+
"github.com/docker/compose-cli/cli/metrics/metadata"
2324
"github.com/docker/compose/v2/pkg/utils"
2425
)
2526

@@ -34,7 +35,7 @@ func Track(context string, args []string, status string) {
3435
c.Send(Command{
3536
Command: command,
3637
Context: context,
37-
Source: CLISource,
38+
Source: metadata.Get(CLISource, args),
3839
Status: status,
3940
})
4041
}

cli/metrics/metrics_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,49 @@ func TestScan(t *testing.T) {
335335
})
336336
}
337337
}
338+
339+
func TestBuild(t *testing.T) {
340+
testCases := []struct {
341+
name string
342+
args []string
343+
expected string
344+
}{
345+
{
346+
name: "build",
347+
args: []string{"build", "."},
348+
expected: "build",
349+
},
350+
{
351+
name: "build with flags",
352+
args: []string{"build", "--file", "./Dockerfile", "--tag", "myimage:latest", "."},
353+
expected: "build",
354+
},
355+
{
356+
name: "buildx build",
357+
args: []string{"buildx", "build", "."},
358+
expected: "buildx build",
359+
},
360+
{
361+
name: "buildx build with flags",
362+
args: []string{"buildx", "build", "--file", "./Dockerfile", "--tag", "myimage:latest", "."},
363+
expected: "buildx build",
364+
},
365+
{
366+
name: "buildx build with flags and builder",
367+
args: []string{"buildx", "--builder", "foo", "build", "--file", "./Dockerfile", "--tag", "myimage:latest", "."},
368+
expected: "buildx --builder build",
369+
},
370+
{
371+
name: "buildx version",
372+
args: []string{"buildx", "version"},
373+
expected: "buildx version",
374+
},
375+
}
376+
377+
for _, testCase := range testCases {
378+
t.Run(testCase.name, func(t *testing.T) {
379+
result := GetCommand(testCase.args)
380+
assert.Equal(t, testCase.expected, result)
381+
})
382+
}
383+
}

0 commit comments

Comments
 (0)