Upgrade configstuffs
Trying to add support for: - Multiple configs in one run - Multiple config path arguments - Allow a config path to be a directory of config files
This commit is contained in:
		
							
								
								
									
										136
									
								
								backup-rotator
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								backup-rotator
									
									
									
									
									
								
							@@ -12,16 +12,26 @@ class BackupRotator:
 | 
			
		||||
	
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		
 | 
			
		||||
		self.__config = None
 | 
			
		||||
		self.__config_path = None
 | 
			
		||||
		self.__dry_run = False
 | 
			
		||||
		self.__configs = []
 | 
			
		||||
		self.__config_paths = []
 | 
			
		||||
		self.__calculated_actions = []
 | 
			
		||||
	
 | 
			
		||||
	def run(self):
 | 
			
		||||
		
 | 
			
		||||
		self.log("Begin")
 | 
			
		||||
		self.consume_arguments()
 | 
			
		||||
		self.consume_config(self.__config_path)
 | 
			
		||||
		self.consume_configs(self.__config_paths)
 | 
			
		||||
		
 | 
			
		||||
		self.rotate_paths()
 | 
			
		||||
		# Rotate once per config
 | 
			
		||||
		for config_index in range(len(self.__configs)):
 | 
			
		||||
			
 | 
			
		||||
			#
 | 
			
		||||
			config = self.__configs[config_index]
 | 
			
		||||
			
 | 
			
		||||
			#
 | 
			
		||||
			self.log("Rotating for config " + str(config_index + 1) + " of " + str(len(self.__configs)), config["__path"])
 | 
			
		||||
			self.do_rotate(config)
 | 
			
		||||
	
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def current_time():
 | 
			
		||||
@@ -42,13 +52,20 @@ class BackupRotator:
 | 
			
		||||
	
 | 
			
		||||
	def consume_arguments(self):
 | 
			
		||||
		
 | 
			
		||||
		self.__config_paths = []
 | 
			
		||||
		
 | 
			
		||||
		for i in range(1, len(sys.argv)):
 | 
			
		||||
			
 | 
			
		||||
			arg = sys.argv[i]
 | 
			
		||||
			
 | 
			
		||||
			if arg == "--config":
 | 
			
		||||
				i, self.__config_path = self.consume_argument_companion(i)
 | 
			
		||||
				print("Found config path:", self.__config_path)
 | 
			
		||||
				i, one_path = self.consume_argument_companion(i)
 | 
			
		||||
				self.__config_paths.append(one_path)
 | 
			
		||||
				print("Found config path argument:", one_path)
 | 
			
		||||
			
 | 
			
		||||
			elif arg == "--dry-run":
 | 
			
		||||
				self.__dry_run = True
 | 
			
		||||
				print("Activating global dry-run mode")
 | 
			
		||||
	
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def consume_argument_companion(arg_index):
 | 
			
		||||
