#!/usr/bin/python3
SVER='2.0.6'
# set noet ci pi sts=0 sw=4 ts=4
##############################################################################
# samgr - Security Announcement manager
# Copyright (C) 2023 SUSE LLC
#
# Description:  Runs new security patterns against supportconfigs in the
#               archive directory using the pat tool.
# Modified:     2023 Aug 25
#
##############################################################################
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; version 2 of the License.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, see <http://www.gnu.org/licenses/>.
#
#  Authors/Contributors:
#     Jason Record <jason.record@suse.com>
#
##############################################################################

import re
import sys
import os
import getopt
import signal
import configparser
import patdevel as pd

title_string = "SCA Pattern Checker"

def usage():
	display = "  {:33s} {}"
	print("Usage: samgr [options]")
	print()
	print("Description:")
	print("  Performs requested actions for Security Announcement management")
	print()
	print("Options:")
	print(display.format('-h, --help', "Display this help"))
	print(display.format('-v, --validate', "Validate patterns created by sagen. This is the default action."))
	print(display.format('-d, --distribute', "Distribute security patterns to associated repositories"))
	print(display.format('-r, --remove', "Remove uncommitted security patterns from repositories"))
	print(display.format('-c, --config', "Show the configuration file data"))
	print(display.format('-s, --status', "Show the current status"))
	print(display.format('-p, --repos', "Update GitHub repositories"))
	print(display.format('-l <level>, --log_level <level>', "Set log level, default: Minimal"))
	print(display.format('', "0 Quiet, 1 Minimal, 2 Normal, 3 Verbose, 4 Debug"))
	print(display.format('-E, --reset', "Clean security patterns from the patterns, logs, errors and duplicates directories"))
	print()

def signal_handler(sig, frame):
	print("\n\nAborting...\n")
	sys.exit(0)

def get_sa_pattern_list(sca_pat_dir):
	valid_pattern = re.compile("_SUSE-SU.*py$|_SUSE-SU.*pl$")
	base_pattern_list = pd.get_pattern_list(sca_pat_dir)
	this_list = []
	for file in base_pattern_list:
		if valid_pattern.search(file):
			this_list.append(file)
	return this_list

##############################################################################
# main
##############################################################################

