fabric/modules/utils.py

567 lines
15 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: string
:parameter value: the value of the variable we want to print
:type value: string
:parameter exit: determines whether or not we exit the program at
this point. defaults to false
:type exit: boolean
"""
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: string
:parameter errcode: the code of the error
:type errcode: integer
:parameter exit: determines whether or not we exit the program at
this point. defaults to True
:type exit: boolean
"""
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: string
:parameter func: the name of the function this logger is going to be used
in
:type func: string
:parameter prefix: anything you want to add to the front of the logger,
ie \'\\n\'. defaults to an empty string.
:type prefix: string
:rtype: logging 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: string
:parameter prepend: defaults to two line spaces, can be anything
:type string: string
:parameter append: defaults to two lines spaces after the string is printed
:type append: string
:parameter sep: the character used to print out one line above the string
and one line after
:type sep: character
:parameter numsep: number of times the separator is printed out on a line
:type numsep: integer
"""
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: string
:parameter module: the name of the module we are in
:type module: string
: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: string
: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: string
:return: converts value to True or False. Note 1 is
considered True and 0 is False
:rtype: boolean
"""
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: string
"""
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: string
"""
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: string
:parameter destination: remote file path to place the file
(includes filename)
:type destination: string
: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: boolean
: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: boolean
:parameter backup: if the destination file already exists, create a backup
of it with the extension .bak
:type backup: boolean
:parameter template_dir: path to the template directory that is used if
jinja is true. directory where the file is located
:type template_dir: string
:parameter debug: if true, just print out the command we are using to
invoke the fabric upload_template method. defaults to False
:type debug: boolean
"""
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():
with prefix("source virtualenvwrapper.sh"):
yield
@_contextmanager
def virtualenv():
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: string
: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: string
: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: string
:return: a path to where the template jinja file is located
:rtype: string
"""
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: string
:parameter prefix: prefix that is added to the string, typically done
for clarity of viewing
:type prefix: string
:parameter suffix: suffix that is added to the string, typically done
for clarity of viewing
:type suffix: string
:returns: an example of how the command will actually look if run
on the command line
:rtype: string
"""
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: string
:parameter message: the message to print out if true
:type message: string
: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: boolean
"""
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: string
:return: if key is in -h or --help
:rtype: boolean
"""
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
path_src - source path
path_dst - destination path
returns: 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
Keyword Arguments:
message -- ask if user wants to continue
default -- what to do if the user hits enter without giving a value
"""
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):
if hasattr(env, 'debug'):
return env.debug
else:
return False