@@ -59,34 +76,64 @@ class BackupRotator:
 | 
			
		||||
		
 | 
			
		||||
		return companion_index, sys.argv[companion_index]
 | 
			
		||||
	
 | 
			
		||||
	def consume_config(self, path=None):
 | 
			
		||||
	def consume_configs(self, paths: list=None):
 | 
			
		||||
		
 | 
			
		||||
		if path is None:
 | 
			
		||||
		if paths is None:
 | 
			
		||||
			raise Exception("Auto-finding of config file not implemented")
 | 
			
		||||
		
 | 
			
		||||
		f = open(path)
 | 
			
		||||
		self.__config = yaml.load(f)
 | 
			
		||||
		# Use each config path
 | 
			
		||||
		for path in paths:
 | 
			
		||||
			
 | 
			
		||||
			# If this is a single path
 | 
			
		||||
			if os.path.isfile(path):
 | 
			
		||||
				self.consume_config(path)
 | 
			
		||||
			
 | 
			
		||||
			# If this is a directory
 | 
			
		||||
			elif os.path.isdir(path):
 | 
			
		||||
				
 | 
			
		||||
				# Iterate over each file inside
 | 
			
		||||
				for file_name in os.listdir(path):
 | 
			
		||||
					self.consume_config(os.path.join(path, file_name))
 | 
			
		||||
				
 | 
			
		||||
	def consume_config(self, path: str):
 | 
			
		||||
		
 | 
			
		||||
		# Open the file
 | 
			
		||||
		f = open(path)
 | 
			
		||||
		if not f:
 | 
			
		||||
			raise Exception("Unable to open config file: " + path)
 | 
			
		||||
		
 | 
			
		||||
		# Parse
 | 
			
		||||
		config = yaml.load(f)
 | 
			
		||||
		
 | 
			
		||||
		# Add its own path
 | 
			
		||||
		config["__path"] = path
 | 
			
		||||
		
 | 
			
		||||
		# Consume to internal
 | 
			
		||||
		self.__configs.append(config)
 | 
			
		||||
		self.log("Consumed config from path:", path)
 | 
			
		||||
	
 | 
			
		||||
	def rotate_paths(self):
 | 
			
		||||
	def do_rotate(self, config):
 | 
			
		||||
	
 | 
			
		||||
		self.log("Begin rotating " + str(len(self.__config["paths"])) + " paths")
 | 
			
		||||
		for path in self.__config["paths"]:
 | 
			
		||||
			self.rotate_path(path)
 | 
			
		||||
		self.rotate_paths(config)
 | 
			
		||||
	
 | 
			
		||||
	def rotate_path(self, path):
 | 
			
		||||
	def rotate_paths(self, config):
 | 
			
		||||
		
 | 
			
		||||
		self.log("Begin rotating " + str(len(config["paths"])) + " paths")
 | 
			
		||||
		for path in config["paths"]:
 | 
			
		||||
			self.rotate_path(config, path)
 | 
			
		||||
	
 | 
			
		||||
	def rotate_path(self, config, path):
 | 
			
		||||
		
 | 
			
		||||
		self.log("Rotating path", path)
 | 
			
		||||
		
 | 
			
		||||
		if "maximum-items" not in self.__config:
 | 
			
		||||
		if "maximum-items" not in config:
 | 
			
		||||
			raise Exception("Please provide config key: \"maximum-items\"")
 | 
			
		||||
		max_items = self.__config["maximum-items"]
 | 
			
		||||
		max_items = config["maximum-items"]
 | 
			
		||||
		
 | 
			
		||||
		if not os.path.isdir(path):
 | 
			
		||||
			raise Exception("Path should be a directory:" + str(path))
 | 
			
		||||
		
 | 
			
		||||
		children = self.gather_rotation_candidates(path)
 | 
			
		||||
		children = self.gather_rotation_candidates(config, path)
 | 
			
		||||
		
 | 
			
		||||
		# Do we need to rotate anything out?
 | 
			
		||||
		if len(children) <= max_items:
 | 
			
		||||
