From e8d9a89492988dd70591bd228d919ec11e3bc912 Mon Sep 17 00:00:00 2001 From: Preston Vasquez Date: Thu, 14 Nov 2024 14:02:34 -0700 Subject: [PATCH 1/3] GODRIVER-1412 Add client metadata support for wrapping libraries --- internal/integration/handshake_test.go | 61 ++++++++++++++---- mongo/options/clientoptions.go | 23 +++++++ x/mongo/driver/auth/auth.go | 10 ++- x/mongo/driver/operation/hello.go | 63 ++++++++++++++++--- x/mongo/driver/operation/hello_test.go | 69 +++++++++++++-------- x/mongo/driver/topology/server.go | 3 +- x/mongo/driver/topology/server_options.go | 29 +++++++++ x/mongo/driver/topology/topology_options.go | 40 +++++++++--- 8 files changed, 240 insertions(+), 58 deletions(-) diff --git a/internal/integration/handshake_test.go b/internal/integration/handshake_test.go index a11339981d..152122790a 100644 --- a/internal/integration/handshake_test.go +++ b/internal/integration/handshake_test.go @@ -1,5 +1,5 @@ -// Copyright (C) MongoDB, Inc. 2023-present. // +// Copyright (C) MongoDB, Inc. 2023-present. // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 @@ -18,6 +18,7 @@ import ( "go.mongodb.org/mongo-driver/v2/internal/handshake" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/require" + "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/version" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage" @@ -34,11 +35,21 @@ func TestHandshakeProse(t *testing.T) { CreateCollection(false). ClientType(mtest.Proxy) - clientMetadata := func(env bson.D) bson.D { + clientMetadata := func(env bson.D, info *options.DriverInfo) bson.D { + driverName := "mongo-go-driver" + driverVersion := version.Driver + platform := runtime.Version() + + if info != nil { + driverName = driverName + "|" + info.Name + driverVersion = driverVersion + "|" + info.Version + platform = platform + "|" + info.Platform + } + elems := bson.D{ {Key: "driver", Value: bson.D{ - {Key: "name", Value: "mongo-go-driver"}, - {Key: "version", Value: version.Driver}, + {Key: "name", Value: driverName}, + {Key: "version", Value: driverVersion}, }}, {Key: "os", Value: bson.D{ {Key: "type", Value: runtime.GOOS}, @@ -46,7 +57,7 @@ func TestHandshakeProse(t *testing.T) { }}, } - elems = append(elems, bson.E{Key: "platform", Value: runtime.Version()}) + elems = append(elems, bson.E{Key: "platform", Value: platform}) // If env is empty, don't include it in the metadata. if env != nil && !reflect.DeepEqual(env, bson.D{}) { @@ -56,6 +67,12 @@ func TestHandshakeProse(t *testing.T) { return elems } + driverInfo := &options.DriverInfo{ + Name: "outer-library-name", + Version: "outer-library-version", + Platform: "outer-library-platform", + } + // Reset the environment variables to avoid environment namespace // collision. t.Setenv("AWS_EXECUTION_ENV", "") @@ -72,6 +89,7 @@ func TestHandshakeProse(t *testing.T) { for _, test := range []struct { name string env map[string]string + opts *options.ClientOptionsBuilder want bson.D }{ { @@ -81,20 +99,22 @@ func TestHandshakeProse(t *testing.T) { "AWS_REGION": "us-east-2", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", }, + opts: nil, want: clientMetadata(bson.D{ {Key: "name", Value: "aws.lambda"}, {Key: "memory_mb", Value: 1024}, {Key: "region", Value: "us-east-2"}, - }), + }, nil), }, { name: "2. valid Azure", env: map[string]string{ "FUNCTIONS_WORKER_RUNTIME": "node", }, + opts: nil, want: clientMetadata(bson.D{ {Key: "name", Value: "azure.func"}, - }), + }, nil), }, { name: "3. valid GCP", @@ -104,12 +124,13 @@ func TestHandshakeProse(t *testing.T) { "FUNCTION_TIMEOUT_SEC": "60", "FUNCTION_REGION": "us-central1", }, + opts: nil, want: clientMetadata(bson.D{ {Key: "name", Value: "gcp.func"}, {Key: "memory_mb", Value: 1024}, {Key: "region", Value: "us-central1"}, {Key: "timeout_sec", Value: 60}, - }), + }, nil), }, { name: "4. valid Vercel", @@ -117,10 +138,11 @@ func TestHandshakeProse(t *testing.T) { "VERCEL": "1", "VERCEL_REGION": "cdg1", }, + opts: nil, want: clientMetadata(bson.D{ {Key: "name", Value: "vercel"}, {Key: "region", Value: "cdg1"}, - }), + }, nil), }, { name: "5. invalid multiple providers", @@ -128,7 +150,8 @@ func TestHandshakeProse(t *testing.T) { "AWS_EXECUTION_ENV": "AWS_Lambda_java8", "FUNCTIONS_WORKER_RUNTIME": "node", }, - want: clientMetadata(nil), + opts: nil, + want: clientMetadata(nil, nil), }, { name: "6. invalid long string", @@ -142,9 +165,10 @@ func TestHandshakeProse(t *testing.T) { return s }(), }, + opts: nil, want: clientMetadata(bson.D{ {Key: "name", Value: "aws.lambda"}, - }), + }, nil), }, { name: "7. invalid wrong types", @@ -152,16 +176,23 @@ func TestHandshakeProse(t *testing.T) { "AWS_EXECUTION_ENV": "AWS_Lambda_java8", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "big", }, + opts: nil, want: clientMetadata(bson.D{ {Key: "name", Value: "aws.lambda"}, - }), + }, nil), }, { name: "8. Invalid - AWS_EXECUTION_ENV does not start with \"AWS_Lambda_\"", env: map[string]string{ "AWS_EXECUTION_ENV": "EC2", }, - want: clientMetadata(nil), + opts: nil, + want: clientMetadata(nil, nil), + }, + { + name: "driver info included", + opts: options.Client().SetDriverInfo(driverInfo), + want: clientMetadata(nil, driverInfo), }, } { test := test @@ -171,6 +202,10 @@ func TestHandshakeProse(t *testing.T) { mt.Setenv(k, v) } + if test.opts != nil { + mt.ResetClient(test.opts) + } + // Ping the server to ensure the handshake has completed. err := mt.Client.Ping(context.Background(), nil) require.NoError(mt, err, "Ping error: %v", err) diff --git a/mongo/options/clientoptions.go b/mongo/options/clientoptions.go index bbca8b0ddb..e19eefb698 100644 --- a/mongo/options/clientoptions.go +++ b/mongo/options/clientoptions.go @@ -223,6 +223,16 @@ type BSONOptions struct { ZeroStructs bool } +// DriverInfo appends the client metadata generated by the driver when +// handshaking the server. These options do not replace the values used +// during the handshake, rather they are deliminated with a | with the +// driver-generated data. +type DriverInfo struct { + Name string // Name of the library wrapping the driver. + Version string // Version of the library wrapping the driver. + Platform string // Platform information for the wrapping driver. +} + // ClientOptions contains arguments to configure a Client instance. Arguments // can be set through the ClientOptions setter functions. See each function for // documentation. @@ -235,6 +245,7 @@ type ClientOptions struct { Dialer ContextDialer Direct *bool DisableOCSPEndpointCheck *bool + DriverInfo *DriverInfo HeartbeatInterval *time.Duration Hosts []string HTTPClient *http.Client @@ -1249,6 +1260,18 @@ func (c *ClientOptionsBuilder) SetSRVServiceName(srvName string) *ClientOptionsB return c } +// SetDriverInfo configures optional data to include in the handshake's client +// metadata, delimited by "|" with the driver-generated data. +func (c *ClientOptionsBuilder) SetDriverInfo(info *DriverInfo) *ClientOptionsBuilder { + c.Opts = append(c.Opts, func(opts *ClientOptions) error { + opts.DriverInfo = info + + return nil + }) + + return c +} + // addCACertFromFile adds a root CA certificate to the configuration given a path // to the containing file. func addCACertFromFile(cfg *tls.Config, file string) error { diff --git a/x/mongo/driver/auth/auth.go b/x/mongo/driver/auth/auth.go index f5e4ee87f9..843715dc17 100644 --- a/x/mongo/driver/auth/auth.go +++ b/x/mongo/driver/auth/auth.go @@ -65,6 +65,11 @@ type HandshakeOptions struct { ClusterClock *session.ClusterClock ServerAPI *driver.ServerAPIOptions LoadBalanced bool + + // Fields provided by a library that wraps the Go Driver. + OuterLibraryName string + OuterLibraryVersion string + OuterLibraryPlatform string } type authHandshaker struct { @@ -94,7 +99,10 @@ func (ah *authHandshaker) GetHandshakeInformation( SASLSupportedMechs(ah.options.DBUser). ClusterClock(ah.options.ClusterClock). ServerAPI(ah.options.ServerAPI). - LoadBalanced(ah.options.LoadBalanced) + LoadBalanced(ah.options.LoadBalanced). + OuterLibraryName(ah.options.OuterLibraryName). + OuterLibraryVersion(ah.options.OuterLibraryVersion). + OuterLibraryPlatform(ah.options.OuterLibraryPlatform) if ah.options.Authenticator != nil { if speculativeAuth, ok := ah.options.Authenticator.(SpeculativeAuthenticator); ok { diff --git a/x/mongo/driver/operation/hello.go b/x/mongo/driver/operation/hello.go index 4e3749aef4..18bdee2523 100644 --- a/x/mongo/driver/operation/hello.go +++ b/x/mongo/driver/operation/hello.go @@ -50,6 +50,11 @@ type Hello struct { loadBalanced bool omitMaxTimeMS bool + // Fields provided by a library that wraps the Go Driver. + outerLibraryName string + outerLibraryVersion string + outerLibraryPlatform string + res bsoncore.Document } @@ -123,6 +128,29 @@ func (h *Hello) LoadBalanced(lb bool) *Hello { return h } +// OuterLibraryName specifies the name of the library wrapping the Go Driver. +func (h *Hello) OuterLibraryName(name string) *Hello { + h.outerLibraryName = name + + return h +} + +// OuterLibraryVersion specifies the version of the library wrapping the Go +// Driver. +func (h *Hello) OuterLibraryVersion(version string) *Hello { + h.outerLibraryVersion = version + + return h +} + +// OuterLibraryPlatform specifies the platform of the library wrapping the Go +// Driver. +func (h *Hello) OuterLibraryPlatform(platform string) *Hello { + h.outerLibraryPlatform = platform + + return h +} + // Result returns the result of executing this operation. func (h *Hello) Result(addr address.Address) description.Server { return driverutil.NewServerDescription(addr, bson.Raw(h.res)) @@ -247,12 +275,22 @@ func appendClientAppName(dst []byte, name string) ([]byte, error) { // appendClientDriver appends the driver metadata to dst. It is the // responsibility of the caller to check that this appending does not cause dst // to exceed any size limitations. -func appendClientDriver(dst []byte) ([]byte, error) { +func appendClientDriver(dst []byte, outerLibraryName, outerLibraryVersion string) ([]byte, error) { var idx int32 idx, dst = bsoncore.AppendDocumentElementStart(dst, "driver") - dst = bsoncore.AppendStringElement(dst, "name", driverName) - dst = bsoncore.AppendStringElement(dst, "version", version.Driver) + name := driverName + if outerLibraryName != "" { + name = name + "|" + outerLibraryName + } + + version := version.Driver + if outerLibraryVersion != "" { + version = version + "|" + outerLibraryVersion + } + + dst = bsoncore.AppendStringElement(dst, "name", name) + dst = bsoncore.AppendStringElement(dst, "version", version) return bsoncore.AppendDocumentEnd(dst, idx) } @@ -374,8 +412,13 @@ func appendClientOS(dst []byte, omitNonType bool) ([]byte, error) { // appendClientPlatform appends the platform metadata to dst. It is the // responsibility of the caller to check that this appending does not cause dst // to exceed any size limitations. -func appendClientPlatform(dst []byte) []byte { - return bsoncore.AppendStringElement(dst, "platform", runtime.Version()) +func appendClientPlatform(dst []byte, outerLibraryPlatform string) []byte { + platform := runtime.Version() + if outerLibraryPlatform != "" { + platform = platform + "|" + outerLibraryPlatform + } + + return bsoncore.AppendStringElement(dst, "platform", platform) } // encodeClientMetadata encodes the client metadata into a BSON document. maxLen @@ -412,7 +455,7 @@ func appendClientPlatform(dst []byte) []byte { // } // } // } -func encodeClientMetadata(appname string, maxLen int) ([]byte, error) { +func encodeClientMetadata(h *Hello, maxLen int) ([]byte, error) { dst := make([]byte, 0, maxLen) omitEnvDoc := false @@ -426,12 +469,12 @@ retry: idx, dst = bsoncore.AppendDocumentStart(dst) var err error - dst, err = appendClientAppName(dst, appname) + dst, err = appendClientAppName(dst, h.appname) if err != nil { return nil, err } - dst, err = appendClientDriver(dst) + dst, err = appendClientDriver(dst, h.outerLibraryName, h.outerLibraryVersion) if err != nil { return nil, err } @@ -442,7 +485,7 @@ retry: } if !truncatePlatform { - dst = appendClientPlatform(dst) + dst = appendClientPlatform(dst, h.outerLibraryPlatform) } if !omitEnvDocument { @@ -519,7 +562,7 @@ func (h *Hello) handshakeCommand(dst []byte, desc description.SelectedServer) ([ } dst, _ = bsoncore.AppendArrayEnd(dst, idx) - clientMetadata, _ := encodeClientMetadata(h.appname, maxClientMetadataSize) + clientMetadata, _ := encodeClientMetadata(h, maxClientMetadataSize) // If the client metadata is empty, do not append it to the command. if len(clientMetadata) > 0 { diff --git a/x/mongo/driver/operation/hello_test.go b/x/mongo/driver/operation/hello_test.go index 23db7d1bac..187c23aae8 100644 --- a/x/mongo/driver/operation/hello_test.go +++ b/x/mongo/driver/operation/hello_test.go @@ -112,12 +112,22 @@ func TestAppendClientDriver(t *testing.T) { t.Parallel() tests := []struct { - name string - want []byte // Extend JSON + name string + outerLibraryName string + outerLibraryVersion string + want []byte // Extend JSON }{ { - name: "full", - want: []byte(fmt.Sprintf(`{"driver":{"name": %q, "version": %q}}`, driverName, version.Driver)), + name: "full", + outerLibraryName: "", + outerLibraryVersion: "", + want: []byte(fmt.Sprintf(`{"driver":{"name": %q, "version": %q}}`, driverName, version.Driver)), + }, + { + name: "with outer library data", + outerLibraryName: "outer-library-name", + outerLibraryVersion: "outer-library-version", + want: []byte(fmt.Sprintf(`{"driver":{"name": "%s|outer-library-name", "version": "%s|outer-library-version"}}`, driverName, version.Driver)), }, } @@ -129,7 +139,7 @@ func TestAppendClientDriver(t *testing.T) { cb := func(_ int, dst []byte) ([]byte, error) { var err error - dst, err = appendClientDriver(dst) + dst, err = appendClientDriver(dst, test.outerLibraryName, test.outerLibraryVersion) return dst, err } @@ -351,12 +361,19 @@ func TestAppendClientPlatform(t *testing.T) { t.Parallel() tests := []struct { - name string - want []byte // Extended JSON + name string + outerLibraryPlatform string + want []byte // Extended JSON }{ { - name: "full", - want: []byte(fmt.Sprintf(`{"platform":%q}`, runtime.Version())), + name: "full", + outerLibraryPlatform: "", + want: []byte(fmt.Sprintf(`{"platform":%q}`, runtime.Version())), + }, + { + name: "with outer library data", + outerLibraryPlatform: "outer-library-platform", + want: []byte(fmt.Sprintf(`{"platform":"%s|outer-library-platform"}`, runtime.Version())), }, } @@ -368,7 +385,7 @@ func TestAppendClientPlatform(t *testing.T) { cb := func(_ int, dst []byte) ([]byte, error) { var err error - dst = appendClientPlatform(dst) + dst = appendClientPlatform(dst, test.outerLibraryPlatform) return dst, err } @@ -435,7 +452,7 @@ func TestEncodeClientMetadata(t *testing.T) { t.Setenv("KUBERNETES_SERVICE_HOST", "0.0.0.0") t.Run("nothing is omitted", func(t *testing.T) { - got, err := encodeClientMetadata("foo", maxClientMetadataSize) + got, err := encodeClientMetadata(NewHello().AppName("foo"), maxClientMetadataSize) assert.Nil(t, err, "error in encodeClientMetadata: %v", err) want := formatJSON(&clientMetadata{ @@ -458,10 +475,10 @@ func TestEncodeClientMetadata(t *testing.T) { t.Run("env is omitted sub env.name", func(t *testing.T) { // Calculate the full length of a bsoncore.Document. - temp, err := encodeClientMetadata("foo", maxClientMetadataSize) + temp, err := encodeClientMetadata(NewHello().AppName("foo"), maxClientMetadataSize) require.NoError(t, err, "error constructing template: %v", err) - got, err := encodeClientMetadata("foo", len(temp)-1) + got, err := encodeClientMetadata(NewHello().AppName("foo"), len(temp)-1) assert.Nil(t, err, "error in encodeClientMetadata: %v", err) want := formatJSON(&clientMetadata{ @@ -482,7 +499,7 @@ func TestEncodeClientMetadata(t *testing.T) { t.Run("os is omitted sub os.type", func(t *testing.T) { // Calculate the full length of a bsoncore.Document. - temp, err := encodeClientMetadata("foo", maxClientMetadataSize) + temp, err := encodeClientMetadata(NewHello().AppName("foo"), maxClientMetadataSize) require.NoError(t, err, "error constructing template: %v", err) // Calculate what the environment costs. @@ -499,7 +516,7 @@ func TestEncodeClientMetadata(t *testing.T) { // Environment sub name. envSubName := len(edst) - len(ndst) - got, err := encodeClientMetadata("foo", len(temp)-envSubName-1) + got, err := encodeClientMetadata(NewHello().AppName("foo"), len(temp)-envSubName-1) assert.Nil(t, err, "error in encodeClientMetadata: %v", err) want := formatJSON(&clientMetadata{ @@ -520,7 +537,7 @@ func TestEncodeClientMetadata(t *testing.T) { t.Run("omit the env doc entirely", func(t *testing.T) { // Calculate the full length of a bsoncore.Document. - temp, err := encodeClientMetadata("foo", maxClientMetadataSize) + temp, err := encodeClientMetadata(NewHello().AppName("foo"), maxClientMetadataSize) require.NoError(t, err, "error constructing template: %v", err) // Calculate what the environment costs. @@ -533,7 +550,7 @@ func TestEncodeClientMetadata(t *testing.T) { // Calculate what the environment plus the os.type costs. envAndOSType := len(edst) + len(odst) - got, err := encodeClientMetadata("foo", len(temp)-envAndOSType-1) + got, err := encodeClientMetadata(NewHello().AppName("foo"), len(temp)-envAndOSType-1) assert.Nil(t, err, "error in encodeClientMetadata: %v", err) want := formatJSON(&clientMetadata{ @@ -548,7 +565,7 @@ func TestEncodeClientMetadata(t *testing.T) { t.Run("omit the platform", func(t *testing.T) { // Calculate the full length of a bsoncore.Document. - temp, err := encodeClientMetadata("foo", maxClientMetadataSize) + temp, err := encodeClientMetadata(NewHello().AppName("foo"), maxClientMetadataSize) require.NoError(t, err, "error constructing template: %v", err) // Calculate what the environment costs. @@ -559,12 +576,12 @@ func TestEncodeClientMetadata(t *testing.T) { odst := bsoncore.AppendStringElement(nil, "type", runtime.GOOS) // Calculate what the platform costs - pdst := appendClientPlatform(nil) + pdst := appendClientPlatform(nil, "") // Calculate what the environment plus the os.type costs. envAndOSTypeAndPlatform := len(edst) + len(odst) + len(pdst) - got, err := encodeClientMetadata("foo", len(temp)-envAndOSTypeAndPlatform) + got, err := encodeClientMetadata(NewHello().AppName("foo"), len(temp)-envAndOSTypeAndPlatform) assert.Nil(t, err, "error in encodeClientMetadata: %v", err) want := formatJSON(&clientMetadata{ @@ -577,7 +594,7 @@ func TestEncodeClientMetadata(t *testing.T) { }) t.Run("0 max len", func(t *testing.T) { - got, err := encodeClientMetadata("foo", 0) + got, err := encodeClientMetadata(NewHello().AppName("foo"), 0) assert.Nil(t, err, "error in encodeClientMetadata: %v", err) assert.Len(t, got, 0) }) @@ -657,7 +674,7 @@ func BenchmarkClientMetadata(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _, err := encodeClientMetadata("foo", maxClientMetadataSize) + _, err := encodeClientMetadata(NewHello().AppName("foo"), maxClientMetadataSize) if err != nil { b.Fatal(err) } @@ -680,7 +697,7 @@ func BenchmarkClientMetadtaLargeEnv(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _, err := encodeClientMetadata("foo", maxClientMetadataSize) + _, err := encodeClientMetadata(NewHello().AppName("foo"), maxClientMetadataSize) if err != nil { b.Fatal(err) } @@ -694,7 +711,7 @@ func FuzzEncodeClientMetadata(f *testing.F) { return } - _, err := encodeClientMetadata(appname, maxClientMetadataSize) + _, err := encodeClientMetadata(NewHello().AppName(appname), maxClientMetadataSize) if err != nil { t.Fatalf("error appending client: %v", err) } @@ -704,7 +721,7 @@ func FuzzEncodeClientMetadata(f *testing.F) { t.Fatalf("error appending client app name: %v", err) } - _, err = appendClientDriver(b) + _, err = appendClientDriver(b, "", "") if err != nil { t.Fatalf("error appending client driver: %v", err) } @@ -739,6 +756,6 @@ func FuzzEncodeClientMetadata(f *testing.F) { t.Fatalf("error appending client os t: %v", err) } - appendClientPlatform(b) + appendClientPlatform(b, "") }) } diff --git a/x/mongo/driver/topology/server.go b/x/mongo/driver/topology/server.go index 55a2ddd8c6..7fab1136f8 100644 --- a/x/mongo/driver/topology/server.go +++ b/x/mongo/driver/topology/server.go @@ -808,7 +808,8 @@ func (s *Server) createConnection() *connection { opts = append(opts, WithHandshaker(func(Handshaker) Handshaker { return operation.NewHello().AppName(s.cfg.appname).Compressors(s.cfg.compressionOpts). - ServerAPI(s.cfg.serverAPI) + ServerAPI(s.cfg.serverAPI).OuterLibraryName(s.cfg.outerLibraryName). + OuterLibraryVersion(s.cfg.outerLibraryVersion).OuterLibraryPlatform(s.cfg.outerLibraryPlatform) }), // Override any monitors specified in options with nil to avoid monitoring heartbeats. WithMonitor(func(*event.CommandMonitor) *event.CommandMonitor { return nil }), diff --git a/x/mongo/driver/topology/server_options.go b/x/mongo/driver/topology/server_options.go index 8d86d5bd85..490834cbef 100644 --- a/x/mongo/driver/topology/server_options.go +++ b/x/mongo/driver/topology/server_options.go @@ -41,6 +41,11 @@ type serverConfig struct { logger *logger.Logger poolMaxIdleTime time.Duration poolMaintainInterval time.Duration + + // Fields provided by a library that wraps the Go Driver. + outerLibraryName string + outerLibraryVersion string + outerLibraryPlatform string } func newServerConfig(connectTimeout time.Duration, opts ...ServerOption) *serverConfig { @@ -96,6 +101,30 @@ func WithServerAppName(fn func(string) string) ServerOption { } } +// WithOuterLibraryName configures the name for the outer library to include +// in the drivers section of the handshake metadata. +func WithOuterLibraryName(fn func(string) string) ServerOption { + return func(cfg *serverConfig) { + cfg.outerLibraryName = fn(cfg.outerLibraryName) + } +} + +// WithOuterLibraryVersion configures the version for the outer library to +// include in the drivers section of the handshake metadata. +func WithOuterLibraryVersion(fn func(string) string) ServerOption { + return func(cfg *serverConfig) { + cfg.outerLibraryVersion = fn(cfg.outerLibraryVersion) + } +} + +// WithOuterLibraryPlatform configures the platform for the outer library to +// include in the platform section of the handshake metadata. +func WithOuterLibraryPlatform(fn func(string) string) ServerOption { + return func(cfg *serverConfig) { + cfg.outerLibraryPlatform = fn(cfg.outerLibraryPlatform) + } +} + // WithHeartbeatInterval configures a server's heartbeat interval. func WithHeartbeatInterval(fn func(time.Duration) time.Duration) ServerOption { return func(cfg *serverConfig) { diff --git a/x/mongo/driver/topology/topology_options.go b/x/mongo/driver/topology/topology_options.go index d98b47d5ef..66bbdb74ff 100644 --- a/x/mongo/driver/topology/topology_options.go +++ b/x/mongo/driver/topology/topology_options.go @@ -226,6 +226,26 @@ func NewConfigFromOptionsWithAuthenticator(opts *options.ClientOptions, clock *s return appName })) } + + var outerLibraryName, outerLibraryVersion, outerLibraryPlatform string + if opts.DriverInfo != nil { + outerLibraryName = opts.DriverInfo.Name + outerLibraryVersion = opts.DriverInfo.Version + outerLibraryPlatform = opts.DriverInfo.Platform + + serverOpts = append(serverOpts, WithOuterLibraryName(func(string) string { + return outerLibraryName + })) + + serverOpts = append(serverOpts, WithOuterLibraryVersion(func(string) string { + return outerLibraryVersion + })) + + serverOpts = append(serverOpts, WithOuterLibraryPlatform(func(string) string { + return outerLibraryPlatform + })) + } + // Compressors & ZlibLevel var comps []string if len(opts.Compressors) > 0 { @@ -264,12 +284,15 @@ func NewConfigFromOptionsWithAuthenticator(opts *options.ClientOptions, clock *s var handshaker func(driver.Handshaker) driver.Handshaker if authenticator != nil { handshakeOpts := &auth.HandshakeOptions{ - AppName: appName, - Authenticator: authenticator, - Compressors: comps, - ServerAPI: serverAPI, - LoadBalanced: loadBalanced, - ClusterClock: clock, + AppName: appName, + Authenticator: authenticator, + Compressors: comps, + ServerAPI: serverAPI, + LoadBalanced: loadBalanced, + ClusterClock: clock, + OuterLibraryName: outerLibraryName, + OuterLibraryVersion: outerLibraryVersion, + OuterLibraryPlatform: outerLibraryPlatform, } if opts.Auth.AuthMechanism == "" { @@ -288,7 +311,10 @@ func NewConfigFromOptionsWithAuthenticator(opts *options.ClientOptions, clock *s Compressors(comps). ClusterClock(clock). ServerAPI(serverAPI). - LoadBalanced(loadBalanced) + LoadBalanced(loadBalanced). + OuterLibraryName(outerLibraryName). + OuterLibraryVersion(outerLibraryVersion). + OuterLibraryPlatform(outerLibraryPlatform) } } From 64e6b9ffb2635aec4fad07f35a1e64481428ca36 Mon Sep 17 00:00:00 2001 From: Preston Vasquez Date: Thu, 14 Nov 2024 15:29:09 -0700 Subject: [PATCH 2/3] GODRIVER-1412 Fix license --- internal/integration/handshake_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/integration/handshake_test.go b/internal/integration/handshake_test.go index 152122790a..54d83a2d31 100644 --- a/internal/integration/handshake_test.go +++ b/internal/integration/handshake_test.go @@ -1,5 +1,5 @@ -// // Copyright (C) MongoDB, Inc. 2023-present. +// // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 @@ -36,9 +36,11 @@ func TestHandshakeProse(t *testing.T) { ClientType(mtest.Proxy) clientMetadata := func(env bson.D, info *options.DriverInfo) bson.D { - driverName := "mongo-go-driver" - driverVersion := version.Driver - platform := runtime.Version() + var ( + driverName = "mongo-go-driver" + driverVersion = version.Driver + platform = runtime.Version() + ) if info != nil { driverName = driverName + "|" + info.Name From 14f62668e27695be0dbf9a45ad68ee1ec23d963c Mon Sep 17 00:00:00 2001 From: Preston Vasquez Date: Thu, 14 Nov 2024 19:50:02 -0700 Subject: [PATCH 3/3] GODRIVER-1412 Update doc --- mongo/options/clientoptions.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mongo/options/clientoptions.go b/mongo/options/clientoptions.go index e19eefb698..63a787308c 100644 --- a/mongo/options/clientoptions.go +++ b/mongo/options/clientoptions.go @@ -226,7 +226,8 @@ type BSONOptions struct { // DriverInfo appends the client metadata generated by the driver when // handshaking the server. These options do not replace the values used // during the handshake, rather they are deliminated with a | with the -// driver-generated data. +// driver-generated data. This should be used by libraries wrapping the driver, +// e.g. ODMs. type DriverInfo struct { Name string // Name of the library wrapping the driver. Version string // Version of the library wrapping the driver. @@ -1261,7 +1262,8 @@ func (c *ClientOptionsBuilder) SetSRVServiceName(srvName string) *ClientOptionsB } // SetDriverInfo configures optional data to include in the handshake's client -// metadata, delimited by "|" with the driver-generated data. +// metadata, delimited by "|" with the driver-generated data. This should be +// used by libraries wrapping the driver, e.g. ODMs. func (c *ClientOptionsBuilder) SetDriverInfo(info *DriverInfo) *ClientOptionsBuilder { c.Opts = append(c.Opts, func(opts *ClientOptions) error { opts.DriverInfo = info