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

Commit

Permalink
add build metrics
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
  • Loading branch information
crazy-max committed Nov 19, 2021
1 parent a4ae60a commit 9c9d3e0
Show file tree
Hide file tree
Showing 16 changed files with 424 additions and 8 deletions.
9 changes: 5 additions & 4 deletions cli/metrics/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ type client struct {

// Command is a command
type Command struct {
Command string `json:"command"`
Context string `json:"context"`
Source string `json:"source"`
Status string `json:"status"`
Command string `json:"command"`
Context string `json:"context"`
Source string `json:"source"`
Status string `json:"status"`
Metadata string `json:"metadata,omitempty"`
}

// CLISource is sent for cli metrics
Expand Down
2 changes: 2 additions & 0 deletions cli/metrics/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package metrics
var commandFlags = []string{
//added to catch scan details
"--version", "--login",
// added for build
"--builder", "--platforms",
}

// Generated with generatecommands/main.go
Expand Down
192 changes: 192 additions & 0 deletions cli/metrics/metadata/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
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"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv"

"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
metadatatypes "github.com/docker/compose-cli/cli/metrics/metadata/types"
"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(command string, args []string) metadatatypes.Build {
var bm metadatatypes.Build
dockercfg := config.LoadDefaultConfigFile(io.Discard)
if alias, ok := dockercfg.Aliases["builder"]; ok {
command = alias
}
if command == "build" {
// TODO(@crazy-max): include cli version (e.g., docker;20.10.10)
bm.Cli = "docker"
bm.Builder = "buildkit"
if enabled, _ := isBuildKitEnabled(); !enabled {
bm.Builder = "legacy"
}
} else if command == "buildx" {
// TODO(@crazy-max): include buildx version (e.g., buildx;0.6.3)
bm.Cli = "buildx"
bm.Builder = buildxDriver(dockercfg, args)
}
return bm
}

// 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)
}
// TODO(@crazy-max): include buildkit version being used by this driver (e.g., docker-container;0.9.2)
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
}
87 changes: 87 additions & 0 deletions cli/metrics/metadata/build_test.go
Original file line number Diff line number Diff line change
@@ -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{"build", "-t", "foo:bar", "."},
expected: "",
},
{
name: "with builder",
args: []string{"--builder", "foo", "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{"build", "-t", "foo:bar", "."},
expected: "docker",
},
{
name: "no flag and current builder",
cfg: "./testdata/buildx-container",
args: []string{"build", "-t", "foo:bar", "."},
expected: "docker-container",
},
{
name: "builder flag",
cfg: "./testdata/buildx-default",
args: []string{"--builder", "graviton2", "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)
})
}
}
37 changes: 37 additions & 0 deletions cli/metrics/metadata/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
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 (
"encoding/json"

metadatatypes "github.com/docker/compose-cli/cli/metrics/metadata/types"
)

// Get returns the JSON metadata linked to the invoked command
func Get(command string, args []string) string {
var m metadatatypes.Metadata
if command == "build" || command == "buildx" {
m.Build = getBuildMetadata(command, args)
}
if (metadatatypes.Metadata{}) != m {
if b, err := json.Marshal(m); err == nil {
return string(b)
}
}
return ""
}
1 change: 1 addition & 0 deletions cli/metrics/metadata/testdata/buildx-container/current
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"Key":"unix:///var/run/docker.sock","Name":"builder","Global":false}
Original file line number Diff line number Diff line change
@@ -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}
1 change: 1 addition & 0 deletions cli/metrics/metadata/testdata/buildx-default/current
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"Key":"unix:///var/run/docker.sock","Name":"","Global":false}
Original file line number Diff line number Diff line change
@@ -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}
26 changes: 26 additions & 0 deletions cli/metrics/metadata/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
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 metadatatypes

type Metadata struct {
Build Build `json:"build,omitempty"`
}

type Build struct {
Cli string `json:"cli,omitempty"`
Builder string `json:"builder,omitempty"`
}
Loading

0 comments on commit 9c9d3e0

Please sign in to comment.