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

Commit 3c99253

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

File tree

12 files changed

+405
-8
lines changed

12 files changed

+405
-8
lines changed

cli/metrics/client.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ type client struct {
3232

3333
// Command is a command
3434
type Command struct {
35-
Command string `json:"command"`
36-
Context string `json:"context"`
37-
Source string `json:"source"`
38-
Status string `json:"status"`
35+
Command string `json:"command"`
36+
Context string `json:"context"`
37+
Source string `json:"source"`
38+
Status string `json:"status"`
39+
Metadata []byte `json:"metadata,omitempty"`
3940
}
4041

4142
// CLISource is sent for cli metrics

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: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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+
"io"
23+
"io/ioutil"
24+
"os"
25+
"path"
26+
"path/filepath"
27+
"strconv"
28+
29+
"github.com/docker/cli/cli/config"
30+
"github.com/docker/cli/cli/config/configfile"
31+
metadatatypes "github.com/docker/compose-cli/cli/metrics/metadata/types"
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(command string, args []string) metadatatypes.Build {
39+
var bm metadatatypes.Build
40+
dockercfg := config.LoadDefaultConfigFile(io.Discard)
41+
if alias, ok := dockercfg.Aliases["builder"]; ok {
42+
command = alias
43+
}
44+
if command == "build" {
45+
// TODO(@crazy-max): include cli version (e.g., docker;20.10.10)
46+
bm.Cli = "docker"
47+
bm.Builder = "buildkit"
48+
if enabled, _ := isBuildKitEnabled(); !enabled {
49+
bm.Builder = "legacy"
50+
}
51+
} else if command == "buildx" {
52+
// TODO(@crazy-max): include buildx version (e.g., buildx;0.6.3)
53+
bm.Cli = "buildx"
54+
bm.Builder = buildxDriver(dockercfg, args)
55+
}
56+
return bm
57+
}
58+
59+
// isBuildKitEnabled returns whether buildkit is enabled either through a
60+
// daemon setting or otherwise the client-side DOCKER_BUILDKIT environment
61+
// variable
62+
func isBuildKitEnabled() (bool, error) {
63+
if buildkitEnv := os.Getenv("DOCKER_BUILDKIT"); len(buildkitEnv) > 0 {
64+
return strconv.ParseBool(buildkitEnv)
65+
}
66+
apiClient, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation())
67+
if err != nil {
68+
return false, err
69+
}
70+
defer apiClient.Close() //nolint:errcheck
71+
ping, err := apiClient.Ping(context.Background())
72+
if err != nil {
73+
return false, err
74+
}
75+
return ping.BuilderVersion == types.BuilderBuildKit, nil
76+
}
77+
78+
// buildxConfigDir will look for correct configuration store path;
79+
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
80+
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
81+
func buildxConfigDir(dockercfg *configfile.ConfigFile) string {
82+
if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
83+
return buildxConfig
84+
}
85+
return filepath.Join(filepath.Dir(dockercfg.Filename), "buildx")
86+
}
87+
88+
// buildxDriver returns the build driver being used for the build command
89+
func buildxDriver(dockercfg *configfile.ConfigFile, buildArgs []string) string {
90+
driver := "error"
91+
configDir := buildxConfigDir(dockercfg)
92+
if _, err := os.Stat(configDir); err != nil {
93+
return driver
94+
}
95+
builder := buildxBuilder(buildArgs)
96+
if len(builder) == 0 {
97+
// if builder not defined in command, seek current in buildx store
98+
// `${DOCKER_CONFIG}/buildx/current`
99+
fileCurrent := path.Join(configDir, "current")
100+
if _, err := os.Stat(fileCurrent); err != nil {
101+
return driver
102+
}
103+
// content looks like
104+
// {
105+
// "Key": "unix:///var/run/docker.sock",
106+
// "Name": "builder",
107+
// "Global": false
108+
// }
109+
rawCurrent, err := ioutil.ReadFile(fileCurrent)
110+
if err != nil {
111+
return driver
112+
}
113+
// unmarshal and returns `Name`
114+
var obj map[string]interface{}
115+
if err = json.Unmarshal(rawCurrent, &obj); err != nil {
116+
return driver
117+
}
118+
if n, ok := obj["Name"]; ok {
119+
builder = n.(string)
120+
// `Name` will be empty if `default` builder is used
121+
// {
122+
// "Key": "unix:///var/run/docker.sock",
123+
// "Name": "",
124+
// "Global": false
125+
// }
126+
if len(builder) == 0 {
127+
builder = "default"
128+
}
129+
} else {
130+
return driver
131+
}
132+
}
133+
134+
// if default builder return docker
135+
if builder == "default" {
136+
return "docker"
137+
}
138+
139+
// read builder info and retrieve the current driver
140+
// `${DOCKER_CONFIG}/buildx/instances/<builder>`
141+
fileBuilder := path.Join(configDir, "instances", builder)
142+
if _, err := os.Stat(fileBuilder); err != nil {
143+
return driver
144+
}
145+
// content looks like
146+
// {
147+
// "Name": "builder",
148+
// "Driver": "docker-container",
149+
// "Nodes": [
150+
// {
151+
// "Name": "builder0",
152+
// "Endpoint": "unix:///var/run/docker.sock",
153+
// "Platforms": null,
154+
// "Flags": null,
155+
// "ConfigFile": "",
156+
// "DriverOpts": null
157+
// }
158+
// ],
159+
// "Dynamic": false
160+
// }
161+
rawBuilder, err := ioutil.ReadFile(fileBuilder)
162+
if err != nil {
163+
return driver
164+
}
165+
// unmarshal and returns `Driver`
166+
var obj map[string]interface{}
167+
if err = json.Unmarshal(rawBuilder, &obj); err != nil {
168+
return driver
169+
}
170+
if d, ok := obj["Driver"]; ok {
171+
driver = d.(string)
172+
}
173+
// TODO(@crazy-max): include buildkit version being used by this driver (e.g., docker-container;0.9.2)
174+
return driver
175+
}
176+
177+
// buildxBuilder returns the builder being used in the build command
178+
func buildxBuilder(buildArgs []string) string {
179+
var builder string
180+
fset := pflag.NewFlagSet("buildx", pflag.ContinueOnError)
181+
fset.String("builder", "", "")
182+
_ = fset.ParseAll(buildArgs, func(flag *pflag.Flag, value string) error {
183+
if flag.Name == "builder" {
184+
builder = value
185+
}
186+
return nil
187+
})
188+
if len(builder) == 0 {
189+
builder = os.Getenv("BUILDX_BUILDER")
190+
}
191+
return builder
192+
}

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{"build", "-t", "foo:bar", "."},
37+
expected: "",
38+
},
39+
{
40+
name: "with builder",
41+
args: []string{"--builder", "foo", "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{"build", "-t", "foo:bar", "."},
64+
expected: "docker",
65+
},
66+
{
67+
name: "no flag and current builder",
68+
cfg: "./testdata/buildx-container",
69+
args: []string{"build", "-t", "foo:bar", "."},
70+
expected: "docker-container",
71+
},
72+
{
73+
name: "builder flag",
74+
cfg: "./testdata/buildx-default",
75+
args: []string{"--builder", "graviton2", "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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
"encoding/json"
21+
22+
metadatatypes "github.com/docker/compose-cli/cli/metrics/metadata/types"
23+
)
24+
25+
// Get returns the JSON metadata linked to the invoked command
26+
func Get(command string, args []string) []byte {
27+
var m metadatatypes.Metadata
28+
if command == "build" || command == "buildx" {
29+
m.Build = getBuildMetadata(command, args)
30+
}
31+
if (metadatatypes.Metadata{}) != m {
32+
if b, err := json.Marshal(m); err == nil {
33+
return b
34+
}
35+
}
36+
return nil
37+
}
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}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 metadatatypes
18+
19+
type Metadata struct {
20+
Build Build `json:"build,omitempty"`
21+
}
22+
23+
type Build struct {
24+
Cli string `json:"cli,omitempty"`
25+
Builder string `json:"builder,omitempty"`
26+
}

0 commit comments

Comments
 (0)