def main(argv):
	global SVER, title_string
	action = "status"

	if( os.path.exists(pd.config_file) ):
		config.read(pd.config_file)
		config_log_level = pd.config_entry(config.get("Common", "log_level"))
		sca_arch_dir = pd.config_entry(config.get("Common", "sca_arch_dir"), '/')
		sca_pat_dir = pd.config_entry(config.get("Security", "pat_dir"), '/')
		config_logging = msg.validate_level(config_log_level)
		if( config_logging >= msg.LOG_QUIET ):
			msg.set_level(config_logging)
		else:
			print("Warning: Invalid log level in config file, using instance default")
	else:
		pd.title(title_string, SVER)
		print("Error: File not found - " + pd.config_file + "\n")
		sys.exit(1)

	try:
		(optlist, args) = getopt.gnu_getopt(argv[1:], "hvdrcspEl:", ["help", "validate", "distribute", "remove", "config", "status", "repos", "reset", "log_level="])
	except getopt.GetoptError as exc:
		pd.title(title_string, SVER)
		print("Error:", exc, file=sys.stderr)
		print()
		usage()
		print()
		sys.exit(2)
	for opt, arg in optlist:
		if opt in {"-h", "--help"}:
			pd.title(title_string, SVER)
			usage()
			sys.exit(0)
		elif opt in {"-v", "--validate"}:
			action = "validate"
		elif opt in {"-d", "--distribute"}:
			action = "distribute"
		elif opt in {"-r", "--remove"}:
			action = "remove"
		elif opt in {"-E", "--reset"}:
			action = "reset"
		elif opt in {"-c", "--config"}:
			action = "config"
		elif opt in {"-s", "--status"}:
			action = "status"
		elif opt in {"-p", "--repos"}:
			action = "repos"
		elif opt in {"-l", "--log_level"}:
			user_logging = msg.validate_level(arg)
			if( user_logging >= msg.LOG_QUIET ):
				msg.set_level(user_logging)
			else:
				print("Warning: Invalid log level, using instance default")

	if( msg.get_level() > msg.LOG_QUIET ):
		pd.title(title_string, SVER)

	if( action == "validate" ):
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Validate Security Patterns")
		msg.normal("Log Level", msg.get_level_str())
		msg.normal("Loading security patterns", sca_pat_dir)
		pattern_list = get_sa_pattern_list(sca_pat_dir)
		msg.verbose("+ Security patterns", str(len(pattern_list)))
		if( len(pattern_list) > 0 ):
			msg.normal("Loading archives", sca_arch_dir)
			archive_list = pd.get_archive_list(sca_arch_dir)
			msg.verbose("+ Supportconfigs", str(len(archive_list)))
			if( len(archive_list) > 0 ):
				patdev_repos = pd.config_entry(config.get("GitHub", "patdev_repos")).split(',')
				bar = pd.ProgressBar("Updating: ", len(patdev_repos))
				msg.normal("Updating Pattern Repositories")
				pd.update_git_repos(config, msg, bar)
				if( msg.get_level() == msg.LOG_MIN ):
					bar.finish()
				pd.validate_sa_patterns(config, msg, pattern_list)
				msg.min("Next: Distribute patterns to their respective repositories, with 'samgr -d'\n")
			else:
				msg.min("Error: No supportconfig archives found in {}\n".format(sca_arch_dir))
				sys.exit(5)
		else:
			msg.min("+ Warning: No security patterns found, run sagen\n")
	elif( action == "distribute" ):
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Distribute Patterns")
		msg.normal("Log Level", msg.get_level_str())
		msg.normal("Loading security patterns", sca_pat_dir)
		pattern_list = get_sa_pattern_list(sca_pat_dir)
		msg.verbose("+ Security patterns", str(len(pattern_list)))
		if( len(pattern_list) > 0 ):
			pd.distribute_sa_patterns(config, msg, pattern_list)
			msg.min("Next: Generate the package change log with sagvc or remove distribution with 'samgr -r'\n")
		else:
			msg.min("+ Warning: No security patterns found, run sagen, then samgr --validate\n")
	elif( action == "remove" ):
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Remove Uncommitted Patterns")
		msg.normal("Log Level", msg.get_level_str())
		pd.remove_sa_patterns(config, msg)
	elif( action == "reset" ):
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Reset Security Patterns")
		msg.normal("Log Level", msg.get_level_str())
		pd.reset_sa_patterns(config, msg)
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Development Status")
		pd.show_status(config, msg)
	elif( action == "config" ):
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Show Configuration File")
		msg.normal("Log Level", msg.get_level_str())
		pd.show_config_file(config)
	elif( action == "repos" ):
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Update Pattern Repositories")
		msg.normal("Log Level", msg.get_level_str())
		patdev_repos = pd.config_entry(config.get("GitHub", "patdev_repos")).split(',')
		bar = pd.ProgressBar("Updating: ", len(patdev_repos))
		msg.normal("Updating Pattern Repositories")
		pd.update_git_repos(config, msg, bar)
		if( msg.get_level() == msg.LOG_MIN ):
			bar.finish()
		else:
			msg.normal()
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Development Status")
		pd.show_status(config, msg)
	elif( action == "status" ):
		if( msg.get_level() > msg.LOG_QUIET ):
			pd.sub_title("Development Status")
		msg.normal("Log Level", msg.get_level_str())
		pd.show_status(config, msg)
	else:
		usage()

# Entry point
if __name__ == "__main__":
	signal.signal(signal.SIGINT, signal_handler)
	config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
	msg = pd.DisplayMessages()
	main(sys.argv)

