import os import sys import errno import logging import fabric.contrib.files from contextlib import contextmanager as _contextmanager from fabric.api import env, prefix, local from fabric.operations import run, sudo def printvar(name, value, exit=False): """ debug helper method to print a variable from within a process :parameter name: name of the variable we are printing out :type name: str :parameter value: the value of the variable we want to print :type value: str :parameter exit: determines whether or not we exit the program at this point. defaults to false :type exit: bool """ print("%s : %s" % (name, value)) if exit: sys.exit() def printerr(message="", errcode=-2, exit=True): """ debug helper method thats prints error message and error code then exits :parameter message: the error message we want to print out. defaults to empty string. :type message: str :parameter errcode: the code of the error :type errcode: int :parameter exit: determines whether or not we exit the program at this point. defaults to True :type exit: bool """ message = "Error\n\t{message}\n\tExiting with code: {errcode}\n".format( message=message, errcode=errcode) print print(message) print sys.exit(errcode) def loggify(module, func, prefix=""): """ I'm tired of rewriting this logging code in every single function, so I decided to just dump it here, and return a logger that can be used and thrown away when it's done :parameter module: name of the module being used, ie 'nginx', 'deploy', etc :type module: str :parameter func: the name of the function this logger is going to be used in :type func: str :parameter prefix: anything you want to add to the front of the logger, ie \'\\n\'. defaults to an empty string. :type prefix: str :return: a logging object :rtype: object """ loggername = '{module}.{func}'.format( module=module, func=func) str_logging = '{prefix}%(levelname)s: {loggername} %(message)s'.format( prefix=prefix, loggername=loggername) logging.basicConfig( format=str_logging, level=logging.DEBUG) return logging.getLogger(loggername) def print_console(string, prepend="\n\n", append="\n\n", sep="-", numsep=44, exit_program=False, exit_code=0): """ helper function to take a string, and format it so it prints to the console in a way that is pleasing to the eye. :parameter string: the string to be printed :type string: str :parameter prepend: defaults to two line spaces, can be anything :type string: str :parameter append: defaults to two lines spaces after the string is printed :type append: str :parameter sep: the character used to print out one line above the string and one line after :type sep: str :parameter numsep: number of times the separator is printed out on a line :type numsep: int :parameter exit_program: whether to exit the program after printing the message. defaults to False :type exit_program: bool :parameter exit_code: what exit code to use if we exit the program after printing the message. defaults to 0 :type exit_code: int """ print(prepend) if sep: print(sep * numsep) print(string) if sep: print(sep * numsep) print(append) if exit_program: import sys sys.exit(exit_code) def print_debug(debugstr, module, function): """ debug helper method to print out a debug string, the name of the module, and the function it is located in. The message will be printed out in the form :: :parameter debugstr: the debugging message we want to print :type debugstr: str :parameter module: the name of the module we are in :type module: str :parameter function: the name of the function we are in :type function: """ print("%s:%s:%s" % (module, function, debugstr)) def executize(config_execute='local'): """ creates an executor to run commands. returns one of the following methods fabric.api.local, fabric.operations.run or fabric.operations.sudo :parameter config_execute: values can be either 'sudo', 'run', or 'local'. defaults to 'local'. :type config_execute: str :return: the fabric command corresponding to the string value in config_execute, either a sudo, a run or a local executor """ if config_execute == 'sudo': _execute = sudo elif config_execute == 'run': _execute = run elif config_execute == 'local': _execute = local else: errmsg = "did not enter a correct value of 'sudo', 'run' or" \ " 'local' to executize.\nExiting." printerr(message=errmsg, errcode=-2) return _execute def booleanize(value): """ take the argument and return it as either True or False if the argument is neither, throw a TypeError explaining that the value given cannot be booleanized :parameter value: takes values of y, n, yes, no, true, false, 1 or 0 :type value: str :return: converts value to True or False. Note 1 is considered True and 0 is False :rtype: bool """ true_values = ("y", "yes", "true", "1") false_values = ("n", "no", "false", "0") if isinstance(value, bool): return value if value.lower() in true_values: return True elif value.lower() in false_values: return False raise TypeError("Cannot booleanize ambiguous value '%s'" % value) def ensure_dir(dirpath): """ Create a directory if it does not exists. Raises an OSError if there is a problem :parameter dirpath: directory path :type dirpath: str """ try: if not os.path.exists(dirpath): print("creating directory: %s" % dirpath) os.makedirs(dirpath) except OSError as e: if e.errno != errno.EEXIST: print("Error occurred while creating directory: %s" % dirpath) raise def ensure_file(filepath): """ Simulates linux 'touch' command :parameter filepath: name of the file we want to create. Note this must include the full path and filename :type filepath: str """ if not os.path.exists(filepath): open(filepath, 'w').close() def upload_template(filename, destination, context, use_jinja, use_sudo, backup, template_dir, debug=False): """ helper wrapper method to upload a template it wraps around fabric.contrib.files.upload_template method. See https://docs.fabfile.org/en/1.11/api/contrib/files.html :parameter filename: the full path to the filename if we are not using jinja, otherwise using template_dir and just the filename this file will be interpolated using the given context and uploaded to the remote host :type filename: str :parameter destination: remote file path to place the file (includes filename) :type destination: str :parameter context: variable context :type context: dict :parameter use_jinja: whether we are using jinja template system or not. Templates will be loaded from the invoking users' current working directory or by template_dir :type use_jinja: bool :parameter use_sudo: whether we are using sudo to place the file. By default the file will be copied as the logged in user :type use_sudo: bool :parameter backup: if the destination file already exists, create a backup of it with the extension .bak :type backup: bool :parameter template_dir: path to the template directory that is used if jinja is true. directory where the file is located :type template_dir: str :parameter debug: if true, just print out the command we are using to invoke the fabric upload_template method. defaults to False :type debug: bool """ if env.debug: logging.basicConfig( format='\n%(levelname)s: utils.upload_template %(message)s', level=logging.DEBUG) command_msg = "\n\tupload_template(" \ "\n\tfilename={filename}," \ "\n\tdestination={destination_available}," \ "\n\tcontext={context}," \ "\n\tuse_jinja={use_jinja}," \ "\n\tuse_sudo={use_sudo}," \ "\n\tbackup={backup}," \ "\n\ttemplate_dir={template_dir})\n".format( filename=filename, destination_available=destination, context=context, use_jinja=use_jinja, use_sudo=use_sudo, backup=backup, template_dir=template_dir) if debug: return command_msg else: fabric.contrib.files.upload_template( filename=filename, destination=destination, context=context, use_jinja=use_jinja, use_sudo=use_sudo, backup=backup, template_dir=template_dir) def get_upload_template_msg(filename, destination, context, use_jinja, use_sudo, backup, template_dir, debug=False): command_msg = "\n\tupload_template(" \ "\n\tfilename={filename}," \ "\n\tdestination={destination_available}," \ "\n\tcontext={context}," \ "\n\tuse_jinja={use_jinja}," \ "\n\tuse_sudo={use_sudo}," \ "\n\tbackup={backup}," \ "\n\ttemplate_dir={template_dir})\n".format( filename=filename, destination_available=destination, context=context, use_jinja=use_jinja, use_sudo=use_sudo, backup=backup, template_dir=template_dir) return command_msg @_contextmanager def virtualenv_source(): """ helper method that sources the virtualenvwrapper for all following commands uses python yield command """ configuration = env.config venv_bin = configuration.virtualenv.bin if venv_bin: venv_activate = f"source {venv_bin}/virtualenvwrapper.sh" else: venv_activate = "source virtualenvwrapper.sh" with prefix(venv_activate): yield @_contextmanager def virtualenv(): """ Context manager for setting the virtual environment for commands. This helper method sets up the virtual environment specified in the configuration before executing any commands within its context. It uses the Python context manager pattern with the `yield` keyword to temporarily modify the environment for the duration of the with-block. The virtual environment is specified by the name in `configuration.virtualenv.name` and is activated using the `workon` command. Note: - This method relies on the Fabric library's `prefix` context manager and assumes the use of virtualenvwrapper or a similar tool for managing virtual environments. - The method expects `configuration.virtualenv.name` to be properly set in `env.config`. Example Usage: with virtualenv(): run("python manage.py migrate") """ configuration = env.config with virtualenv_source(): with prefix("workon %s" % configuration.virtualenv.name): yield # with prefix("/bin/bash -c -l 'source %s'" % # configuration.virtualenv.activate): # yield # with prefix("/bin/bash -c -l 'source /usr/local/bin/' \ # 'virtualenvwrapper.sh ' \ # '&& workon %s'" % configuration.virtualenv.name): # yield def generate_template_build_path(section, template_name='conf'): """ helper function to automate creation of build path :parameter section: the template section we are building off of :type section: str :parameter template_name: by default this is "conf", but can be different, for example, the 'database' section has 3 different template names :type template_name: str :return: a path to where the template should be placed """ import os configuration = env.config conf_section = getattr(configuration.templates, section) conf_section_templatename = getattr(conf_section, template_name) build_path = os.path.join( conf_section.path.remote, 'build', conf_section_templatename.dst) return build_path def generate_template_files_path(section): """ helper function to automate creation of build path :parameter section: the template section we are building off of :type section: str :return: a path to where the template jinja file is located :rtype: str """ import os configuration = env.config conf_section = getattr(configuration.templates, section) files_path = os.path.join( conf_section.path.local, 'files') return files_path def print_run(command, prefix="\"\n\t", suffix="\n\t\""): """ helper function to print out what a command will actually look like when run :parameter command: a typical bash command that I want to run :type command: str :parameter prefix: prefix that is added to the string, typically done for clarity of viewing :type prefix: str :parameter suffix: suffix that is added to the string, typically done for clarity of viewing :type suffix: str :returns: an example of how the command will actually look if run on the command line :rtype: str """ return "run ({prefix}{command}{suffix})".format( prefix=prefix, suffix=suffix, command=command) def handle_flag_message(param, message, values=['-h', '--help']): """ if a given parameter is in the given values then return the given message :parameter param: checks if the given parameter is in the values. :type param: str :parameter message: the message to print out if true :type message: str :parameter values: the values we are checking to see. by default this is ['-h', '--help'] :type values: list :return: whether the given param is in values :rtype: bool """ if isinstance(param, str): if param.lower() in values: print(message) return True return False def is_help(key): """ helper method to determine if the given key is a help key :parameter key: the key we are checking :type key: str :return: if key is in -h or --help :rtype: bool """ help_keys = ['-h', '--help'] return key in help_keys def link_create(path_src, path_dst, debug=False): """ takes a source and destination path, then links it if the destination path already exists and is a link, then delete it. Otherwise sys.exit with an error message. :parameter path_src: source path :type path_src: str :paramter path_dst: destination path :type path_dst: str :parameter debug: whether we do nothing and just return a message :type debug: bool :return: if debug=True then it returns a msg """ from fabric.contrib.files import is_link from fabric.contrib.files import exists from fabric.operations import run cmd_rm = "rm {path_dst}".format( path_dst=path_dst ) cmd_link = "ln -sf {path_src} {path_dst}".format( path_src=path_src, path_dst=path_dst ) msg_debug = "" if exists(path_dst): if is_link(path_dst): if debug: msg_debug += "link already exists at dst, removing\n" \ "link_create:cmd_rm : %s" % cmd_rm else: run(cmd_rm) else: msg_error = "something exists at dst - '%s' " \ "- and it's not a link\n kicking out" sys.exit(msg_error.format(path_dst)) if debug: msg_debug += "link_create:cmd_link : %s" % cmd_link else: run(cmd_link) return msg_debug def prompt_continue(message="Do you want to continue? Y/n", default="Y", exit=True): """ prompts user if he wants to continue :parameter message: ask if user wants to continue :type message: str :parameter default: the default entry that we expect for the user if he hits enter without giving a value :type default: str :parameter exit: whether we do a sys.exit if it fails. default True :type exit: bool """ from fabric.operations import prompt prompt_val = prompt(message) if prompt_val == "": prompt_val = default if env.debug: printvar( "prompt_val", prompt_val, not booleanize(prompt_val)) return True else: if not booleanize(prompt_val): if exit: sys.exit() else: return False def check_debug(env): """ helper method to determine if debug was set to True in the fabric environment :parameter env: the fabric environment :type env: dict :return: True if debug was set :rtype: bool """ if hasattr(env, 'debug'): return env.debug else: return False