// determineOplogCollectionName uses a command to infer // the name of the oplog collection in the connected db func (dump *MongoDump) determineOplogCollectionName() error { session := dump.SessionProvider.GetSession() masterDoc := bson.M{} err := session.Run("isMaster", &masterDoc) if err != nil { return fmt.Errorf("error running command: %v", err) } if _, ok := masterDoc["hosts"]; ok { log.Logf(2, "determined cluster to be a replica set") log.Logf(3, "oplog located in local.oplog.rs") dump.oplogCollection = "oplog.rs" return nil } if isMaster := masterDoc["ismaster"]; util.IsFalsy(isMaster) { log.Logf(1, "mongodump is not connected to a master") return fmt.Errorf("not connected to master") } // TODO stop assuming master/slave, be smarter and check if it is really // master/slave...though to be fair legacy mongodump doesn't do this either... log.Logf(2, "not connected to a replica set, assuming master/slave") log.Logf(3, "oplog located in local.oplog.$main") dump.oplogCollection = "oplog.$main" return nil }
// ApplyOps is a wrapper for the applyOps database command, we pass in // a session to avoid opening a new connection for a few inserts at a time. func (restore *MongoRestore) ApplyOps(session *mgo.Session, entries []interface{}) error { res := bson.M{} err := session.Run(bson.D{{"applyOps", entries}}, &res) if err != nil { return fmt.Errorf("applyOps: %v", err) } if util.IsFalsy(res["ok"]) { return fmt.Errorf("applyOps command: %v", res["errmsg"]) } return nil }
// CreateCollection creates the collection specified in the intent with the // given options. func (restore *MongoRestore) CreateCollection(intent *intents.Intent, options bson.D) error { command := append(bson.D{{"create", intent.C}}, options...) session, err := restore.SessionProvider.GetSession() if err != nil { return fmt.Errorf("error establishing connection: %v", err) } defer session.Close() res := bson.M{} err = session.DB(intent.DB).Run(command, &res) if err != nil { return fmt.Errorf("error running create command: %v", err) } if util.IsFalsy(res["ok"]) { return fmt.Errorf("create command: %v", res["errmsg"]) } return nil }
// determineOplogCollectionName uses a command to infer // the name of the oplog collection in the connected db func (dump *MongoDump) determineOplogCollectionName() error { masterDoc := bson.M{} err := dump.sessionProvider.Run("isMaster", &masterDoc, "admin") if err != nil { return fmt.Errorf("error running command: %v", err) } if _, ok := masterDoc["hosts"]; ok { log.Logvf(log.DebugLow, "determined cluster to be a replica set") log.Logvf(log.DebugHigh, "oplog located in local.oplog.rs") dump.oplogCollection = "oplog.rs" return nil } if isMaster := masterDoc["ismaster"]; util.IsFalsy(isMaster) { log.Logvf(log.Info, "mongodump is not connected to a master") return fmt.Errorf("not connected to master") } log.Logvf(log.DebugLow, "not connected to a replica set, assuming master/slave") log.Logvf(log.DebugHigh, "oplog located in local.oplog.$main") dump.oplogCollection = "oplog.$main" return nil }
// RestoreUsersOrRoles accepts a users intent and a roles intent, and restores // them via _mergeAuthzCollections. Either or both can be nil. In the latter case // nothing is done. func (restore *MongoRestore) RestoreUsersOrRoles(users, roles *intents.Intent) error { type loopArg struct { intent *intents.Intent intentType string mergeParamName string tempCollectionName string } if users == nil && roles == nil { return nil } if users != nil && roles != nil && users.DB != roles.DB { return fmt.Errorf("can't restore users and roles to different databases, %v and %v", users.DB, roles.DB) } args := []loopArg{} mergeArgs := bson.D{} userTargetDB := "" if users != nil { args = append(args, loopArg{users, "users", "tempUsersCollection", restore.tempUsersCol}) } if roles != nil { args = append(args, loopArg{roles, "roles", "tempRolesCollection", restore.tempRolesCol}) } session, err := restore.SessionProvider.GetSession() if err != nil { return fmt.Errorf("error establishing connection: %v", err) } defer session.Close() // For each of the users and roles intents: // build up the mergeArgs component of the _mergeAuthzCollections command // upload the BSONFile to a temporary collection for _, arg := range args { if arg.intent.Size == 0 { // MongoDB complains if we try and remove a non-existent collection, so we should // just skip auth collections with empty .bson files to avoid gnarly logic later on. log.Logf(log.Always, "%v file '%v' is empty; skipping %v restoration", arg.intentType, arg.intent.BSONPath, arg.intentType) } log.Logf(log.Always, "restoring %v from %v", arg.intentType, arg.intent.BSONPath) mergeArgs = append(mergeArgs, bson.DocElem{arg.mergeParamName, "admin." + arg.tempCollectionName}) err := arg.intent.BSONFile.Open() if err != nil { return err } defer arg.intent.BSONFile.Close() bsonSource := db.NewDecodedBSONSource(db.NewBSONSource(arg.intent.BSONFile)) defer bsonSource.Close() tempCollectionNameExists, err := restore.CollectionExists(&intents.Intent{DB: "admin", C: arg.tempCollectionName}) if err != nil { return err } if tempCollectionNameExists { log.Logf(log.Info, "dropping preexisting temporary collection admin.%v", arg.tempCollectionName) err = session.DB("admin").C(arg.tempCollectionName).DropCollection() if err != nil { return fmt.Errorf("error dropping preexisting temporary collection %v: %v", arg.tempCollectionName, err) } } log.Logf(log.DebugLow, "restoring %v to temporary collection", arg.intentType) if _, err = restore.RestoreCollectionToDB("admin", arg.tempCollectionName, bsonSource, 0); err != nil { return fmt.Errorf("error restoring %v: %v", arg.intentType, err) } // make sure we always drop the temporary collection defer func() { session, e := restore.SessionProvider.GetSession() if e != nil { // logging errors here because this has no way of returning that doesn't mask other errors log.Logf(log.Info, "error establishing connection to drop temporary collection admin.%v: %v", arg.tempCollectionName, e) return } defer session.Close() log.Logf(log.DebugHigh, "dropping temporary collection admin.%v", arg.tempCollectionName) e = session.DB("admin").C(arg.tempCollectionName).DropCollection() if e != nil { log.Logf(log.Info, "error dropping temporary collection admin.%v: %v", arg.tempCollectionName, e) } }() userTargetDB = arg.intent.DB } if userTargetDB == "admin" { // _mergeAuthzCollections uses an empty db string as a sentinel for "all databases" userTargetDB = "" } // we have to manually convert mgo's safety to a writeconcern object writeConcern := bson.M{} if restore.safety == nil { writeConcern["w"] = 0 } else { if restore.safety.WMode != "" { writeConcern["w"] = restore.safety.WMode } else { writeConcern["w"] = restore.safety.W } } command := bsonutil.MarshalD{} command = append(command, bson.DocElem{"_mergeAuthzCollections", 1}) command = append(command, mergeArgs...) command = append(command, bson.DocElem{"drop", restore.OutputOptions.Drop}, bson.DocElem{"writeConcern", writeConcern}, bson.DocElem{"db", userTargetDB}) log.Logf(log.DebugLow, "merging users/roles from temp collections") res := bson.M{} err = session.Run(command, &res) if err != nil { return fmt.Errorf("error running merge command: %v", err) } if util.IsFalsy(res["ok"]) { return fmt.Errorf("_mergeAuthzCollections command: %v", res["errmsg"]) } return nil }
// RestoreUsersOrRoles accepts a collection type (Users or Roles) and restores the intent // in the appropriate collection. func (restore *MongoRestore) RestoreUsersOrRoles(collectionType string, intent *intents.Intent) error { log.Logf(log.Always, "restoring %v from %v", collectionType, intent.BSONPath) if intent.Size == 0 { // MongoDB complains if we try and remove a non-existent collection, so we should // just skip auth collections with empty .bson files to avoid gnarly logic later on. log.Logf(log.Always, "%v file '%v' is empty; skipping %v restoration", collectionType, intent.BSONPath, collectionType) return nil } var tempCol, tempColCommandField string switch collectionType { case Users: tempCol = restore.tempUsersCol tempColCommandField = "tempUsersCollection" case Roles: tempCol = restore.tempRolesCol tempColCommandField = "tempRolesCollection" default: return fmt.Errorf("cannot use %v as a collection type in RestoreUsersOrRoles", collectionType) } err := intent.BSONFile.Open() if err != nil { return err } defer intent.BSONFile.Close() bsonSource := db.NewDecodedBSONSource(db.NewBSONSource(intent.BSONFile)) defer bsonSource.Close() tempColExists, err := restore.CollectionExists(&intents.Intent{DB: "admin", C: tempCol}) if err != nil { return err } if tempColExists { return fmt.Errorf("temporary collection admin.%v already exists. "+ "Drop it or specify new temporary collections with --tempUsersColl "+ "and --tempRolesColl", tempCol) } log.Logf(log.DebugLow, "restoring %v to temporary collection", collectionType) err = restore.RestoreCollectionToDB("admin", tempCol, bsonSource, 0) if err != nil { return fmt.Errorf("error restoring %v: %v", collectionType, err) } // make sure we always drop the temporary collection defer func() { session, err := restore.SessionProvider.GetSession() if err != nil { // logging errors here because this has no way of returning that doesn't mask other errors log.Logf(log.Always, "error establishing connection to drop temporary collection %v: %v", tempCol, err) return } defer session.Close() log.Logf(log.DebugHigh, "dropping temporary collection %v", tempCol) err = session.DB("admin").C(tempCol).DropCollection() if err != nil { log.Logf(log.Always, "error dropping temporary collection %v: %v", tempCol, err) } }() // If we are restoring a single database (--restoreDBUsersAndRoles), then the // target database will be that database, and the _mergeAuthzCollections command // will only restore users/roles of that database. If we are restoring the admin db or // doing a full restore, we tell the command to merge users/roles of all databases. userTargetDB := intent.DB if userTargetDB == "admin" { // _mergeAuthzCollections uses an empty db string as a sentinel for "all databases" userTargetDB = "" } // we have to manually convert mgo's safety to a writeconcern object writeConcern := bson.M{} if restore.safety == nil { writeConcern["w"] = 0 } else { if restore.safety.WMode != "" { writeConcern["w"] = restore.safety.WMode } else { writeConcern["w"] = restore.safety.W } } command := bsonutil.MarshalD{ {"_mergeAuthzCollections", 1}, {tempColCommandField, "admin." + tempCol}, {"drop", restore.OutputOptions.Drop}, {"writeConcern", writeConcern}, {"db", userTargetDB}, } session, err := restore.SessionProvider.GetSession() if err != nil { return fmt.Errorf("error establishing connection: %v", err) } defer session.Close() log.Logf(log.DebugLow, "merging %v from temp collection '%v'", collectionType, tempCol) res := bson.M{} err = session.Run(command, &res) if err != nil { return fmt.Errorf("error running merge command: %v", err) } if util.IsFalsy(res["ok"]) { return fmt.Errorf("_mergeAuthzCollections command: %v", res["errmsg"]) } return nil }