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 """ with prefix("source virtualenvwrapper.sh"): yield @_contextmanager def virtualenv(): """ helper method to set the virtual environment for the following commands uses python yield command """ 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