603 lines
16 KiB
Python
603 lines
16 KiB
Python
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):
|
|
"""
|
|
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
|
|
"""
|
|
|
|
print(prepend)
|
|
|
|
if sep:
|
|
print(sep * numsep)
|
|
|
|
print(string)
|
|
|
|
if sep:
|
|
print(sep * numsep)
|
|
|
|
print(append)
|
|
|
|
|
|
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 <debugstr>:<module>:<function>
|
|
|
|
: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> 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
|