@@ -11,6 +11,7 @@ import (
11
11
"time"
12
12
13
13
"github.com/mongodb/mongo-tools/common/db"
14
+ "go.mongodb.org/mongo-driver/bson"
14
15
"go.mongodb.org/mongo-driver/bson/primitive"
15
16
"gopkg.in/yaml.v2"
16
17
@@ -28,6 +29,15 @@ import (
28
29
"github.com/percona/percona-backup-mongodb/sdk"
29
30
)
30
31
32
+ var (
33
+ ErrNSFromMissing = errors .New ("--ns-from should be specified as the cloning source" )
34
+ ErrNSToMissing = errors .New ("--ns-to should be specified as the cloning destination" )
35
+ ErrSelAndCloning = errors .New ("cloning with selective restore is not possible (remove --ns option)" )
36
+ ErrCloningWithUAndR = errors .New ("cloning with restoring users and rolles is not possible" )
37
+ ErrCloningWithPITR = errors .New ("cloning with restore to the point-in-time is not possible" )
38
+ ErrCloningWithWildCards = errors .New ("cloning with wild-cards is not possible" )
39
+ )
40
+
31
41
type restoreOpts struct {
32
42
bcp string
33
43
pitr string
@@ -36,6 +46,8 @@ type restoreOpts struct {
36
46
waitTime time.Duration
37
47
extern bool
38
48
ns string
49
+ nsFrom string
50
+ nsTo string
39
51
usersAndRoles bool
40
52
rsMap string
41
53
conf string
@@ -116,6 +128,9 @@ func runRestore(
116
128
if err != nil {
117
129
return nil , errors .Wrap (err , "parse --ns option" )
118
130
}
131
+ if err := validateNSFromNSTo (o ); err != nil {
132
+ return nil , errors .Wrap (err , "parse --ns-from and --ns-to options" )
133
+ }
119
134
if err := validateRestoreUsersAndRoles (o .usersAndRoles , nss ); err != nil {
120
135
return nil , errors .Wrap (err , "parse --with-users-and-roles option" )
121
136
}
@@ -139,7 +154,7 @@ func runRestore(
139
154
}
140
155
tdiff := time .Now ().Unix () - int64 (clusterTime .T )
141
156
142
- m , err := doRestore (ctx , conn , o , numParallelColls , nss , rsMap , node , outf )
157
+ m , err := doRestore (ctx , conn , o , numParallelColls , nss , o . nsFrom , o . nsTo , rsMap , node , outf )
143
158
if err != nil {
144
159
return nil , err
145
160
}
@@ -283,6 +298,8 @@ func checkBackup(
283
298
conn connect.Client ,
284
299
o * restoreOpts ,
285
300
nss []string ,
301
+ nsFrom string ,
302
+ nsTo string ,
286
303
) (string , defs.BackupType , error ) {
287
304
if o .extern && o .bcp == "" {
288
305
return "" , defs .ExternalBackup , nil
@@ -318,28 +335,65 @@ func checkBackup(
318
335
if len (nss ) != 0 && bcp .Type != defs .LogicalBackup {
319
336
return "" , "" , errors .New ("--ns flag is only allowed for logical restore" )
320
337
}
338
+ if nsFrom != "" && nsTo != "" && bcp .Type != defs .LogicalBackup {
339
+ return "" , "" , errors .New ("--ns-from and ns-to flags are only allowed for logical restore" )
340
+ }
321
341
if bcp .Status != defs .StatusDone {
322
342
return "" , "" , errors .Errorf ("backup '%s' didn't finish successfully" , b )
323
343
}
324
344
325
345
return bcp .Name , bcp .Type , nil
326
346
}
327
347
348
+ // nsIsTaken returns error in case when specified namesapce is already in use (collection is created)
349
+ // or when any other error ocurres within the checking process.
350
+ func nsIsTaken (
351
+ ctx context.Context ,
352
+ conn connect.Client ,
353
+ ns string ,
354
+ ) error {
355
+ ns = strings .TrimSpace (ns )
356
+ db , coll , ok := strings .Cut (ns , "." )
357
+ if ! ok {
358
+ return errors .Wrap (ErrInvalidNamespace , ns )
359
+ }
360
+
361
+ collNames , err := conn .MongoClient ().Database (db ).ListCollectionNames (ctx , bson.D {{"name" , coll }})
362
+ if err != nil {
363
+ return errors .Wrap (err , "list collection names for cloning target validation" )
364
+ }
365
+
366
+ if len (collNames ) > 0 {
367
+ return errors .New ("cloning namespace (--ns-to) is already in use, specify another one that doesn't exist in database" )
368
+ }
369
+
370
+ return nil
371
+ }
372
+
328
373
func doRestore (
329
374
ctx context.Context ,
330
375
conn connect.Client ,
331
376
o * restoreOpts ,
332
377
numParallelColls * int32 ,
333
378
nss []string ,
379
+ nsFrom string ,
380
+ nsTo string ,
334
381
rsMapping map [string ]string ,
335
382
node string ,
336
383
outf outFormat ,
337
384
) (* restore.RestoreMeta , error ) {
338
- bcp , bcpType , err := checkBackup (ctx , conn , o , nss )
385
+ bcp , bcpType , err := checkBackup (ctx , conn , o , nss , nsFrom , nsTo )
339
386
if err != nil {
340
387
return nil , err
341
388
}
342
389
390
+ // check if namespace exists when cloning collection
391
+ if nsFrom != "" && nsTo != "" {
392
+ if err := nsIsTaken (ctx , conn , nsTo ); err != nil {
393
+ return nil , err
394
+ }
395
+ }
396
+
343
397
name := time .Now ().UTC ().Format (time .RFC3339Nano )
344
398
345
399
cmd := ctrl.Cmd {
@@ -349,6 +403,8 @@ func doRestore(
349
403
BackupName : bcp ,
350
404
NumParallelColls : numParallelColls ,
351
405
Namespaces : nss ,
406
+ NamespaceFrom : nsFrom ,
407
+ NamespaceTo : nsTo ,
352
408
UsersAndRoles : o .usersAndRoles ,
353
409
RSMap : rsMapping ,
354
410
External : o .extern ,
@@ -715,3 +771,36 @@ func validateRestoreUsersAndRoles(usersAndRoles bool, nss []string) error {
715
771
716
772
return nil
717
773
}
774
+
775
+ func validateNSFromNSTo (o * restoreOpts ) error {
776
+ if o .nsFrom == "" && o .nsTo == "" {
777
+ return nil
778
+ }
779
+ if o .nsFrom == "" && o .nsTo != "" {
780
+ return ErrNSFromMissing
781
+ }
782
+ if o .nsFrom != "" && o .nsTo == "" {
783
+ return ErrNSToMissing
784
+ }
785
+ if _ , _ , ok := strings .Cut (o .nsFrom , "." ); ! ok {
786
+ return errors .Wrap (ErrInvalidNamespace , o .nsFrom )
787
+ }
788
+ if _ , _ , ok := strings .Cut (o .nsTo , "." ); ! ok {
789
+ return errors .Wrap (ErrInvalidNamespace , o .nsTo )
790
+ }
791
+ if o .nsFrom != "" && o .nsTo != "" && o .ns != "" {
792
+ return ErrSelAndCloning
793
+ }
794
+ if o .nsFrom != "" && o .nsTo != "" && o .usersAndRoles {
795
+ return ErrCloningWithUAndR
796
+ }
797
+ if o .nsFrom != "" && o .nsTo != "" && o .pitr != "" {
798
+ // this check will be removed with: PBM-1422
799
+ return ErrCloningWithPITR
800
+ }
801
+ if strings .Contains (o .nsTo , "*" ) || strings .Contains (o .nsFrom , "*" ) {
802
+ return ErrCloningWithWildCards
803
+ }
804
+
805
+ return nil
806
+ }
0 commit comments