55:file: db_backup.py
66:language: python3
77:author: Peter Bailie (Systems Programmer, Dept. of Computer Science, RPI)
8- :date: August 22 2018
8+ :date: August 28 2018
99
1010This script will take backup dumps of each individual Submitty course
1111database. This should be set up by a sysadmin to be run on the Submitty
3030"""
3131
3232import argparse
33+ import calendar
3334import datetime
3435import json
3536import os
4344# WHERE DUMP FILES ARE WRITTEN
4445DUMP_PATH = '/var/local/submitty/submitty-dumps'
4546
46- def delete_obsolete_dumps (working_path , expiration_stamp ):
47+ def delete_obsolete_dumps (working_path , monthly_retention , expiration_date ):
4748 """
4849 Recurse through folders/files and delete any obsolete dump files
4950
50- :param working_path: path to recurse through
51- :param expiration_stamp: date to begin purging old dump files
52- :type working_path: string
53- :type expiration_stamp: string
51+ :param working_path: path to recurse through
52+ :param monthly_retention: day of month that dump is always preserved (val < 1 when disabled)
53+ :param expiration_date: date to begin purging old dump files
54+ :type working_path: string
55+ :type monthly_retention: integer
56+ :type expiration_date: datetime.date object
5457 """
5558
5659 # Filter out '.', '..', and any "hidden" files/directories.
@@ -62,24 +65,26 @@ def delete_obsolete_dumps(working_path, expiration_stamp):
6265 for file in files_list :
6366 if os .path .isdir (file ):
6467 # If the file is a folder, recurse
65- delete_obsolete_dumps (file , expiration_stamp )
68+ delete_obsolete_dumps (file , monthly_retention , expiration_date )
6669 else :
67- # File date was concat'ed into the file's name. Use regex to isolate date from full path.
68- # e.g. "/var/local/submitty-dumps/s18/cs1000/180424_s18_cs1000.dbdump"
69- # The date substring can be located with high confidence by looking for:
70- # - final token of the full path (the actual file name)
71- # - file name consists of three tokens delimited by '_' chars
72- # - first token is exactly 6 digits, the date stamp.
73- # - second token is the semester code, at least one 'word' char
74- # - third token is the course code, at least one 'word' char
75- # - filename always ends in ".dbdump"
76- # - then take substring [0:6] to get "180424".
77- match = re .search ('(\d{6}_\w+_\w+\.dbdump)$' , file )
78- if match is not None :
79- file_date_stamp = match .group (0 )[0 :6 ]
80- if file_date_stamp <= expiration_stamp :
81- os .remove (file )
82-
70+ # Determine file's date from its filename
71+ # Note: datetime.date.fromisoformat() doesn't exist in Python 3.6 or earlier.
72+ filename = file .split ('/' )[- 1 ]
73+ datestamp = filename .split ('_' )[0 ]
74+ year , month , day = map (int , datestamp .split ('-' ))
75+ file_date = datetime .date (year , month , day )
76+
77+ # Conditions to NOT delete old file:
78+ if file_date > expiration_date :
79+ pass
80+ elif file_date .day == monthly_retention :
81+ pass
82+ # A month can be as few as 28 days, but we NEVER skip months even when "-m" is 28, 29, 30, or 31.
83+ elif monthly_retention > 28 and (file_date .day == calendar .monthrange (file_date .year , file_date .month )[1 ] and file_date .day <= monthly_retention ):
84+ pass
85+ else :
86+ # os.remove(file)
87+ print ("remove " + file )
8388def main ():
8489 """ Main """
8590
@@ -89,18 +94,19 @@ def main():
8994
9095 # READ COMMAND LINE ARGUMENTS
9196 # Note that -t and -g are different args and mutually exclusive
92- parser = argparse .ArgumentParser (description = 'Dump all Submitty databases for a particular academic term.' )
93- parser .add_argument ('-e' , action = 'store' , nargs = '?' , type = int , default = 0 , help = 'Set number of days expiration of older dumps (default: no expiration).' , metavar = 'days' )
97+ parser = argparse .ArgumentParser (description = 'Dump all Submitty databases for a particular academic term.' , prefix_chars = '-' , add_help = True )
98+ parser .add_argument ('-e' , action = 'store' , type = int , default = 0 , help = 'Set number of days expiration of older dumps (default: no expiration).' , metavar = 'days' )
99+ parser .add_argument ('-m' , action = 'store' , type = int , default = 0 , choices = range (0 ,32 ), help = 'Day of month to ALWAYS retain a dumpfile (default: no monthly retention).' , metavar = 'day of month' )
94100 group = parser .add_mutually_exclusive_group (required = True )
95- group .add_argument ('-t' , action = 'store' , nargs = '?' , type = str , help = 'Set the term code.' , metavar = 'term code' )
101+ group .add_argument ('-t' , action = 'store' , type = str , help = 'Set the term code.' , metavar = 'term code' )
96102 group .add_argument ('-g' , action = 'store_true' , help = 'Guess term code based on calender month and year.' )
103+
97104 args = parser .parse_args ()
98105
99106 # Get current date -- needed throughout the script, but also used when guessing default term code.
100- # (today.year % 100) determines the two digit year. e.g. '2017' -> '17'
101107 today = datetime .date .today ()
102- year = str ( today .year % 100 )
103- today_stamp = '{:0>2}{:0>2}{:0>2}' . format ( year , today .month , today . day )
108+ year = today .strftime ( "%y" )
109+ today_stamp = today .isoformat ( )
104110
105111 # PARSE COMMAND LINE ARGUMENTS
106112 expiration = args .e
@@ -112,6 +118,9 @@ def main():
112118 else :
113119 semester = args .t
114120
121+ # MONTHLY RETENTION DATE
122+ monthly_retention = args .m
123+
115124 # GET DATABASE CONFIG FROM SUBMITTY
116125 fh = open (DB_CONFIG_PATH , "r" )
117126 db_config = json .load (fh )
@@ -170,12 +179,11 @@ def main():
170179 # DETERMINE EXPIRATION DATE (to delete obsolete dump files)
171180 # (do this BEFORE recursion so it is not calculated recursively n times)
172181 if expiration > 0 :
173- expiration_date = datetime .date .fromordinal (today .toordinal () - expiration )
174- expiration_stamp = '{:0>2}{:0>2}{:0>2}' .format (expiration_date .year % 100 , expiration_date .month , expiration_date .day )
182+ expiration_date = datetime .date .fromordinal (today .toordinal () - expiration )
175183 working_path = "{}/{}" .format (DUMP_PATH , semester )
176184
177185 # RECURSIVELY CULL OBSOLETE DUMPS
178- delete_obsolete_dumps (working_path , expiration_stamp )
186+ delete_obsolete_dumps (working_path , monthly_retention , expiration_date )
179187
180188if __name__ == "__main__" :
181189 main ()
0 commit comments