summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRamon Fernandez <ramon@mongodb.com>2017-06-27 12:47:20 -0400
committerRamon Fernandez <ramon@mongodb.com>2017-06-27 12:47:20 -0400
commita2773b8e23f78522d30546e748b6110519239edb (patch)
treed33c0f4733ff1da7d77c80a5c039cecc3da343a8
parent62f52d2eba138a8729ac31b589d87bde6f1b5cf5 (diff)
downloadmongo-a2773b8e23f78522d30546e748b6110519239edb.tar.gz
Import tools: 29b8883c560319b016f8bd4927807fa36f1a682f from branch v3.4
ref: 4d4d96583c..29b8883c56 for: 3.4.6 TOOLS-1567 Support MongoURI for connections TOOLS-1676 Mongodump fails with OpenSSL 0.9.8 TOOLS-1694 Fix hidden options defaults TOOLS-1699 3.4.5 Server causing crash in extended_json_metadata.js
-rw-r--r--src/mongo/gotools/Godeps2
-rw-r--r--src/mongo/gotools/bsondump/main/bsondump.go2
-rw-r--r--src/mongo/gotools/common/connstring/connstring.go398
-rw-r--r--src/mongo/gotools/common/db/connector.go15
-rw-r--r--src/mongo/gotools/common/db/openssl/openssl.go10
-rw-r--r--src/mongo/gotools/common/db/write_concern.go54
-rw-r--r--src/mongo/gotools/common/db/write_concern_test.go166
-rw-r--r--src/mongo/gotools/common/options/options.go213
-rw-r--r--src/mongo/gotools/common/options/options_gssapi.go7
-rw-r--r--src/mongo/gotools/common/options/options_ssl.go9
-rw-r--r--src/mongo/gotools/common/options/options_test.go306
-rw-r--r--src/mongo/gotools/import.data2
-rw-r--r--src/mongo/gotools/mongodump/main/mongodump.go11
-rw-r--r--src/mongo/gotools/mongodump/options.go11
-rw-r--r--src/mongo/gotools/mongoexport/main/mongoexport.go10
-rw-r--r--src/mongo/gotools/mongofiles/main/mongofiles.go16
-rw-r--r--src/mongo/gotools/mongofiles/mongofiles.go4
-rw-r--r--src/mongo/gotools/mongofiles/mongofiles_test.go1
-rw-r--r--src/mongo/gotools/mongoimport/main/mongoimport.go11
-rw-r--r--src/mongo/gotools/mongoimport/mongoimport.go3
-rw-r--r--src/mongo/gotools/mongoimport/mongoimport_test.go23
-rw-r--r--src/mongo/gotools/mongooplog/main/mongooplog.go10
-rw-r--r--src/mongo/gotools/mongorestore/main/mongorestore.go13
-rw-r--r--src/mongo/gotools/mongorestore/mongorestore.go3
-rw-r--r--src/mongo/gotools/mongorestore/mongorestore_archive_test.go1
-rw-r--r--src/mongo/gotools/mongorestore/mongorestore_test.go1
-rw-r--r--src/mongo/gotools/mongostat/main/mongostat.go17
-rw-r--r--src/mongo/gotools/mongotop/main/mongotop.go18
-rw-r--r--src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go7
-rw-r--r--src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go5
-rw-r--r--src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go5
31 files changed, 1232 insertions, 122 deletions
diff --git a/src/mongo/gotools/Godeps b/src/mongo/gotools/Godeps
index 509c3865580..d3b757426d3 100644
--- a/src/mongo/gotools/Godeps
+++ b/src/mongo/gotools/Godeps
@@ -6,7 +6,7 @@ github.com/smartystreets/assertions 287b4346dc4e71a038c346375a9d572453bc469b
github.com/smartystreets/goconvey bf58a9a1291224109919756b4dcc469c670cc7e4
github.com/jessevdk/go-flags 97448c91aac742cbca3d020b3e769013a420a06f
github.com/3rf/mongo-lint 3550fdcf1f43b89aaeabaa4559eaae6dc4407e42
-github.com/spacemonkeygo/openssl 5be686e264d836e7a01ca7fc7c53acdb8edbe768 github.com/10gen/openssl
+github.com/spacemonkeygo/openssl 2869e8ca1a6eb35fb727f41611fd52b55cd0f49c github.com/10gen/openssl
github.com/spacemonkeygo/spacelog ae95ccc1eb0c8ce2496c43177430efd61930f7e4
github.com/howeyc/gopass 44476384cd4721b68705e72f19e95d1a3a504370
github.com/nsf/termbox-go 0723e7c3d0a317dea811f0fbe4d6edd81908c971
diff --git a/src/mongo/gotools/bsondump/main/bsondump.go b/src/mongo/gotools/bsondump/main/bsondump.go
index 2613d3a014a..60fa0de5461 100644
--- a/src/mongo/gotools/bsondump/main/bsondump.go
+++ b/src/mongo/gotools/bsondump/main/bsondump.go
@@ -17,7 +17,7 @@ func main() {
bsonDumpOpts := &bsondump.BSONDumpOptions{}
opts.AddOptions(bsonDumpOpts)
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'bsondump --help' for more information")
diff --git a/src/mongo/gotools/common/connstring/connstring.go b/src/mongo/gotools/common/connstring/connstring.go
new file mode 100644
index 00000000000..d1da3e87514
--- /dev/null
+++ b/src/mongo/gotools/common/connstring/connstring.go
@@ -0,0 +1,398 @@
+package connstring
+
+import (
+ "fmt"
+ "net"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Parse parses the provided uri and returns a URI object.
+func ParseURIConnectionString(s string) (ConnString, error) {
+ var p parser
+ err := p.parse(s)
+ if err != nil {
+ err = fmt.Errorf("error parsing uri (%s): %s", s, err)
+ }
+ return p.ConnString, err
+}
+
+// ConnString represents a connection string to mongodb.
+type ConnString struct {
+ Original string
+ AppName string
+ AuthMechanism string
+ AuthMechanismProperties map[string]string
+ AuthSource string
+ Connect ConnectMode
+ ConnectTimeout time.Duration
+ Database string
+ FSync bool
+ HeartbeatInterval time.Duration
+ Hosts []string
+ Journal bool
+ KerberosService string
+ KerberosServiceHost string
+ MaxConnIdleTime time.Duration
+ MaxConnLifeTime time.Duration
+ MaxConnsPerHost uint16
+ MaxConnsPerHostSet bool
+ MaxIdleConnsPerHost uint16
+ MaxIdleConnsPerHostSet bool
+ Password string
+ PasswordSet bool
+ ReadPreference string
+ ReadPreferenceTagSets []map[string]string
+ ReplicaSet string
+ ServerSelectionTimeout time.Duration
+ SocketTimeout time.Duration
+ Username string
+ UseSSL bool
+ W string
+ WTimeout time.Duration
+
+ Options map[string][]string
+ UnknownOptions map[string][]string
+}
+
+func (u *ConnString) String() string {
+ return u.Original
+}
+
+// ConnectMode informs the driver on how to connect
+// to the server.
+type ConnectMode uint8
+
+// ConnectMode constants.
+const (
+ AutoConnect ConnectMode = iota
+ SingleConnect
+)
+
+type parser struct {
+ ConnString
+
+ haveWTimeoutMS bool
+}
+
+func (p *parser) parse(original string) error {
+ p.Original = original
+
+ uri := original
+ var err error
+ // scheme
+ if !strings.HasPrefix(uri, "mongodb://") {
+ return fmt.Errorf("scheme must be \"mongodb\"")
+ }
+
+ // user info
+ uri = uri[10:]
+
+ if idx := strings.Index(uri, "@"); idx != -1 {
+ userInfo := uri[:idx]
+ uri = uri[idx+1:]
+
+ username := userInfo
+ var password string
+
+ if idx := strings.Index(userInfo, ":"); idx != -1 {
+ username = userInfo[:idx]
+ password = userInfo[idx+1:]
+ }
+
+ p.Username, err = url.QueryUnescape(username)
+ if err != nil {
+ return fmt.Errorf("invalid username: %s", err)
+ }
+ if len(password) > 1 {
+ if strings.Contains(password, ":") {
+ return fmt.Errorf("unescaped colon in password")
+ }
+ p.Password, err = url.QueryUnescape(password)
+ if err != nil {
+ return fmt.Errorf("invalid password: %s", err)
+ }
+ p.PasswordSet = true
+ }
+
+ }
+
+ // hosts
+ hosts := uri
+ if idx := strings.IndexAny(uri, "/?@"); idx != -1 {
+ if uri[idx] == '@' {
+ return fmt.Errorf("unescaped @ sign in user info")
+ }
+ if uri[idx] == '?' {
+ return fmt.Errorf("must have a / before the query ?")
+ }
+
+ hosts = uri[:idx]
+ }
+
+ for _, host := range strings.Split(hosts, ",") {
+ err = p.addHost(host)
+ if err != nil {
+ return fmt.Errorf("invalid host \"%s\": %s", host, err)
+ }
+ }
+
+ if len(p.Hosts) == 0 {
+ return fmt.Errorf("must have at least 1 host")
+ }
+
+ uri = uri[len(hosts):]
+
+ if len(uri) == 0 {
+ return nil
+ }
+
+ if uri[0] != '/' {
+ return fmt.Errorf("must have a / separator between hosts and path")
+ }
+
+ uri = uri[1:]
+ if len(uri) == 0 {
+ return nil
+ }
+
+ database := uri
+ if idx := strings.IndexAny(uri, "?"); idx != -1 {
+ database = uri[:idx]
+ }
+
+ p.Database, err = url.QueryUnescape(database)
+ if err != nil {
+ return fmt.Errorf("invalid database \"%s\": %s", database, err)
+ }
+
+ uri = uri[len(database):]
+
+ if len(uri) == 0 {
+ return nil
+ }
+
+ if uri[0] != '?' {
+ return fmt.Errorf("must have a ? separator between path and query")
+ }
+
+ uri = uri[1:]
+ if len(uri) == 0 {
+ return nil
+ }
+
+ for _, pair := range strings.FieldsFunc(uri, func(r rune) bool { return r == ';' || r == '&' }) {
+ err = p.addOption(pair)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (p *parser) addHost(host string) error {
+ if host == "" {
+ return nil
+ }
+ host, err := url.QueryUnescape(host)
+ if err != nil {
+ return fmt.Errorf("invalid host \"%s\": %s", host, err)
+ }
+
+ _, port, err := net.SplitHostPort(host)
+ // this is unfortunate that SplitHostPort actually requires
+ // a port to exist.
+ if err != nil {
+ if addrError, ok := err.(*net.AddrError); !ok || addrError.Err != "missing port in address" {
+ return err
+ }
+ }
+
+ if port != "" {
+ d, err := strconv.Atoi(port)
+ if err != nil {
+ return fmt.Errorf("port must be an integer: %s", err)
+ }
+ if d <= 0 || d >= 65536 {
+ return fmt.Errorf("port must be in the range [1, 65535]")
+ }
+ }
+ p.Hosts = append(p.Hosts, host)
+ return nil
+}
+
+func (p *parser) addOption(pair string) error {
+ kv := strings.SplitN(pair, "=", 2)
+ if len(kv) != 2 || kv[0] == "" {
+ return fmt.Errorf("invalid option")
+ }
+
+ key, err := url.QueryUnescape(kv[0])
+ if err != nil {
+ return fmt.Errorf("invalid option key \"%s\": %s", kv[0], err)
+ }
+
+ value, err := url.QueryUnescape(kv[1])
+ if err != nil {
+ return fmt.Errorf("invalid option value \"%s\": %s", kv[1], err)
+ }
+
+ lowerKey := strings.ToLower(key)
+ switch lowerKey {
+ case "appname":
+ p.AppName = value
+ case "authmechanism":
+ p.AuthMechanism = value
+ case "authmechanismproperties":
+ p.AuthMechanismProperties = make(map[string]string)
+ pairs := strings.Split(value, ",")
+ for _, pair := range pairs {
+ kv := strings.SplitN(pair, ":", 2)
+ if len(kv) != 2 || kv[0] == "" {
+ return fmt.Errorf("invalid authMechanism property")
+ }
+ p.AuthMechanismProperties[kv[0]] = kv[1]
+ }
+ case "authsource":
+ p.AuthSource = value
+ case "connect":
+ switch strings.ToLower(value) {
+ case "auto", "automatic":
+ p.Connect = AutoConnect
+ case "direct", "single":
+ p.Connect = SingleConnect
+ default:
+ return fmt.Errorf("invalid 'connect' value: %s", value)
+ }
+ case "connecttimeoutms":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.ConnectTimeout = time.Duration(n) * time.Millisecond
+ case "heartbeatintervalms", "heartbeatfrequencyms":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.HeartbeatInterval = time.Duration(n) * time.Millisecond
+ case "fsync":
+ f, err := strconv.ParseBool(value)
+ if err != nil {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.FSync = f
+ case "j":
+ j, err := strconv.ParseBool(value)
+ if err != nil {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.Journal = j
+ case "gssapiservicename":
+ p.KerberosService = value
+ case "gssapihostname":
+ p.KerberosServiceHost = value
+ case "maxconnsperhost":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.MaxConnsPerHost = uint16(n)
+ p.MaxConnsPerHostSet = true
+ case "maxidleconnsperhost":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.MaxIdleConnsPerHost = uint16(n)
+ p.MaxIdleConnsPerHostSet = true
+ case "maxidletimems":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.MaxConnIdleTime = time.Duration(n) * time.Millisecond
+ case "maxlifetimems":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.MaxConnLifeTime = time.Duration(n) * time.Millisecond
+ case "maxpoolsize":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.MaxConnsPerHost = uint16(n)
+ p.MaxConnsPerHostSet = true
+ p.MaxIdleConnsPerHost = uint16(n)
+ p.MaxIdleConnsPerHostSet = true
+ case "readpreference":
+ p.ReadPreference = value
+ case "readpreferencetags":
+ tags := make(map[string]string)
+ items := strings.Split(value, ",")
+ for _, item := range items {
+ parts := strings.Split(item, ":")
+ if len(parts) != 2 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ tags[parts[0]] = parts[1]
+ }
+ p.ReadPreferenceTagSets = append(p.ReadPreferenceTagSets, tags)
+ case "replicaset":
+ p.ReplicaSet = value
+ case "serverselectiontimeoutms":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.ServerSelectionTimeout = time.Duration(n) * time.Millisecond
+ case "sockettimeoutms":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.SocketTimeout = time.Duration(n) * time.Millisecond
+ case "ssl":
+ b, err := strconv.ParseBool(value)
+ if err != nil {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.UseSSL = b
+ case "w":
+ p.W = value
+ case "wtimeoutms":
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.WTimeout = time.Duration(n) * time.Millisecond
+ p.haveWTimeoutMS = true
+ case "wtimeout":
+ if p.haveWTimeoutMS {
+ // use wtimeoutMS if it exists
+ break
+ }
+ n, err := strconv.Atoi(value)
+ if err != nil || n < 0 {
+ return fmt.Errorf("invalid value for %s: %s", key, value)
+ }
+ p.WTimeout = time.Duration(n) * time.Millisecond
+ default:
+ if p.UnknownOptions == nil {
+ p.UnknownOptions = make(map[string][]string)
+ }
+ p.UnknownOptions[lowerKey] = append(p.UnknownOptions[lowerKey], value)
+ }
+
+ if p.Options == nil {
+ p.Options = make(map[string][]string)
+ }
+ p.Options[lowerKey] = append(p.Options[lowerKey], value)
+
+ return nil
+}
diff --git a/src/mongo/gotools/common/db/connector.go b/src/mongo/gotools/common/db/connector.go
index c3e50ddfa5d..87b6a830fc9 100644
--- a/src/mongo/gotools/common/db/connector.go
+++ b/src/mongo/gotools/common/db/connector.go
@@ -27,23 +27,28 @@ type VanillaDBConnector struct {
// connection string and then sets up the dial information using the default
// dial timeout.
func (self *VanillaDBConnector) Configure(opts options.ToolOptions) error {
- // create the addresses to be used to connect
- connectionAddrs := util.CreateConnectionAddrs(opts.Host, opts.Port)
-
timeout := time.Duration(opts.Timeout) * time.Second
// set up the dial info
self.dialInfo = &mgo.DialInfo{
- Addrs: connectionAddrs,
- Timeout: timeout,
Direct: opts.Direct,
ReplicaSetName: opts.ReplicaSetName,
Username: opts.Auth.Username,
Password: opts.Auth.Password,
Source: opts.GetAuthenticationDatabase(),
Mechanism: opts.Auth.Mechanism,
+ Timeout: timeout,
}
+
+ // create or fetch the addresses to be used to connect
+ if opts.URI != nil && opts.URI.ConnectionString != "" {
+ self.dialInfo.Addrs = opts.URI.GetConnectionAddrs()
+ } else {
+ self.dialInfo.Addrs = util.CreateConnectionAddrs(opts.Host, opts.Port)
+ }
+
kerberos.AddKerberosOpts(opts, self.dialInfo)
+
return nil
}
diff --git a/src/mongo/gotools/common/db/openssl/openssl.go b/src/mongo/gotools/common/db/openssl/openssl.go
index 1cc4c2ccd1f..d938cf5d532 100644
--- a/src/mongo/gotools/common/db/openssl/openssl.go
+++ b/src/mongo/gotools/common/db/openssl/openssl.go
@@ -24,8 +24,6 @@ type SSLDBConnector struct {
// connection string, and sets up the correct function to dial the server
// based on the ssl options passed in.
func (self *SSLDBConnector) Configure(opts options.ToolOptions) error {
- // create the addresses to be used to connect
- connectionAddrs := util.CreateConnectionAddrs(opts.Host, opts.Port)
var err error
self.ctx, err = setupCtx(opts)
@@ -49,7 +47,6 @@ func (self *SSLDBConnector) Configure(opts options.ToolOptions) error {
// set up the dial info
self.dialInfo = &mgo.DialInfo{
- Addrs: connectionAddrs,
Timeout: timeout,
Direct: opts.Direct,
ReplicaSetName: opts.ReplicaSetName,
@@ -59,6 +56,13 @@ func (self *SSLDBConnector) Configure(opts options.ToolOptions) error {
Source: opts.GetAuthenticationDatabase(),
Mechanism: opts.Auth.Mechanism,
}
+
+ // create or fetch the addresses to be used to connect
+ if opts.URI != nil && opts.URI.ConnectionString != "" {
+ self.dialInfo.Addrs = opts.URI.GetConnectionAddrs()
+ } else {
+ self.dialInfo.Addrs = util.CreateConnectionAddrs(opts.Host, opts.Port)
+ }
kerberos.AddKerberosOpts(opts, self.dialInfo)
return nil
diff --git a/src/mongo/gotools/common/db/write_concern.go b/src/mongo/gotools/common/db/write_concern.go
index 0a9a16214c8..cf7a8e98d56 100644
--- a/src/mongo/gotools/common/db/write_concern.go
+++ b/src/mongo/gotools/common/db/write_concern.go
@@ -1,12 +1,15 @@
package db
import (
- "fmt"
+ "github.com/mongodb/mongo-tools/common/connstring"
"github.com/mongodb/mongo-tools/common/json"
"github.com/mongodb/mongo-tools/common/log"
"github.com/mongodb/mongo-tools/common/util"
"gopkg.in/mgo.v2"
+
+ "fmt"
"strconv"
+ "time"
)
// write concern fields
@@ -85,13 +88,54 @@ func constructWCObject(writeConcern string) (sessionSafety *mgo.Safe, err error)
return sessionSafety, nil
}
+// constructSafetyFromConnString takes in a parsed connection string and attempts
+// to construct an mgo.Safe object from it. It returns an error if it is unable
+// to parse the write concern value.
+func constructSafetyFromConnString(cs *connstring.ConnString) (*mgo.Safe, error) {
+ safe := &mgo.Safe{}
+
+ wValue, err := strconv.Atoi(cs.W)
+ if err != nil {
+ safe.WMode = cs.W
+ } else {
+ safe.W = wValue
+ if wValue < 0 {
+ return nil, fmt.Errorf("invalid '%v' argument: %v", w, wValue)
+ }
+ }
+
+ safe.WTimeout = int(cs.WTimeout / time.Second)
+ safe.FSync = cs.FSync
+ safe.J = cs.Journal
+
+ if safe.WMode == "" && safe.W == 0 && !safe.J {
+ return nil, nil
+ }
+
+ return safe, nil
+}
+
// BuildWriteConcern takes a string and a NodeType indicating the type of node the write concern
// is intended to be used against, and converts the write concern string argument into an
// mgo.Safe object that's usable on sessions for that node type.
-func BuildWriteConcern(writeConcern string, nodeType NodeType) (*mgo.Safe, error) {
- sessionSafety, err := constructWCObject(writeConcern)
- if err != nil {
- return nil, err
+func BuildWriteConcern(writeConcern string, nodeType NodeType, cs *connstring.ConnString) (*mgo.Safe, error) {
+ var sessionSafety *mgo.Safe
+ var err error
+
+ if cs != nil && writeConcern != "" {
+ return nil, fmt.Errorf("cannot specify writeConcern string and connectionString object")
+ }
+
+ if cs != nil {
+ sessionSafety, err = constructSafetyFromConnString(cs)
+ if err != nil {
+ return nil, err
+ }
+ } else if writeConcern != "" {
+ sessionSafety, err = constructWCObject(writeConcern)
+ if err != nil {
+ return nil, err
+ }
}
if sessionSafety == nil {
diff --git a/src/mongo/gotools/common/db/write_concern_test.go b/src/mongo/gotools/common/db/write_concern_test.go
index 96bd8e0ed89..bdc6ef69018 100644
--- a/src/mongo/gotools/common/db/write_concern_test.go
+++ b/src/mongo/gotools/common/db/write_concern_test.go
@@ -1,53 +1,87 @@
package db
import (
+ "github.com/mongodb/mongo-tools/common/connstring"
. "github.com/smartystreets/goconvey/convey"
+
+ "fmt"
"testing"
+ "time"
)
func TestBuildWriteConcern(t *testing.T) {
- Convey("Given a write concern string value, and a boolean indicating if the "+
- "write concern is to be used on a replica set, on calling BuildWriteConcern...", t, func() {
- Convey("no error should be returned if the write concern is valid", func() {
- writeConcern, err := BuildWriteConcern(`{w:34}`, ReplSet)
- So(err, ShouldBeNil)
- So(writeConcern.W, ShouldEqual, 34)
- writeConcern, err = BuildWriteConcern(`{w:"majority"}`, ReplSet)
- So(err, ShouldBeNil)
- So(writeConcern.WMode, ShouldEqual, "majority")
- writeConcern, err = BuildWriteConcern(`majority`, ReplSet)
- So(err, ShouldBeNil)
- So(writeConcern.WMode, ShouldEqual, "majority")
- writeConcern, err = BuildWriteConcern(`tagset`, ReplSet)
- So(err, ShouldBeNil)
- So(writeConcern.WMode, ShouldEqual, "tagset")
+ Convey("When building write concern object", t, func() {
+ Convey("and given a write concern string value, and a boolean indicating if the "+
+ "write concern is to be used on a replica set, on calling BuildWriteConcern...", func() {
+ Convey("no error should be returned if the write concern is valid", func() {
+ writeConcern, err := BuildWriteConcern(`{w:34}`, ReplSet, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 34)
+ writeConcern, err = BuildWriteConcern(`{w:"majority"}`, ReplSet, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.WMode, ShouldEqual, "majority")
+ writeConcern, err = BuildWriteConcern(`majority`, ReplSet, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.WMode, ShouldEqual, "majority")
+ writeConcern, err = BuildWriteConcern(`tagset`, ReplSet, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.WMode, ShouldEqual, "tagset")
+ })
+ Convey("on replica sets, only a write concern of 1 or 0 should be returned", func() {
+ writeConcern, err := BuildWriteConcern(`{w:34}`, Standalone, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 1)
+ writeConcern, err = BuildWriteConcern(`{w:"majority"}`, Standalone, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 1)
+ writeConcern, err = BuildWriteConcern(`tagset`, Standalone, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 1)
+ })
+ Convey("with a w value of 0, without j set, a nil write concern should be returned", func() {
+ writeConcern, err := BuildWriteConcern(`{w:0}`, Standalone, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern, ShouldBeNil)
+ })
+ Convey("with a negative w value, an error should be returned", func() {
+ _, err := BuildWriteConcern(`{w:-1}`, ReplSet, nil)
+ So(err, ShouldNotBeNil)
+ _, err = BuildWriteConcern(`{w:-2}`, ReplSet, nil)
+ So(err, ShouldNotBeNil)
+ })
+ Convey("with a w value of 0, with j set, a non-nil write concern should be returned", func() {
+ writeConcern, err := BuildWriteConcern(`{w:0, j:true}`, Standalone, nil)
+ So(err, ShouldBeNil)
+ So(writeConcern.J, ShouldBeTrue)
+ })
})
- Convey("on replica sets, only a write concern of 1 or 0 should be returned", func() {
- writeConcern, err := BuildWriteConcern(`{w:34}`, Standalone)
- So(err, ShouldBeNil)
- So(writeConcern.W, ShouldEqual, 1)
- writeConcern, err = BuildWriteConcern(`{w:"majority"}`, Standalone)
- So(err, ShouldBeNil)
- So(writeConcern.W, ShouldEqual, 1)
- writeConcern, err = BuildWriteConcern(`tagset`, Standalone)
- So(err, ShouldBeNil)
- So(writeConcern.W, ShouldEqual, 1)
- })
- Convey("with a w value of 0, without j set, a nil write concern should be returned", func() {
- writeConcern, err := BuildWriteConcern(`{w:0}`, Standalone)
- So(err, ShouldBeNil)
- So(writeConcern, ShouldBeNil)
+ Convey("and given a connection string", func() {
+ Convey("with a w value of 0, without j set, a nil write concern should be returned", func() {
+ writeConcern, err := BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "0"})
+ So(err, ShouldBeNil)
+ So(writeConcern, ShouldBeNil)
+ })
+ Convey("with a negative w value, an error should be returned", func() {
+ _, err := BuildWriteConcern(``, ReplSet, &connstring.ConnString{W: "-1"})
+ So(err, ShouldNotBeNil)
+ _, err = BuildWriteConcern(``, ReplSet, &connstring.ConnString{W: "-2"})
+ So(err, ShouldNotBeNil)
+ })
+ Convey("on replica sets, only a write concern of 1 or 0 should be returned", func() {
+ writeConcern, err := BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "34"})
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 1)
+ writeConcern, err = BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "majority"})
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 1)
+ writeConcern, err = BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "tagset"})
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 1)
+ })
})
- Convey("with a negative w value, an error should be returned", func() {
- _, err := BuildWriteConcern(`{w:-1}`, ReplSet)
+ Convey("and given both, should error", func() {
+ _, err := BuildWriteConcern(`ab`, ReplSet, &connstring.ConnString{W: "-1"})
So(err, ShouldNotBeNil)
- _, err = BuildWriteConcern(`{w:-2}`, ReplSet)
- So(err, ShouldNotBeNil)
- })
- Convey("with a w value of 0, with j set, a non-nil write concern should be returned", func() {
- writeConcern, err := BuildWriteConcern(`{w:0, j:true}`, Standalone)
- So(err, ShouldBeNil)
- So(writeConcern.J, ShouldBeTrue)
})
})
}
@@ -164,3 +198,57 @@ func TestConstructWCObject(t *testing.T) {
})
})
}
+
+func TestConstructSafetyFromConnString(t *testing.T) {
+ Convey("Given a parsed &connstring, on calling constructSafetyFromConnString...", t, func() {
+
+ Convey("non string values should be assigned to the 'WMode' "+
+ "field in their entirety", func() {
+ writeConcernString := "majority"
+ cs := &connstring.ConnString{
+ W: writeConcernString,
+ }
+ writeConcern, err := constructSafetyFromConnString(cs)
+ So(err, ShouldBeNil)
+ So(writeConcern.WMode, ShouldEqual, writeConcernString)
+ })
+
+ Convey("Int values should be assigned to the 'w' field ", func() {
+ cs := &connstring.ConnString{
+ W: "4",
+ }
+ writeConcern, err := constructSafetyFromConnString(cs)
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, 4)
+ })
+
+ Convey("&connstrings with valid j, wtimeout, fsync and w, should be "+
+ "assigned accordingly", func() {
+ expectedW := 3
+ expectedWTimeout := 43
+ cs := &connstring.ConnString{
+ W: "3",
+ Journal: true,
+ FSync: false,
+ WTimeout: time.Second * 43,
+ }
+ writeConcern, err := constructSafetyFromConnString(cs)
+ So(err, ShouldBeNil)
+ So(writeConcern.W, ShouldEqual, expectedW)
+ So(writeConcern.J, ShouldBeTrue)
+ So(writeConcern.FSync, ShouldBeFalse)
+ So(writeConcern.WTimeout, ShouldEqual, expectedWTimeout)
+ })
+
+ Convey("Unacknowledge write concern strings should return a nil object "+
+ "if journaling is not required", func() {
+ cs := &connstring.ConnString{
+ W: "0",
+ }
+ writeConcern, err := constructSafetyFromConnString(cs)
+ fmt.Println(writeConcern)
+ So(err, ShouldBeNil)
+ So(writeConcern, ShouldBeNil)
+ })
+ })
+}
diff --git a/src/mongo/gotools/common/options/options.go b/src/mongo/gotools/common/options/options.go
index 5e52c733a7f..1e7cb8c6ca3 100644
--- a/src/mongo/gotools/common/options/options.go
+++ b/src/mongo/gotools/common/options/options.go
@@ -3,16 +3,18 @@
package options
import (
+ "fmt"
"github.com/jessevdk/go-flags"
+ "github.com/mongodb/mongo-tools/common/connstring"
"github.com/mongodb/mongo-tools/common/failpoint"
"github.com/mongodb/mongo-tools/common/log"
-
- "fmt"
+ "github.com/mongodb/mongo-tools/common/util"
"os"
"regexp"
"runtime"
"strconv"
"strings"
+ "time"
)
// Gitspec that the tool was built with. Needs to be set using -ldflags
@@ -21,6 +23,23 @@ var (
Gitspec = "built-without-git-spec"
)
+var (
+ KnownURIOptionsAuth = []string{"authsource", "authmechanism"}
+ KnownURIOptionsConnection = []string{"connecttimeoutms"}
+ KnownURIOptionsSSL = []string{"ssl"}
+ KnownURIOptionsReadPreference = []string{"readpreference"}
+ KnownURIOptionsKerberos = []string{"gssapiservicename", "gssapihostname"}
+ KnownURIOptionsWriteConcern = []string{"wtimeout", "w", "j", "fsync"}
+ KnownURIOptionsReplicaSet = []string{"replicaset"}
+)
+
+var (
+ BuiltWithSSL bool
+ BuiltWithGSSAPI bool
+)
+
+const IncompatibleArgsErrorFormat = "illegal argument combination: cannot specfy %s and --uri"
+
// Struct encompassing all of the options that are reused across tools: "help",
// "version", verbosity settings, ssl settings, etc.
type ToolOptions struct {
@@ -32,6 +51,7 @@ type ToolOptions struct {
VersionStr string
// Sub-option types
+ *URI
*General
*Verbosity
*Connection
@@ -52,6 +72,9 @@ type ToolOptions struct {
// for caching the parser
parser *flags.Parser
+
+ // for checking which options were enabled on this tool
+ enabledOptions EnabledOptions
}
type Namespace struct {
@@ -65,7 +88,7 @@ type General struct {
Help bool `long:"help" description:"print usage"`
Version bool `long:"version" description:"print the tool version and exit"`
- MaxProcs int `long:"numThreads" default:"0" hidden:"true"`
+ MaxProcs int `long:"numThreads" hidden:"true"`
Failpoints string `long:"failpoints" hidden:"true"`
}
@@ -84,6 +107,14 @@ func (v Verbosity) IsQuiet() bool {
return v.Quiet
}
+type URI struct {
+ ConnectionString string `long:"uri" value-name:"mongodb-uri" description:"mongodb uri connection string"`
+
+ knownURIParameters []string
+ extraOptionsRegistry []ExtraOptions
+ connString connstring.ConnString
+}
+
// Struct holding connection-related options
type Connection struct {
Host string `short:"h" long:"host" value-name:"<hostname>" description:"mongodb host to connect to (setname/host1,host2 for replica sets)"`
@@ -117,6 +148,16 @@ type Kerberos struct {
Service string `long:"gssapiServiceName" value-name:"<service-name>" description:"service name to use when authenticating using GSSAPI/Kerberos ('mongodb' by default)"`
ServiceHost string `long:"gssapiHostName" value-name:"<host-name>" description:"hostname to use when authenticating using GSSAPI/Kerberos (remote server's address by default)"`
}
+type WriteConcern struct {
+ // Specifies the write concern for each write operation that mongofiles writes to the target database.
+ // By default, mongofiles waits for a majority of members from the replica set to respond before returning.
+ WriteConcern string `long:"writeConcern" value-name:"<write-concern>" default:"majority" default-mask:"-" description:"write concern options e.g. --writeConcern majority, --writeConcern '{w: 3, wtimeout: 500, fsync: true, j: true}' (defaults to 'majority')"`
+
+ w int
+ wtimeout int
+ fsync bool
+ journal bool
+}
type OptionRegistrationFunction func(o *ToolOptions) error
@@ -126,6 +167,7 @@ type EnabledOptions struct {
Auth bool
Connection bool
Namespace bool
+ URI bool
}
func parseVal(val string) int {
@@ -146,12 +188,14 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions {
General: &General{},
Verbosity: &Verbosity{},
Connection: &Connection{},
+ URI: &URI{},
SSL: &SSL{},
Auth: &Auth{},
Namespace: &Namespace{},
Kerberos: &Kerberos{},
parser: flags.NewNamedParser(
fmt.Sprintf("%v %v", appName, usageStr), flags.None),
+ enabledOptions: enabled,
}
// Called when -v or --verbose is parsed
@@ -172,6 +216,8 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions {
opts.parser.UnknownOptionHandler = opts.handleUnknownOption
+ opts.URI.AddKnownURIParameters(KnownURIOptionsReplicaSet)
+
if _, err := opts.parser.AddGroup("general options", "", opts.General); err != nil {
panic(fmt.Errorf("couldn't register general options: %v", err))
}
@@ -183,6 +229,7 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions {
EnableFailpoints(opts)
if enabled.Connection {
+ opts.URI.AddKnownURIParameters(KnownURIOptionsConnection)
if _, err := opts.parser.AddGroup("connection options", "", opts.Connection); err != nil {
panic(fmt.Errorf("couldn't register connection options: %v", err))
}
@@ -196,6 +243,7 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions {
}
if enabled.Auth {
+ opts.URI.AddKnownURIParameters(KnownURIOptionsAuth)
if _, err := opts.parser.AddGroup("authentication options", "", opts.Auth); err != nil {
panic(fmt.Errorf("couldn't register auth options"))
}
@@ -205,7 +253,11 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions {
panic(fmt.Errorf("couldn't register namespace options"))
}
}
-
+ if enabled.URI {
+ if _, err := opts.parser.AddGroup("uri options", "", opts.URI); err != nil {
+ panic(fmt.Errorf("couldn't register URI options"))
+ }
+ }
if opts.MaxProcs <= 0 {
opts.MaxProcs = runtime.NumCPU()
}
@@ -266,6 +318,13 @@ type ExtraOptions interface {
Name() string
}
+type URISetter interface {
+ // SetOptionsFromURI provides a way for tools to fetch any options that were
+ // set in the URI and set them on the ExtraOptions that they pass to the options
+ // package.
+ SetOptionsFromURI(connstring.ConnString) error
+}
+
func (auth *Auth) RequiresExternalDB() bool {
return auth.Mechanism == "GSSAPI" || auth.Mechanism == "PLAIN" || auth.Mechanism == "MONGODB-X509"
}
@@ -277,6 +336,52 @@ func (auth *Auth) ShouldAskForPassword() bool {
!(auth.Mechanism == "MONGODB-X509" || auth.Mechanism == "GSSAPI")
}
+func (uri *URI) GetConnectionAddrs() []string {
+ return uri.connString.Hosts
+}
+func (uri *URI) ParsedConnString() *connstring.ConnString {
+ if uri.ConnectionString == "" {
+ return nil
+ }
+ return &uri.connString
+}
+func (uri *URI) AddKnownURIParameters(uriFieldNames []string) {
+ uri.knownURIParameters = append(uri.knownURIParameters, uriFieldNames...)
+}
+
+func (opts *ToolOptions) EnabledToolOptions() EnabledOptions {
+ return opts.enabledOptions
+}
+
+func (uri *URI) LogUnsupportedOptions() {
+ allOptionsFromURI := map[string]struct{}{}
+
+ for optName := range uri.connString.Options {
+ allOptionsFromURI[optName] = struct{}{}
+ }
+
+ for optName := range uri.connString.UnknownOptions {
+ allOptionsFromURI[optName] = struct{}{}
+ }
+
+ for _, optName := range uri.knownURIParameters {
+ if _, ok := allOptionsFromURI[optName]; ok {
+ delete(allOptionsFromURI, optName)
+ }
+ }
+
+ unsupportedOptions := make([]string, len(allOptionsFromURI))
+ optionIndex := 0
+ for optionName := range allOptionsFromURI {
+ unsupportedOptions[optionIndex] = optionName
+ optionIndex++
+ }
+
+ for _, optName := range unsupportedOptions {
+ log.Logvf(log.Always, "WARNING: ignoring unsupported URI parameter '%v'", optName)
+ }
+}
+
// Get the authentication database to use. Should be the value of
// --authenticationDatabase if it's provided, otherwise, the database that's
// specified in the tool's --db arg.
@@ -298,13 +403,39 @@ func (o *ToolOptions) AddOptions(opts ExtraOptions) {
panic(fmt.Sprintf("error setting command line options for %v: %v",
opts.Name(), err))
}
+
+ if o.enabledOptions.URI {
+ o.URI.extraOptionsRegistry = append(o.URI.extraOptionsRegistry, opts)
+ }
}
// Parse the command line args. Returns any extra args not accounted for by
// parsing, as well as an error if the parsing returns an error.
-func (o *ToolOptions) Parse() ([]string, error) {
- args, err := o.parser.Parse()
+func (o *ToolOptions) ParseArgs(args []string) ([]string, error) {
+ args, err := o.parser.ParseArgs(args)
+ if err != nil {
+ return []string{}, err
+ }
+
+ // connect directly, unless a replica set name is explicitly specified
+ if o.Host != "" {
+ _, o.ReplicaSetName = util.ParseConnectionString(o.Host)
+ o.Direct = (o.ReplicaSetName == "")
+ }
+
failpoint.ParseFailpoints(o.Failpoints)
+
+ if o.URI != nil && o.URI.ConnectionString != "" {
+ cs, err := connstring.ParseURIConnectionString(o.URI.ConnectionString)
+ if err != nil {
+ return []string{}, err
+ }
+ err = o.setOptionsFromURI(cs)
+ if err != nil {
+ return []string{}, err
+ }
+ }
+
return args, err
}
@@ -317,6 +448,76 @@ func (opts *ToolOptions) handleUnknownOption(option string, arg flags.SplitArgum
return args, fmt.Errorf(`unknown option "%v"`, option)
}
+func (opts *ToolOptions) setOptionsFromURI(cs connstring.ConnString) error {
+ opts.URI.connString = cs
+
+ // if Connection settings are enabled, then verify that other methods
+ // of specifying weren't used and set timeout
+ if opts.enabledOptions.Connection {
+ switch {
+ case opts.Connection.Host != "":
+ return fmt.Errorf(IncompatibleArgsErrorFormat, "--host")
+ case opts.Connection.Port != "":
+ return fmt.Errorf(IncompatibleArgsErrorFormat, "--port")
+ case opts.Connection.Timeout != 3:
+ return fmt.Errorf(IncompatibleArgsErrorFormat, "--dialTimeout")
+ }
+ opts.Connection.Timeout = int(cs.ConnectTimeout / time.Millisecond)
+ }
+
+ if opts.enabledOptions.Auth {
+ switch {
+ case opts.Username != "":
+ return fmt.Errorf(IncompatibleArgsErrorFormat, "--username")
+ case opts.Password != "" && cs.Password != "":
+ return fmt.Errorf(IncompatibleArgsErrorFormat,
+ "illegal argument combination: cannot specify password in uri and --password")
+ case opts.Source != "":
+ return fmt.Errorf(IncompatibleArgsErrorFormat, "--authenticationDatabase")
+ case opts.Auth.Mechanism != "":
+ return fmt.Errorf(IncompatibleArgsErrorFormat, "--authenticationMechanism")
+ }
+ opts.Username = cs.Username
+ opts.Password = cs.Password
+ opts.Source = cs.AuthSource
+ opts.Auth.Mechanism = cs.AuthMechanism
+ }
+ if opts.enabledOptions.Namespace {
+ if opts.Namespace != nil && opts.Namespace.DB != "" {
+ return fmt.Errorf(IncompatibleArgsErrorFormat, "--db")
+ }
+ }
+
+ opts.Namespace.DB = cs.Database
+ opts.Direct = (cs.Connect == connstring.SingleConnect)
+ opts.ReplicaSetName = cs.ReplicaSet
+
+ if cs.UseSSL && !BuiltWithSSL {
+ return fmt.Errorf("cannot use ssl: tool not built with SSL support")
+ }
+ opts.SSL.UseSSL = cs.UseSSL
+
+ if cs.KerberosService != "" && !BuiltWithGSSAPI {
+ return fmt.Errorf("cannot specify gssapiservicename: tool not built with kerberos support")
+ }
+ if cs.KerberosServiceHost != "" && !BuiltWithGSSAPI {
+ return fmt.Errorf("cannot specify gssapihostname: tool not built with kerberos support")
+ }
+
+ opts.Kerberos.Service = cs.KerberosService
+ opts.Kerberos.ServiceHost = cs.KerberosServiceHost
+
+ for _, extraOpts := range opts.URI.extraOptionsRegistry {
+ if uriSetter, ok := extraOpts.(URISetter); ok {
+ err := uriSetter.SetOptionsFromURI(cs)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
// getIntArg returns 3 args: the parsed int value, a bool set to true if a value
// was consumed from the incoming args array during parsing, and an error
// value if parsing failed
diff --git a/src/mongo/gotools/common/options/options_gssapi.go b/src/mongo/gotools/common/options/options_gssapi.go
index f10aa3a2b3b..93d3e9fa930 100644
--- a/src/mongo/gotools/common/options/options_gssapi.go
+++ b/src/mongo/gotools/common/options/options_gssapi.go
@@ -8,5 +8,10 @@ func init() {
func registerGSSAPIOptions(self *ToolOptions) error {
_, err := self.parser.AddGroup("kerberos options", "", self.Kerberos)
- return err
+ if err != nil {
+ return err
+ }
+ self.URI.AddKnownURIParameters(KnownURIOptionsKerberos)
+ BuiltWithGSSAPI = true
+ return nil
}
diff --git a/src/mongo/gotools/common/options/options_ssl.go b/src/mongo/gotools/common/options/options_ssl.go
index e8b8f27171f..6fcd4da13b3 100644
--- a/src/mongo/gotools/common/options/options_ssl.go
+++ b/src/mongo/gotools/common/options/options_ssl.go
@@ -14,5 +14,12 @@ func init() {
func registerSSLOptions(self *ToolOptions) error {
_, err := self.parser.AddGroup("ssl options", "", self.SSL)
- return err
+ if err != nil {
+ return err
+ }
+ if self.enabledOptions.URI {
+ self.URI.AddKnownURIParameters(KnownURIOptionsSSL)
+ }
+ BuiltWithSSL = true
+ return nil
}
diff --git a/src/mongo/gotools/common/options/options_test.go b/src/mongo/gotools/common/options/options_test.go
index cc18af21506..39579ea36c5 100644
--- a/src/mongo/gotools/common/options/options_test.go
+++ b/src/mongo/gotools/common/options/options_test.go
@@ -1,13 +1,17 @@
package options
import (
+ "github.com/mongodb/mongo-tools/common/connstring"
. "github.com/smartystreets/goconvey/convey"
+
+ "runtime"
"testing"
+ "time"
)
func TestVerbosityFlag(t *testing.T) {
Convey("With a new ToolOptions", t, func() {
- enabled := EnabledOptions{false, false, false}
+ enabled := EnabledOptions{false, false, false, false}
optPtr := New("", "", enabled)
So(optPtr, ShouldNotBeNil)
So(optPtr.parser, ShouldNotBeNil)
@@ -73,3 +77,303 @@ func TestVerbosityFlag(t *testing.T) {
})
})
}
+
+type uriTester struct {
+ Name string
+ CS connstring.ConnString
+ OptsIn *ToolOptions
+ OptsExpected *ToolOptions
+ WithSSL bool
+ WithGSSAPI bool
+ ShouldError bool
+ ShouldAskForPassword bool
+}
+
+func TestParseAndSetOptions(t *testing.T) {
+ Convey("With a matrix of URIs and expected results", t, func() {
+ enabledURIOnly := EnabledOptions{false, false, false, true}
+ testCases := []uriTester{
+ {
+ Name: "not built with ssl",
+ CS: connstring.ConnString{
+ UseSSL: true,
+ },
+ WithSSL: false,
+ OptsIn: New("", "", enabledURIOnly),
+ OptsExpected: New("", "", enabledURIOnly),
+ ShouldError: true,
+ },
+ {
+ Name: "built with ssl",
+ CS: connstring.ConnString{
+ UseSSL: true,
+ },
+ WithSSL: true, OptsIn: New("", "", enabledURIOnly),
+ OptsExpected: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{
+ UseSSL: true,
+ },
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: enabledURIOnly,
+ },
+ ShouldError: false,
+ },
+ {
+ Name: "not built with gssapi",
+ CS: connstring.ConnString{
+ KerberosService: "service",
+ },
+ WithGSSAPI: false,
+ OptsIn: New("", "", enabledURIOnly),
+ OptsExpected: New("", "", enabledURIOnly),
+ ShouldError: true,
+ },
+ {
+ Name: "built with gssapi",
+ CS: connstring.ConnString{
+ KerberosService: "service",
+ KerberosServiceHost: "servicehost",
+ },
+ WithGSSAPI: true,
+ OptsIn: New("", "", enabledURIOnly),
+ OptsExpected: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{
+ Service: "service",
+ ServiceHost: "servicehost",
+ },
+ enabledOptions: enabledURIOnly,
+ },
+ ShouldError: false,
+ },
+ {
+ Name: "connection fields set",
+ CS: connstring.ConnString{
+ ConnectTimeout: time.Duration(100) * time.Millisecond,
+ },
+ OptsIn: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{
+ Timeout: 3,
+ },
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{Connection: true, URI: true},
+ },
+ OptsExpected: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{
+ Timeout: 100,
+ },
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{Connection: true, URI: true},
+ },
+ ShouldError: false,
+ },
+ {
+ Name: "auth fields set",
+ CS: connstring.ConnString{
+ AuthMechanism: "MONGODB-X509",
+ AuthSource: "authSource",
+ Username: "user",
+ Password: "password",
+ },
+ OptsIn: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{Auth: true, URI: true},
+ },
+ OptsExpected: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{
+ Username: "user",
+ Password: "password",
+ Source: "authSource",
+ Mechanism: "MONGODB-X509",
+ },
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{Connection: true, URI: true},
+ },
+ ShouldError: false,
+ },
+ {
+ Name: "should ask for password",
+ CS: connstring.ConnString{
+ AuthMechanism: "MONGODB-X509",
+ AuthSource: "authSource",
+ Username: "user",
+ },
+ OptsIn: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{Auth: true, URI: true},
+ },
+ OptsExpected: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{
+ Username: "user",
+ Source: "authSource",
+ Mechanism: "MONGODB-X509",
+ },
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{Connection: true, URI: true},
+ },
+ ShouldError: false,
+ ShouldAskForPassword: true,
+ },
+ {
+ Name: "single connect sets 'Direct'",
+ CS: connstring.ConnString{
+ Connect: connstring.SingleConnect,
+ },
+ OptsIn: New("", "", enabledURIOnly),
+ OptsExpected: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Direct: true,
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{URI: true},
+ },
+ ShouldError: false,
+ },
+ {
+ Name: "ReplSetName is set when CS contains it",
+ CS: connstring.ConnString{
+ ReplicaSet: "replset",
+ },
+ OptsIn: New("", "", enabledURIOnly),
+ OptsExpected: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{},
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{URI: true},
+ ReplicaSetName: "replset",
+ },
+ ShouldError: false,
+ },
+ {
+ Name: "fail when uri and options set",
+ CS: connstring.ConnString{
+ Hosts: []string{"host"},
+ },
+ OptsIn: &ToolOptions{
+ General: &General{},
+ Verbosity: &Verbosity{},
+ Connection: &Connection{
+ Host: "host",
+ },
+ URI: &URI{},
+ SSL: &SSL{},
+ Auth: &Auth{},
+ Namespace: &Namespace{},
+ Kerberos: &Kerberos{},
+ enabledOptions: EnabledOptions{Connection: true, URI: true},
+ },
+ OptsExpected: New("", "", EnabledOptions{Connection: true, URI: true}),
+ ShouldError: true,
+ },
+ }
+
+ Convey("results should match expected", func() {
+ for _, testCase := range testCases {
+ t.Log("Test Case:", testCase.Name)
+
+ testCase.OptsIn.URI.ConnectionString = "mongodb://dummy"
+ testCase.OptsExpected.URI.ConnectionString = "mongodb://dummy"
+
+ BuiltWithSSL = testCase.WithSSL
+ BuiltWithGSSAPI = testCase.WithGSSAPI
+
+ testCase.OptsIn.URI.connString = testCase.CS
+
+ err := testCase.OptsIn.setOptionsFromURI(testCase.CS)
+
+ if testCase.ShouldError {
+ So(err, ShouldNotBeNil)
+ } else {
+ So(err, ShouldBeNil)
+ }
+
+ So(testCase.OptsIn.Connection.Timeout, ShouldResemble, testCase.OptsExpected.Connection.Timeout)
+ So(testCase.OptsIn.Username, ShouldResemble, testCase.OptsExpected.Username)
+ So(testCase.OptsIn.Password, ShouldResemble, testCase.OptsExpected.Password)
+ So(testCase.OptsIn.Source, ShouldResemble, testCase.OptsExpected.Source)
+ So(testCase.OptsIn.Auth.Mechanism, ShouldResemble, testCase.OptsExpected.Auth.Mechanism)
+ So(testCase.OptsIn.Direct, ShouldResemble, testCase.OptsExpected.Direct)
+ So(testCase.OptsIn.ReplicaSetName, ShouldResemble, testCase.OptsExpected.ReplicaSetName)
+ So(testCase.OptsIn.SSL.UseSSL, ShouldResemble, testCase.OptsExpected.SSL.UseSSL)
+ So(testCase.OptsIn.Kerberos.Service, ShouldResemble, testCase.OptsExpected.Kerberos.Service)
+ So(testCase.OptsIn.Kerberos.ServiceHost, ShouldResemble, testCase.OptsExpected.Kerberos.ServiceHost)
+ So(testCase.OptsIn.Auth.ShouldAskForPassword(), ShouldEqual, testCase.OptsIn.ShouldAskForPassword())
+ }
+ })
+ })
+}
+
+// Regression test for TOOLS-1694 to prevent issue from TOOLS-1115
+func TestHiddenOptionsDefaults(t *testing.T) {
+ Convey("With a ToolOptions parsed", t, func() {
+ enabled := EnabledOptions{Connection: true}
+ opts := New("", "", enabled)
+ _, err := opts.parser.ParseArgs([]string{})
+ So(err, ShouldBeNil)
+ Convey("hidden options should have expected values", func() {
+ So(opts.MaxProcs, ShouldEqual, runtime.NumCPU())
+ So(opts.Timeout, ShouldEqual, 3)
+ })
+ })
+
+}
diff --git a/src/mongo/gotools/import.data b/src/mongo/gotools/import.data
index 55561b11c0d..4c780b2ed0b 100644
--- a/src/mongo/gotools/import.data
+++ b/src/mongo/gotools/import.data
@@ -1,5 +1,5 @@
{
- "commit": "4d4d96583c40a25a4ee7e2d038d75181a300ec3c",
+ "commit": "29b8883c560319b016f8bd4927807fa36f1a682f",
"github": "mongodb/mongo-tools.git",
"vendor": "tools",
"branch": "v3.4"
diff --git a/src/mongo/gotools/mongodump/main/mongodump.go b/src/mongo/gotools/mongodump/main/mongodump.go
index 1845c4afdb1..9f6945b9e01 100644
--- a/src/mongo/gotools/mongodump/main/mongodump.go
+++ b/src/mongo/gotools/mongodump/main/mongodump.go
@@ -20,14 +20,15 @@ const (
func main() {
// initialize command-line opts
- opts := options.New("mongodump", mongodump.Usage, options.EnabledOptions{true, true, true})
+ opts := options.New("mongodump", mongodump.Usage, options.EnabledOptions{Auth: true, Connection: true, Namespace: true, URI: true})
inputOpts := &mongodump.InputOptions{}
opts.AddOptions(inputOpts)
outputOpts := &mongodump.OutputOptions{}
opts.AddOptions(outputOpts)
+ opts.URI.AddKnownURIParameters(options.KnownURIOptionsReadPreference)
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongodump --help' for more information")
@@ -53,10 +54,8 @@ func main() {
// init logger
log.SetVerbosity(opts.Verbosity)
- // connect directly, unless a replica set name is explicitly specified
- _, setName := util.ParseConnectionString(opts.Host)
- opts.Direct = (setName == "")
- opts.ReplicaSetName = setName
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
// kick off the progress bar manager
progressManager := progress.NewBarWriter(log.Writer(0), progressBarWaitTime, progressBarLength, false)
diff --git a/src/mongo/gotools/mongodump/options.go b/src/mongo/gotools/mongodump/options.go
index 9de83c3358f..80396119fe0 100644
--- a/src/mongo/gotools/mongodump/options.go
+++ b/src/mongo/gotools/mongodump/options.go
@@ -1,6 +1,9 @@
package mongodump
import (
+ "github.com/mongodb/mongo-tools/common/connstring"
+ "github.com/mongodb/mongo-tools/common/options"
+
"fmt"
"io/ioutil"
)
@@ -26,6 +29,14 @@ func (*InputOptions) Name() string {
return "query"
}
+func (inputOpts *InputOptions) SetOptionsFromURI(cs connstring.ConnString) error {
+ if inputOpts.ReadPreference != "" {
+ return fmt.Errorf(options.IncompatibleArgsErrorFormat, "--readPreference")
+ }
+ inputOpts.ReadPreference = cs.ReadPreference
+ return nil
+}
+
func (inputOptions *InputOptions) HasQuery() bool {
return inputOptions.Query != "" || inputOptions.QueryFile != ""
}
diff --git a/src/mongo/gotools/mongoexport/main/mongoexport.go b/src/mongo/gotools/mongoexport/main/mongoexport.go
index d7c7dba3e06..87407a3b246 100644
--- a/src/mongo/gotools/mongoexport/main/mongoexport.go
+++ b/src/mongo/gotools/mongoexport/main/mongoexport.go
@@ -24,14 +24,14 @@ const (
func main() {
// initialize command-line opts
opts := options.New("mongoexport", mongoexport.Usage,
- options.EnabledOptions{Auth: true, Connection: true, Namespace: true})
+ options.EnabledOptions{Auth: true, Connection: true, Namespace: true, URI: true})
outputOpts := &mongoexport.OutputFormatOptions{}
opts.AddOptions(outputOpts)
inputOpts := &mongoexport.InputOptions{}
opts.AddOptions(inputOpts)
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongoexport --help' for more information")
@@ -56,10 +56,8 @@ func main() {
return
}
- // connect directly, unless a replica set name is explicitly specified
- _, setName := util.ParseConnectionString(opts.Host)
- opts.Direct = (setName == "")
- opts.ReplicaSetName = setName
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
provider, err := db.NewSessionProvider(*opts)
if err != nil {
diff --git a/src/mongo/gotools/mongofiles/main/mongofiles.go b/src/mongo/gotools/mongofiles/main/mongofiles.go
index 028dc940302..bff5748c8b3 100644
--- a/src/mongo/gotools/mongofiles/main/mongofiles.go
+++ b/src/mongo/gotools/mongofiles/main/mongofiles.go
@@ -2,26 +2,28 @@
package main
import (
- "fmt"
"github.com/mongodb/mongo-tools/common/db"
"github.com/mongodb/mongo-tools/common/log"
"github.com/mongodb/mongo-tools/common/options"
"github.com/mongodb/mongo-tools/common/signals"
"github.com/mongodb/mongo-tools/common/util"
"github.com/mongodb/mongo-tools/mongofiles"
+
+ "fmt"
"os"
)
func main() {
// initialize command-line opts
- opts := options.New("mongofiles", mongofiles.Usage, options.EnabledOptions{Auth: true, Connection: true, Namespace: false})
+ opts := options.New("mongofiles", mongofiles.Usage, options.EnabledOptions{Auth: true, Connection: true, Namespace: false, URI: true})
storageOpts := &mongofiles.StorageOptions{}
opts.AddOptions(storageOpts)
inputOpts := &mongofiles.InputOptions{}
opts.AddOptions(inputOpts)
+ opts.URI.AddKnownURIParameters(options.KnownURIOptionsReadPreference)
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongofiles --help' for more information")
@@ -40,14 +42,12 @@ func main() {
log.SetVerbosity(opts.Verbosity)
signals.Handle()
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
+
// add the specified database to the namespace options struct
opts.Namespace.DB = storageOpts.DB
- // connect directly, unless a replica set name is explicitly specified
- _, setName := util.ParseConnectionString(opts.Host)
- opts.Direct = (setName == "")
- opts.ReplicaSetName = setName
-
// create a session provider to connect to the db
provider, err := db.NewSessionProvider(*opts)
if err != nil {
diff --git a/src/mongo/gotools/mongofiles/mongofiles.go b/src/mongo/gotools/mongofiles/mongofiles.go
index c2063185735..6f3f50ff43c 100644
--- a/src/mongo/gotools/mongofiles/mongofiles.go
+++ b/src/mongo/gotools/mongofiles/mongofiles.go
@@ -312,7 +312,9 @@ func (mf *MongoFiles) Run(displayHost bool) (string, error) {
log.Logvf(log.DebugLow, "connected to node type: %v", nodeType)
- safety, err := db.BuildWriteConcern(mf.StorageOptions.WriteConcern, nodeType)
+ safety, err := db.BuildWriteConcern(mf.StorageOptions.WriteConcern, nodeType,
+ mf.ToolOptions.URI.ParsedConnString())
+
if err != nil {
return "", fmt.Errorf("error parsing write concern: %v", err)
}
diff --git a/src/mongo/gotools/mongofiles/mongofiles_test.go b/src/mongo/gotools/mongofiles/mongofiles_test.go
index 2e0ad431ef8..9bb001defd6 100644
--- a/src/mongo/gotools/mongofiles/mongofiles_test.go
+++ b/src/mongo/gotools/mongofiles/mongofiles_test.go
@@ -33,6 +33,7 @@ var (
Connection: connection,
Auth: &auth,
Verbosity: &options.Verbosity{},
+ URI: &options.URI{},
}
)
diff --git a/src/mongo/gotools/mongoimport/main/mongoimport.go b/src/mongo/gotools/mongoimport/main/mongoimport.go
index 837e6d0ac26..6d63df2e99e 100644
--- a/src/mongo/gotools/mongoimport/main/mongoimport.go
+++ b/src/mongo/gotools/mongoimport/main/mongoimport.go
@@ -16,14 +16,15 @@ import (
func main() {
// initialize command-line opts
opts := options.New("mongoimport", mongoimport.Usage,
- options.EnabledOptions{Auth: true, Connection: true, Namespace: true})
+ options.EnabledOptions{Auth: true, Connection: true, Namespace: true, URI: true})
inputOpts := &mongoimport.InputOptions{}
opts.AddOptions(inputOpts)
ingestOpts := &mongoimport.IngestOptions{}
opts.AddOptions(ingestOpts)
+ opts.URI.AddKnownURIParameters(options.KnownURIOptionsWriteConcern)
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongoimport --help' for more information")
@@ -43,10 +44,8 @@ func main() {
return
}
- // connect directly, unless a replica set name is explicitly specified
- _, setName := util.ParseConnectionString(opts.Host)
- opts.Direct = (setName == "")
- opts.ReplicaSetName = setName
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
// create a session provider to connect to the db
sessionProvider, err := db.NewSessionProvider(*opts)
diff --git a/src/mongo/gotools/mongoimport/mongoimport.go b/src/mongo/gotools/mongoimport/mongoimport.go
index 7fcdf91a990..2b2fde270b6 100644
--- a/src/mongo/gotools/mongoimport/mongoimport.go
+++ b/src/mongo/gotools/mongoimport/mongoimport.go
@@ -441,7 +441,8 @@ func (imp *MongoImport) ingestDocuments(readDocs chan bson.D) (retErr error) {
func (imp *MongoImport) configureSession(session *mgo.Session) error {
// sockets to the database will never be forcibly closed
session.SetSocketTimeout(0)
- sessionSafety, err := db.BuildWriteConcern(imp.IngestOptions.WriteConcern, imp.nodeType)
+ sessionSafety, err := db.BuildWriteConcern(imp.IngestOptions.WriteConcern, imp.nodeType,
+ imp.ToolOptions.ParsedConnString())
if err != nil {
return fmt.Errorf("write concern error: %v", err)
}
diff --git a/src/mongo/gotools/mongoimport/mongoimport_test.go b/src/mongo/gotools/mongoimport/mongoimport_test.go
index abc3d167975..248a4a45104 100644
--- a/src/mongo/gotools/mongoimport/mongoimport_test.go
+++ b/src/mongo/gotools/mongoimport/mongoimport_test.go
@@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"reflect"
+ "runtime"
"strings"
"testing"
@@ -63,12 +64,14 @@ func getBasicToolOptions() *options.ToolOptions {
Host: "localhost",
Port: db.DefaultTestPort,
}
+
return &options.ToolOptions{
General: general,
SSL: &ssl,
Namespace: namespace,
Connection: connection,
Auth: &auth,
+ URI: &options.URI{},
}
}
@@ -774,3 +777,23 @@ func TestImportDocuments(t *testing.T) {
})
})
}
+
+// Regression test for TOOLS-1694 to prevent issue from TOOLS-1115
+func TestHiddenOptionsDefaults(t *testing.T) {
+ Convey("With a new mongoimport with empty options", t, func() {
+ imp, err := NewMongoImport()
+ imp.ToolOptions = options.New("", "", options.EnabledOptions{})
+ So(err, ShouldBeNil)
+ Convey("Then parsing should fill args with expected defaults", func() {
+ _, err := imp.ToolOptions.ParseArgs([]string{})
+ So(err, ShouldBeNil)
+
+ // collection cannot be empty in validate
+ imp.ToolOptions.Collection = "col"
+ So(imp.ValidateSettings([]string{}), ShouldBeNil)
+ So(imp.IngestOptions.NumDecodingWorkers, ShouldEqual, runtime.NumCPU())
+ So(imp.IngestOptions.BulkBufferSize, ShouldEqual, 1000)
+ })
+ })
+
+}
diff --git a/src/mongo/gotools/mongooplog/main/mongooplog.go b/src/mongo/gotools/mongooplog/main/mongooplog.go
index a9215a64c36..c038db057da 100644
--- a/src/mongo/gotools/mongooplog/main/mongooplog.go
+++ b/src/mongo/gotools/mongooplog/main/mongooplog.go
@@ -14,7 +14,7 @@ import (
func main() {
// initialize command line options
opts := options.New("mongooplog", mongooplog.Usage,
- options.EnabledOptions{Auth: true, Connection: true, Namespace: false})
+ options.EnabledOptions{Auth: true, Connection: true, Namespace: false, URI: true})
// add the mongooplog-specific options
sourceOpts := &mongooplog.SourceOptions{}
@@ -23,7 +23,7 @@ func main() {
log.Logvf(log.Always, "warning: mongooplog is deprecated, and will be removed completely in a future release")
// parse the command line options
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongooplog --help' for more information")
@@ -50,10 +50,8 @@ func main() {
log.SetVerbosity(opts.Verbosity)
signals.Handle()
- // connect directly, unless a replica set name is explicitly specified
- _, setName := util.ParseConnectionString(opts.Host)
- opts.Direct = (setName == "")
- opts.ReplicaSetName = setName
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
// validate the mongooplog options
if sourceOpts.From == "" {
diff --git a/src/mongo/gotools/mongorestore/main/mongorestore.go b/src/mongo/gotools/mongorestore/main/mongorestore.go
index fa6b6d84caa..d5832bcc51d 100644
--- a/src/mongo/gotools/mongorestore/main/mongorestore.go
+++ b/src/mongo/gotools/mongorestore/main/mongorestore.go
@@ -23,15 +23,16 @@ const (
func main() {
// initialize command-line opts
opts := options.New("mongorestore", mongorestore.Usage,
- options.EnabledOptions{Auth: true, Connection: true})
+ options.EnabledOptions{Auth: true, Connection: true, URI: true})
nsOpts := &mongorestore.NSOptions{}
opts.AddOptions(nsOpts)
inputOpts := &mongorestore.InputOptions{}
opts.AddOptions(inputOpts)
outputOpts := &mongorestore.OutputOptions{}
opts.AddOptions(outputOpts)
+ opts.URI.AddKnownURIParameters(options.KnownURIOptionsWriteConcern)
- extraArgs, err := opts.Parse()
+ extraArgs, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongorestore --help' for more information")
@@ -53,6 +54,9 @@ func main() {
log.SetVerbosity(opts.Verbosity)
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
+
targetDir, err := getTargetDirFromArgs(extraArgs, inputOpts.Directory)
if err != nil {
log.Logvf(log.Always, "%v", err)
@@ -61,11 +65,6 @@ func main() {
}
targetDir = util.ToUniversalPath(targetDir)
- // connect directly, unless a replica set name is explicitly specified
- _, setName := util.ParseConnectionString(opts.Host)
- opts.Direct = (setName == "")
- opts.ReplicaSetName = setName
-
provider, err := db.NewSessionProvider(*opts)
if err != nil {
log.Logvf(log.Always, "error connecting to host: %v", err)
diff --git a/src/mongo/gotools/mongorestore/mongorestore.go b/src/mongo/gotools/mongorestore/mongorestore.go
index c79b04e5acf..41ac27560d5 100644
--- a/src/mongo/gotools/mongorestore/mongorestore.go
+++ b/src/mongo/gotools/mongorestore/mongorestore.go
@@ -136,7 +136,8 @@ func (restore *MongoRestore) ParseAndValidateOptions() error {
}
log.Logvf(log.DebugLow, "connected to node type: %v", nodeType)
- restore.safety, err = db.BuildWriteConcern(restore.OutputOptions.WriteConcern, nodeType)
+ restore.safety, err = db.BuildWriteConcern(restore.OutputOptions.WriteConcern, nodeType,
+ restore.ToolOptions.URI.ParsedConnString())
if err != nil {
return fmt.Errorf("error parsing write concern: %v", err)
}
diff --git a/src/mongo/gotools/mongorestore/mongorestore_archive_test.go b/src/mongo/gotools/mongorestore/mongorestore_archive_test.go
index cbcefe40696..8398e749854 100644
--- a/src/mongo/gotools/mongorestore/mongorestore_archive_test.go
+++ b/src/mongo/gotools/mongorestore/mongorestore_archive_test.go
@@ -38,6 +38,7 @@ func TestMongorestoreShortArchive(t *testing.T) {
Host: testServer,
Port: testPort,
},
+ URI: &options.URI{},
Auth: &auth,
SSL: &ssl,
}
diff --git a/src/mongo/gotools/mongorestore/mongorestore_test.go b/src/mongo/gotools/mongorestore/mongorestore_test.go
index 2221645c14c..3063fb1c541 100644
--- a/src/mongo/gotools/mongorestore/mongorestore_test.go
+++ b/src/mongo/gotools/mongorestore/mongorestore_test.go
@@ -35,6 +35,7 @@ func TestMongorestore(t *testing.T) {
Host: testServer,
Port: testPort,
},
+ URI: &options.URI{},
Auth: &auth,
SSL: &ssl,
}
diff --git a/src/mongo/gotools/mongostat/main/mongostat.go b/src/mongo/gotools/mongostat/main/mongostat.go
index 97c79f0ca4f..d6175d03d0f 100644
--- a/src/mongo/gotools/mongostat/main/mongostat.go
+++ b/src/mongo/gotools/mongostat/main/mongostat.go
@@ -50,7 +50,7 @@ func main() {
opts := options.New(
"mongostat",
mongostat.Usage,
- options.EnabledOptions{Connection: true, Auth: true, Namespace: false})
+ options.EnabledOptions{Connection: true, Auth: true, Namespace: false, URI: true})
opts.UseReadOnlyHostDescription()
// add mongostat-specific options
@@ -64,7 +64,7 @@ func main() {
interactiveOption.ShortName = 0
}
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongostat --help' for more information")
@@ -102,7 +102,16 @@ func main() {
return
}
- if opts.Auth.Username != "" && opts.Auth.Source == "" && !opts.Auth.RequiresExternalDB() {
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
+
+ if opts.Auth.Username != "" && opts.GetAuthenticationDatabase() == "" && !opts.Auth.RequiresExternalDB() {
+ // add logic to have different error if using uri
+ if opts.URI != nil && opts.URI.ConnectionString != "" {
+ log.Logvf(log.Always, "authSource is required when authenticating against a non $external database")
+ os.Exit(util.ExitBadOptions)
+ }
+
log.Logvf(log.Always, "--authenticationDatabase is required when authenticating against a non $external database")
os.Exit(util.ExitBadOptions)
}
@@ -212,8 +221,6 @@ func main() {
}
opts.Direct = true
- _, setName := util.ParseConnectionString(opts.Host)
- opts.ReplicaSetName = setName
stat := &mongostat.MongoStat{
Options: opts,
StatOptions: statOpts,
diff --git a/src/mongo/gotools/mongotop/main/mongotop.go b/src/mongo/gotools/mongotop/main/mongotop.go
index fe06f0e0d64..6a4317dbc8f 100644
--- a/src/mongo/gotools/mongotop/main/mongotop.go
+++ b/src/mongo/gotools/mongotop/main/mongotop.go
@@ -17,14 +17,14 @@ import (
func main() {
// initialize command-line opts
opts := options.New("mongotop", mongotop.Usage,
- options.EnabledOptions{Auth: true, Connection: true, Namespace: false})
+ options.EnabledOptions{Auth: true, Connection: true, Namespace: false, URI: true})
opts.UseReadOnlyHostDescription()
// add mongotop-specific options
outputOpts := &mongotop.Output{}
opts.AddOptions(outputOpts)
- args, err := opts.Parse()
+ args, err := opts.ParseArgs(os.Args[1:])
if err != nil {
log.Logvf(log.Always, "error parsing command line options: %v", err)
log.Logvf(log.Always, "try 'mongotop --help' for more information")
@@ -44,6 +44,9 @@ func main() {
log.SetVerbosity(opts.Verbosity)
signals.Handle()
+ // verify uri options and log them
+ opts.URI.LogUnsupportedOptions()
+
if len(args) > 1 {
log.Logvf(log.Always, "too many positional arguments")
log.Logvf(log.Always, "try 'mongotop --help' for more information")
@@ -64,15 +67,14 @@ func main() {
}
if opts.Auth.Username != "" && opts.Auth.Source == "" && !opts.Auth.RequiresExternalDB() {
+ if opts.URI != nil && opts.URI.ConnectionString != "" {
+ log.Logvf(log.Always, "authSource is required when authenticating against a non $external database")
+ os.Exit(util.ExitBadOptions)
+ }
log.Logvf(log.Always, "--authenticationDatabase is required when authenticating against a non $external database")
os.Exit(util.ExitBadOptions)
}
- // connect directly, unless a replica set name is explicitly specified
- _, setName := util.ParseConnectionString(opts.Host)
- opts.Direct = (setName == "")
- opts.ReplicaSetName = setName
-
// create a session provider to connect to the db
sessionProvider, err := db.NewSessionProvider(*opts)
if err != nil {
@@ -80,7 +82,7 @@ func main() {
os.Exit(util.ExitError)
}
- if setName == "" {
+ if opts.ReplicaSetName == "" {
sessionProvider.SetReadPreference(mgo.PrimaryPreferred)
}
diff --git a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go
index 01ad6fefe97..314e5415c18 100644
--- a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go
+++ b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go
@@ -94,16 +94,17 @@ package openssl
#include <openssl/engine.h>
extern int Goopenssl_init_locks();
+extern unsigned long Goopenssl_thread_id_callback();
extern void Goopenssl_thread_locking_callback(int, int, const char*, int);
static int Goopenssl_init_threadsafety() {
- // Set up OPENSSL thread safety callbacks. We only set the locking
- // callback because the default id callback implementation is good
- // enough for us.
+ // Set up OPENSSL thread safety callbacks.
+ // TOOLS-1694 added setting of thread id callback for compatibility with openssl 0.9.8
int rc = Goopenssl_init_locks();
if (rc == 0) {
CRYPTO_set_locking_callback(Goopenssl_thread_locking_callback);
}
+ CRYPTO_set_id_callback(Goopenssl_thread_id_callback);
return rc;
}
diff --git a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go
index 03ed0f01bd0..99558298e3a 100644
--- a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go
+++ b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go
@@ -52,6 +52,7 @@ int Goopenssl_init_locks() {
return rc;
}
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
void Goopenssl_thread_locking_callback(int mode, int n, const char *file,
int line) {
if (mode & CRYPTO_LOCK) {
@@ -60,5 +61,9 @@ void Goopenssl_thread_locking_callback(int mode, int n, const char *file,
pthread_mutex_unlock(&goopenssl_locks[n]);
}
}
+unsigned long Goopenssl_thread_id_callback() {
+ return (unsigned long) pthread_self();
+}
+#endif
*/
import "C"
diff --git a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go
index 5eca9fa0eac..ec817926b7a 100644
--- a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go
+++ b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go
@@ -56,5 +56,10 @@ void Goopenssl_thread_locking_callback(int mode, int n, const char *file,
LeaveCriticalSection(&goopenssl_locks[n]);
}
}
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+unsigned long Goopenssl_thread_id_callback() {
+ return (unsigned long) GetCurrentThreadId();
+}
+#endif
*/
import "C"