11# rotate-backups: Simple command line interface for backup rotation.
22#
33# Author: Peter Odding <peter@peterodding.com>
4- # Last Change: April 27 , 2018
4+ # Last Change: August 2 , 2018
55# URL: https://github.com/xolox/python-rotate-backups
66
77"""
1919import numbers
2020import os
2121import re
22+ import shlex
2223
2324# External dependencies.
2425from dateutil .relativedelta import relativedelta
@@ -197,8 +198,11 @@ def load_config_file(configuration_file=None, expand=True):
197198 exclude_list = split (items .get ('exclude-list' , '' )),
198199 io_scheduling_class = items .get ('ionice' ),
199200 strict = coerce_boolean (items .get ('strict' , 'yes' )),
200- prefer_recent = coerce_boolean (items .get ('prefer-recent' , 'no' )),
201- rmdir = items .get ('use-rmdir' ))
201+ prefer_recent = coerce_boolean (items .get ('prefer-recent' , 'no' )))
202+ # Don't override the value of the 'removal_command' property unless the
203+ # 'removal-command' configuration file option has a value set.
204+ if items .get ('removal-command' ):
205+ options ['removal_command' ] = shlex .split (items ['removal-command' ])
202206 # Expand filename patterns?
203207 if expand and location .have_wildcards :
204208 logger .verbose ("Expanding filename pattern %s on %s .." , location .directory , location .context )
@@ -240,11 +244,12 @@ def __init__(self, rotation_scheme, **options):
240244 Initialize a :class:`RotateBackups` object.
241245
242246 :param rotation_scheme: Used to set :attr:`rotation_scheme`.
243- :param options: Any keyword arguments are used to set the values of the
244- properties :attr:`config_file`, :attr:`dry_run`,
245- :attr:`rmdir`, :attr:`exclude_list`,
246- :attr:`include_list`, :attr:`io_scheduling_class` and
247- :attr:`strict`.
247+ :param options: Any keyword arguments are used to set the values of
248+ instance properties that support assignment
249+ (:attr:`config_file`, :attr:`dry_run`,
250+ :attr:`exclude_list`, :attr:`include_list`,
251+ :attr:`io_scheduling_class`, :attr:`removal_command`
252+ and :attr:`strict`).
248253 """
249254 options .update (rotation_scheme = rotation_scheme )
250255 super (RotateBackups , self ).__init__ (** options )
@@ -271,18 +276,6 @@ def dry_run(self):
271276 """
272277 return False
273278
274- @mutable_property
275- def rmdir (self ):
276- """
277- :data:`True` to use `rmdir` to remove the snapshots, :data:`False` to use `rm -r` (defaults to :data:`False`).
278-
279- Normally the backups are removed one file at a file using the command `rm -r`.
280- Some file-systems are capable of removing a whole directory with the command `rmdir`,
281- even when the directory is not empty. For example, this is how CephFS snapshots are
282- removed.
283- """
284- return False
285-
286279 @cached_property (writable = True )
287280 def exclude_list (self ):
288281 """
@@ -339,6 +332,24 @@ def prefer_recent(self):
339332 """
340333 return False
341334
335+ @mutable_property
336+ def removal_command (self ):
337+ """
338+ The command used to remove backups (a list of strings).
339+
340+ By default the command ``rm -fR`` is used. This choice was made because
341+ it works regardless of whether the user's "backups to be rotated" are
342+ files or directories or a mixture of both.
343+
344+ .. versionadded: 5.3
345+ This option was added as a generalization of the idea suggested in
346+ `pull request 11`_, which made it clear to me that being able to
347+ customize the removal command has its uses.
348+
349+ .. _pull request 11: https://github.com/xolox/python-rotate-backups/pull/11
350+ """
351+ return ['rm' , '-fR' ]
352+
342353 @required_property
343354 def rotation_scheme (self ):
344355 """
@@ -481,18 +492,16 @@ class together to implement backup rotation with an easy to use Python
481492 else :
482493 logger .info ("Deleting %s .." , friendly_name )
483494 if not self .dry_run :
484- if not self .rmdir :
485- command = location .context .prepare (
486- 'rm' , '-Rf' , backup .pathname ,
487- group_by = (location .ssh_alias , location .mount_point ),
488- ionice = self .io_scheduling_class ,
489- )
490- else :
491- command = location .context .prepare (
492- 'rmdir' , backup .pathname ,
493- group_by = (location .ssh_alias , location .mount_point ),
494- ionice = self .io_scheduling_class ,
495- )
495+ # Copy the list with the (possibly user defined) removal command.
496+ removal_command = list (self .removal_command )
497+ # Add the pathname of the backup as the final argument.
498+ removal_command .append (backup .pathname )
499+ # Construct the command object.
500+ command = location .context .prepare (
501+ command = removal_command ,
502+ group_by = (location .ssh_alias , location .mount_point ),
503+ ionice = self .io_scheduling_class ,
504+ )
496505 rotation_commands .append (command )
497506 if not prepare :
498507 timer = Timer ()
0 commit comments