Browse code

dockerversion: un-export UAStringKey, add WithUpstreamUserAgent

Add a WithUpstreamUserAgent utility instead of exporting the context-key
used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2026/05/12 21:29:29
Showing 3 changed files
... ...
@@ -61,7 +61,7 @@ func (s *Server) makeHTTPHandler(route router.Route) http.HandlerFunc {
61 61
 		// use intermediate variable to prevent "should not use basic type
62 62
 		// string as key in context.WithValue" golint errors
63 63
 		ua := r.Header.Get("User-Agent")
64
-		ctx := baggage.ContextWithBaggage(context.WithValue(r.Context(), dockerversion.UAStringKey{}, ua), otelutil.MustNewBaggage(
64
+		ctx := baggage.ContextWithBaggage(dockerversion.WithUpstreamUserAgent(r.Context(), ua), otelutil.MustNewBaggage(
65 65
 			otelutil.MustNewMemberRaw(otelutil.TriggerKey, "api"),
66 66
 		))
67 67
 
... ...
@@ -10,8 +10,17 @@ import (
10 10
 	"github.com/moby/moby/v2/pkg/useragent"
11 11
 )
12 12
 
13
-// UAStringKey is used as key type for user-agent string in net/context struct
14
-type UAStringKey struct{}
13
+// uaStringKey is used as key type for user-agent string in net/context struct
14
+type uaStringKey struct{}
15
+
16
+// WithUpstreamUserAgent returns a new context carrying the upstream client's
17
+// User-Agent string.
18
+func WithUpstreamUserAgent(ctx context.Context, ua string) context.Context {
19
+	if ua == "" {
20
+		return ctx
21
+	}
22
+	return context.WithValue(ctx, uaStringKey{}, ua)
23
+}
15 24
 
16 25
 // DockerUserAgent is the User-Agent used by the daemon.
17 26
 //
... ...
@@ -71,7 +80,7 @@ func getDaemonUserAgent() string {
71 71
 //
72 72
 // It returns an empty string if no user-agent is present in the context.
73 73
 func getUpstreamUserAgent(ctx context.Context) string {
74
-	upstreamUA, ok := ctx.Value(UAStringKey{}).(string)
74
+	upstreamUA, ok := ctx.Value(uaStringKey{}).(string)
75 75
 	if !ok || upstreamUA == "" {
76 76
 		return ""
77 77
 	}
... ...
@@ -32,12 +32,12 @@ func TestDockerUserAgent(t *testing.T) {
32 32
 		},
33 33
 		{
34 34
 			doc:      "daemon user-agent with upstream",
35
-			ctx:      context.WithValue(t.Context(), UAStringKey{}, "Magic-Client/1.2.3 (linux)"),
35
+			ctx:      WithUpstreamUserAgent(t.Context(), "Magic-Client/1.2.3 (linux)"),
36 36
 			expected: getDaemonUserAgent() + ` UpstreamClient(Magic-Client/1.2.3 \(linux\))`,
37 37
 		},
38 38
 		{
39 39
 			doc: "daemon user-agent with upstream and custom metadata",
40
-			ctx: context.WithValue(t.Context(), UAStringKey{}, "Magic-Client/1.2.3 (linux)"),
40
+			ctx: WithUpstreamUserAgent(t.Context(), "Magic-Client/1.2.3 (linux)"),
41 41
 			metadata: []useragent.VersionInfo{
42 42
 				{Name: "hello", Version: "world"},
43 43
 				{Name: "foo", Version: "bar"},
... ...
@@ -46,12 +46,12 @@ func TestDockerUserAgent(t *testing.T) {
46 46
 		},
47 47
 		{
48 48
 			doc:      "daemon user-agent with upstream special chars",
49
-			ctx:      context.WithValue(t.Context(), UAStringKey{}, `Magic-Client/1.2.3 (linux); \ test`),
49
+			ctx:      WithUpstreamUserAgent(t.Context(), `Magic-Client/1.2.3 (linux); \ test`),
50 50
 			expected: getDaemonUserAgent() + ` UpstreamClient(Magic-Client/1.2.3 \(linux\)\; \\ test)`,
51 51
 		},
52 52
 		{
53 53
 			doc:      "daemon user-agent with upstream control chars",
54
-			ctx:      context.WithValue(t.Context(), UAStringKey{}, "Magic-Client/1.2.3\r\nInjected: evil"),
54
+			ctx:      WithUpstreamUserAgent(t.Context(), "Magic-Client/1.2.3\r\nInjected: evil"),
55 55
 			expected: getDaemonUserAgent() + ` UpstreamClient(Magic-Client/1.2.3Injected: evil)`,
56 56
 		},
57 57
 	}