Skip to content

Commit 09e46d2

Browse files
GODRIVER-1412 Add client metadata support for wrapping libraries (#1892)
1 parent 563588a commit 09e46d2

File tree

8 files changed

+243
-57
lines changed

8 files changed

+243
-57
lines changed

internal/integration/handshake_test.go

+49-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"go.mongodb.org/mongo-driver/v2/internal/handshake"
1919
"go.mongodb.org/mongo-driver/v2/internal/integration/mtest"
2020
"go.mongodb.org/mongo-driver/v2/internal/require"
21+
"go.mongodb.org/mongo-driver/v2/mongo/options"
2122
"go.mongodb.org/mongo-driver/v2/version"
2223
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
2324
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage"
@@ -34,19 +35,31 @@ func TestHandshakeProse(t *testing.T) {
3435
CreateCollection(false).
3536
ClientType(mtest.Proxy)
3637

37-
clientMetadata := func(env bson.D) bson.D {
38+
clientMetadata := func(env bson.D, info *options.DriverInfo) bson.D {
39+
var (
40+
driverName = "mongo-go-driver"
41+
driverVersion = version.Driver
42+
platform = runtime.Version()
43+
)
44+
45+
if info != nil {
46+
driverName = driverName + "|" + info.Name
47+
driverVersion = driverVersion + "|" + info.Version
48+
platform = platform + "|" + info.Platform
49+
}
50+
3851
elems := bson.D{
3952
{Key: "driver", Value: bson.D{
40-
{Key: "name", Value: "mongo-go-driver"},
41-
{Key: "version", Value: version.Driver},
53+
{Key: "name", Value: driverName},
54+
{Key: "version", Value: driverVersion},
4255
}},
4356
{Key: "os", Value: bson.D{
4457
{Key: "type", Value: runtime.GOOS},
4558
{Key: "architecture", Value: runtime.GOARCH},
4659
}},
4760
}
4861

49-
elems = append(elems, bson.E{Key: "platform", Value: runtime.Version()})
62+
elems = append(elems, bson.E{Key: "platform", Value: platform})
5063

5164
// If env is empty, don't include it in the metadata.
5265
if env != nil && !reflect.DeepEqual(env, bson.D{}) {
@@ -56,6 +69,12 @@ func TestHandshakeProse(t *testing.T) {
5669
return elems
5770
}
5871

72+
driverInfo := &options.DriverInfo{
73+
Name: "outer-library-name",
74+
Version: "outer-library-version",
75+
Platform: "outer-library-platform",
76+
}
77+
5978
// Reset the environment variables to avoid environment namespace
6079
// collision.
6180
t.Setenv("AWS_EXECUTION_ENV", "")
@@ -72,6 +91,7 @@ func TestHandshakeProse(t *testing.T) {
7291
for _, test := range []struct {
7392
name string
7493
env map[string]string
94+
opts *options.ClientOptionsBuilder
7595
want bson.D
7696
}{
7797
{
@@ -81,20 +101,22 @@ func TestHandshakeProse(t *testing.T) {
81101
"AWS_REGION": "us-east-2",
82102
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024",
83103
},
104+
opts: nil,
84105
want: clientMetadata(bson.D{
85106
{Key: "name", Value: "aws.lambda"},
86107
{Key: "memory_mb", Value: 1024},
87108
{Key: "region", Value: "us-east-2"},
88-
}),
109+
}, nil),
89110
},
90111
{
91112
name: "2. valid Azure",
92113
env: map[string]string{
93114
"FUNCTIONS_WORKER_RUNTIME": "node",
94115
},
116+
opts: nil,
95117
want: clientMetadata(bson.D{
96118
{Key: "name", Value: "azure.func"},
97-
}),
119+
}, nil),
98120
},
99121
{
100122
name: "3. valid GCP",
@@ -104,31 +126,34 @@ func TestHandshakeProse(t *testing.T) {
104126
"FUNCTION_TIMEOUT_SEC": "60",
105127
"FUNCTION_REGION": "us-central1",
106128
},
129+
opts: nil,
107130
want: clientMetadata(bson.D{
108131
{Key: "name", Value: "gcp.func"},
109132
{Key: "memory_mb", Value: 1024},
110133
{Key: "region", Value: "us-central1"},
111134
{Key: "timeout_sec", Value: 60},
112-
}),
135+
}, nil),
113136
},
114137
{
115138
name: "4. valid Vercel",
116139
env: map[string]string{
117140
"VERCEL": "1",
118141
"VERCEL_REGION": "cdg1",
119142
},
143+
opts: nil,
120144
want: clientMetadata(bson.D{
121145
{Key: "name", Value: "vercel"},
122146
{Key: "region", Value: "cdg1"},
123-
}),
147+
}, nil),
124148
},
125149
{
126150
name: "5. invalid multiple providers",
127151
env: map[string]string{
128152
"AWS_EXECUTION_ENV": "AWS_Lambda_java8",
129153
"FUNCTIONS_WORKER_RUNTIME": "node",
130154
},
131-
want: clientMetadata(nil),
155+
opts: nil,
156+
want: clientMetadata(nil, nil),
132157
},
133158
{
134159
name: "6. invalid long string",
@@ -142,26 +167,34 @@ func TestHandshakeProse(t *testing.T) {
142167
return s
143168
}(),
144169
},
170+
opts: nil,
145171
want: clientMetadata(bson.D{
146172
{Key: "name", Value: "aws.lambda"},
147-
}),
173+
}, nil),
148174
},
149175
{
150176
name: "7. invalid wrong types",
151177
env: map[string]string{
152178
"AWS_EXECUTION_ENV": "AWS_Lambda_java8",
153179
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "big",
154180
},
181+
opts: nil,
155182
want: clientMetadata(bson.D{
156183
{Key: "name", Value: "aws.lambda"},
157-
}),
184+
}, nil),
158185
},
159186
{
160187
name: "8. Invalid - AWS_EXECUTION_ENV does not start with \"AWS_Lambda_\"",
161188
env: map[string]string{
162189
"AWS_EXECUTION_ENV": "EC2",
163190
},
164-
want: clientMetadata(nil),
191+
opts: nil,
192+
want: clientMetadata(nil, nil),
193+
},
194+
{
195+
name: "driver info included",
196+
opts: options.Client().SetDriverInfo(driverInfo),
197+
want: clientMetadata(nil, driverInfo),
165198
},
166199
} {
167200
test := test
@@ -171,6 +204,10 @@ func TestHandshakeProse(t *testing.T) {
171204
mt.Setenv(k, v)
172205
}
173206

207+
if test.opts != nil {
208+
mt.ResetClient(test.opts)
209+
}
210+
174211
// Ping the server to ensure the handshake has completed.
175212
err := mt.Client.Ping(context.Background(), nil)
176213
require.NoError(mt, err, "Ping error: %v", err)

mongo/options/clientoptions.go

+25
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,17 @@ type BSONOptions struct {
223223
ZeroStructs bool
224224
}
225225

226+
// DriverInfo appends the client metadata generated by the driver when
227+
// handshaking the server. These options do not replace the values used
228+
// during the handshake, rather they are deliminated with a | with the
229+
// driver-generated data. This should be used by libraries wrapping the driver,
230+
// e.g. ODMs.
231+
type DriverInfo struct {
232+
Name string // Name of the library wrapping the driver.
233+
Version string // Version of the library wrapping the driver.
234+
Platform string // Platform information for the wrapping driver.
235+
}
236+
226237
// ClientOptions contains arguments to configure a Client instance. Arguments
227238
// can be set through the ClientOptions setter functions. See each function for
228239
// documentation.
@@ -235,6 +246,7 @@ type ClientOptions struct {
235246
Dialer ContextDialer
236247
Direct *bool
237248
DisableOCSPEndpointCheck *bool
249+
DriverInfo *DriverInfo
238250
HeartbeatInterval *time.Duration
239251
Hosts []string
240252
HTTPClient *http.Client
@@ -1249,6 +1261,19 @@ func (c *ClientOptionsBuilder) SetSRVServiceName(srvName string) *ClientOptionsB
12491261
return c
12501262
}
12511263

1264+
// SetDriverInfo configures optional data to include in the handshake's client
1265+
// metadata, delimited by "|" with the driver-generated data. This should be
1266+
// used by libraries wrapping the driver, e.g. ODMs.
1267+
func (c *ClientOptionsBuilder) SetDriverInfo(info *DriverInfo) *ClientOptionsBuilder {
1268+
c.Opts = append(c.Opts, func(opts *ClientOptions) error {
1269+
opts.DriverInfo = info
1270+
1271+
return nil
1272+
})
1273+
1274+
return c
1275+
}
1276+
12521277
// addCACertFromFile adds a root CA certificate to the configuration given a path
12531278
// to the containing file.
12541279
func addCACertFromFile(cfg *tls.Config, file string) error {

x/mongo/driver/auth/auth.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ type HandshakeOptions struct {
6565
ClusterClock *session.ClusterClock
6666
ServerAPI *driver.ServerAPIOptions
6767
LoadBalanced bool
68+
69+
// Fields provided by a library that wraps the Go Driver.
70+
OuterLibraryName string
71+
OuterLibraryVersion string
72+
OuterLibraryPlatform string
6873
}
6974

7075
type authHandshaker struct {
@@ -94,7 +99,10 @@ func (ah *authHandshaker) GetHandshakeInformation(
9499
SASLSupportedMechs(ah.options.DBUser).
95100
ClusterClock(ah.options.ClusterClock).
96101
ServerAPI(ah.options.ServerAPI).
97-
LoadBalanced(ah.options.LoadBalanced)
102+
LoadBalanced(ah.options.LoadBalanced).
103+
OuterLibraryName(ah.options.OuterLibraryName).
104+
OuterLibraryVersion(ah.options.OuterLibraryVersion).
105+
OuterLibraryPlatform(ah.options.OuterLibraryPlatform)
98106

99107
if ah.options.Authenticator != nil {
100108
if speculativeAuth, ok := ah.options.Authenticator.(SpeculativeAuthenticator); ok {

x/mongo/driver/operation/hello.go

+53-10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ type Hello struct {
5050
loadBalanced bool
5151
omitMaxTimeMS bool
5252

53+
// Fields provided by a library that wraps the Go Driver.
54+
outerLibraryName string
55+
outerLibraryVersion string
56+
outerLibraryPlatform string
57+
5358
res bsoncore.Document
5459
}
5560

@@ -123,6 +128,29 @@ func (h *Hello) LoadBalanced(lb bool) *Hello {
123128
return h
124129
}
125130

131+
// OuterLibraryName specifies the name of the library wrapping the Go Driver.
132+
func (h *Hello) OuterLibraryName(name string) *Hello {
133+
h.outerLibraryName = name
134+
135+
return h
136+
}
137+
138+
// OuterLibraryVersion specifies the version of the library wrapping the Go
139+
// Driver.
140+
func (h *Hello) OuterLibraryVersion(version string) *Hello {
141+
h.outerLibraryVersion = version
142+
143+
return h
144+
}
145+
146+
// OuterLibraryPlatform specifies the platform of the library wrapping the Go
147+
// Driver.
148+
func (h *Hello) OuterLibraryPlatform(platform string) *Hello {
149+
h.outerLibraryPlatform = platform
150+
151+
return h
152+
}
153+
126154
// Result returns the result of executing this operation.
127155
func (h *Hello) Result(addr address.Address) description.Server {
128156
return driverutil.NewServerDescription(addr, bson.Raw(h.res))
@@ -247,12 +275,22 @@ func appendClientAppName(dst []byte, name string) ([]byte, error) {
247275
// appendClientDriver appends the driver metadata to dst. It is the
248276
// responsibility of the caller to check that this appending does not cause dst
249277
// to exceed any size limitations.
250-
func appendClientDriver(dst []byte) ([]byte, error) {
278+
func appendClientDriver(dst []byte, outerLibraryName, outerLibraryVersion string) ([]byte, error) {
251279
var idx int32
252280
idx, dst = bsoncore.AppendDocumentElementStart(dst, "driver")
253281

254-
dst = bsoncore.AppendStringElement(dst, "name", driverName)
255-
dst = bsoncore.AppendStringElement(dst, "version", version.Driver)
282+
name := driverName
283+
if outerLibraryName != "" {
284+
name = name + "|" + outerLibraryName
285+
}
286+
287+
version := version.Driver
288+
if outerLibraryVersion != "" {
289+
version = version + "|" + outerLibraryVersion
290+
}
291+
292+
dst = bsoncore.AppendStringElement(dst, "name", name)
293+
dst = bsoncore.AppendStringElement(dst, "version", version)
256294

257295
return bsoncore.AppendDocumentEnd(dst, idx)
258296
}
@@ -374,8 +412,13 @@ func appendClientOS(dst []byte, omitNonType bool) ([]byte, error) {
374412
// appendClientPlatform appends the platform metadata to dst. It is the
375413
// responsibility of the caller to check that this appending does not cause dst
376414
// to exceed any size limitations.
377-
func appendClientPlatform(dst []byte) []byte {
378-
return bsoncore.AppendStringElement(dst, "platform", runtime.Version())
415+
func appendClientPlatform(dst []byte, outerLibraryPlatform string) []byte {
416+
platform := runtime.Version()
417+
if outerLibraryPlatform != "" {
418+
platform = platform + "|" + outerLibraryPlatform
419+
}
420+
421+
return bsoncore.AppendStringElement(dst, "platform", platform)
379422
}
380423

381424
// encodeClientMetadata encodes the client metadata into a BSON document. maxLen
@@ -412,7 +455,7 @@ func appendClientPlatform(dst []byte) []byte {
412455
// }
413456
// }
414457
// }
415-
func encodeClientMetadata(appname string, maxLen int) ([]byte, error) {
458+
func encodeClientMetadata(h *Hello, maxLen int) ([]byte, error) {
416459
dst := make([]byte, 0, maxLen)
417460

418461
omitEnvDoc := false
@@ -426,12 +469,12 @@ retry:
426469
idx, dst = bsoncore.AppendDocumentStart(dst)
427470

428471
var err error
429-
dst, err = appendClientAppName(dst, appname)
472+
dst, err = appendClientAppName(dst, h.appname)
430473
if err != nil {
431474
return nil, err
432475
}
433476

434-
dst, err = appendClientDriver(dst)
477+
dst, err = appendClientDriver(dst, h.outerLibraryName, h.outerLibraryVersion)
435478
if err != nil {
436479
return nil, err
437480
}
@@ -442,7 +485,7 @@ retry:
442485
}
443486

444487
if !truncatePlatform {
445-
dst = appendClientPlatform(dst)
488+
dst = appendClientPlatform(dst, h.outerLibraryPlatform)
446489
}
447490

448491
if !omitEnvDocument {
@@ -519,7 +562,7 @@ func (h *Hello) handshakeCommand(dst []byte, desc description.SelectedServer) ([
519562
}
520563
dst, _ = bsoncore.AppendArrayEnd(dst, idx)
521564

522-
clientMetadata, _ := encodeClientMetadata(h.appname, maxClientMetadataSize)
565+
clientMetadata, _ := encodeClientMetadata(h, maxClientMetadataSize)
523566

524567
// If the client metadata is empty, do not append it to the command.
525568
if len(clientMetadata) > 0 {

0 commit comments

Comments
 (0)