parent
8f7a6c5647
commit
b2903c1490
13 changed files with 2089 additions and 28 deletions
@ -0,0 +1,6 @@ |
||||
class InvalidInput(Exception): |
||||
pass |
||||
|
||||
|
||||
class UnknownCommand(Exception): |
||||
pass |
@ -0,0 +1,389 @@ |
||||
# Imports |
||||
|
||||
from commonkit import parse_jinja_template, read_file |
||||
from jinja2 import TemplateNotFound, TemplateError |
||||
import logging |
||||
import os |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
# Exports |
||||
|
||||
__all__ = ( |
||||
"EXCLUDED_KWARGS", |
||||
"run", |
||||
"Command", |
||||
"ItemizedCommand", |
||||
"Sudo", |
||||
"Template", |
||||
) |
||||
|
||||
# Constants |
||||
|
||||
EXCLUDED_KWARGS = [ |
||||
"cd", |
||||
"comment", |
||||
"condition", |
||||
"prefix", |
||||
"register", |
||||
"stop", |
||||
"sudo", |
||||
"tags", |
||||
] |
||||
|
||||
# Functions |
||||
|
||||
|
||||
def run(statement, **kwargs): |
||||
"""Run any statement. |
||||
|
||||
- statement (str): The statement to be executed. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "run statement") |
||||
return Command(statement, **kwargs) |
||||
|
||||
# Classes |
||||
|
||||
|
||||
class Command(object): |
||||
|
||||
def __init__(self, statement, cd=None, comment=None, condition=None, prefix=None, register=None, stop=False, sudo=None, tags=None, **kwargs): |
||||
self.cd = cd |
||||
self.comment = comment |
||||
self.condition = condition |
||||
self.prefix = prefix |
||||
self.register = register |
||||
self.statement = statement |
||||
self.stop = stop |
||||
self.tags = tags or list() |
||||
|
||||
if isinstance(sudo, Sudo): |
||||
self.sudo = sudo |
||||
elif type(sudo) is str: |
||||
self.sudo = Sudo(enabled=True, user=sudo) |
||||
elif sudo is True: |
||||
self.sudo = Sudo(enabled=True) |
||||
else: |
||||
self.sudo = Sudo() |
||||
|
||||
self.options = kwargs |
||||
|
||||
def __getattr__(self, item): |
||||
return self.options.get(item) |
||||
|
||||
def __repr__(self): |
||||
if self.comment: |
||||
return "<%s %s>" % (self.__class__.__name__, self.comment) |
||||
|
||||
return "<%s>" % self.__class__.__name__ |
||||
|
||||
def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True): |
||||
"""Get the full statement. |
||||
|
||||
:param cd: Include the directory change, if given. |
||||
:type cd: bool |
||||
|
||||
:param suppress_comment: Don't include the comment. |
||||
:type suppress_comment: bool |
||||
|
||||
:rtype: str |
||||
|
||||
""" |
||||
a = list() |
||||
|
||||
if cd and self.cd is not None: |
||||
a.append("( cd %s &&" % self.cd) |
||||
|
||||
if self.prefix is not None: |
||||
a.append("%s &&" % self.prefix) |
||||
|
||||
if self.sudo: |
||||
statement = "%s %s" % (self.sudo, self._get_statement()) |
||||
else: |
||||
statement = self._get_statement() |
||||
|
||||
a.append("%s" % statement) |
||||
|
||||
if cd and self.cd is not None: |
||||
a.append(")") |
||||
|
||||
b = list() |
||||
if self.comment is not None and include_comment: |
||||
b.append("# %s" % self.comment) |
||||
|
||||
if self.condition is not None: |
||||
b.append("if [[ %s ]]; then %s; fi;" % (self.condition, " ".join(a))) |
||||
else: |
||||
b.append(" ".join(a)) |
||||
|
||||
if self.register is not None and include_register: |
||||
b.append("%s=$?;" % self.register) |
||||
|
||||
if self.stop and include_stop: |
||||
b.append("if [[ $%s -gt 0 ]]; exit 1; fi;" % self.register) |
||||
elif self.stop and include_stop: |
||||
b.append("if [[ $? -gt 0 ]]; exit 1; fi;") |
||||
else: |
||||
pass |
||||
|
||||
return "\n".join(b) |
||||
|
||||
@property |
||||
def is_itemized(self): |
||||
"""Always returns ``False``.""" |
||||
return False |
||||
|
||||
def _get_statement(self): |
||||
"""By default, get the statement passed upon command initialization. |
||||
|
||||
:rtype: str |
||||
|
||||
""" |
||||
return self.statement |
||||
|
||||
|
||||
class Sudo(object): |
||||
"""Helper class for defining sudo options.""" |
||||
|
||||
def __init__(self, enabled=False, user="root"): |
||||
"""Initialize the helper. |
||||
|
||||
:param enabled: Indicates sudo is enabled. |
||||
:type enabled: bool |
||||
|
||||
:param user: The user to be invoked. |
||||
:type user: str |
||||
|
||||
""" |
||||
self.enabled = enabled |
||||
self.user = user |
||||
|
||||
def __bool__(self): |
||||
return self.enabled |
||||
|
||||
def __str__(self): |
||||
if self.enabled: |
||||
return "sudo -u %s" % self.user |
||||
|
||||
return "" |
||||
|
||||
|
||||
class ItemizedCommand(object): |
||||
"""An itemized command represents multiple commands of with the same statement but different parameters.""" |
||||
|
||||
def __init__(self, callback, items, *args, name=None, **kwargs): |
||||
"""Initialize the command. |
||||
|
||||
:param callback: The function to be used to generate the command. |
||||
|
||||
:param items: The command arguments. |
||||
:type items: list[str] |
||||
|
||||
:param name: The name of the command from the mapping. Not used and not required for programmatic use, but |
||||
automatically assigned during factory instantiation. |
||||
:type name: str |
||||
|
||||
:param args: The itemized arguments. ``$item`` should be included. |
||||
|
||||
Keyword arguments are passed to the command class upon instantiation. |
||||
|
||||
""" |
||||
self.args = args |
||||
self.callback = callback |
||||
self.items = items |
||||
self.kwargs = kwargs |
||||
self.name = name |
||||
|
||||
# Set defaults for when ItemizedCommand is referenced directly before individual commands are instantiated. For |
||||
# example, when command filtering occurs. |
||||
self.kwargs.setdefault("tags", list()) |
||||
|
||||
def __getattr__(self, item): |
||||
return self.kwargs.get(item) |
||||
|
||||
def __repr__(self): |
||||
return "<%s %s>" % (self.__class__.__name__, self.callback.__name__) |
||||
|
||||
def get_commands(self): |
||||
"""Get the commands to be executed. |
||||
|
||||
:rtype: list[BaseType(Command)] |
||||
|
||||
""" |
||||
kwargs = self.kwargs.copy() |
||||
|
||||
a = list() |
||||
for item in self.items: |
||||
args = list() |
||||
for arg in self.args: |
||||
args.append(arg.replace("$item", item)) |
||||
|
||||
command = self.callback(*args, **kwargs) |
||||
a.append(command) |
||||
|
||||
return a |
||||
|
||||
def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True): |
||||
"""Override to get multiple commands.""" |
||||
kwargs = self.kwargs.copy() |
||||
comment = kwargs.pop("comment", "execute multiple commands") |
||||
|
||||
a = list() |
||||
# a.append("# %s" % comment) |
||||
|
||||
commands = self.get_commands() |
||||
for c in commands: |
||||
a.append(c.get_statement(cd=cd, include_comment=False, include_register=include_register, include_stop=include_stop)) |
||||
a.append("") |
||||
|
||||
return "\n".join(a) |
||||
|
||||
@property |
||||
def is_itemized(self): |
||||
"""Always returns ``True``.""" |
||||
return True |
||||
|
||||
|
||||
class Template(object): |
||||
|
||||
PARSER_JINJA = "jinja2" |
||||
PARSER_PYTHON = "python" |
||||
PARSER_SIMPLE = "simple" |
||||
|
||||
def __init__(self, source, target, backup=True, parser=PARSER_JINJA, **kwargs): |
||||
self.backup_enabled = backup |
||||
self.context = kwargs.pop("context", dict()) |
||||
self.name = "template" |
||||
self.parser = parser |
||||
self.language = kwargs.pop("lang", None) |
||||
self.locations = kwargs.pop("locations", list()) |
||||
self.source = os.path.expanduser(source) |
||||
self.target = target |
||||
|
||||
sudo = kwargs.pop("sudo", None) |
||||
if isinstance(sudo, Sudo): |
||||
self.sudo = sudo |
||||
elif type(sudo) is str: |
||||
self.sudo = Sudo(enabled=True, user=sudo) |
||||
elif sudo is True: |
||||
self.sudo = Sudo(enabled=True) |
||||
else: |
||||
self.sudo = Sudo() |
||||
|
||||
self.kwargs = kwargs |
||||
|
||||
def __getattr__(self, item): |
||||
return self.kwargs.get(item) |
||||
|
||||
def __str__(self): |
||||
return "template" |
||||
|
||||
def get_content(self): |
||||
"""Parse the template. |
||||
|
||||
:rtype: str | None |
||||
|
||||
""" |
||||
template = self.get_template() |
||||
|
||||
if self.parser == self.PARSER_SIMPLE: |
||||
content = read_file(template) |
||||
for key, value in self.context.items(): |
||||
replace = "$%s$" % key |
||||
content = content.replace(replace, str(value)) |
||||
|
||||
return content |
||||
|
||||
if self.parser == self.PARSER_PYTHON: |
||||
content = read_file(template) |
||||
return content % self.context |
||||
|
||||
try: |
||||
return parse_jinja_template(template, self.context) |
||||
except TemplateNotFound: |
||||
log.error("Template not found: %s" % template) |
||||
return None |
||||
except TemplateError as e: |
||||
log.error("Could not parse %s template: %s" % (template, e)) |
||||
return None |
||||
|
||||
# noinspection PyUnusedLocal |
||||
def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True): |
||||
lines = list() |
||||
if include_comment and self.comment is not None: |
||||
lines.append("# %s" % self.comment) |
||||
|
||||
# TODO: Backing up a template's target is currently specific to bash. |
||||
if self.backup_enabled: |
||||
command = "%s mv %s %s.b" % (self.sudo, self.target, self.target) |
||||
lines.append('if [[ -f "%s" ]]; then %s; fi;' % (self.target, command.lstrip())) |
||||
|
||||
# Get the content; e.g. parse the template. |
||||
content = self.get_content() |
||||
|
||||
# Templates that are bash scripts will fail to write because of the shebang. |
||||
if content.startswith("#!"): |
||||
_content = content.split("\n") |
||||
first_line = _content.pop(0) |
||||
command = '%s echo "%s" > %s' % (self.sudo, first_line, self.target) |
||||
lines.append(command.lstrip()) |
||||
command = "%s cat > %s << EOF" % (self.sudo, self.target) |
||||
lines.append(command.lstrip()) |
||||
lines.append("\n".join(_content)) |
||||
lines.append("EOF") |
||||
else: |
||||
command = "%s cat > %s << EOF" % (self.sudo, self.target) |
||||
lines.append(command.lstrip()) |
||||
lines.append(content) |
||||
lines.append("EOF") |
||||
|
||||
if include_register and self.register is not None: |
||||
lines.append("%s=$?;" % self.register) |
||||
|
||||
if include_stop and self.stop: |
||||
lines.append("if [[ $%s -gt 0 ]]; exit 1; fi;" % self.register) |
||||
elif include_stop and self.stop: |
||||
lines.append("if [[ $? -gt 0 ]]; exit 1; fi;") |
||||
else: |
||||
pass |
||||
|
||||
return "\n".join(lines) |
||||
|
||||
def get_target_language(self): |
||||
if self.language is not None: |
||||
return self.language |
||||
|
||||
if self.target.endswith(".conf"): |
||||
return "conf" |
||||
elif self.target.endswith(".ini"): |
||||
return "ini" |
||||
elif self.target.endswith(".php"): |
||||
return "php" |
||||
elif self.target.endswith(".py"): |
||||
return "python" |
||||
elif self.target.endswith(".sh"): |
||||
return "bash" |
||||
elif self.target.endswith(".yml"): |
||||
return "yaml" |
||||
else: |
||||
return "text" |
||||
|
||||
def get_template(self): |
||||
"""Get the template path. |
||||
|
||||
:rtype: str |
||||
|
||||
""" |
||||
source = self.source |
||||
for location in self.locations: |
||||
_source = os.path.join(location, self.source) |
||||
if os.path.exists(_source): |
||||
return _source |
||||
|
||||
return source |
||||
|
||||
@property |
||||
def is_itemized(self): |
||||
# return "$item" in self.target |
||||
return False |
@ -0,0 +1,279 @@ |
||||
# Imports |
||||
|
||||
from commonkit import split_csv |
||||
from .base import Command, Template |
||||
from .django import DJANGO_MAPPINGS |
||||
from .mysql import MYSQL_MAPPINGS |
||||
from .pgsql import PGSQL_MAPPINGS |
||||
from .posix import POSIX_MAPPINGS |
||||
|
||||
# Exports |
||||
|
||||
__all__ = ( |
||||
"MAPPINGS", |
||||
"apache", |
||||
"apache_reload", |
||||
"apache_restart", |
||||
"apache_start", |
||||
"apache_stop", |
||||
"apache_test", |
||||
"command_exists", |
||||
"service_reload", |
||||
"service_restart", |
||||
"service_start", |
||||
"service_stop", |
||||
"system", |
||||
"system_install", |
||||
"system_reboot", |
||||
"system_update", |
||||
"system_upgrade", |
||||
"system_uninstall", |
||||
"template", |
||||
"user", |
||||
) |
||||
|
||||
|
||||
def command_exists(name): |
||||
"""Indicates whether a given command exists in this overlay. |
||||
|
||||
:param name: The name of the command. |
||||
:type name: str |
||||
|
||||
:rtype: bool |
||||
|
||||
""" |
||||
return name in MAPPINGS |
||||
|
||||
|
||||
def apache(op, **kwargs): |
||||
"""Execute an Apache-related command. |
||||
|
||||
- op (str): The operation to perform; reload, restart, start, stop, test. |
||||
|
||||
""" |
||||
if op == "reload": |
||||
return apache_reload(**kwargs) |
||||
elif op == "restart": |
||||
return apache_restart(**kwargs) |
||||
elif op == "start": |
||||
return apache_start(**kwargs) |
||||
elif op == "stop": |
||||
return apache_stop(**kwargs) |
||||
elif op == "test": |
||||
return apache_test(**kwargs) |
||||
else: |
||||
raise NameError("Unrecognized or unsupported apache operation: %s" % op) |
||||
|
||||
|
||||
def apache_reload(**kwargs): |
||||
kwargs.setdefault("comment", "reload apache") |
||||
kwargs.setdefault("register", "apache_reloaded") |
||||
|
||||
return Command("apachectl –k reload", **kwargs) |
||||
|
||||
|
||||
def apache_restart(**kwargs): |
||||
kwargs.setdefault("comment", "restart apache") |
||||
kwargs.setdefault("register", "apache_restarted") |
||||
|
||||
return Command("apachectl –k restart", **kwargs) |
||||
|
||||
|
||||
def apache_start(**kwargs): |
||||
kwargs.setdefault("comment", "start apache") |
||||
kwargs.setdefault("register", "apache_started") |
||||
|
||||
return Command("apachectl –k start", **kwargs) |
||||
|
||||
|
||||
def apache_stop(**kwargs): |
||||
kwargs.setdefault("comment", "stop apache") |
||||
|
||||
return Command("apachectl –k stop", **kwargs) |
||||
|
||||
|
||||
def apache_test(**kwargs): |
||||
kwargs.setdefault("comment", "check apache configuration") |
||||
kwargs.setdefault("register", "apache_checks_out") |
||||
|
||||
return Command("apachectl configtest", **kwargs) |
||||
|
||||
|
||||
def service_reload(name, **kwargs): |
||||
"""Reload a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "reload %s service" % name) |
||||
kwargs.setdefault("register", "%s_reloaded" % name) |
||||
|
||||
return Command("systemctl reload %s" % name, **kwargs) |
||||
|
||||
|
||||
def service_restart(name, **kwargs): |
||||
"""Restart a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "restart %s service" % name) |
||||
kwargs.setdefault("register", "%s_restarted" % name) |
||||
|
||||
return Command("ssystemctl restart %s" % name, **kwargs) |
||||
|
||||
|
||||
def service_start(name, **kwargs): |
||||
"""Start a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "start %s service" % name) |
||||
kwargs.setdefault("register", "%s_started" % name) |
||||
|
||||
return Command("systemctl start %s" % name, **kwargs) |
||||
|
||||
|
||||
def service_stop(name, **kwargs): |
||||
"""Stop a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "stop %s service" % name) |
||||
kwargs.setdefault("register", "%s_stopped" % name) |
||||
|
||||
return Command("systemctl stop %s" % name, **kwargs) |
||||
|
||||
|
||||
def system(op, **kwargs): |
||||
"""Perform a system operation. |
||||
|
||||
- op (str): The operation to perform; reboot, update, upgrade. |
||||
|
||||
""" |
||||
if op == "reboot": |
||||
return system_reboot(**kwargs) |
||||
elif op == "update": |
||||
return system_update(**kwargs) |
||||
elif op == "upgrade": |
||||
return system_upgrade(**kwargs) |
||||
else: |
||||
raise NameError("Unrecognized or unsupported system operation: %s" % op) |
||||
|
||||
|
||||
def system_install(name, **kwargs): |
||||
"""Install a system-level package. |
||||
|
||||
- name (str): The name of the package to install. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "install system package %s" % name) |
||||
|
||||
return Command("yum install -y %s" % name, **kwargs) |
||||
|
||||
|
||||
def system_reboot(**kwargs): |
||||
kwargs.setdefault("comment", "reboot the system") |
||||
|
||||
return Command("reboot", **kwargs) |
||||
|
||||
|
||||
def system_uninstall(name, **kwargs): |
||||
"""Uninstall a system-level package. |
||||
|
||||
- name (str): The name of the package to uninstall. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "remove system package %s" % name) |
||||
|
||||
return Command("yum remove -y %s" % name, **kwargs) |
||||
|
||||
|
||||
def system_update(**kwargs): |
||||
kwargs.setdefault("comment", "update system package info") |
||||
|
||||
return Command("yum check-update", **kwargs) |
||||
|
||||
|
||||
def system_upgrade(**kwargs): |
||||
kwargs.setdefault("comment", "upgrade the system") |
||||
|
||||
return Command("yum update -y", **kwargs) |
||||
|
||||
|
||||
def template(source, target, backup=True, parser=None, **kwargs): |
||||
"""Create a file from a template. |
||||
|
||||
- source (str): The path to the template file. |
||||
- target (str): The path to where the new file should be created. |
||||
- backup (bool): Indicates whether a backup should be made if the target file already exists. |
||||
- parser (str): The parser to use ``jinja`` (the default) or ``simple``. |
||||
|
||||
""" |
||||
return Template(source, target, backup=backup, parser=parser, **kwargs) |
||||
|
||||
|
||||
def user(name, groups=None, home=None, op="add", password=None, **kwargs): |
||||
"""Create or remove a user. |
||||
|
||||
- name (str): The user name. |
||||
- groups (str | list): A list of groups to which the user should belong. |
||||
- home (str): The path to the user's home directory. |
||||
- op (str); The operation to perform; ``add`` or ``remove``. |
||||
- password (str): The user's password. (NOT IMPLEMENTED) |
||||
|
||||
""" |
||||
if op == "add": |
||||
kwargs.setdefault("comment", "create a user named %s" % name) |
||||
|
||||
commands = list() |
||||
|
||||
a = list() |
||||
a.append('adduser %s' % name) |
||||
if home is not None: |
||||
a.append("--home %s" % home) |
||||
|
||||
commands.append(Command(" ".join(a), **kwargs)) |
||||
|
||||
if type(groups) is str: |
||||
groups = split_csv(groups, smart=False) |
||||
|
||||
if type(groups) in [list, tuple]: |
||||
for group in groups: |
||||
commands.append(Command("gpasswd -a %s %s" % (name, group), **kwargs)) |
||||
|
||||
a = list() |
||||
for c in commands: |
||||
a.append(c.get_statement(include_comment=True)) |
||||
|
||||
return Command("\n".join(a), **kwargs) |
||||
elif op == "remove": |
||||
kwargs.setdefault("comment", "remove a user named %s" % name) |
||||
return Command("userdel -r %s" % name, **kwargs) |
||||
else: |
||||
raise NameError("Unsupported or unrecognized operation: %s" % op) |
||||
|
||||
|
||||
MAPPINGS = { |
||||
'apache': apache, |
||||
'install': system_install, |
||||
'reboot': system_reboot, |
||||
'reload': service_reload, |
||||
'restart': service_restart, |
||||
'start': service_start, |
||||
'stop': service_stop, |
||||
'system': system, |
||||
'template': template, |
||||
'update': system_update, |
||||
'uninstall': system_uninstall, |
||||
'upgrade': system_upgrade, |
||||
'user': user, |
||||
} |
||||
|
||||
MAPPINGS.update(COMMON_MAPPINGS) |
||||
MAPPINGS.update(DJANGO_MAPPINGS) |
||||
MAPPINGS.update(MYSQL_MAPPINGS) |
||||
MAPPINGS.update(PGSQL_MAPPINGS) |
||||
MAPPINGS.update(POSIX_MAPPINGS) |
@ -0,0 +1,92 @@ |
||||
""" |
||||
[run django checks] |
||||
django: check |
||||
|
||||
[export fixtures] |
||||
django: dump lookups.Category |
||||
|
||||
[import fixtures] |
||||
django: load lookups.Category |
||||
|
||||
[migrate the database] |
||||
django: migrate |
||||
|
||||
[collect static files] |
||||
django: static |
||||
|
||||
[create super user (ad hoc command)] |
||||
django: createsuperuser root |
||||
|
||||
""" |
||||
from .base import EXCLUDED_KWARGS, Command |
||||
|
||||
|
||||
def django(management_command, *args, excluded_kwargs=None, **kwargs): |
||||
# The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default. |
||||
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS |
||||
|
||||
# Django's management commands can have a number of options. We need to filter out internal parameters so that these |
||||
# are not used as options for the management command. |
||||
_kwargs = dict() |
||||
for key in excluded_kwargs: |
||||
if key in kwargs: |
||||
_kwargs[key] = kwargs.pop(key) |
||||
|
||||
if 'comment' not in _kwargs: |
||||
_kwargs['comment'] = "run %s django management command" % management_command |
||||
|
||||
a = list() |
||||
a.append("./manage.py %s" % management_command) |
||||
for key, value in kwargs.items(): |
||||
key = key.replace("_", "-") |
||||
if type(value) is bool and value is True: |
||||
a.append("--%s" % key) |
||||
elif type(value) is str: |
||||
a.append('--%s="%s"' % (key, value)) |
||||
else: |
||||
a.append('--%s=%s' % (key, value)) |
||||
|
||||
_args = list(args) |
||||
if len(_args) > 0: |
||||
a.append(" ".join(_args)) |
||||
|
||||
statement = " ".join(a) |
||||
|
||||
return Command(statement, **_kwargs) |
||||
|
||||
|
||||
def django_check(**kwargs): |
||||
kwargs.setdefault("comment", "run django checks") |
||||
kwargs.setdefault("register", "django_checks_out") |
||||
return django("check", **kwargs) |
||||
|
||||
|
||||
def django_dump(target, path=None, **kwargs): |
||||
kwargs.setdefault("comment", "dump app/model data") |
||||
kwargs.setdefault("format", "json") |
||||
kwargs.setdefault("indent", 4) |
||||
|
||||
if path is None: |
||||
path = "../deploy/fixtures/%s.%s" % (target, kwargs['format']) |
||||
|
||||
return django("dumpdata", target, "> %s" % path, **kwargs) |
||||
|
||||
|
||||
def django_load(target, path=None, **kwargs): |
||||
kwargs.setdefault("comment", "load app/model data") |
||||
input_format = kwargs.pop("format", "json") |
||||
if path is None: |
||||
path = "../deploy/fixtures/%s.%s" % (target, input_format) |
||||
|
||||
return django("loaddata", path, **kwargs) |
||||
|
||||
|
||||
def django_migrate(**kwargs): |
||||
kwargs.setdefault("comment", "apply database migrations") |
||||
return django("migrate", **kwargs) |
||||
|
||||
|
||||
def django_static(**kwargs): |
||||
kwargs.setdefault("comment", "collect static files") |
||||
kwargs.setdefault("noinput", True) |
||||
return django("collectstatic", **kwargs) |
@ -0,0 +1,51 @@ |
||||
from .base import Command |
||||
from ...exceptions import InvalidInput |
||||
|
||||
|
||||
def dialog(message, height=15, title="Message", width=100, **kwargs): |
||||
statement = list() |
||||
statement.append("dialog --clear") |
||||
statement.append('--backtitle "%s"' % title) |
||||
statement.append('--msgbox "%s" %s %s;' % (message, height, width)) |
||||
statement.append("clear;") |
||||
|
||||
return Command(" ".join(statement), **kwargs) |
||||
|
||||
|
||||
def echo(message, **kwargs): |
||||
return Command('echo "%s"' % message, **kwargs) |
||||
|
||||
|
||||
def explain(message, heading=None, **kwargs): |
||||
kwargs['heading'] = heading |
||||
return Command(message, **kwargs) |
||||
|
||||
|
||||
def screenshot(image, caption=None, **kwargs): |
||||
kwargs['caption'] = caption |
||||
|
||||
return Command(image, **kwargs) |
||||
|
||||
|
||||
def slack(message, url=None, **kwargs): |
||||
if url is None: |
||||
raise InvalidInput("Slack command requires a url parameter.") |
||||
|
||||
statement = list() |
||||
statement.append("curl -X POST -H 'Content-type: application/json' --data") |
||||
statement.append('{"text": "%s"}' % message) |
||||
statement.append(url) |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def twist(message, title="Notice", url=None, **kwargs): |
||||
if url is None: |
||||
raise InvalidInput("Twist command requires a url parameter.") |
||||
|
||||
statement = list() |
||||
statement.append("curl -X POST -H 'Content-type: application/json' --data") |
||||
statement.append('{"content": "%s", "title": "%s"' % (message, title)) |
||||
statement.append(url) |
||||
|
||||
return Command(" ".join(statement), **kwargs) |
@ -0,0 +1,173 @@ |
||||
from ...exceptions import InvalidInput |
||||
from .base import EXCLUDED_KWARGS, Command |
||||
|
||||
|
||||
__all__ = ( |
||||
"mysql_create", |
||||
"mysql_dump", |
||||
"mysql_exists", |
||||
"mysql_load", |
||||
"mysql_user", |
||||
) |
||||
|
||||
|
||||
def mysql(command, *args, host="localhost", excluded_kwargs=None, password=None, port=3306, user="root", **kwargs): |
||||
# The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default. |
||||
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS |
||||
|
||||
# if 'comment' not in kwargs: |
||||
# kwargs['comment'] = "run %s mysql command" % command |
||||
|
||||
# Allow additional command line switches to pass through? |
||||
# Django's management commands can have a number of options. We need to filter out internal parameters so that these |
||||
# are not used as options for the management command. |
||||
_kwargs = dict() |
||||
for key in excluded_kwargs: |
||||
if key in kwargs: |
||||
_kwargs[key] = kwargs.pop(key) |
||||
|
||||
# MySQL commands always run without sudo because the --user may be provided. |
||||
_kwargs['sudo'] = False |
||||
|
||||
a = list() |
||||
|
||||
a.append(command) |
||||
a.append("--user %s --host=%s --port=%s" % (user, host, port)) |
||||
|
||||
if password: |
||||
a.append('--password="%s"' % password) |
||||
|
||||
for key, value in kwargs.items(): |
||||
key = key.replace("_", "-") |
||||
if type(value) is bool and value is True: |
||||
a.append("--%s" % key) |
||||
elif type(value) is str: |
||||
a.append('--%s="%s"' % (key, value)) |
||||
else: |
||||
a.append('--%s=%s' % (key, value)) |
||||
|
||||
_args = list(args) |
||||
if len(_args) > 0: |
||||
a.append(" ".join(_args)) |
||||
|
||||
statement = " ".join(a) |
||||
|
||||
return Command(statement, **_kwargs) |
||||
|
||||
|
||||
def mysql_create(database, owner=None, **kwargs): |
||||
kwargs.setdefault("comment", "create mysql database") |
||||
|
||||
command = mysql("mysqladmin create", database, **kwargs) |
||||
|
||||
if owner is not None: |
||||
grant = mysql_grant(owner, database=database, **kwargs) |
||||
command.statement += " && " + grant.statement |
||||
|
||||
return command |
||||
|
||||
|
||||
def mysql_drop(database, **kwargs): |
||||
kwargs.setdefault("comment", "drop %s mysql database" % database) |
||||
|
||||
return mysql("mysqladmin drop", database, **kwargs) |
||||
|
||||
|
||||
def mysql_dump(database, path=None, **kwargs): |
||||
kwargs.setdefault("comment", "dump mysql database") |
||||
kwargs.setdefault("complete_inserts", True) |
||||
|
||||
if path is None: |
||||
path = "%s.sql" % database |
||||
|
||||
return mysql("mysqldump", database, "> %s" % path, **kwargs) |
||||
|
||||
|
||||
def mysql_exists(database, **kwargs): |
||||
kwargs.setdefault("comment", "determine if %s mysql database exists" % database) |
||||
kwargs.setdefault("register", "%s_exists" % database) |
||||
|
||||
command = mysql("mysql", **kwargs) |
||||
|
||||
sql = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '%s'" % database |
||||
|
||||
command.statement += '--execute="%s"' % sql |
||||
|
||||
return command |
||||
|
||||
|
||||
def mysql_grant(to, database=None, privileges="ALL", **kwargs): |
||||
"""Grant privileges to a user. |
||||
|
||||
- to (str): The user name to which privileges are granted. |
||||
- database (str): The database name. |
||||
- host (str): The database host name or IP address. |
||||
- password (str): The password for the user with sufficient access privileges to execute the command. |
||||
- port (int): The TCP port number of the MySQL service running on the host. |
||||
- privileges (str): The privileges to be granted. |
||||
- user (str): The name of the user with sufficient access privileges to execute the command. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "grant mysql privileges to %s" % to) |
||||
|
||||
host = kwargs.get("host", "localhost") |
||||
|
||||
command = mysql("mysql", **kwargs) |
||||
|
||||
# See https://dev.mysql.com/doc/refman/5.7/en/grant.html |
||||
_database = database or "*" |
||||
sql = "GRANT %(privileges)s ON %(database)s.* TO '%(user)s'@'%(host)s'" % { |
||||
'database': _database, |
||||
'host': host, |
||||
'privileges': privileges, |
||||
'user': to, |
||||
} |
||||
command.statement += ' --execute="%s"' % sql |
||||
|
||||
return command |
||||
|
||||
|
||||
def mysql_load(database, path, **kwargs): |
||||
kwargs.setdefault("comment", "load data into a mysql database") |
||||
|
||||
return mysql("psql", database, "< %s" % path, **kwargs) |
||||
|
||||
|
||||
def mysql_user(name, admin_pass=None, admin_user="root", op="create", password=None, **kwargs): |
||||
host = kwargs.get("host", "localhost") |
||||
|
||||
if op == "create": |
||||
kwargs.setdefault("comment", "create %s mysql user" % name) |
||||
|
||||
command = mysql("mysql", password=admin_pass, user=admin_user, **kwargs) |
||||
|
||||
sql = "CREATE USER IF NOT EXISTS '%s'@'%s'" % (name, host) |
||||
if password is not None: |
||||
sql += " IDENTIFIED BY PASSWORD('%s')" % password |
||||
|
||||
command.statement += ' --execute="%s"' % sql |
||||
|
||||
return command |
||||
elif op == "drop": |
||||
kwargs.setdefault("comment", "remove %s mysql user" % name) |
||||
|
||||
command = mysql("mysql", password=admin_pass, user=admin_user, **kwargs) |
||||
|
||||
sql = "DROP USER IF EXISTS '%s'@'%s'" % (name, host) |
||||
|
||||
command.statement += ' --execute="%s"' % sql |
||||
|
||||
return command |
||||
elif op == "exists": |
||||
kwargs.setdefault("comment", "determine if %s mysql user exists" % name) |
||||
kwargs.setdefault("register", "mysql_use_exists") |
||||
|
||||
command = mysql("mysql", password=admin_pass, user=admin_user, **kwargs) |
||||
|
||||
sql = "SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '%s')" % name |
||||
|
||||
command.statement += ' --execute "%s"' % sql |
||||
|
||||
return command |
||||
else: |
||||
raise InvalidInput("Unrecognized or unsupported MySQL user operation: %s" % op) |
@ -0,0 +1,155 @@ |
||||
""" |
||||
[run django checks] |
||||
django: check |
||||
|
||||
[export fixtures] |
||||
django: dump lookups.Category |
||||
|
||||
[import fixtures] |
||||
django: load lookups.Category |
||||
|
||||
[migrate the database] |
||||
django: migrate |
||||
|
||||
[collect static files] |
||||
django: static |
||||
|
||||
[create super user (ad hoc command)] |
||||
django: createsuperuser root |
||||
|
||||
""" |
||||
from ...exceptions import InvalidInput |
||||
from .base import EXCLUDED_KWARGS, Command |
||||
|
||||
|
||||
__all__ = ( |
||||
"pgsql_create", |
||||
"pgsql_drop", |
||||
"pgsql_dump", |
||||
"pgsql_exists", |
||||
"pgsql_load", |
||||
"pgsql_user", |
||||
) |
||||
|
||||
def pgsql(command, *args, host="localhost", excluded_kwargs=None, password=None, port=5432, user="postgres", **kwargs): |
||||
# The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default. |
||||
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS |
||||
|
||||
# if 'comment' not in kwargs: |
||||
# kwargs['comment'] = "run %s postgres command" % command |
||||
|
||||
# Allow additional command line switches to pass through? |
||||
# Django's management commands can have a number of options. We need to filter out internal parameters so that these |
||||
# are not used as options for the management command. |
||||
_kwargs = dict() |
||||
for key in excluded_kwargs: |
||||
if key in kwargs: |
||||
_kwargs[key] = kwargs.pop(key) |
||||
|
||||
# Postgres commands always run without sudo because the -U may be provided. |
||||
_kwargs['sudo'] = False |
||||
|
||||
a = list() |
||||
|
||||
if password is not None: |
||||
a.append('export PGPASSWORD="%s" &&' % password) |
||||
|
||||
a.append(command) |
||||
a.append("-U %s --host=%s --port=%s" % (user, host, port)) |
||||
for key, value in kwargs.items(): |
||||
key = key.replace("_", "-") |
||||
if type(value) is bool and value is True: |
||||
a.append("--%s" % key) |
||||
elif type(value) is str: |
||||
a.append('--%s="%s"' % (key, value)) |
||||
else: |
||||
a.append('--%s=%s' % (key, value)) |
||||
|
||||
_args = list(args) |
||||
if len(_args) > 0: |
||||
a.append(" ".join(_args)) |
||||
|
||||
statement = " ".join(a) |
||||
|
||||
return Command(statement, **_kwargs) |
||||
|
||||
|
||||
def pgsql_create(database, owner=None, template=None, **kwargs): |
||||
kwargs.setdefault("comment", "create %s postgres database" % database) |
||||
|
||||
if owner is not None: |
||||
kwargs['owner'] = owner |
||||
|
||||
if template is not None: |
||||
kwargs['template'] = template |
||||
|
||||
return pgsql("createdb", database, **kwargs) |
||||
|
||||
|
||||
def pgsql_drop(database, **kwargs): |
||||
kwargs.setdefault("comment", "drop %s postgres database" % database) |
||||
|
||||
return pgsql("dropdb", database, **kwargs) |
||||
|
||||
|
||||
def pgsql_dump(database, path=None, **kwargs): |
||||
kwargs.setdefault("comment", "dump postgres database") |
||||
kwargs.setdefault("column_inserts", True) |
||||
|
||||
if path is None: |
||||
path = "%s.sql" % database |
||||
|
||||
kwargs['dbname'] = database |
||||
kwargs['file'] = path |
||||
|
||||
return pgsql("pg_dump", **kwargs) |
||||
|
||||
|
||||
def pgsql_exists(database, **kwargs): |
||||
kwargs.setdefault("comment", "determine if %s postgres database exists" % database) |
||||
kwargs.setdefault("register", "%s_exists" % database) |
||||
|
||||
command = pgsql("psql", **kwargs) |
||||
command.statement += r" -lqt | cut -d \| -f 1 | grep -qw %s" % database |
||||
|
||||
return command |
||||
|
||||
|
||||
def pgsql_load(database, path, **kwargs): |
||||
kwargs.setdefault("comment", "load data into a postgres database") |
||||
|
||||
kwargs['dbname'] = database |
||||
kwargs['file'] = path |
||||
|
||||
return pgsql("psql", **kwargs) |
||||
|
||||
|
||||
def pgsql_user(name, admin_pass=None, admin_user="postgres", op="create", password=None, **kwargs): |
||||
if op == "create": |
||||
kwargs.setdefault("comment", "create %s postgres user" % name) |
||||
|
||||
command = pgsql("createuser", "-DRS %s" % name, password=admin_pass, user=admin_user, **kwargs) |
||||
|
||||
if password is not None: |
||||
extra = pgsql("psql", password=admin_pass, user=admin_user, **kwargs) |
||||
command.statement += " && " + extra.statement |
||||
command.statement += " -c \"ALTER USER %s WITH ENCRYPTED PASSWORD '%s';\"" % (name, password) |
||||
|
||||
return command |
||||
elif op == "drop": |
||||
kwargs.setdefault("comment", "remove %s postgres user" % name) |
||||
|
||||
return pgsql("dropuser", name, password=admin_pass, user=admin_user, **kwargs) |
||||
elif op == "exists": |
||||
kwargs.setdefault("comment", "determine if %s postgres user exists" % name) |
||||
kwargs.setdefault("register", "pgsql_use_exists") |
||||
|
||||
command = pgsql("psql", password=admin_pass, user=admin_user, **kwargs) |
||||
|
||||
sql = "SELECT 1 FROM pgsql_roles WHERE rolname='%s'" % name |
||||
|
||||
command.statement += ' -c "%s"' % sql |
||||
|
||||
return command |
||||
else: |
||||
raise InvalidInput("Unrecognized or unsupported Postgres user operation: %s" % op) |
@ -0,0 +1,532 @@ |
||||
import os |
||||
from .base import Command |
||||
|
||||
|
||||
def append(path, content=None, **kwargs): |
||||
"""Append content to a file. |
||||
|
||||
- path (str): The path to the file. |
||||
- content (str): The content to be appended. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "append to %s" % path) |
||||
|
||||
statement = 'echo "%s" >> %s' % (content or "", path) |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def archive(from_path, absolute=False, exclude=None, file_name="archive.tgz", strip=None, to_path=".", view=False, |
||||
**kwargs): |
||||
"""Create a file archive. |
||||
|
||||
- from_path (str): The path that should be archived. |
||||
- absolute (bool): Set to ``True`` to preserve the leading slash. |
||||
- exclude (str): A pattern to be excluded from the archive. |
||||
- strip (int): Remove the specified number of leading elements from the path. |
||||
- to_path (str): Where the archive should be created. This should *not* include the file name. |
||||
- view (bool): View the output of the command as it happens. |
||||
|
||||
""" |
||||
tokens = ["tar"] |
||||
switches = ["-cz"] |
||||
|
||||
if absolute: |
||||
switches.append("P") |
||||
|
||||
if view: |
||||
switches.append("v") |
||||
|
||||
tokens.append("".join(switches)) |
||||
|
||||
if exclude: |
||||
tokens.append("--exclude %s" % exclude) |
||||
|
||||
if strip: |
||||
tokens.append("--strip-components %s" % strip) |
||||
|
||||
to_path = "%s/%s" % (to_path, file_name) |
||||
tokens.append('-f %s %s' % (to_path, from_path)) |
||||
|
||||
name = " ".join(tokens) |
||||
|
||||
return Command(name, **kwargs) |
||||
|
||||
|
||||
def certbot(domain_name, email=None, webroot=None, **kwargs): |
||||
"""Get new SSL certificate from Let's Encrypt. |
||||
|
||||
- domain_name (str): The domain name for which the SSL certificate is requested. |
||||
- email (str): The email address of the requester sent to the certificate authority. Required. |
||||
- webroot (str): The directory where the challenge file will be created. |
||||
|
||||
""" |
||||
_email = email or os.environ.get("SCRIPTTEASE_CERTBOT_EMAIL", None) |
||||
_webroot = webroot or os.path.join("/var", "www", "domains", domain_name.replace(".", "_"), "www") |
||||
|
||||
if not _email: |
||||
raise ValueError("Email is required for certbot command.") |
||||
|
||||
template = "certbot certonly --agree-tos --email %(email)s -n --webroot -w %(webroot)s -d %(domain_name)s" |
||||
statement = template % { |
||||
'domain_name': domain_name, |
||||
'email': _email, |
||||
'webroot': _webroot, |
||||
} |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs): |
||||
"""Copy a file or directory. |
||||
|
||||
- from_path (str): The file or directory to be copied. |
||||
- to_path (str): The location to which the file or directory should be copied. |
||||
- overwrite (bool): Indicates files and directories should be overwritten if they exist. |
||||
- recursive (bool): Copy sub-directories. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "copy %s to %s" % (from_path, to_path)) |
||||
|
||||
a = list() |
||||
a.append("cp") |
||||
|
||||
if not overwrite: |
||||
a.append("-n") |
||||
|
||||
if recursive: |
||||
a.append("-R") |
||||
|
||||
a.append(from_path) |
||||
a.append(to_path) |
||||
|
||||
return Command(" ".join(a), **kwargs) |
||||
|
||||
|
||||
def dir(path, group=None, mode=None, owner=None, recursive=True, **kwargs): |
||||
"""Create a directory. |
||||
|
||||
- path (str): The path to be created. |
||||
- mode (int | str): The access permissions of the new directory. |
||||
- recursive (bool): Create all directories along the path. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "create directory %s" % path) |
||||
|
||||
statement = ["mkdir"] |
||||
if mode is not None: |
||||
statement.append("-m %s" % mode) |
||||
|
||||
if recursive: |
||||
statement.append("-p") |
||||
|
||||
if group: |
||||
if recursive: |
||||
statement.append("&& chgrp -R %s" % group) |
||||
else: |
||||
statement.append("&& chgrp %s" % group) |
||||
|
||||
if owner: |
||||
if recursive: |
||||
statement.append("&& chown -R %s" % owner) |
||||
else: |
||||
statement.append("&& chown %s" % owner) |
||||
|
||||
statement.append(path) |
||||
|
||||
return Command(" ".join(statement), **kwargs) |
||||
|
||||
|
||||
def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, view=False, **kwargs): |
||||
"""Extract a file archive. |
||||
|
||||
- from_path (str): The path that should be archived. |
||||
- absolute (bool): Set to ``True`` to preserve the leading slash. |
||||
- exclude (str): A pattern to be excluded from the archive. |
||||
- strip (int): Remove the specified number of leading elements from the path. |
||||
- to_path (str): Where the archive should be extracted. This should *not* include the file name. |
||||
- view (bool): View the output of the command as it happens. |
||||
|
||||
""" |
||||
_to_path = to_path or "./" |
||||
|
||||
tokens = ["tar"] |
||||
switches = ["-xz"] |
||||
|
||||
if absolute: |
||||
switches.append("P") |
||||
|
||||
if view: |
||||
switches.append("v") |
||||
|
||||
tokens.append("".join(switches)) |
||||
|
||||
if exclude: |
||||
tokens.append("--exclude %s" % exclude) |
||||
|
||||
if strip: |
||||
tokens.append("--strip-components %s" % strip) |
||||
|
||||
tokens.append('-f %s %s' % (from_path, _to_path)) |
||||
|
||||
statement = " ".join(tokens) |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def link(source, force=False, target=None, **kwargs): |
||||
"""Create a symlink. |
||||
|
||||
- source (str): The source of the link. |
||||
- force (bool): Force the creation of the link. |
||||
- target (str): The name or path of the target. Defaults to the base name of the source path. |
||||
|
||||
""" |
||||
_target = target or os.path.basename(source) |
||||
|
||||
kwargs.setdefault("comment", "link to %s" % source) |
||||
|
||||
statement = ["ln -s"] |
||||
|
||||
if force: |
||||
statement.append("-f") |
||||
|
||||
statement.append(source) |
||||
statement.append(_target) |
||||
|
||||
return Command(" ".join(statement), **kwargs) |
||||
|
||||
|
||||
def move(from_path, to_path, **kwargs): |
||||
"""Move a file or directory. |
||||
|
||||
- from_path (str): The current path. |
||||
- to_path (str): The new path. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "move %s to %s" % (from_path, to_path)) |
||||
statement = "mv %s %s" % (from_path, to_path) |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs): |
||||
"""Set permissions on a file or directory. |
||||
|
||||
- path (str): The path to be changed. |
||||
- group (str): The name of the group to be applied. |
||||
- mode (int | str): The access permissions of the file or directory. |
||||
- owner (str): The name of the user to be applied. |
||||
- recursive: Create all directories along the path. |
||||
|
||||
""" |
||||
commands = list() |
||||
|
||||
kwargs['comment'] = "set permissions on %s" % path |
||||
|
||||
if group is not None: |
||||
statement = ["chgrp"] |
||||
|
||||
if recursive: |
||||
statement.append("-R") |
||||
|
||||
statement.append(group) |
||||
statement.append(path) |
||||
|
||||
commands.append(Command(" ".join(statement), **kwargs)) |
||||
|
||||
if owner is not None: |
||||
statement = ["chown"] |
||||
|
||||
if recursive: |
||||
statement.append("-R") |
||||
|
||||
statement.append(owner) |
||||
statement.append(path) |
||||
|
||||
commands.append(Command(" ".join(statement), **kwargs)) |
||||
|
||||
if mode is not None: |
||||
statement = ["chmod"] |
||||
|
||||
if recursive: |
||||
statement.append("-R") |
||||
|
||||
statement.append(str(mode)) |
||||
statement.append(path) |
||||
|
||||
commands.append(Command(" ".join(statement), **kwargs)) |
||||
|
||||
kwargs.setdefault("comment", "set permissions on %s" % path) |
||||
|
||||
a = list() |
||||
for c in commands: |
||||
a.append(c.get_statement(include_comment=True)) |
||||
|
||||
return Command("\n".join(a), **kwargs) |
||||
|
||||
|
||||
def remove(path, force=False, recursive=False, **kwargs): |
||||
"""Remove a file or directory. |
||||
|
||||
- path (str): The path to be removed. |
||||
- force (bool): Force the removal. |
||||
- recursive (bool): Remove all directories along the path. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "remove %s" % path) |
||||
|
||||
statement = ["rm"] |
||||
|
||||
if force: |
||||
statement.append("-f") |
||||
|
||||
if recursive: |
||||
statement.append("-r") |
||||
|
||||
statement.append(path) |
||||
|
||||
return Command(" ".join(statement), **kwargs) |
||||
|
||||
|
||||
def replace(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs): |
||||
"""Find and replace text in a file. |
||||
|
||||
- path (str): The path to the file to be edited. |
||||
- backup (str): The backup file extension to use. |
||||
- delimiter (str): The pattern delimiter. |
||||
- find (str): The old text. Required. |
||||
- replace (str): The new text. Required. |
||||
|
||||
""" |
||||
|
||||
kwargs.setdefault("comment", "find and replace in %s" % path) |
||||
|
||||
context = { |
||||
'backup': backup, |
||||
'delimiter': delimiter, |
||||
'path': path, |
||||
'pattern': find, |
||||
'replace': replace, |
||||
} |
||||
|
||||
template = "sed -i %(backup)s 's%(delimiter)s%(pattern)s%(delimiter)s%(replace)s%(delimiter)sg' %(path)s" |
||||
|
||||
statement = template % context |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def rsync(source, target, delete=False, exclude=None, host=None, key_file=None, links=True, port=22, |
||||
recursive=True, user=None, **kwargs): |
||||
"""Synchronize a directory structure. |
||||
|
||||
- source (str): The source directory. |
||||
- target (str): The target directory. |
||||
- delete (bool): Indicates target files that exist in source but not in target should be removed. |
||||
- exclude (str): The path to an exclude file. |
||||
- host (str): The host name or IP address. This causes the command to run over SSH. |
||||
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied. |
||||
- links (bool): Include symlinks in the sync. |
||||
- port (int): The SSH port to use for remote connections. |
||||
- recursive (bool): Indicates source contents should be recursively synchronized. |
||||
- user (str): The user name to use for remote connections. |
||||
|
||||
""" |
||||
# - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of |
||||
# the source path. |
||||
# :type guess: bool |
||||
# if guess: |
||||
# host = host or os.path.basename(source).replace("_", ".") |
||||
# key_file = key_file or os.path.expanduser(os.path.join("~/.ssh", os.path.basename(source))) |
||||
# user = user or os.path.basename(source) |
||||
# else: |
||||
# host = host |
||||
# key_file = key_file |
||||
# user = user |
||||
|
||||
kwargs.setdefault("comment", "sync %s with %s" % (source, target)) |
||||
|
||||
# rsync -e "ssh -i $(SSH_KEY) -p $(SSH_PORT)" -P -rvzc --delete |
||||
# $(OUTPUTH_PATH) $(SSH_USER)@$(SSH_HOST):$(UPLOAD_PATH) --cvs-exclude; |
||||
|
||||
tokens = list() |
||||
tokens.append("rsync") |
||||
tokens.append("--cvs-exclude") |
||||
tokens.append("--checksum") |
||||
tokens.append("--compress") |
||||
|
||||
if links: |
||||
tokens.append("--copy-links") |
||||
|
||||
if delete: |
||||
tokens.append("--delete") |
||||
|
||||
if exclude is not None: |
||||
tokens.append("--exclude-from=%s" % exclude) |
||||
|
||||
# --partial and --progress |
||||
tokens.append("-P") |
||||
|
||||
if recursive: |
||||
tokens.append("--recursive") |
||||
|
||||
tokens.append(source) |
||||
|
||||
conditions = [ |
||||
host is not None, |
||||
key_file is not None, |
||||
user is not None, |
||||
] |
||||
if all(conditions): |
||||
tokens.append('-e "ssh -i %s -p %s"' % (key_file, port)) |
||||
tokens.append("%s@%s:%s" % (user, host, target)) |
||||
else: |
||||
tokens.append(target) |
||||
|
||||
statement = " ".join(tokens) |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def scopy(from_path, to_path, host=None, key_file=None, port=22, user=None, **kwargs): |
||||
"""Copy a file or directory to a remote server. |
||||
|
||||
- from_path (str): The source directory. |
||||
- to_path (str): The target directory. |
||||
- host (str): The host name or IP address. Required. |
||||
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied. |
||||
- port (int): The SSH port to use for remote connections. |
||||
- user (str): The user name to use for remote connections. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "copy %s to remote %s" % (from_path, to_path)) |
||||
|
||||
# TODO: What to do to force local versus remote commands? |
||||
# kwargs['local'] = True |
||||
|
||||
kwargs['sudo'] = False |
||||
|
||||
statement = ["scp"] |
||||
|
||||
if key_file is not None: |
||||
statement.append("-i %s" % key_file) |
||||
|
||||
statement.append("-P %s" % port) |
||||
statement.append(from_path) |
||||
|
||||
if host is not None and user is not None: |
||||
statement.append("%s@%s:%s" % (user, host, to_path)) |
||||
elif host is not None: |
||||
statement.append("%s:%s" % (host, to_path)) |
||||
else: |
||||
raise ValueError("Host is a required keyword argument.") |
||||
|
||||
return Command(" ".join(statement), **kwargs) |
||||
|
||||
|
||||
def sync(source, target, delete=False, exclude=None, links=True, recursive=True, **kwargs): |
||||
"""Synchronize a local directory structure. |
||||
|
||||
- source (str): The source directory. |
||||
- target (str): The target directory. |
||||
- delete (bool): Indicates target files that exist in source but not in target should be removed. |
||||
- exclude (str): The path to an exclude file. |
||||
- host (str): The host name or IP address. This causes the command to run over SSH. |
||||
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied. |
||||
- links (bool): Include symlinks in the sync. |
||||
- port (int): The SSH port to use for remote connections. |
||||
- recursive (bool): Indicates source contents should be recursively synchronized. |
||||
- user (str): The user name to use for remote connections. |
||||
|
||||
""" |
||||
# - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of |
||||
# the source path. |
||||
# :type guess: bool |
||||
# if guess: |
||||
# host = host or os.path.basename(source).replace("_", ".") |
||||
# key_file = key_file or os.path.expanduser(os.path.join("~/.ssh", os.path.basename(source))) |
||||
# user = user or os.path.basename(source) |
||||
# else: |
||||
# host = host |
||||
# key_file = key_file |
||||
# user = user |
||||
|
||||
kwargs.setdefault("comment", "sync %s with %s" % (source, target)) |
||||
|
||||
# rsync -e "ssh -i $(SSH_KEY) -p $(SSH_PORT)" -P -rvzc --delete |
||||
# $(OUTPUTH_PATH) $(SSH_USER)@$(SSH_HOST):$(UPLOAD_PATH) --cvs-exclude; |
||||
|
||||
tokens = list() |
||||
tokens.append("rsync") |
||||
tokens.append("--cvs-exclude") |
||||
tokens.append("--checksum") |
||||
tokens.append("--compress") |
||||
|
||||
if links: |
||||
tokens.append("--copy-links") |
||||
|
||||
if delete: |
||||
tokens.append("--delete") |
||||
|
||||
if exclude is not None: |
||||
tokens.append("--exclude-from=%s" % exclude) |
||||
|
||||
# --partial and --progress |
||||
tokens.append("-P") |
||||
|
||||
if recursive: |
||||
tokens.append("--recursive") |
||||
|
||||
tokens.append(source) |
||||
tokens.append(target) |
||||
|
||||
statement = " ".join(tokens) |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def touch(path, **kwargs): |
||||
"""Touch a file or directory. |
||||
|
||||
- path (str): The file or directory to touch. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "touch %s" % path) |
||||
|
||||
return Command("touch %s" % path, **kwargs) |
||||
|
||||
|
||||
def wait(seconds, **kwargs): |
||||
"""Pause execution for a number of seconds. |
||||
|
||||
- seconds (int): The number of seconds to wait. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "pause for %s seconds" % seconds) |
||||
|
||||
return Command("sleep %s" % seconds, **kwargs) |
||||
|
||||
|
||||
def write(path, content=None, **kwargs): |
||||
"""Write to a file. |
||||
|
||||
- path (str): The file to be written. |
||||
- content (str): The content to be written. Note: If omitted, this command is equivalent to ``touch``. |
||||
|
||||
""" |
||||
_content = content or "" |
||||
|
||||
kwargs.setdefault("comment", "write to %s" % path) |
||||
|
||||
a = list() |
||||
|
||||
if len(_content.split("\n")) > 1: |
||||
a.append("cat > %s << EOF" % path) |
||||
a.append(_content) |
||||
a.append("EOF") |
||||
else: |
||||
a.append('echo "%s" > %s' % (_content, path)) |
||||
|
||||
return Command(" ".join(a), **kwargs) |
||||
|
@ -0,0 +1,39 @@ |
||||
from .base import Command |
||||
|
||||
|
||||
def python_pip(name, op="install", upgrade=False, venv=None, version=3, **kwargs): |
||||
"""Use pip to install or uninstall a Python package. |
||||
|
||||
- name (str): The name of the package. |
||||
- op (str): The operation to perform; install, uninstall |
||||
- upgrade (bool): Upgrade an installed package. |
||||
- venv (str): The name of the virtual environment to load. |
||||
- version (int): The Python version to use, e.g. ``2`` or ``3``. |
||||
|
||||
""" |
||||
manager = "pip" |
||||
if version == 3: |
||||
manager = "pip3" |
||||
|
||||
if upgrade: |
||||
statement = "%s install --upgrade %s" % (manager, name) |
||||
else: |
||||
statement = "%s %s %s" % (manager, op, name) |
||||
|
||||
if venv is not None: |
||||
kwargs['prefix'] = "source %s/bin/activate" % venv |
||||
|
||||
kwargs.setdefault("comment", "%s %s" % (op, name)) |
||||
|
||||
return Command(statement, **kwargs) |
||||
|
||||
|
||||
def python_virtualenv(name, **kwargs): |
||||
"""Create a Python virtual environment. |
||||
|
||||
- name (str): The name of the environment to create. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "create %s virtual environment" % name) |
||||
|
||||
return Command("virtualenv %s" % name, **kwargs) |
@ -0,0 +1,337 @@ |
||||
# Imports |
||||
|
||||
from commonkit import split_csv |
||||
from .base import Command, Template |
||||
from .common import COMMON_MAPPINGS |
||||
from .django import DJANGO_MAPPINGS |
||||
from .mysql import MYSQL_MAPPINGS |
||||
from .pgsql import PGSQL_MAPPINGS |
||||
from .posix import POSIX_MAPPINGS |
||||
|
||||
# Exports |
||||
|
||||
__all__ = ( |
||||
"MAPPINGS", |
||||
"apache", |
||||
"apache_disable_module", |
||||
"apache_disable_site", |
||||
"apache_enable_module", |
||||
"apache_enable_site", |
||||
"apache_reload", |
||||
"apache_restart", |
||||
"apache_start", |
||||
"apache_stop", |
||||
"apache_test", |
||||
"command_exists", |
||||
"service_reload", |
||||
"service_restart", |
||||
"service_start", |
||||
"service_stop", |
||||
"system", |
||||
"system_install", |
||||
"system_reboot", |
||||
"system_update", |
||||
"system_upgrade", |
||||
"system_uninstall", |
||||
"template", |
||||
"user", |
||||
) |
||||
|
||||
|
||||
def command_exists(name): |
||||
"""Indicates whether a given command exists in this overlay. |
||||
|
||||
:param name: The name of the command. |
||||
:type name: str |
||||
|
||||
:rtype: bool |
||||
|
||||
""" |
||||
return name in MAPPINGS |
||||
|
||||
|
||||
def apache(op, **kwargs): |
||||
"""Execute an Apache-related command. |
||||
|
||||
- op (str): The operation to perform; reload, restart, start, stop, test. |
||||
|
||||
""" |
||||
if op == "reload": |
||||
return apache_reload(**kwargs) |
||||
elif op == "restart": |
||||
return apache_restart(**kwargs) |
||||
elif op == "start": |
||||
return apache_start(**kwargs) |
||||
elif op == "stop": |
||||
return apache_stop(**kwargs) |
||||
elif op == "test": |
||||
return apache_test(**kwargs) |
||||
else: |
||||
raise NameError("Unrecognized or unsupported apache operation: %s" % op) |
||||
|
||||
|
||||
def apache_disable_module(name, **kwargs): |
||||
"""Disable an Apache module. |
||||
|
||||
- name (str): The module name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "disable %s apache module" % name) |
||||
|
||||
return Command("a2dismod %s" % name, **kwargs) |
||||
|
||||
|
||||
def apache_disable_site(name, **kwargs): |
||||
"""Disable an Apache site. |
||||
|
||||
- name (str): The domain name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "disable %s apache site" % name) |
||||
|
||||
return Command("a2dissite %s" % name, **kwargs) |
||||
|
||||
|
||||
def apache_enable_module(name, **kwargs): |
||||
"""Enable an Apache module. |
||||
|
||||
- name (str): The module name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "enable %s apache module" % name) |
||||
|
||||
return Command("a2enmod %s" % name, **kwargs) |
||||
|
||||
|
||||
def apache_enable_site(name, **kwargs): |
||||
"""Enable an Apache site. |
||||
|
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "enable %s apache module" % name) |
||||
|
||||
return Command("a2ensite %s" % name, **kwargs) |
||||
|
||||
|
||||
def apache_reload(**kwargs): |
||||
kwargs.setdefault("comment", "reload apache") |
||||
kwargs.setdefault("register", "apache_reloaded") |
||||
|
||||
return Command("service apache2 reload", **kwargs) |
||||
|
||||
|
||||
def apache_restart(**kwargs): |
||||
kwargs.setdefault("comment", "restart apache") |
||||
kwargs.setdefault("register", "apache_restarted") |
||||
|
||||
return Command("service apache2 restart", **kwargs) |
||||
|
||||
|
||||
def apache_start(**kwargs): |
||||
kwargs.setdefault("comment", "start apache") |
||||
kwargs.setdefault("register", "apache_started") |
||||
|
||||
return Command("service apache2 start", **kwargs) |
||||
|
||||
|
||||
def apache_stop(**kwargs): |
||||
kwargs.setdefault("comment", "stop apache") |
||||
|
||||
return Command("service apache2 stop", **kwargs) |
||||
|
||||
|
||||
def apache_test(**kwargs): |
||||
kwargs.setdefault("comment", "check apache configuration") |
||||
kwargs.setdefault("register", "apache_checks_out") |
||||
|
||||
return Command("apachectl configtest", **kwargs) |
||||
|
||||
|
||||
def service_reload(name, **kwargs): |
||||
"""Reload a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "reload %s service" % name) |
||||
kwargs.setdefault("register", "%s_reloaded" % name) |
||||
|
||||
return Command("service %s reload" % name, **kwargs) |
||||
|
||||
|
||||
def service_restart(name, **kwargs): |
||||
"""Restart a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "restart %s service" % name) |
||||
kwargs.setdefault("register", "%s_restarted" % name) |
||||
|
||||
return Command("service %s restart" % name, **kwargs) |
||||
|
||||
|
||||
def service_start(name, **kwargs): |
||||
"""Start a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "start %s service" % name) |
||||
kwargs.setdefault("register", "%s_started" % name) |
||||
|
||||
return Command("service %s start" % name, **kwargs) |
||||
|
||||
|
||||
def service_stop(name, **kwargs): |
||||
"""Stop a service. |
||||
|
||||
- name (str): The service name. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "stop %s service" % name) |
||||
kwargs.setdefault("register", "%s_stopped" % name) |
||||
|
||||
return Command("service %s stop" % name, **kwargs) |
||||
|
||||
|
||||
def system(op, **kwargs): |
||||
"""Perform a system operation. |
||||
|
||||
- op (str): The operation to perform; reboot, update, upgrade. |
||||
|
||||
""" |
||||
if op == "reboot": |
||||
return system_reboot(**kwargs) |
||||
elif op == "update": |
||||
return system_update(**kwargs) |
||||
elif op == "upgrade": |
||||
return system_upgrade(**kwargs) |
||||
else: |
||||
raise NameError("Unrecognized or unsupported system operation: %s" % op) |
||||
|
||||
|
||||
def system_install(name, **kwargs): |
||||
"""Install a system-level package. |
||||
|
||||
- name (str): The name of the package to install. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "install system package %s" % name) |
||||
|
||||
return Command("apt-get install -y %s" % name, **kwargs) |
||||
|
||||
|
||||
def system_reboot(**kwargs): |
||||
kwargs.setdefault("comment", "reboot the system") |
||||
|
||||
return Command("reboot", **kwargs) |
||||
|
||||
|
||||
def system_uninstall(name, **kwargs): |
||||
"""Uninstall a system-level package. |
||||
|
||||
- name (str): The name of the package to uninstall. |
||||
|
||||
""" |
||||
kwargs.setdefault("comment", "remove system package %s" % name) |
||||
|
||||
return Command("apt-get uninstall -y %s" % name, **kwargs) |
||||
|
||||
|
||||
def system_update(**kwargs): |
||||
kwargs.setdefault("comment", "update system package info") |
||||
|
||||
return Command("apt-get update -y", **kwargs) |
||||
|
||||
|
||||
def system_upgrade(**kwargs): |
||||
kwargs.setdefault("comment", "upgrade the system") |
||||
|
||||
return Command("apt-get upgrade -y", **kwargs) |
||||
|
||||
|
||||
def template(source, target, backup=True, parser=None, **kwargs): |
||||
"""Create a file from a template. |
||||
|
||||
- source (str): The path to the template file. |
||||
- target (str): The path to where the new file should be created. |
||||
- backup (bool): Indicates whether a backup should be made if the target file already exists. |
||||
- parser (str): The parser to use ``jinja`` (the default) or ``simple``. |
||||
|
||||
""" |
||||
return Template(source, target, backup=backup, parser=parser, **kwargs) |
||||
|
||||
|
||||
def user(name, groups=None, home=None, op="add", password=None, **kwargs): |
||||
"""Create or remove a user. |
||||
|
||||
- name (str): The user name. |
||||
- groups (str | list): A list of groups to which the user should belong. |
||||
- home (str): The path to the user's home directory. |
||||
- op (str); The operation to perform; ``add`` or ``remove``. |
||||
- password (str): The user's password. (NOT IMPLEMENTED) |
||||
|
||||
""" |
||||
if op == "add": |
||||
kwargs.setdefault("comment", "create a user named %s" % name) |
||||
|
||||
commands = list() |
||||
|
||||
# The gecos switch eliminates the prompts. |
||||
a = list() |
||||
a.append('adduser %s --disabled-password --gecos ""' % name) |
||||
if home is not None: |
||||
a.append("--home %s" % home) |
||||
|
||||
commands.append(Command(" ".join(a), **kwargs)) |
||||
|
||||
if type(groups) is str: |
||||
groups = split_csv(groups, smart=False) |
||||
|
||||
if type(groups) in [list, tuple]: |
||||
for group in groups: |
||||
commands.append(Command("adduser %s %s" % (name, group), **kwargs)) |
||||
|
||||
a = list() |
||||
for c in commands: |
||||
a.append(c.get_statement(include_comment=True)) |
||||
|
||||
return Command("\n".join(a), **kwargs) |
||||
elif op == "remove": |
||||
kwargs.setdefault("comment", "remove a user named %s" % name) |
||||
return Command("deluser %s" % name, **kwargs) |
||||
else: |
||||
raise NameError("Unsupported or unrecognized operation: %s" % op) |
||||
|
||||
|
||||
MAPPINGS = { |
||||
'apache': apache, |
||||
'apache.disable_module': apache_disable_module, |
||||
'apache.disable_site': apache_disable_site, |
||||
'apache.enable_module': apache_enable_module, |
||||
'apache.enable_site': apache_enable_site, |
||||
'apache.reload': apache_reload, |
||||
'apache.restart': apache_restart, |
||||
'apache.start': apache_start, |
||||
'apache.stop': apache_stop, |
||||
'apache.test': apache_test, |
||||
'install': system_install, |
||||
'reboot': system_reboot, |
||||
'reload': service_reload, |
||||
'restart': service_restart, |
||||
'start': service_start, |
||||
'stop': service_stop, |
||||
'system': system, |
||||
'template': template, |
||||
'update': system_update, |
||||
'uninstall': system_uninstall, |
||||
'upgrade': system_upgrade, |
||||
'user': user, |
||||
} |
||||
|
||||
MAPPINGS.update(COMMON_MAPPINGS) |
||||
MAPPINGS.update(DJANGO_MAPPINGS) |
||||
MAPPINGS.update(MYSQL_MAPPINGS) |
||||
MAPPINGS.update(PGSQL_MAPPINGS) |
||||
MAPPINGS.update(POSIX_MAPPINGS) |
Loading…
Reference in new issue