@@ -104,31 +151,35 @@ class BackupRotator:
 | 
			
		||||
		)
 | 
			
		||||
		
 | 
			
		||||
		for purge_index in range(purge_count):
 | 
			
		||||
			item_to_purge = self.pick_item_to_purge(children)
 | 
			
		||||
			
 | 
			
		||||
			#
 | 
			
		||||
			item_to_purge = self.pick_item_to_purge(config, children)
 | 
			
		||||
			children.remove(item_to_purge)
 | 
			
		||||
			self.log("Purging item:", item_to_purge)
 | 
			
		||||
			
 | 
			
		||||
			#
 | 
			
		||||
			if os.path.isfile(item_to_purge):
 | 
			
		||||
				os.remove(item_to_purge)
 | 
			
		||||
				self.remove_file(config, item_to_purge)
 | 
			
		||||
			elif os.path.isdir(item_to_purge):
 | 
			
		||||
				shutil.rmtree(item_to_purge)
 | 
			
		||||
				self.remove_directory(config, item_to_purge)
 | 
			
		||||
			else:
 | 
			
		||||
				raise Exception("Don't know how to remove this item: " + str(item_to_purge))
 | 
			
		||||
	
 | 
			
		||||
	def gather_rotation_candidates(self, path):
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def gather_rotation_candidates(config, path):
 | 
			
		||||
		
 | 
			
		||||
		candidates = []
 | 
			
		||||
		
 | 
			
		||||
		if "target-type" not in self.__config.keys():
 | 
			
		||||
		if "target-type" not in config.keys():
 | 
			
		||||
			raise Exception("Please provide the configuration key: target-type")
 | 
			
		||||
		
 | 
			
		||||
		for item_name in os.listdir(path):
 | 
			
		||||
			
 | 
			
		||||
			item_path = os.path.join(path, item_name)
 | 
			
		||||
			
 | 
			
		||||
			if self.__config["target-type"] == "file":
 | 
			
		||||
			if config["target-type"] == "file":
 | 
			
		||||
				if not os.path.isfile(item_path):
 | 
			
		||||
					continue
 | 
			
		||||
			elif self.__config["target-type"] == "directory":
 | 
			
		||||
			elif config["target-type"] == "directory":
 | 
			
		||||
				if not os.path.isdir(item_path):
 | 
			
		||||
					continue
 | 
			
		||||
			else:
 | 
			
		||||
@@ -138,12 +189,13 @@ class BackupRotator:
 | 
			
		||||
		
 | 
			
		||||
		return candidates
 | 
			
		||||
	
 | 
			
		||||
	def pick_item_to_purge(self, items):
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def pick_item_to_purge(config, items):
 | 
			
		||||
		
 | 
			
		||||
		if "date-detection" not in self.__config.keys():
 | 
			
		||||
		if "date-detection" not in config.keys():
 | 
			
		||||
			raise Exception("Please provide config key: \"date-detection\"")
 | 
			
		||||
		
 | 
			
		||||
		detection = self.__config["date-detection"]
 | 
			
		||||
		detection = config["date-detection"]
 | 
			
		||||
		best_item = None
 | 
			
		||||
		best_ctime = None
 | 
			
		||||
		for item in items:
 | 
			
		||||
@@ -158,6 +210,32 @@ class BackupRotator:
 | 
			
		||||
		
 | 
			
		||||
		return best_item
 | 
			
		||||
	
 | 
			
		||||
	def remove_file(self, config, file_path):
 | 
			
		||||
		
 | 
			
		||||
		if not os.path.isfile(file_path):
 | 
			
		||||
			raise Exception("Tried to remove a file, but this path isn't a file: " + str(file_path))
 | 
			
		||||
		
 | 
			
		||||
		if self.__dry_run:
 | 
			
		||||
			self.log("Won't purge file during global-level dry run: ", file_path)
 | 
			
		||||
		elif "dry-run" in config.keys() and config["dry-run"] is True:
 | 
			
		||||
			self.log("Won't purge file during config-level dry run: ", file_path)
 | 
			
		||||
		else:
 | 
			
		||||
			self.log("Purging file:", file_path)
 | 
			
		||||
			os.remove(file_path)
 | 
			
		||||
	
 | 
			
		||||
	def remove_directory(self, config, dir_path):
 | 
			
		||||
		
 | 
			
		||||
		if not os.path.isdir(dir_path):
 | 
			
		||||
			raise Exception("Tried to remove a directory, but this path isn't a directory: " + str(dir_path))
 | 
			
		||||
		
 | 
			
		||||
		if self.__dry_run:
 | 
			
		||||
			self.log("Won't purge directory during global-level dry run: ", dir_path)
 | 
			
		||||
		elif "dry-run" in config.keys() and config["dry-run"] is True:
 | 
			
		||||
			self.log("Won't purge directory during config-level dry run: ", dir_path)
 | 
			
		||||
		else:
 | 
			
		||||
			self.log("Purging directory:", dir_path)
 | 
			
		||||
			shutil.rmtree(dir_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
	
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user