first version of rotator
This commit is contained in:
parent
3814d8b07a
commit
863d7e77f8
204
rotator/__init__.py
Normal file
204
rotator/__init__.py
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = 'rotator'
|
||||||
|
|
||||||
|
def parse_filename(name, regex, timeformat):
|
||||||
|
log = logging.getLogger(_logger)
|
||||||
|
match = regex.search(name)
|
||||||
|
if match:
|
||||||
|
prefix, date = match.groups()
|
||||||
|
log.debug('successfully parsed filename: {}'.format(name))
|
||||||
|
return (prefix, datetime.datetime.strptime(date, timeformat))
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
def get_files_in_dir(path, regex, timeformat, recurse=False):
|
||||||
|
res = {}
|
||||||
|
for root,dirs,files in os.walk(path, topdown=False):
|
||||||
|
for f in files:
|
||||||
|
prefix, timestamp = parse_filename(f, regex, timeformat)
|
||||||
|
if prefix and prefix not in res:
|
||||||
|
res[prefix] = set()
|
||||||
|
res[prefix].add((os.path.join(root,f), timestamp))
|
||||||
|
if not recurse:
|
||||||
|
return res
|
||||||
|
return res
|
||||||
|
|
||||||
|
def rotate(
|
||||||
|
dirs,
|
||||||
|
regex,
|
||||||
|
timeformat,
|
||||||
|
recurse = False,
|
||||||
|
keep_yearly = 0,
|
||||||
|
keep_monthly = 0,
|
||||||
|
keep_weekly = 0,
|
||||||
|
keep_daily = 0,
|
||||||
|
keep_hourly = 0,
|
||||||
|
keep_30min = 0,
|
||||||
|
keep_15min = 0,
|
||||||
|
keep_5min = 0,
|
||||||
|
keep_1min = 0):
|
||||||
|
|
||||||
|
log = logging.getLogger(_logger)
|
||||||
|
|
||||||
|
merged_filelist = {}
|
||||||
|
for d in dirs:
|
||||||
|
files = get_files_in_dir(d, regex, timeformat, recurse)
|
||||||
|
for prefix in files.keys():
|
||||||
|
if prefix not in merged_filelist:
|
||||||
|
merged_filelist[prefix] = set()
|
||||||
|
merged_filelist[prefix].update(files[prefix])
|
||||||
|
|
||||||
|
for prefix, filelist in merged_filelist.items():
|
||||||
|
to_remove = get_deprecated_files(
|
||||||
|
filelist,
|
||||||
|
keep_yearly,
|
||||||
|
keep_monthly,
|
||||||
|
keep_weekly,
|
||||||
|
keep_daily,
|
||||||
|
keep_hourly,
|
||||||
|
keep_30min,
|
||||||
|
keep_15min,
|
||||||
|
keep_5min,
|
||||||
|
keep_1min)
|
||||||
|
for f in to_remove:
|
||||||
|
log.info("removing: {}".format(f))
|
||||||
|
os.remove(f)
|
||||||
|
|
||||||
|
def get_deprecated_files(
|
||||||
|
filelist,
|
||||||
|
keep_yearly,
|
||||||
|
keep_monthly,
|
||||||
|
keep_weekly,
|
||||||
|
keep_daily,
|
||||||
|
keep_hourly,
|
||||||
|
keep_30min,
|
||||||
|
keep_15min,
|
||||||
|
keep_5min,
|
||||||
|
keep_1min):
|
||||||
|
|
||||||
|
dates = [date for path,date in filelist]
|
||||||
|
|
||||||
|
keep = {
|
||||||
|
'year' : [],
|
||||||
|
'month' : [],
|
||||||
|
'week' : [],
|
||||||
|
'day' : [],
|
||||||
|
'hour' : [],
|
||||||
|
'min30' : [],
|
||||||
|
'min15' : [],
|
||||||
|
'min5' : [],
|
||||||
|
'min1' : []
|
||||||
|
}
|
||||||
|
saved = {
|
||||||
|
'year' : [],
|
||||||
|
'month' : [],
|
||||||
|
'week' : [],
|
||||||
|
'day' : [],
|
||||||
|
'hour' : [],
|
||||||
|
'min30' : [],
|
||||||
|
'min15' : [],
|
||||||
|
'min5' : [],
|
||||||
|
'min1' : []
|
||||||
|
}
|
||||||
|
|
||||||
|
for date in sorted(dates):
|
||||||
|
min1 = date-datetime.timedelta(seconds=date.second, microseconds=date.microsecond)
|
||||||
|
min5 = date-datetime.timedelta(minutes=date.minute%5, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
min15 = date-datetime.timedelta(minutes=date.minute%15, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
min30 = date-datetime.timedelta(minutes=date.minute%30, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
hour = date-datetime.timedelta(minutes=date.minute, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
day = datetime.datetime.combine(date.date(), datetime.time.min)
|
||||||
|
week = datetime.datetime.combine(date.date()-datetime.timedelta(days=date.weekday()), datetime.time.min)
|
||||||
|
month = datetime.datetime(year=date.year, month=date.month, day=1)
|
||||||
|
year = datetime.datetime(year=date.year, month=1, day=1)
|
||||||
|
|
||||||
|
if year not in saved['year']:
|
||||||
|
saved['year'].append(year)
|
||||||
|
keep['year'].append(date)
|
||||||
|
if month not in saved['month']:
|
||||||
|
saved['month'].append(month)
|
||||||
|
keep['month'].append(date)
|
||||||
|
if week not in saved['week']:
|
||||||
|
saved['week'].append(week)
|
||||||
|
keep['week'].append(date)
|
||||||
|
if day not in saved['day']:
|
||||||
|
saved['day'].append(day)
|
||||||
|
keep['day'].append(date)
|
||||||
|
if hour not in saved['hour']:
|
||||||
|
saved['hour'].append(hour)
|
||||||
|
keep['hour'].append(date)
|
||||||
|
if min30 not in saved['min30']:
|
||||||
|
saved['min30'].append(min30)
|
||||||
|
keep['min30'].append(date)
|
||||||
|
if min15 not in saved['min15']:
|
||||||
|
saved['min15'].append(min15)
|
||||||
|
keep['min15'].append(date)
|
||||||
|
if min5 not in saved['min5']:
|
||||||
|
saved['min5'].append(min5)
|
||||||
|
keep['min5'].append(date)
|
||||||
|
if min1 not in saved['min1']:
|
||||||
|
saved['min1'].append(min1)
|
||||||
|
keep['min1'].append(date)
|
||||||
|
|
||||||
|
if keep_yearly:
|
||||||
|
keep['year'] = keep['year'][-keep_yearly:]
|
||||||
|
else:
|
||||||
|
keep['year'] = []
|
||||||
|
|
||||||
|
if keep_monthly:
|
||||||
|
keep['month'] = keep['month'][-keep_monthly:]
|
||||||
|
else:
|
||||||
|
keep['month'] = []
|
||||||
|
|
||||||
|
if keep_weekly:
|
||||||
|
keep['week'] = keep['week'][-keep_weekly:]
|
||||||
|
else:
|
||||||
|
keep['week'] = []
|
||||||
|
|
||||||
|
if keep_daily:
|
||||||
|
keep['day'] = keep['day'][-keep_daily:]
|
||||||
|
else:
|
||||||
|
keep['day'] = []
|
||||||
|
|
||||||
|
if keep_hourly:
|
||||||
|
keep['hour'] = keep['hour'][-keep_hourly:]
|
||||||
|
else:
|
||||||
|
keep['hour'] = []
|
||||||
|
|
||||||
|
if keep_30min:
|
||||||
|
keep['min30'] = keep['min30'][-keep_30min:]
|
||||||
|
else:
|
||||||
|
keep['min30'] = []
|
||||||
|
|
||||||
|
if keep_15min:
|
||||||
|
keep['min15'] = keep['min15'][-keep_15min:]
|
||||||
|
else:
|
||||||
|
keep['min15'] = []
|
||||||
|
|
||||||
|
if keep_5min:
|
||||||
|
keep['min5'] = keep['min5'][-keep_5min:]
|
||||||
|
else:
|
||||||
|
keep['min5'] = []
|
||||||
|
|
||||||
|
if keep_1min:
|
||||||
|
keep['min1'] = keep['min1'][-keep_1min:]
|
||||||
|
else:
|
||||||
|
keep['min1'] = []
|
||||||
|
|
||||||
|
all_keep = []
|
||||||
|
all_keep.extend(keep['year'])
|
||||||
|
all_keep.extend(keep['month'])
|
||||||
|
all_keep.extend(keep['week'])
|
||||||
|
all_keep.extend(keep['day'])
|
||||||
|
all_keep.extend(keep['hour'])
|
||||||
|
all_keep.extend(keep['min30'])
|
||||||
|
all_keep.extend(keep['min15'])
|
||||||
|
all_keep.extend(keep['min5'])
|
||||||
|
all_keep.extend(keep['min1'])
|
||||||
|
all_keep = set(all_keep)
|
||||||
|
|
||||||
|
to_remove = [date for date in dates if date not in all_keep]
|
||||||
|
return [path for path,date in filelist if date not in all_keep]
|
120
rotator/__main__.py
Normal file
120
rotator/__main__.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import rotator
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description="Rotate backups based on timestamp in names")
|
||||||
|
parser.add_argument(
|
||||||
|
'-y', '--yearly',
|
||||||
|
help='number of yearly backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'-m', '--monthly',
|
||||||
|
help='number of monthly backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'-w', '--weekly',
|
||||||
|
help='number of weekly backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--daily',
|
||||||
|
help='number of daily backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'-o', '--hourly',
|
||||||
|
help='number of hourly backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'--min30',
|
||||||
|
help='number of half-hourly backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'--min15',
|
||||||
|
help='number of quarterly-hourly backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'--min5',
|
||||||
|
help='number of 5-minutely backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
parser.add_argument(
|
||||||
|
'--min1',
|
||||||
|
help='number of minutely backups to keep',
|
||||||
|
type=int,
|
||||||
|
default=0)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-r', '--regex',
|
||||||
|
help='''only rotate backups matching the given regex. Regex must
|
||||||
|
contain at least two match groups, the first being identifier for
|
||||||
|
the backup set and the second being the date.''',
|
||||||
|
default=r'^(.*)[-_.]([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{4})')
|
||||||
|
parser.add_argument(
|
||||||
|
'-t', '--time-format',
|
||||||
|
help='time format of the timestamp (python format)',
|
||||||
|
default='%Y-%m-%d_%H%M')
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-R', '--recurse',
|
||||||
|
action='store_true',
|
||||||
|
help='process paths recursively')
|
||||||
|
parser.add_argument(
|
||||||
|
'path',
|
||||||
|
help='Full path to the directory containing the backup files',
|
||||||
|
nargs='+')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
log = logging.getLogger(rotator._logger)
|
||||||
|
log.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setLevel(logging.WARNING)
|
||||||
|
log.addHandler(handler)
|
||||||
|
|
||||||
|
handler = logging.handlers.SysLogHandler(address='/dev/log')
|
||||||
|
formatter = logging.Formatter(fmt='rotator[%(process)s] %(message)s')
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
handler.setLevel(logging.INFO)
|
||||||
|
log.addHandler(handler)
|
||||||
|
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
if all([not x for x in
|
||||||
|
[args.yearly,
|
||||||
|
args.monthly,
|
||||||
|
args.weekly,
|
||||||
|
args.daily,
|
||||||
|
args.hourly,
|
||||||
|
args.min30,
|
||||||
|
args.min15,
|
||||||
|
args.min5,
|
||||||
|
args.min1]]):
|
||||||
|
log.error('All time intervals set to 0, this would remove all backups, refusing to run')
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
rotator.rotate(
|
||||||
|
args.path,
|
||||||
|
re.compile(args.regex),
|
||||||
|
args.time_format,
|
||||||
|
recurse=args.recurse,
|
||||||
|
keep_yearly=args.yearly,
|
||||||
|
keep_monthly=args.monthly,
|
||||||
|
keep_weekly=args.weekly,
|
||||||
|
keep_daily=args.daily,
|
||||||
|
keep_hourly=args.hourly,
|
||||||
|
keep_30min=args.min30,
|
||||||
|
keep_15min=args.min15,
|
||||||
|
keep_5min=args.min5,
|
||||||
|
keep_1min=args.min1)
|
29
setup.py
Normal file
29
setup.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import setuptools
|
||||||
|
|
||||||
|
with open('README.md', 'r') as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
name='rotator',
|
||||||
|
version='0.1.0',
|
||||||
|
author='Fredrik Eriksson',
|
||||||
|
author_email='feffe@fulh.ax',
|
||||||
|
description='Simple script for rotating backups',
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
url='https://gitea.fulh.ax/feffe/rotator',
|
||||||
|
packages=setuptools.find_packages(),
|
||||||
|
license='BSD 3-caluse',
|
||||||
|
entry_points = {
|
||||||
|
'console_scripts': [
|
||||||
|
'rotator = rotator.__main__:main'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 3 - Alpha',
|
||||||
|
'Environment :: Console',
|
||||||
|
'Intended Audience :: System Administrators',
|
||||||
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Operating System :: POSIX',
|
||||||
|
],
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user