Skip to content

Commit 048e931

Browse files
milasthaJeztah
authored andcommitted
cli/command: add WithUserAgent option
Add support to the `cli/command` package to accept a custom User Agent to pass to the underlying client. This is used as the `UpstreamClient` portion of the `User-Agent` when the Moby daemon makes requests. For example, pushing and pulling images with Compose might result in the registry seeing a `User-Agent` value of: ``` docker/24.0.7 go/go1.20.10 git-commit/311b9ff kernel/6.5.13-linuxkit os/linux arch/arm64 UpstreamClient(docker-cli-plugin-compose/v2.24.0) ``` Signed-off-by: Milas Bowman <[email protected]> Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 8fbb70a commit 048e931

File tree

4 files changed

+44
-5
lines changed

4 files changed

+44
-5
lines changed

cli/command/cli.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ type DockerCli struct {
7777
dockerEndpoint docker.Endpoint
7878
contextStoreConfig *store.Config
7979
initTimeout time.Duration
80+
userAgent string
8081
res telemetryResource
8182

8283
// baseCtx is the base context used for internal operations. In the future
@@ -312,10 +313,10 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
312313
if err != nil {
313314
return nil, fmt.Errorf("unable to resolve docker endpoint: %w", err)
314315
}
315-
return newAPIClientFromEndpoint(endpoint, configFile)
316+
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
316317
}
317318

318-
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
319+
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
319320
opts, err := ep.ClientOpts()
320321
if err != nil {
321322
return nil, err
@@ -330,7 +331,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
330331
if withCustomHeaders != nil {
331332
opts = append(opts, withCustomHeaders)
332333
}
333-
opts = append(opts, client.WithUserAgent(UserAgent()))
334+
opts = append(opts, extraOpts...)
334335
return client.NewClientWithOpts(opts...)
335336
}
336337

@@ -551,7 +552,8 @@ func (cli *DockerCli) initialize() error {
551552
return
552553
}
553554
if cli.client == nil {
554-
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
555+
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
556+
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
555557
return
556558
}
557559
}
@@ -598,6 +600,7 @@ func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
598600
WithContentTrustFromEnv(),
599601
WithDefaultContextStoreConfig(),
600602
WithStandardStreams(),
603+
WithUserAgent(UserAgent()),
601604
}
602605
ops = append(defaultOps, ops...)
603606

@@ -621,7 +624,7 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
621624
}
622625
}
623626

624-
// UserAgent returns the user agent string used for making API requests
627+
// UserAgent returns the default user agent string used for making API requests.
625628
func UserAgent() string {
626629
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
627630
}

cli/command/cli_options.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package command
33
import (
44
"context"
55
"encoding/csv"
6+
"errors"
67
"fmt"
78
"io"
89
"net/http"
@@ -236,3 +237,14 @@ func withCustomHeadersFromEnv() (client.Opt, error) {
236237
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
237238
return client.WithHTTPHeaders(env), nil
238239
}
240+
241+
// WithUserAgent configures the User-Agent string for cli HTTP requests.
242+
func WithUserAgent(userAgent string) CLIOption {
243+
return func(cli *DockerCli) error {
244+
if userAgent == "" {
245+
return errors.New("user agent cannot be blank")
246+
}
247+
cli.userAgent = userAgent
248+
return nil
249+
}
250+
}

cli/command/cli_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,26 @@ func TestSetGoDebug(t *testing.T) {
373373
assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG"))
374374
})
375375
}
376+
377+
func TestNewDockerCliWithCustomUserAgent(t *testing.T) {
378+
var received string
379+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
380+
received = r.UserAgent()
381+
w.WriteHeader(http.StatusOK)
382+
}))
383+
defer ts.Close()
384+
host := strings.Replace(ts.URL, "http://", "tcp://", 1)
385+
opts := &flags.ClientOptions{Hosts: []string{host}}
386+
387+
cli, err := NewDockerCli(
388+
WithUserAgent("fake-agent/0.0.1"),
389+
)
390+
assert.NilError(t, err)
391+
cli.currentContext = DefaultContextName
392+
cli.options = opts
393+
cli.configFile = &configfile.ConfigFile{}
394+
395+
_, err = cli.Client().Ping(context.Background())
396+
assert.NilError(t, err)
397+
assert.DeepEqual(t, received, "fake-agent/0.0.1")
398+
}

cli/command/manifest/annotate.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient
5353
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
5454
return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index)
5555
}
56+
// FIXME(thaJeztah): this should use the userAgent as configured on the dockerCLI.
5657
return registryclient.NewRegistryClient(resolver, command.UserAgent(), allowInsecure)
5758
}
5859

0 commit comments

Comments
 (0)