mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-23 20:27:46 +08:00
Merge pull request #2225 from jsternberg/command-duration-metric
metrics: add build command duration metric
This commit is contained in:
commit
c9d1c41d20
@ -4,10 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
_ "crypto/sha256" // ensure digests can be computed
|
_ "crypto/sha256" // ensure digests can be computed
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -26,9 +24,11 @@ import (
|
|||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/resolver"
|
"github.com/docker/buildx/util/resolver"
|
||||||
"github.com/docker/buildx/util/waitmap"
|
"github.com/docker/buildx/util/waitmap"
|
||||||
@ -389,7 +389,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
|
|||||||
if p, err := filepath.Abs(sharedKey); err == nil {
|
if p, err := filepath.Abs(sharedKey); err == nil {
|
||||||
sharedKey = filepath.Base(p)
|
sharedKey = filepath.Base(p)
|
||||||
}
|
}
|
||||||
so.SharedKey = sharedKey + ":" + tryNodeIdentifier(configDir)
|
so.SharedKey = sharedKey + ":" + confutil.TryNodeIdentifier(configDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.Pull {
|
if opt.Pull {
|
||||||
@ -1148,7 +1148,7 @@ func LoadInputs(ctx context.Context, d *driver.DriverHandle, inp Inputs, pw prog
|
|||||||
target.LocalDirs["context"] = inp.ContextPath
|
target.LocalDirs["context"] = inp.ContextPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case isLocalDir(inp.ContextPath):
|
case osutil.IsLocalDir(inp.ContextPath):
|
||||||
target.LocalDirs["context"] = inp.ContextPath
|
target.LocalDirs["context"] = inp.ContextPath
|
||||||
switch inp.DockerfilePath {
|
switch inp.DockerfilePath {
|
||||||
case "-":
|
case "-":
|
||||||
@ -1465,31 +1465,6 @@ func handleLowercaseDockerfile(dir, p string) string {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeIdentifierMu sync.Mutex
|
|
||||||
|
|
||||||
func tryNodeIdentifier(configDir string) (out string) {
|
|
||||||
nodeIdentifierMu.Lock()
|
|
||||||
defer nodeIdentifierMu.Unlock()
|
|
||||||
sessionFile := filepath.Join(configDir, ".buildNodeID")
|
|
||||||
if _, err := os.Lstat(sessionFile); err != nil {
|
|
||||||
if os.IsNotExist(err) { // create a new file with stored randomness
|
|
||||||
b := make([]byte, 8)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dt, err := os.ReadFile(sessionFile)
|
|
||||||
if err == nil {
|
|
||||||
return string(dt)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func noPrintFunc(opt map[string]Options) bool {
|
func noPrintFunc(opt map[string]Options) bool {
|
||||||
for _, v := range opt {
|
for _, v := range opt {
|
||||||
if v.PrintFunc != nil {
|
if v.PrintFunc != nil {
|
||||||
|
19
build/git.go
19
build/git.go
@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/util/gitutil"
|
"github.com/docker/buildx/util/gitutil"
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -46,9 +47,9 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
|||||||
if filepath.IsAbs(contextPath) {
|
if filepath.IsAbs(contextPath) {
|
||||||
wd = contextPath
|
wd = contextPath
|
||||||
} else {
|
} else {
|
||||||
wd, _ = filepath.Abs(filepath.Join(getWd(), contextPath))
|
wd, _ = filepath.Abs(filepath.Join(osutil.GetWd(), contextPath))
|
||||||
}
|
}
|
||||||
wd = gitutil.SanitizePath(wd)
|
wd = osutil.SanitizePath(wd)
|
||||||
|
|
||||||
gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -104,7 +105,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
|||||||
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
||||||
}
|
}
|
||||||
if !filepath.IsAbs(dockerfilePath) {
|
if !filepath.IsAbs(dockerfilePath) {
|
||||||
dockerfilePath = filepath.Join(getWd(), dockerfilePath)
|
dockerfilePath = filepath.Join(osutil.GetWd(), dockerfilePath)
|
||||||
}
|
}
|
||||||
if r, err := filepath.Rel(root, dockerfilePath); err == nil && !strings.HasPrefix(r, "..") {
|
if r, err := filepath.Rel(root, dockerfilePath); err == nil && !strings.HasPrefix(r, "..") {
|
||||||
res["label:"+DockerfileLabel] = r
|
res["label:"+DockerfileLabel] = r
|
||||||
@ -124,21 +125,13 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if lp, err := getLongPathName(dir); err == nil {
|
if lp, err := osutil.GetLongPathName(dir); err == nil {
|
||||||
dir = lp
|
dir = lp
|
||||||
}
|
}
|
||||||
dir = gitutil.SanitizePath(dir)
|
dir = osutil.SanitizePath(dir)
|
||||||
if r, err := filepath.Rel(root, dir); err == nil && !strings.HasPrefix(r, "..") {
|
if r, err := filepath.Rel(root, dir); err == nil && !strings.HasPrefix(r, "..") {
|
||||||
so.FrontendAttrs["vcs:localdir:"+k] = r
|
so.FrontendAttrs["vcs:localdir:"+k] = r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWd() string {
|
|
||||||
wd, _ := os.Getwd()
|
|
||||||
if lp, err := getLongPathName(wd); err == nil {
|
|
||||||
return lp
|
|
||||||
}
|
|
||||||
return wd
|
|
||||||
}
|
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package build
|
|
||||||
|
|
||||||
// getLongPathName is a no-op on non-Windows platforms.
|
|
||||||
func getLongPathName(path string) (string, error) {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
@ -34,11 +33,6 @@ func IsRemoteURL(c string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLocalDir(c string) bool {
|
|
||||||
st, err := os.Stat(c)
|
|
||||||
return err == nil && st.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
func isArchive(header []byte) bool {
|
func isArchive(header []byte) bool {
|
||||||
for _, m := range [][]byte{
|
for _, m := range [][]byte{
|
||||||
{0x42, 0x5A, 0x68}, // bzip2
|
{0x42, 0x5A, 0x68}, // bzip2
|
||||||
|
@ -3,8 +3,10 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -13,6 +15,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
@ -28,9 +32,11 @@ import (
|
|||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/docker/buildx/util/cobrautil"
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/ioset"
|
"github.com/docker/buildx/util/ioset"
|
||||||
"github.com/docker/buildx/util/metricutil"
|
"github.com/docker/buildx/util/metricutil"
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/tracing"
|
"github.com/docker/buildx/util/tracing"
|
||||||
"github.com/docker/cli-docs-tool/annotation"
|
"github.com/docker/cli-docs-tool/annotation"
|
||||||
@ -52,6 +58,8 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -211,6 +219,52 @@ func (o *buildOptions) toDisplayMode() (progressui.DisplayMode, error) {
|
|||||||
return progress, nil
|
return progress, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildMetricAttributes(dockerCli command.Cli, b *builder.Builder, options *buildOptions) attribute.Set {
|
||||||
|
return attribute.NewSet(
|
||||||
|
attribute.String("command.name", "build"),
|
||||||
|
attribute.Stringer("command.options.hash", &buildOptionsHash{
|
||||||
|
buildOptions: options,
|
||||||
|
configDir: confutil.ConfigDir(dockerCli),
|
||||||
|
}),
|
||||||
|
attribute.String("driver.name", options.builder),
|
||||||
|
attribute.String("driver.type", b.Driver),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildOptionsHash computes a hash for the buildOptions when the String method is invoked.
|
||||||
|
// This is done so we can delay the computation of the hash until needed by OTEL using
|
||||||
|
// the fmt.Stringer interface.
|
||||||
|
type buildOptionsHash struct {
|
||||||
|
*buildOptions
|
||||||
|
configDir string
|
||||||
|
result string
|
||||||
|
resultOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *buildOptionsHash) String() string {
|
||||||
|
o.resultOnce.Do(func() {
|
||||||
|
target := o.target
|
||||||
|
contextPath := o.contextPath
|
||||||
|
dockerfile := o.dockerfileName
|
||||||
|
if dockerfile == "" {
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
if contextPath != "-" && osutil.IsLocalDir(contextPath) {
|
||||||
|
contextPath = osutil.ToAbs(contextPath)
|
||||||
|
}
|
||||||
|
salt := confutil.TryNodeIdentifier(o.configDir)
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
for _, s := range []string{target, contextPath, dockerfile, salt} {
|
||||||
|
_, _ = io.WriteString(h, s)
|
||||||
|
h.Write([]byte{0})
|
||||||
|
}
|
||||||
|
o.result = hex.EncodeToString(h.Sum(nil))
|
||||||
|
})
|
||||||
|
return o.result
|
||||||
|
}
|
||||||
|
|
||||||
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) {
|
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) {
|
||||||
mp, err := metricutil.NewMeterProvider(ctx, dockerCli)
|
mp, err := metricutil.NewMeterProvider(ctx, dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -279,6 +333,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attributes := buildMetricAttributes(dockerCli, b, &options)
|
||||||
|
done := timeBuildCommand(mp, attributes)
|
||||||
var resp *client.SolveResponse
|
var resp *client.SolveResponse
|
||||||
var retErr error
|
var retErr error
|
||||||
if isExperimental() {
|
if isExperimental() {
|
||||||
@ -290,6 +346,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
|||||||
if err := printer.Wait(); retErr == nil {
|
if err := printer.Wait(); retErr == nil {
|
||||||
retErr = err
|
retErr = err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done(retErr)
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
@ -933,3 +991,38 @@ func maybeJSONArray(v string) []string {
|
|||||||
}
|
}
|
||||||
return []string{v}
|
return []string{v}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeBuildCommand will start a timer for timing the build command. It records the time when the returned
|
||||||
|
// function is invoked into a metric.
|
||||||
|
func timeBuildCommand(mp metric.MeterProvider, attrs attribute.Set) func(err error) {
|
||||||
|
meter := metricutil.Meter(mp)
|
||||||
|
counter, _ := meter.Float64Counter("command.time",
|
||||||
|
metric.WithDescription("Measures the duration of the build command."),
|
||||||
|
metric.WithUnit("ms"),
|
||||||
|
)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
return func(err error) {
|
||||||
|
dur := float64(time.Since(start)) / float64(time.Millisecond)
|
||||||
|
extraAttrs := attribute.NewSet()
|
||||||
|
if err != nil {
|
||||||
|
extraAttrs = attribute.NewSet(
|
||||||
|
attribute.String("error.type", otelErrorType(err)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
counter.Add(context.Background(), dur,
|
||||||
|
metric.WithAttributeSet(attrs),
|
||||||
|
metric.WithAttributeSet(extraAttrs),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otelErrorType returns an attribute for the error type based on the error category.
|
||||||
|
// If nil, this function returns an invalid attribute.
|
||||||
|
func otelErrorType(err error) string {
|
||||||
|
name := "generic"
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
name = "canceled"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
34
util/confutil/node.go
Normal file
34
util/confutil/node.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package confutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nodeIdentifierMu sync.Mutex
|
||||||
|
|
||||||
|
func TryNodeIdentifier(configDir string) (out string) {
|
||||||
|
nodeIdentifierMu.Lock()
|
||||||
|
defer nodeIdentifierMu.Unlock()
|
||||||
|
sessionFile := filepath.Join(configDir, ".buildNodeID")
|
||||||
|
if _, err := os.Lstat(sessionFile); err != nil {
|
||||||
|
if os.IsNotExist(err) { // create a new file with stored randomness
|
||||||
|
b := make([]byte, 8)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(sessionFile)
|
||||||
|
if err == nil {
|
||||||
|
return string(dt)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ func (c *Git) RootDir() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return SanitizePath(root), nil
|
return osutil.SanitizePath(root), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Git) GitDir() (string, error) {
|
func (c *Git) GitDir() (string, error) {
|
||||||
|
@ -7,8 +7,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/moby/sys/mountinfo"
|
"github.com/moby/sys/mountinfo"
|
||||||
)
|
)
|
||||||
@ -42,18 +40,3 @@ func gitPath(wd string) (string, error) {
|
|||||||
}
|
}
|
||||||
return exec.LookPath("git")
|
return exec.LookPath("git")
|
||||||
}
|
}
|
||||||
|
|
||||||
var windowsPathRegex = regexp.MustCompile(`^[A-Za-z]:[\\/].*$`)
|
|
||||||
|
|
||||||
func SanitizePath(path string) string {
|
|
||||||
// If we're running in WSL, we need to convert Windows paths to Unix paths.
|
|
||||||
// This is because the git binary can be invoked through `git.exe` and
|
|
||||||
// therefore returns Windows paths.
|
|
||||||
if os.Getenv("WSL_DISTRO_NAME") != "" && windowsPathRegex.MatchString(path) {
|
|
||||||
unixPath := strings.ReplaceAll(path, "\\", "/")
|
|
||||||
drive := strings.ToLower(string(unixPath[0]))
|
|
||||||
rest := filepath.Clean(unixPath[3:])
|
|
||||||
return filepath.Join("/mnt", drive, rest)
|
|
||||||
}
|
|
||||||
return filepath.Clean(path)
|
|
||||||
}
|
|
||||||
|
@ -6,14 +6,15 @@ package gitutil
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSanitizePathUnix(t *testing.T) {
|
func TestSanitizePathUnix(t *testing.T) {
|
||||||
assert.Equal(t, "/home/foobar", SanitizePath("/home/foobar"))
|
assert.Equal(t, "/home/foobar", osutil.SanitizePath("/home/foobar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSanitizePathWSL(t *testing.T) {
|
func TestSanitizePathWSL(t *testing.T) {
|
||||||
t.Setenv("WSL_DISTRO_NAME", "Ubuntu")
|
t.Setenv("WSL_DISTRO_NAME", "Ubuntu")
|
||||||
assert.Equal(t, "/mnt/c/Users/foobar", SanitizePath("C:\\Users\\foobar"))
|
assert.Equal(t, "/mnt/c/Users/foobar", osutil.SanitizePath("C:\\Users\\foobar"))
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,8 @@ package gitutil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func gitPath(wd string) (string, error) {
|
func gitPath(wd string) (string, error) {
|
||||||
return exec.LookPath("git.exe")
|
return exec.LookPath("git.exe")
|
||||||
}
|
}
|
||||||
|
|
||||||
func SanitizePath(path string) string {
|
|
||||||
return filepath.ToSlash(filepath.Clean(path))
|
|
||||||
}
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/osutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ func TestSanitizePathWindows(t *testing.T) {
|
|||||||
if isGitBash() {
|
if isGitBash() {
|
||||||
expected = "C:/Users/foobar"
|
expected = "C:/Users/foobar"
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, SanitizePath("C:/Users/foobar"))
|
assert.Equal(t, expected, osutil.SanitizePath("C:/Users/foobar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGitBash() bool {
|
func isGitBash() bool {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/docker/buildx/version"
|
"github.com/docker/buildx/version"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
"go.opentelemetry.io/otel/metric/noop"
|
"go.opentelemetry.io/otel/metric/noop"
|
||||||
@ -86,6 +87,7 @@ func (m *MeterProvider) Report(ctx context.Context) {
|
|||||||
var rm metricdata.ResourceMetrics
|
var rm metricdata.ResourceMetrics
|
||||||
if err := m.reader.Collect(ctx, &rm); err != nil {
|
if err := m.reader.Collect(ctx, &rm); err != nil {
|
||||||
// Error when collecting metrics. Do not send any.
|
// Error when collecting metrics. Do not send any.
|
||||||
|
otel.Handle(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +95,9 @@ func (m *MeterProvider) Report(ctx context.Context) {
|
|||||||
for _, exp := range m.exporters {
|
for _, exp := range m.exporters {
|
||||||
exp := exp
|
exp := exp
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
_ = exp.Export(ctx, &rm)
|
if err := exp.Export(ctx, &rm); err != nil {
|
||||||
|
otel.Handle(err)
|
||||||
|
}
|
||||||
_ = exp.Shutdown(ctx)
|
_ = exp.Shutdown(ctx)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
33
util/metricutil/resource_test.go
Normal file
33
util/metricutil/resource_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package metricutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResource(t *testing.T) {
|
||||||
|
setErrorHandler(t)
|
||||||
|
|
||||||
|
// Ensure resource creation doesn't result in an error.
|
||||||
|
// This is because the schema urls for the various attributes need to be
|
||||||
|
// the same, but it's really easy to import the wrong package when upgrading
|
||||||
|
// otel to anew version and the buildx CLI swallows any visible errors.
|
||||||
|
res := Resource()
|
||||||
|
|
||||||
|
// Ensure an attribute is present.
|
||||||
|
assert.True(t, res.Set().HasValue("telemetry.sdk.version"), "resource attribute missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setErrorHandler(tb testing.TB) {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
errorHandler := otel.GetErrorHandler()
|
||||||
|
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
|
||||||
|
tb.Errorf("otel error: %s", err)
|
||||||
|
}))
|
||||||
|
tb.Cleanup(func() {
|
||||||
|
otel.SetErrorHandler(errorHandler)
|
||||||
|
})
|
||||||
|
}
|
30
util/osutil/path.go
Normal file
30
util/osutil/path.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package osutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetWd retrieves the current working directory.
|
||||||
|
//
|
||||||
|
// On Windows, this function will return the long path name
|
||||||
|
// version of the path.
|
||||||
|
func GetWd() string {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
if lp, err := GetLongPathName(wd); err == nil {
|
||||||
|
return lp
|
||||||
|
}
|
||||||
|
return wd
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsLocalDir(c string) bool {
|
||||||
|
st, err := os.Stat(c)
|
||||||
|
return err == nil && st.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToAbs(path string) string {
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
path, _ = filepath.Abs(filepath.Join(GetWd(), path))
|
||||||
|
}
|
||||||
|
return SanitizePath(path)
|
||||||
|
}
|
31
util/osutil/path_unix.go
Normal file
31
util/osutil/path_unix.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package osutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetLongPathName is a no-op on non-Windows platforms.
|
||||||
|
func GetLongPathName(path string) (string, error) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var windowsPathRegex = regexp.MustCompile(`^[A-Za-z]:[\\/].*$`)
|
||||||
|
|
||||||
|
func SanitizePath(path string) string {
|
||||||
|
// If we're running in WSL, we need to convert Windows paths to Unix paths.
|
||||||
|
// This is because the git binary can be invoked through `git.exe` and
|
||||||
|
// therefore returns Windows paths.
|
||||||
|
if os.Getenv("WSL_DISTRO_NAME") != "" && windowsPathRegex.MatchString(path) {
|
||||||
|
unixPath := strings.ReplaceAll(path, "\\", "/")
|
||||||
|
drive := strings.ToLower(string(unixPath[0]))
|
||||||
|
rest := filepath.Clean(unixPath[3:])
|
||||||
|
return filepath.Join("/mnt", drive, rest)
|
||||||
|
}
|
||||||
|
return filepath.Clean(path)
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
package build
|
package osutil
|
||||||
|
|
||||||
import "golang.org/x/sys/windows"
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
// getLongPathName converts Windows short pathnames to full pathnames.
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetLongPathName converts Windows short pathnames to full pathnames.
|
||||||
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
|
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
|
||||||
func getLongPathName(path string) (string, error) {
|
func GetLongPathName(path string) (string, error) {
|
||||||
// See https://groups.google.com/forum/#!topic/golang-dev/1tufzkruoTg
|
// See https://groups.google.com/forum/#!topic/golang-dev/1tufzkruoTg
|
||||||
p, err := windows.UTF16FromString(path)
|
p, err := windows.UTF16FromString(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -24,3 +28,7 @@ func getLongPathName(path string) (string, error) {
|
|||||||
}
|
}
|
||||||
return windows.UTF16ToString(b), nil
|
return windows.UTF16ToString(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SanitizePath(path string) string {
|
||||||
|
return filepath.ToSlash(filepath.Clean(path))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user