mirror of https://github.com/docker/buildx.git
vendor: update buildx to latest docker/cli
This version of docker/cli has changes to remove compose-cli wrapper and move all CLI metrics to OTEL. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
parent
afcb609966
commit
4fc4bc07ae
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/util/stack"
|
"github.com/moby/buildkit/util/stack"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
|
||||||
//nolint:staticcheck // vendored dependencies may still use this
|
//nolint:staticcheck // vendored dependencies may still use this
|
||||||
"github.com/containerd/containerd/pkg/seed"
|
"github.com/containerd/containerd/pkg/seed"
|
||||||
|
@ -38,10 +40,27 @@ func runStandalone(cmd *command.DockerCli) error {
|
||||||
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
|
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer flushMetrics(cmd)
|
||||||
|
|
||||||
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
|
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
|
||||||
return rootCmd.Execute()
|
return rootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flushMetrics will manually flush metrics from the configured
|
||||||
|
// meter provider. This is needed when running in standalone mode
|
||||||
|
// because the meter provider is initialized by the cli library,
|
||||||
|
// but the mechanism for forcing it to report is not presently
|
||||||
|
// exposed and not invoked when run in standalone mode.
|
||||||
|
// There are plans to fix that in the next release, but this is
|
||||||
|
// needed temporarily until the API for this is more thorough.
|
||||||
|
func flushMetrics(cmd *command.DockerCli) {
|
||||||
|
if mp, ok := cmd.MeterProvider().(command.MeterProvider); ok {
|
||||||
|
if err := mp.ForceFlush(context.Background()); err != nil {
|
||||||
|
otel.Handle(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func runPlugin(cmd *command.DockerCli) error {
|
func runPlugin(cmd *command.DockerCli) error {
|
||||||
rootCmd := commands.NewRootCmd("buildx", true, cmd)
|
rootCmd := commands.NewRootCmd("buildx", true, cmd)
|
||||||
return plugin.RunPlugin(cmd, rootCmd, manager.Metadata{
|
return plugin.RunPlugin(cmd, rootCmd, manager.Metadata{
|
||||||
|
|
|
@ -269,8 +269,7 @@ func (o *buildOptionsHash) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 := dockerCli.MeterProvider(ctx)
|
mp := dockerCli.MeterProvider()
|
||||||
defer metricutil.Shutdown(ctx, mp)
|
|
||||||
|
|
||||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -14,7 +14,7 @@ require (
|
||||||
github.com/containerd/typeurl/v2 v2.1.1
|
github.com/containerd/typeurl/v2 v2.1.1
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/distribution/reference v0.5.0
|
github.com/distribution/reference v0.5.0
|
||||||
github.com/docker/cli v26.0.1-0.20240410153731-b6c552212837+incompatible // v26.1.0-dev
|
github.com/docker/cli v26.1.3+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.7.0
|
github.com/docker/cli-docs-tool v0.7.0
|
||||||
github.com/docker/docker v26.0.0+incompatible
|
github.com/docker/docker v26.0.0+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -117,8 +117,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v26.0.1-0.20240410153731-b6c552212837+incompatible h1:KTmSJjZSQM+cpaczHecGsBNlgJtRccef/62pCOeiA9o=
|
github.com/docker/cli v26.1.3+incompatible h1:bUpXT/N0kDE3VUHI2r5VMsYQgi38kYuoC0oL9yt3lqc=
|
||||||
github.com/docker/cli v26.0.1-0.20240410153731-b6c552212837+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v26.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli-docs-tool v0.7.0 h1:M2Da98Unz2kz3A5d4yeSGbhyOge2mfYSNjAFt01Rw0M=
|
github.com/docker/cli-docs-tool v0.7.0 h1:M2Da98Unz2kz3A5d4yeSGbhyOge2mfYSNjAFt01Rw0M=
|
||||||
github.com/docker/cli-docs-tool v0.7.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
|
github.com/docker/cli-docs-tool v0.7.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
package metricutil
|
package metricutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/version"
|
"github.com/docker/buildx/version"
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"go.opentelemetry.io/otel"
|
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,10 +11,3 @@ func Meter(mp metric.MeterProvider) metric.Meter {
|
||||||
return mp.Meter(version.Package,
|
return mp.Meter(version.Package,
|
||||||
metric.WithInstrumentationVersion(version.Version))
|
metric.WithInstrumentationVersion(version.Version))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown invokes Shutdown on the MeterProvider and then reports any error to the OTEL handler.
|
|
||||||
func Shutdown(ctx context.Context, mp command.MeterProvider) {
|
|
||||||
if err := mp.Shutdown(ctx); err != nil {
|
|
||||||
otel.Handle(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -71,18 +72,18 @@ func TemplateReplaceArg(i int) string {
|
||||||
return fmt.Sprintf(hookTemplateArg, strconv.Itoa(i))
|
return fmt.Sprintf(hookTemplateArg, strconv.Itoa(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseTemplate(hookTemplate string, cmd *cobra.Command) (string, error) {
|
func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
|
||||||
tmpl := template.New("").Funcs(commandFunctions)
|
tmpl := template.New("").Funcs(commandFunctions)
|
||||||
tmpl, err := tmpl.Parse(hookTemplate)
|
tmpl, err := tmpl.Parse(hookTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
b := bytes.Buffer{}
|
b := bytes.Buffer{}
|
||||||
err = tmpl.Execute(&b, cmd)
|
err = tmpl.Execute(&b, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return b.String(), nil
|
return strings.Split(b.String(), "\n"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
||||||
|
|
|
@ -14,31 +14,42 @@ import (
|
||||||
// that plugins declaring support for hooks get passed when
|
// that plugins declaring support for hooks get passed when
|
||||||
// being invoked following a CLI command execution.
|
// being invoked following a CLI command execution.
|
||||||
type HookPluginData struct {
|
type HookPluginData struct {
|
||||||
|
// RootCmd is a string representing the matching hook configuration
|
||||||
|
// which is currently being invoked. If a hook for `docker context` is
|
||||||
|
// configured and the user executes `docker context ls`, the plugin will
|
||||||
|
// be invoked with `context`.
|
||||||
RootCmd string
|
RootCmd string
|
||||||
Flags map[string]string
|
Flags map[string]string
|
||||||
|
CommandError string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunPluginHooks calls the hook subcommand for all present
|
// RunCLICommandHooks is the entrypoint into the hooks execution flow after
|
||||||
// CLI plugins that declare support for hooks in their metadata
|
// a main CLI command was executed. It calls the hook subcommand for all
|
||||||
// and parses/prints their responses.
|
// present CLI plugins that declare support for hooks in their metadata and
|
||||||
func RunPluginHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, plugin string, args []string) error {
|
// parses/prints their responses.
|
||||||
subCmdName := subCommand.Name()
|
func RunCLICommandHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) {
|
||||||
if plugin != "" {
|
commandName := strings.TrimPrefix(subCommand.CommandPath(), rootCmd.Name()+" ")
|
||||||
subCmdName = plugin
|
flags := getCommandFlags(subCommand)
|
||||||
}
|
|
||||||
var flags map[string]string
|
runHooks(dockerCli, rootCmd, subCommand, commandName, flags, cmdErrorMessage)
|
||||||
if plugin == "" {
|
}
|
||||||
flags = getCommandFlags(subCommand)
|
|
||||||
} else {
|
// RunPluginHooks is the entrypoint for the hooks execution flow
|
||||||
flags = getNaiveFlags(args)
|
// after a plugin command was just executed by the CLI.
|
||||||
}
|
func RunPluginHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, args []string) {
|
||||||
nextSteps := invokeAndCollectHooks(dockerCli, rootCmd, subCommand, subCmdName, flags)
|
commandName := strings.Join(args, " ")
|
||||||
|
flags := getNaiveFlags(args)
|
||||||
|
|
||||||
|
runHooks(dockerCli, rootCmd, subCommand, commandName, flags, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
|
||||||
|
nextSteps := invokeAndCollectHooks(dockerCli, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
|
||||||
|
|
||||||
hooks.PrintNextSteps(dockerCli.Err(), nextSteps)
|
hooks.PrintNextSteps(dockerCli.Err(), nextSteps)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, hookCmdName string, flags map[string]string) []string {
|
func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
|
||||||
pluginsCfg := dockerCli.ConfigFile().Plugins
|
pluginsCfg := dockerCli.ConfigFile().Plugins
|
||||||
if pluginsCfg == nil {
|
if pluginsCfg == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -46,7 +57,8 @@ func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command
|
||||||
|
|
||||||
nextSteps := make([]string, 0, len(pluginsCfg))
|
nextSteps := make([]string, 0, len(pluginsCfg))
|
||||||
for pluginName, cfg := range pluginsCfg {
|
for pluginName, cfg := range pluginsCfg {
|
||||||
if !registersHook(cfg, hookCmdName) {
|
match, ok := pluginMatch(cfg, subCmdStr)
|
||||||
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +67,11 @@ func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
hookReturn, err := p.RunHook(hookCmdName, flags)
|
hookReturn, err := p.RunHook(HookPluginData{
|
||||||
|
RootCmd: match,
|
||||||
|
Flags: flags,
|
||||||
|
CommandError: cmdErrorMessage,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// skip misbehaving plugins, but don't halt execution
|
// skip misbehaving plugins, but don't halt execution
|
||||||
continue
|
continue
|
||||||
|
@ -76,23 +92,46 @@ func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nextSteps = append(nextSteps, processedHook)
|
nextSteps = append(nextSteps, processedHook...)
|
||||||
}
|
}
|
||||||
return nextSteps
|
return nextSteps
|
||||||
}
|
}
|
||||||
|
|
||||||
func registersHook(pluginCfg map[string]string, subCmdName string) bool {
|
// pluginMatch takes a plugin configuration and a string representing the
|
||||||
hookCmdStr, ok := pluginCfg["hooks"]
|
// command being executed (such as 'image ls' – the root 'docker' is omitted)
|
||||||
if !ok {
|
// and, if the configuration includes a hook for the invoked command, returns
|
||||||
return false
|
// the configured hook string.
|
||||||
|
func pluginMatch(pluginCfg map[string]string, subCmd string) (string, bool) {
|
||||||
|
configuredPluginHooks, ok := pluginCfg["hooks"]
|
||||||
|
if !ok || configuredPluginHooks == "" {
|
||||||
|
return "", false
|
||||||
}
|
}
|
||||||
commands := strings.Split(hookCmdStr, ",")
|
|
||||||
|
commands := strings.Split(configuredPluginHooks, ",")
|
||||||
for _, hookCmd := range commands {
|
for _, hookCmd := range commands {
|
||||||
if hookCmd == subCmdName {
|
if hookMatch(hookCmd, subCmd) {
|
||||||
return true
|
return hookCmd, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hookMatch(hookCmd, subCmd string) bool {
|
||||||
|
hookCmdTokens := strings.Split(hookCmd, " ")
|
||||||
|
subCmdTokens := strings.Split(subCmd, " ")
|
||||||
|
|
||||||
|
if len(hookCmdTokens) > len(subCmdTokens) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range hookCmdTokens {
|
||||||
|
if v != subCmdTokens[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCommandFlags(cmd *cobra.Command) map[string]string {
|
func getCommandFlags(cmd *cobra.Command) map[string]string {
|
||||||
|
|
|
@ -240,8 +240,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = append(cmd.Environ(), ReexecEnvvar+"="+os.Args[0])
|
||||||
cmd.Env = append(cmd.Env, ReexecEnvvar+"="+os.Args[0])
|
|
||||||
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin)
|
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin)
|
||||||
|
|
||||||
return cmd, nil
|
return cmd, nil
|
||||||
|
|
|
@ -2,6 +2,7 @@ package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -104,16 +105,16 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
||||||
|
|
||||||
// RunHook executes the plugin's hooks command
|
// RunHook executes the plugin's hooks command
|
||||||
// and returns its unprocessed output.
|
// and returns its unprocessed output.
|
||||||
func (p *Plugin) RunHook(cmdName string, flags map[string]string) ([]byte, error) {
|
func (p *Plugin) RunHook(hookData HookPluginData) ([]byte, error) {
|
||||||
hDataBytes, err := json.Marshal(HookPluginData{
|
hDataBytes, err := json.Marshal(hookData)
|
||||||
RootCmd: cmdName,
|
|
||||||
Flags: flags,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapAsPluginError(err, "failed to marshall hook data")
|
return nil, wrapAsPluginError(err, "failed to marshall hook data")
|
||||||
}
|
}
|
||||||
|
|
||||||
hookCmdOutput, err := exec.Command(p.Path, p.Name, HookSubcommandName, string(hDataBytes)).Output()
|
pCmd := exec.Command(p.Path, p.Name, HookSubcommandName, string(hDataBytes))
|
||||||
|
pCmd.Env = os.Environ()
|
||||||
|
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
|
||||||
|
hookCmdOutput, err := pCmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand")
|
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand")
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,24 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
|
||||||
opts = append(opts, withPluginClientConn(plugin.Name()))
|
opts = append(opts, withPluginClientConn(plugin.Name()))
|
||||||
}
|
}
|
||||||
err = tcmd.Initialize(opts...)
|
err = tcmd.Initialize(opts...)
|
||||||
|
ogRunE := cmd.RunE
|
||||||
|
if ogRunE == nil {
|
||||||
|
ogRun := cmd.Run
|
||||||
|
// necessary because error will always be nil here
|
||||||
|
// see: https://github.com/golangci/golangci-lint/issues/1379
|
||||||
|
//nolint:unparam
|
||||||
|
ogRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
ogRun(cmd, args)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cmd.Run = nil
|
||||||
|
}
|
||||||
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
stopInstrumentation := dockerCli.StartInstrumentation(cmd)
|
||||||
|
err := ogRunE(cmd, args)
|
||||||
|
stopInstrumentation(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,6 +273,11 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||||
return ResolveDefaultContext(cli.options, cli.contextStoreConfig)
|
return ResolveDefaultContext(cli.options, cli.contextStoreConfig)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(krissetto): pass ctx to the funcs instead of using this
|
||||||
|
cli.createGlobalMeterProvider(cli.baseCtx)
|
||||||
|
cli.createGlobalTracerProvider(cli.baseCtx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,35 +41,25 @@ type TelemetryClient interface {
|
||||||
// each time this function is invoked.
|
// each time this function is invoked.
|
||||||
Resource() *resource.Resource
|
Resource() *resource.Resource
|
||||||
|
|
||||||
// TracerProvider returns a TracerProvider. This TracerProvider will be configured
|
// TracerProvider returns the currently initialized TracerProvider. This TracerProvider will be configured
|
||||||
// with the default tracing components for a CLI program along with any options given
|
// with the default tracing components for a CLI program
|
||||||
// for the SDK.
|
TracerProvider() trace.TracerProvider
|
||||||
TracerProvider(ctx context.Context, opts ...sdktrace.TracerProviderOption) TracerProvider
|
|
||||||
|
|
||||||
// MeterProvider returns a MeterProvider. This MeterProvider will be configured
|
// MeterProvider returns the currently initialized MeterProvider. This MeterProvider will be configured
|
||||||
// with the default metric components for a CLI program along with any options given
|
// with the default metric components for a CLI program
|
||||||
// for the SDK.
|
MeterProvider() metric.MeterProvider
|
||||||
MeterProvider(ctx context.Context, opts ...sdkmetric.Option) MeterProvider
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) Resource() *resource.Resource {
|
func (cli *DockerCli) Resource() *resource.Resource {
|
||||||
return cli.res.Get()
|
return cli.res.Get()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) TracerProvider(ctx context.Context, opts ...sdktrace.TracerProviderOption) TracerProvider {
|
func (cli *DockerCli) TracerProvider() trace.TracerProvider {
|
||||||
allOpts := make([]sdktrace.TracerProviderOption, 0, len(opts)+2)
|
return otel.GetTracerProvider()
|
||||||
allOpts = append(allOpts, sdktrace.WithResource(cli.Resource()))
|
|
||||||
allOpts = append(allOpts, dockerSpanExporter(ctx, cli)...)
|
|
||||||
allOpts = append(allOpts, opts...)
|
|
||||||
return sdktrace.NewTracerProvider(allOpts...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) MeterProvider(ctx context.Context, opts ...sdkmetric.Option) MeterProvider {
|
func (cli *DockerCli) MeterProvider() metric.MeterProvider {
|
||||||
allOpts := make([]sdkmetric.Option, 0, len(opts)+2)
|
return otel.GetMeterProvider()
|
||||||
allOpts = append(allOpts, sdkmetric.WithResource(cli.Resource()))
|
|
||||||
allOpts = append(allOpts, dockerMetricExporter(ctx, cli)...)
|
|
||||||
allOpts = append(allOpts, opts...)
|
|
||||||
return sdkmetric.NewMeterProvider(allOpts...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithResourceOptions configures additional options for the default resource. The default
|
// WithResourceOptions configures additional options for the default resource. The default
|
||||||
|
@ -122,6 +112,28 @@ func (r *telemetryResource) init() {
|
||||||
r.opts = nil
|
r.opts = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createGlobalMeterProvider creates a new MeterProvider from the initialized DockerCli struct
|
||||||
|
// with the given options and sets it as the global meter provider
|
||||||
|
func (cli *DockerCli) createGlobalMeterProvider(ctx context.Context, opts ...sdkmetric.Option) {
|
||||||
|
allOpts := make([]sdkmetric.Option, 0, len(opts)+2)
|
||||||
|
allOpts = append(allOpts, sdkmetric.WithResource(cli.Resource()))
|
||||||
|
allOpts = append(allOpts, dockerMetricExporter(ctx, cli)...)
|
||||||
|
allOpts = append(allOpts, opts...)
|
||||||
|
mp := sdkmetric.NewMeterProvider(allOpts...)
|
||||||
|
otel.SetMeterProvider(mp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createGlobalTracerProvider creates a new TracerProvider from the initialized DockerCli struct
|
||||||
|
// with the given options and sets it as the global tracer provider
|
||||||
|
func (cli *DockerCli) createGlobalTracerProvider(ctx context.Context, opts ...sdktrace.TracerProviderOption) {
|
||||||
|
allOpts := make([]sdktrace.TracerProviderOption, 0, len(opts)+2)
|
||||||
|
allOpts = append(allOpts, sdktrace.WithResource(cli.Resource()))
|
||||||
|
allOpts = append(allOpts, dockerSpanExporter(ctx, cli)...)
|
||||||
|
allOpts = append(allOpts, opts...)
|
||||||
|
tp := sdktrace.NewTracerProvider(allOpts...)
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
}
|
||||||
|
|
||||||
func defaultResourceOptions() []resource.Option {
|
func defaultResourceOptions() []resource.Option {
|
||||||
return []resource.Option{
|
return []resource.Option{
|
||||||
resource.WithDetectors(serviceNameDetector{}),
|
resource.WithDetectors(serviceNameDetector{}),
|
||||||
|
@ -174,11 +186,6 @@ func newCLIReader(exp sdkmetric.Exporter) sdkmetric.Reader {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *cliReader) Shutdown(ctx context.Context) error {
|
func (r *cliReader) Shutdown(ctx context.Context) error {
|
||||||
var rm metricdata.ResourceMetrics
|
|
||||||
if err := r.Reader.Collect(ctx, &rm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place a pretty tight constraint on the actual reporting.
|
// Place a pretty tight constraint on the actual reporting.
|
||||||
// We don't want CLI metrics to prevent the CLI from exiting
|
// We don't want CLI metrics to prevent the CLI from exiting
|
||||||
// so if there's some kind of issue we need to abort pretty
|
// so if there's some kind of issue we need to abort pretty
|
||||||
|
@ -186,6 +193,15 @@ func (r *cliReader) Shutdown(ctx context.Context) error {
|
||||||
ctx, cancel := context.WithTimeout(ctx, exportTimeout)
|
ctx, cancel := context.WithTimeout(ctx, exportTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
return r.ForceFlush(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cliReader) ForceFlush(ctx context.Context) error {
|
||||||
|
var rm metricdata.ResourceMetrics
|
||||||
|
if err := r.Reader.Collect(ctx, &rm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return r.exporter.Export(ctx, &rm)
|
return r.exporter.Export(ctx, &rm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,7 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
|
||||||
otelCfg = m[otelContextFieldName]
|
otelCfg = m[otelContextFieldName]
|
||||||
}
|
}
|
||||||
|
|
||||||
if otelCfg == nil {
|
if otelCfg != nil {
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
otelMap, ok := otelCfg.(map[string]any)
|
otelMap, ok := otelCfg.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
otel.Handle(errors.Errorf(
|
otel.Handle(errors.Errorf(
|
||||||
|
@ -53,11 +50,10 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
|
||||||
otelCfg,
|
otelCfg,
|
||||||
otelMap,
|
otelMap,
|
||||||
))
|
))
|
||||||
return "", false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// keys from https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
|
// keys from https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
|
||||||
endpoint, _ = otelMap[otelExporterOTLPEndpoint].(string)
|
endpoint, _ = otelMap[otelExporterOTLPEndpoint].(string)
|
||||||
|
}
|
||||||
|
|
||||||
// Override with env var value if it exists AND IS SET
|
// Override with env var value if it exists AND IS SET
|
||||||
// (ignore otel defaults for this override when the key exists but is empty)
|
// (ignore otel defaults for this override when the key exists but is empty)
|
||||||
|
|
|
@ -26,8 +26,7 @@ func BaseCommandAttributes(cmd *cobra.Command, streams Streams) []attribute.KeyV
|
||||||
// Note: this should be the last func to wrap/modify the PersistentRunE/RunE funcs before command execution.
|
// Note: this should be the last func to wrap/modify the PersistentRunE/RunE funcs before command execution.
|
||||||
//
|
//
|
||||||
// can also be used for spans!
|
// can also be used for spans!
|
||||||
func (cli *DockerCli) InstrumentCobraCommands(cmd *cobra.Command, mp metric.MeterProvider) {
|
func (cli *DockerCli) InstrumentCobraCommands(ctx context.Context, cmd *cobra.Command) {
|
||||||
meter := getDefaultMeter(mp)
|
|
||||||
// If PersistentPreRunE is nil, make it execute PersistentPreRun and return nil by default
|
// If PersistentPreRunE is nil, make it execute PersistentPreRun and return nil by default
|
||||||
ogPersistentPreRunE := cmd.PersistentPreRunE
|
ogPersistentPreRunE := cmd.PersistentPreRunE
|
||||||
if ogPersistentPreRunE == nil {
|
if ogPersistentPreRunE == nil {
|
||||||
|
@ -55,10 +54,9 @@ func (cli *DockerCli) InstrumentCobraCommands(cmd *cobra.Command, mp metric.Mete
|
||||||
}
|
}
|
||||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||||
// start the timer as the first step of every cobra command
|
// start the timer as the first step of every cobra command
|
||||||
baseAttrs := BaseCommandAttributes(cmd, cli)
|
stopInstrumentation := cli.StartInstrumentation(cmd)
|
||||||
stopCobraCmdTimer := startCobraCommandTimer(cmd, meter, baseAttrs)
|
|
||||||
cmdErr := ogRunE(cmd, args)
|
cmdErr := ogRunE(cmd, args)
|
||||||
stopCobraCmdTimer(cmdErr)
|
stopInstrumentation(cmdErr)
|
||||||
return cmdErr
|
return cmdErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +64,17 @@ func (cli *DockerCli) InstrumentCobraCommands(cmd *cobra.Command, mp metric.Mete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startCobraCommandTimer(cmd *cobra.Command, meter metric.Meter, attrs []attribute.KeyValue) func(err error) {
|
// StartInstrumentation instruments CLI commands with the individual metrics and spans configured.
|
||||||
ctx := cmd.Context()
|
// It's the main command OTel utility, and new command-related metrics should be added to it.
|
||||||
|
// It should be called immediately before command execution, and returns a stopInstrumentation function
|
||||||
|
// that must be called with the error resulting from the command execution.
|
||||||
|
func (cli *DockerCli) StartInstrumentation(cmd *cobra.Command) (stopInstrumentation func(error)) {
|
||||||
|
baseAttrs := BaseCommandAttributes(cmd, cli)
|
||||||
|
return startCobraCommandTimer(cli.MeterProvider(), baseAttrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startCobraCommandTimer(mp metric.MeterProvider, attrs []attribute.KeyValue) func(err error) {
|
||||||
|
meter := getDefaultMeter(mp)
|
||||||
durationCounter, _ := meter.Float64Counter(
|
durationCounter, _ := meter.Float64Counter(
|
||||||
"command.time",
|
"command.time",
|
||||||
metric.WithDescription("Measures the duration of the cobra command"),
|
metric.WithDescription("Measures the duration of the cobra command"),
|
||||||
|
@ -76,12 +83,20 @@ func startCobraCommandTimer(cmd *cobra.Command, meter metric.Meter, attrs []attr
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
return func(err error) {
|
return func(err error) {
|
||||||
|
// Use a new context for the export so that the command being cancelled
|
||||||
|
// doesn't affect the metrics, and we get metrics for cancelled commands.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), exportTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
duration := float64(time.Since(start)) / float64(time.Millisecond)
|
duration := float64(time.Since(start)) / float64(time.Millisecond)
|
||||||
cmdStatusAttrs := attributesFromError(err)
|
cmdStatusAttrs := attributesFromError(err)
|
||||||
durationCounter.Add(ctx, duration,
|
durationCounter.Add(ctx, duration,
|
||||||
metric.WithAttributes(attrs...),
|
metric.WithAttributes(attrs...),
|
||||||
metric.WithAttributes(cmdStatusAttrs...),
|
metric.WithAttributes(cmdStatusAttrs...),
|
||||||
)
|
)
|
||||||
|
if mp, ok := mp.(MeterProvider); ok {
|
||||||
|
mp.ForceFlush(ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ github.com/davecgh/go-spew/spew
|
||||||
# github.com/distribution/reference v0.5.0
|
# github.com/distribution/reference v0.5.0
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
github.com/distribution/reference
|
github.com/distribution/reference
|
||||||
# github.com/docker/cli v26.0.1-0.20240410153731-b6c552212837+incompatible
|
# github.com/docker/cli v26.1.3+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
github.com/docker/cli/cli
|
github.com/docker/cli/cli
|
||||||
github.com/docker/cli/cli-plugins/hooks
|
github.com/docker/cli/cli-plugins/hooks
|
||||||
|
|
Loading…
Reference in New Issue