def __run_dependencies(config, should_run): store = config_to_dict(config) importlib.reload(config) importlib.reload(config.confply) confply.config.run = should_run depends_return = 0 if len(store["confply"]["dependencies"]) > 0: for d in store["confply"]["dependencies"]: if d not in __configs_run: confply.config.log_topic = store["confply"]["log_topic"] log.normal("running dependency: " + str(d)) confply.config.log_topic = "confply" log.linebreak() __configs_run.add(d) depends_return = run_commandline(["--in", d]) if depends_return < 0: confply.config.log_topic = store["confply"]["log_topic"] confply.config.log_file = store["confply"]["log_file"] log.error("failed to run: " + str(d)) if not input_prompt("continue execution?"): log.normal("aborting final commands") break else: log.normal("continuing execution.") pass # reset confply.config apply_to_config(store) # #todo: make this jump to the mail section return depends_return
def __handle_version_arg(in_args): log.linebreak() log.normal("Confply " + get_version()) log.normal("Copyright (C) 2021 Graham Hughes.") log.normal("License MIT.") log.normal("This is free software; " + "you are free to change and redistribute it.") log.normal("There is NO WARRANTY, to the extent permitted by law.")
def launcher(in_args, aliases): return_code = -999999 def print_header(): log.confply_header() log.linebreak() confply_dir = __get_confply_dir() if len(in_args) != 0: alias = in_args[0] args = " ".join(in_args[1:]) if alias in aliases: system_code = 0 cmd = ("python " + confply_dir + "/confply.py ") cmd += (aliases[alias] + " " + args) cmd = cmd.replace(" -- ", " " + args + " -- ") # windows doesn't support os.WEXITSTATUS if os.name == 'nt': system_code = os.system(cmd) else: system_code = os.WEXITSTATUS(os.system(cmd)) if (system_code > return_code) and (system_code != 0): return_code = system_code else: print_header() log.error(alias + " is not in aliases.") return_code = -1 else: print_header() log.error("no arguements supplied.") with open(os.path.join(confply_dir, "help.md")) as help_file: print("\n" + help_file.read()) return_code = -1 log.linebreak() if (return_code != -999999): exit(abs(return_code)) else: exit(0)
def run_dict(in_dict): """ runs the confply dict for the supplied config_type usage: run_dict(in_dict) """ log.linebreak() log.header("run config") log.linebreak() # setup config run should_run = confply.config.run if ("config_path" in in_dict["confply"]): path = in_dict["confply"]["config_path"] else: path = None __load_vcs_info(path) confply.config.config_path = path config_name = os.path.basename(path) confply.config.config_name = config_name confply.config.modified = os.path.getmtime(path).real config_locals = apply_to_config(in_dict) if not config_locals: log.error("failed to load: json") return -1 if ("config" not in config_locals): log.error("confply failed import") log.normal( "\tuse: '__package__': 'confply.[config_type].config' in json/ini") clean_modules() return -1 if (not __validate_types(config_locals["config"])): log.error("failed to run config") return -1 # ensure we don't run if should_run was EVER false if should_run is not True: confply.config.run = should_run return __run_config(config_locals)
def __run_shell_cmd(shell_cmd, cmd_env, tool): if confply.config.log_file: sys.stdout.flush() # #todo: check if this can be ansi-coloured result = subprocess.run(shell_cmd, stdout=sys.stdout, stderr=subprocess.STDOUT, text=True, shell=True, env=cmd_env) else: result = subprocess.run(shell_cmd, shell=True, env=cmd_env) if result.returncode == 0: log.linebreak() log.success(tool + " succeeded!") return 0 else: log.linebreak() log.error(tool + " failed.") log.error(tool + " return code: " + str(result.returncode)) return -2
#!/usr/bin/env python import os import sys import confply import confply.log as log if __name__ == "__main__": in_args = sys.argv[1:] version = sys.version_info version = (version.major, version.minor, version.micro) if "--no_header" not in in_args: log.confply_header() log.linebreak() log.normal("python" + str(version)) log.normal("confply " + confply.get_version()) log.normal("date: " + str(confply.datetime.now())) if (version[0] < 3 or (version[0] == 3 and version[1] < 8)): log.error("python version must be 3.8 or above") log.linebreak() exit(1) return_code = -999999 if len(in_args) != 0: while len(in_args) > 0: cmd_return = confply.run_commandline(in_args) if (cmd_return > return_code and cmd_return != 0): return_code = cmd_return # fatal error. if return_code == -1:
def print_header(): log.confply_header() log.linebreak()
def __run_config(config_locals): in_args = confply.config.args config = config_locals["config"] return_code = 0 should_run = confply.config.run file_path = confply.config.config_path __apply_overrides(config) new_working_dir = os.path.dirname(file_path) old_stdout = sys.stdout # setting confply command configuration up with pushd(new_working_dir): if confply.config.log_file: log.normal("writing to: " + confply.config.log_file + "....") try: sys.stdout = open(confply.config.log_file, "w") version = sys.version_info version = (version.major, version.minor, version.micro) if "--no_header" not in in_args: log.confply_header() log.linebreak() log.normal("python" + str(version)) log.normal("confply " + get_version()) log.normal("date: " + str(datetime.now())) log.linebreak() log.normal("confply logging to " + confply.config.log_file) except Exception: log.error("couldn't open " + confply.config.log_file + " for write.") return_code = -1 try: if (confply.config.post_load and inspect.isfunction(confply.config.post_load)): log.normal("running post load script: " + confply.config.post_load.__name__) sys.stdout.flush() exec(confply.config.post_load.__code__, config_locals, {}) log.linebreak() except Exception: log.error("failed to exec " + confply.config.post_load.__name__) trace = traceback.format_exc() log.normal("traceback:\n\n" + trace) pass diff_config = __get_diff_config(config) should_run = confply.config.run report = { "config_path": file_path, "config_json": json.dumps(diff_config, indent=4), "config_type": "unknown", "tool": "unknown", "status": "failure", "vcs_root": confply.config.vcs_root, "vcs_log": confply.config.vcs_log, "vcs": confply.config.vcs, "vcs_branch": confply.config.vcs_branch, "vcs_author": confply.config.vcs_author, "time_taken": "00:00:00" } if return_code >= 0: if confply.config.log_config is not False: __print_config(os.path.basename(file_path), config) try: time_start = timeit.default_timer() # #todo: tool selection phase should happen first. tools = __validate_config(config) config_type = config.__package__ tool = confply.config.tool report["tool"] = tool report["config_type"] = config_type if tools: shell_cmd = tools[tool] shell_cmd.handle_args() # #todo: rename generate to gen_config_type. shell_cmd = shell_cmd.generate() if tools else None else: shell_cmd = None __run_dependencies(config, should_run) if shell_cmd is not None: cmd_env = tools[tool].get_environ() if len(shell_cmd) > 0: if isinstance(shell_cmd, list): log.normal("final commands:\n") for shell_str in shell_cmd: cmd = confply.config.command_prepend + shell_str cmd = cmd + confply.config.command_append print(cmd) print("") else: cmd = confply.config.command_prepend + shell_cmd cmd = shell_cmd + confply.config.command_append log.normal("final command:\n\n" + str(cmd) + "\n") if should_run: log.header("begin " + tool) sys.stdout.flush() if should_run and isinstance(shell_cmd, list): for cmd in shell_cmd: cmd_time_start = timeit.default_timer() sys.stdout.flush() log.linebreak() cmd = confply.config.command_prepend + cmd cmd = cmd + confply.config.command_append log.normal(cmd) log.normal("", flush=True) return_code = __run_shell_cmd(cmd, cmd_env, tool) cmd_time_end = timeit.default_timer() # #todo: this can be tidied with format var capture s = cmd_time_end - cmd_time_start m = int(s / 60) h = int(m / 60) # time formating via format specifiers # https://docs.python.org/3.8/library/string.html#formatspec time = f"{h:0>2.0f}:{m:0>2.0f}:{s:0>5.2f}" log.normal("time elapsed " + time) elif should_run: return_code = __run_shell_cmd(shell_cmd, cmd_env, tool) else: log.warning("no commands run") else: log.error("failed to generate a valid command.") return_code = -1 time_end = timeit.default_timer() s = time_end - time_start m = int(s / 60) h = int(m / 60) # time formating via format specifiers # https://docs.python.org/3.8/library/string.html#formatspec time = f"{h:0>2.0f}:{m:0>2.0f}:{s:0>5.2f}" log.normal("total time elapsed " + time) report["time_taken"] = time report["status"] = "success" if return_code == 0 else "failure" except Exception: log.error("failed to run config: ") trace = traceback.format_exc() log.normal("traceback:\n\n" + trace) return_code = -1 sys.stdout.flush() if (confply.config.post_run and inspect.isfunction(confply.config.post_run)): try: log.linebreak() log.normal("running post run script: " + confply.config.post_run.__name__) sys.stdout.flush() exec(confply.config.post_run.__code__, config_locals, {}) log.linebreak() except Exception: log.error("failed to exec " + confply.config.post_run.__name__) trace = traceback.format_exc() log.normal("traceback:\n\n" + trace) log.normal("date: " + str(datetime.now())) log.linebreak() sys.stdout.flush() if sys.stdout != old_stdout: sys.stdout.close() sys.stdout = old_stdout if confply.config.log_echo_file: with open(confply.config.log_file) as out_log: log_str = out_log.read() log_str = log_str.split("confply logging to " + confply.config.log_file)[1] log.normal("wrote:" + log_str) if (confply.config.mail_send == report["status"] or confply.config.mail_send == "all"): mail.host = confply.config.__mail_host mail.sender = confply.config.mail_from mail.recipients = confply.config.mail_to mail.login = confply.config.__mail_login mail.attachments = confply.config.mail_attachments if (confply.config.log_file and report["status"] == "failure"): mail.attachments.append( os.path.abspath(confply.config.log_file)) pass if mail.login: mail.send_report(report) if (confply.config.slack_send == report["status"] or confply.config.slack_send == "all"): slack.bot_token = confply.config.__slack_bot_token slack.uploads = confply.config.slack_uploads if (confply.config.log_file and report["status"] == "failure"): slack.uploads.append( os.path.abspath(confply.config.log_file)) if slack.bot_token: slack.send_report(report) clean_modules() return return_code
def load_config(path): global config_modules if os.name == "nt": confply.config.platform = "windows" elif os.name == "posix": confply.config.platform = "linux" __load_vcs_info(path) # find group config in parent directories directory_paths = __get_group_configs(path) directory_paths.append(path) config_locals = {} # load and execute the config files for dir_path in directory_paths: if dir_path is None: continue if os.path.exists(dir_path) and os.path.isfile(dir_path): config_name = os.path.basename(dir_path) confply.config.config_name = config_name confply.config.modified = os.path.getmtime(dir_path).real with open(dir_path) as config_file: with pushd(os.path.dirname(dir_path)): try: exec(config_file.read(), {}, config_locals) # find imported confply configs for cleanup for k, v in config_locals.items(): m_valid = isinstance(v, types.ModuleType) if not m_valid: continue v_name = v.__name__ m_valid = m_valid and v not in config_modules m_valid = m_valid and v_name.startswith("confply.") m_valid = m_valid and v_name.endswith(".config") if m_valid: config_modules.add(v) # validate there are less than 2 imported configs if len(config_modules) > 1: log.error("too many confply configs imported:") for c in config_modules: log.normal("\t " + c) log.normal( "confply only supports one config import.") clean_modules() return None, None log.linebreak() log.success("loaded: " + str(dir_path)) except Exception: log.error("failed to exec: " + str(dir_path)) trace = traceback.format_exc().replace( "<string>", str(dir_path)) log.normal("traceback:\n\n" + trace) return None, None else: log.error("failed to find: " + str(dir_path)) return None, None return config_locals
def run_commandline(in_args): """ runs the confply config, with supplied arguements. confply reservered options will be stripped. e.g. --help see help.md usage: run_commandline(["path_to_config", "optional", "arguements"]) """ log.normal("called with args: " + str(in_args)) in_args = __strip_confply_args(in_args) confply.config.args = in_args if confply.config.config_path: config_path = confply.config.config_path if not os.path.exists(config_path): log.error("couldn't find: " + config_path) return -1 if config_path.endswith(".py"): log.linebreak() log.header("run config") log.linebreak() # setup config run should_run = confply.config.run config_locals = load_config(config_path) if not config_locals: log.error("failed to load: " + config_path) return -1 if ("config" not in config_locals): log.error("confply config incorrectly imported") log.normal( "\tuse: 'import confply.[config_type].config as config'") clean_modules() return -1 config = config_locals["config"] if (not __validate_types(config)): log.error("failed to run config") return -1 # ensure we don't run if should_run was EVER false if should_run is not True: confply.config.run = should_run # config config_hash = md5_file(config.__file__) if ("config_hash" not in config_locals or config_hash != config_locals["config_hash"]): log.warning("warning: config_hash doesn't match expected hash") log.normal("\tconfig file might not function correctly") log.normal("\texpected:") log.normal("\t\t" + "config_hash='" + config_hash + "'") log.normal("") return __run_config(config_locals) elif config_path.endswith(".json"): if os.path.exists(config_path): with open(config_path) as in_json: in_dict = json.loads(in_json.read()) in_dict["confply"].update({"config_path": config_path}) return run_dict(in_dict) elif config_path.endswith(".ini"): if os.path.exists(config_path): import configparser def parse_lit(in_val): try: return ast.literal_eval(in_val) except Exception: return in_val conf = configparser.ConfigParser() conf.read(config_path) in_dict = {"confply": {"config_path": config_path}} for (key, val) in conf["config"].items(): in_dict[key] = parse_lit(val) for (key, val) in conf["confply"].items(): in_dict["confply"][key] = parse_lit(val) return run_dict(in_dict) else: log.error("unsupported config type: " + config_path) return -1 else: return 0 return 0