diff --git a/cli/command/cli.go b/cli/command/cli.go index c62d6be3c44b..2d5e3088b9f3 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -78,6 +78,7 @@ type DockerCli struct { dockerEndpoint docker.Endpoint contextStoreConfig *store.Config initTimeout time.Duration + userAgent string res telemetryResource // baseCtx is the base context used for internal operations. In the future @@ -313,10 +314,10 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile. if err != nil { return nil, errors.Wrap(err, "unable to resolve docker endpoint") } - return newAPIClientFromEndpoint(endpoint, configFile) + return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent())) } -func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) { +func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) { opts, err := ep.ClientOpts() if err != nil { return nil, err @@ -331,7 +332,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF if withCustomHeaders != nil { opts = append(opts, withCustomHeaders) } - opts = append(opts, client.WithUserAgent(UserAgent())) + opts = append(opts, extraOpts...) return client.NewClientWithOpts(opts...) } @@ -552,7 +553,8 @@ func (cli *DockerCli) initialize() error { return } if cli.client == nil { - if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil { + ops := []client.Opt{client.WithUserAgent(cli.userAgent)} + if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil { return } } @@ -599,6 +601,7 @@ func NewDockerCli(ops ...CLIOption) (*DockerCli, error) { WithContentTrustFromEnv(), WithDefaultContextStoreConfig(), WithStandardStreams(), + WithUserAgent(UserAgent()), } ops = append(defaultOps, ops...) @@ -622,7 +625,7 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) { } } -// UserAgent returns the user agent string used for making API requests +// UserAgent returns the default user agent string used for making API requests. func UserAgent() string { return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")" } diff --git a/cli/command/cli_options.go b/cli/command/cli_options.go index a8bdd88eb486..0318fb8c27ab 100644 --- a/cli/command/cli_options.go +++ b/cli/command/cli_options.go @@ -236,3 +236,14 @@ func withCustomHeadersFromEnv() (client.Opt, error) { // see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc) return client.WithHTTPHeaders(env), nil } + +// WithUserAgent configures the User-Agent string for cli HTTP requests. +func WithUserAgent(userAgent string) CLIOption { + return func(cli *DockerCli) error { + if userAgent == "" { + return errors.New("user agent cannot be blank") + } + cli.userAgent = userAgent + return nil + } +} diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index cc30aed92689..859d6865fa78 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -374,3 +374,26 @@ func TestSetGoDebug(t *testing.T) { assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG")) }) } + +func TestNewDockerCliWithCustomUserAgent(t *testing.T) { + var received string + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + received = r.UserAgent() + w.WriteHeader(http.StatusOK) + })) + defer ts.Close() + host := strings.Replace(ts.URL, "http://", "tcp://", 1) + opts := &flags.ClientOptions{Hosts: []string{host}} + + cli, err := NewDockerCli( + WithUserAgent("fake-agent/0.0.1"), + ) + assert.NilError(t, err) + cli.currentContext = DefaultContextName + cli.options = opts + cli.configFile = &configfile.ConfigFile{} + + _, err = cli.Client().Ping(context.Background()) + assert.NilError(t, err) + assert.DeepEqual(t, received, "fake-agent/0.0.1") +} diff --git a/cli/command/manifest/annotate.go b/cli/command/manifest/annotate.go index cf9f21944517..d832588453e1 100644 --- a/cli/command/manifest/annotate.go +++ b/cli/command/manifest/annotate.go @@ -54,6 +54,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig { return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index) } + // FIXME(thaJeztah): this should use the userAgent as configured on the dockerCLI. return registryclient.NewRegistryClient(resolver, command.UserAgent(), allowInsecure) }