diff --git a/daemon/containerd/resolver.go b/daemon/containerd/resolver.go index 01f06b48ae..85f115f83a 100644 --- a/daemon/containerd/resolver.go +++ b/daemon/containerd/resolver.go @@ -8,8 +8,10 @@ import ( "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" + "github.com/containerd/containerd/version" registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/dockerversion" + "github.com/docker/docker/pkg/useragent" "github.com/docker/docker/registry" "github.com/sirupsen/logrus" ) @@ -19,7 +21,7 @@ func (i *ImageService) newResolverFromAuthConfig(ctx context.Context, authConfig hosts := hostsWrapper(i.registryHosts, authConfig, i.registryService) headers := http.Header{} - headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx)) + headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx, useragent.VersionInfo{Name: "containerd-client", Version: version.Version}, useragent.VersionInfo{Name: "storage-driver", Version: i.snapshotter})) return docker.NewResolver(docker.ResolverOptions{ Hosts: hosts, diff --git a/dockerversion/useragent.go b/dockerversion/useragent.go index 7fc3b69a75..7013a4543d 100644 --- a/dockerversion/useragent.go +++ b/dockerversion/useragent.go @@ -17,12 +17,12 @@ type UAStringKey struct{} // In accordance with RFC 7231 (5.5.3) is of the form: // // [docker client's UA] UpstreamClient([upstream client's UA]) -func DockerUserAgent(ctx context.Context) string { - daemonUA := getDaemonUserAgent() - if upstreamUA := getUserAgentFromContext(ctx); len(upstreamUA) > 0 { - return insertUpstreamUserAgent(upstreamUA, daemonUA) +func DockerUserAgent(ctx context.Context, extraVersions ...useragent.VersionInfo) string { + ua := useragent.AppendVersions(getDaemonUserAgent(), extraVersions...) + if upstreamUA := getUpstreamUserAgent(ctx); upstreamUA != "" { + ua += " " + upstreamUA } - return daemonUA + return ua } var ( @@ -57,20 +57,29 @@ func getDaemonUserAgent() string { return daemonUA } -// getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists -func getUserAgentFromContext(ctx context.Context) string { +// getUpstreamUserAgent returns the previously saved user-agent context stored +// in ctx, if one exists, and formats it as: +// +// UpstreamClient() +// +// It returns an empty string if no user-agent is present in the context. +func getUpstreamUserAgent(ctx context.Context) string { var upstreamUA string if ctx != nil { - var ki interface{} = ctx.Value(UAStringKey{}) - if ki != nil { + if ki := ctx.Value(UAStringKey{}); ki != nil { upstreamUA = ctx.Value(UAStringKey{}).(string) } } - return upstreamUA + if upstreamUA == "" { + return "" + } + return fmt.Sprintf("UpstreamClient(%s)", escapeStr(upstreamUA)) } +const charsToEscape = `();\` + // escapeStr returns s with every rune in charsToEscape escaped by a backslash -func escapeStr(s string, charsToEscape string) string { +func escapeStr(s string) string { var ret string for _, currRune := range s { appended := false @@ -87,13 +96,3 @@ func escapeStr(s string, charsToEscape string) string { } return ret } - -// insertUpstreamUserAgent adds the upstream client useragent to create a user-agent -// string of the form: -// -// $dockerUA UpstreamClient($upstreamUA) -func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string { - charsToEscape := `();\` - upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape) - return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped) -} diff --git a/dockerversion/useragent_test.go b/dockerversion/useragent_test.go new file mode 100644 index 0000000000..b9fe3d2dfa --- /dev/null +++ b/dockerversion/useragent_test.go @@ -0,0 +1,38 @@ +package dockerversion + +import ( + "context" + "testing" + + "github.com/docker/docker/pkg/useragent" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestDockerUserAgent(t *testing.T) { + t.Run("daemon user-agent", func(t *testing.T) { + ua := DockerUserAgent(context.TODO()) + expected := getDaemonUserAgent() + assert.Check(t, is.Equal(ua, expected)) + }) + + t.Run("daemon user-agent custom metadata", func(t *testing.T) { + ua := DockerUserAgent(context.TODO(), useragent.VersionInfo{Name: "hello", Version: "world"}, useragent.VersionInfo{Name: "foo", Version: "bar"}) + expected := getDaemonUserAgent() + ` hello/world foo/bar` + assert.Check(t, is.Equal(ua, expected)) + }) + + t.Run("daemon user-agent with upstream", func(t *testing.T) { + ctx := context.WithValue(context.TODO(), UAStringKey{}, "Magic-Client/1.2.3 (linux)") + ua := DockerUserAgent(ctx) + expected := getDaemonUserAgent() + ` UpstreamClient(Magic-Client/1.2.3 \(linux\))` + assert.Check(t, is.Equal(ua, expected)) + }) + + t.Run("daemon user-agent with upstream and custom metadata", func(t *testing.T) { + ctx := context.WithValue(context.TODO(), UAStringKey{}, "Magic-Client/1.2.3 (linux)") + ua := DockerUserAgent(ctx, useragent.VersionInfo{Name: "hello", Version: "world"}, useragent.VersionInfo{Name: "foo", Version: "bar"}) + expected := getDaemonUserAgent() + ` hello/world foo/bar UpstreamClient(Magic-Client/1.2.3 \(linux\))` + assert.Check(t, is.Equal(ua, expected)) + }) +}