Clean up and improvements for v7.

development
Shawn Davis 2 years ago
parent 549a7cfb1e
commit 4fb46eb508
  1. 2
      VERSION.txt
  2. 6
      sandbox/tease.py
  3. 356
      scripttease/cli/__init__.py
  4. 7
      scripttease/lib/commands/base.py
  5. 4
      scripttease/lib/commands/django.py
  6. 18
      scripttease/lib/commands/pgsql.py
  7. 13
      scripttease/lib/commands/posix.py
  8. 28
      scripttease/lib/commands/python.py
  9. 4
      scripttease/lib/commands/ubuntu.py
  10. 4
      scripttease/lib/factories.py
  11. 3
      scripttease/lib/loaders/base.py
  12. 6
      scripttease/version.py
  13. 2
      setup.py

@ -1 +1 @@
6.8.13 6.8.17

@ -5,8 +5,10 @@ import sys
sys.path.insert(0, "../") sys.path.insert(0, "../")
from scripttease.cli import main_command from scripttease.cli import execute
if __name__ == '__main__': if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main_command()) sys.exit(execute())
# old:
# sys.exit(main_command())

@ -1,12 +1,22 @@
# Imports # Imports
from argparse import ArgumentParser, RawDescriptionHelpFormatter from argparse import ArgumentParser, RawDescriptionHelpFormatter
from commonkit.logging import LoggingHelper from commonkit.logging import LoggingHelper
from ..variables import LOGGER_NAME from ..variables import LOGGER_NAME, PATH_TO_SCRIPT_TEASE
from ..version import DATE as VERSION_DATE, VERSION from ..version import DATE as VERSION_DATE, VERSION
from . import initialize from . import initialize
from . import subcommands from . import subcommands
# New:
from commonkit import highlight_code, indent, smart_cast, write_file
from commonkit.shell import EXIT
from markdown import markdown
import os
from scripttease.lib.contexts import Context
from scripttease.lib.factories import command_factory
from scripttease.lib.loaders import load_variables, INILoader, YMLLoader
DEBUG = 10 DEBUG = 10
logging = LoggingHelper(colorize=True, name=LOGGER_NAME) logging = LoggingHelper(colorize=True, name=LOGGER_NAME)
@ -15,6 +25,350 @@ log = logging.setup()
# Commands # Commands
def execute():
"""Process script configurations."""
__author__ = "Shawn Davis <shawn@develmaycare.com>"
__date__ = VERSION_DATE
__help__ = """NOTES
This command is used to parse configuration files and output the commands.
"""
__version__ = VERSION
# Main argument parser from which sub-commands are created.
parser = ArgumentParser(description=__doc__, epilog=__help__, formatter_class=RawDescriptionHelpFormatter)
parser.add_argument(
"path",
default="steps.ini",
nargs="?",
help="The path to the configuration file."
)
parser.add_argument(
"-c",
"--color",
action="store_true",
dest="color_enabled",
help="Enable code highlighting for terminal output."
)
parser.add_argument(
"-C=",
"--context=",
action="append",
dest="variables",
help="Context variables for use in pre-parsing the config and templates. In the form of: name:value"
)
parser.add_argument(
"-d=",
"--docs=",
choices=["html", "markdown", "plain", "rst"],
# default="markdown",
dest="docs",
help="Output documentation instead of code."
)
parser.add_argument(
"-D",
"--debug",
action="store_true",
dest="debug_enabled",
help="Enable debug output."
)
parser.add_argument(
"-f=",
"--filter=",
action="append",
dest="filters",
help="Filter the commands in the form of: attribute:value"
)
parser.add_argument(
"-i=",
"--inventory=",
dest="inventory",
help="Copy an inventory item to a local directory."
)
parser.add_argument(
"-o=",
"--option=",
action="append",
dest="options",
help="Common command options in the form of: name:value"
)
parser.add_argument(
"-P=",
"--profile=",
choices=["centos", "ubuntu"],
default="ubuntu",
dest="profile",
help="The OS profile to use."
)
parser.add_argument(
"-T=",
"--template-path=",
action="append",
dest="template_locations",
help="The location of template files that may be used with the template command."
)
parser.add_argument(
"-w=",
"--write=",
dest="output_file",
help="Write the output to disk."
)
parser.add_argument(
"-V=",
"--variables-file=",
dest="variables_file",
help="Load variables from a file."
)
# Access to the version number requires special consideration, especially
# when using sub parsers. The Python 3.3 behavior is different. See this
# answer: http://stackoverflow.com/questions/8521612/argparse-optional-subparser-for-version
parser.add_argument(
"-v",
action="version",
help="Show version number and exit.",
version=__version__
)
parser.add_argument(
"--version",
action="version",
help="Show verbose version information and exit.",
version="%(prog)s" + " %s %s by %s" % (__version__, __date__, __author__)
)
# Parse arguments.
args = parser.parse_args()
if args.debug_enabled:
log.setLevel(DEBUG)
log.debug("Namespace: %s" % args)
# Create the global context.
context = Context()
if args.variables_file:
variables = load_variables(args.variables_file)
for v in variables:
context.variables[v.name] = v
if args.variables:
for token in args.variables:
try:
key, value = token.split(":")
context.add(key, smart_cast(value))
except ValueError:
context.add(token, True)
# Capture filters.
if args.filters:
filters = dict()
for token in args.filters:
key, value = token.split(":")
if key not in filters:
filters[key] = list()
filters[key].append(value)
# Handle global command options.
options = dict()
if args.options:
for token in args.options:
try:
key, value = token.split(":")
options[key] = smart_cast(value)
except ValueError:
options[token] = True
# The path may have been given as a file name (steps.ini), path, or an inventory name.
input_locations = [
args.path,
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", args.path, "steps.ini"),
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", args.path, "steps.yml"),
]
path = None
for location in input_locations:
if os.path.exists(location):
path = location
break
if path is None:
log.warning("Path does not exist: %s" % args.path)
exit(EXIT.INPUT)
# Load the commands.
if path.endswith(".ini"):
loader = INILoader(
path,
context=context,
locations=args.template_locations,
profile=args.profile,
**options
)
elif path.endswith(".yml"):
loader = YMLLoader(
path,
context=context,
locations=args.template_locations,
profile=args.profile,
**options
)
else:
log.error("Unsupported file format: %s" % path)
exit(EXIT.ERROR)
# noinspection PyUnboundLocalVariable
if not loader.load():
log.error("Failed to load the input file: %s" % path)
exit(EXIT.ERROR)
# Generate output.
if args.docs:
output = list()
for command in loader.commands:
# Will this every happen?
# if command is None:
# continue
if command.name == "explain":
if command.header:
if args.docs == "plain":
output.append("***** %s *****" % command.name.title())
elif args.docs == "rst":
output.append(command.name.title())
output.append("=" * len(command.name))
else:
output.append("## %s" % command.name.title())
output.append("")
output.append(command.content)
output.append("")
elif command.name == "screenshot":
if args.docs == "html":
b = list()
b.append('<img src="%s"' % command.args[0])
b.append('alt="%s"' % command.caption or command.comment)
if command.classes:
b.append('class="%s"' % command.classes)
if command.height:
b.append('height="%s"' % command.height)
if command.width:
b.append('width="%s"' % command)
output.append(" ".join(b) + ">")
output.append("")
elif args.docs == "plain":
output.append(command.args[0])
output.append("")
elif args.docs == "rst":
output.append(".. figure:: %s" % command.args[0])
if command.caption:
output.append(indent(":alt: %s" % command.caption or command.comment))
if command.height:
output.append(indent(":height: %s" % command.height))
if command.width:
output.append(indent(":width: %s" % command.width))
output.append("")
else:
output.append("![%s](%s)" % (command.caption or command.comment, command.args[0]))
output.append("")
elif command.name == "template":
if args.docs == "plain":
output.append("+++")
output.append(command.get_content())
output.append("+++")
elif args.docs == "rst":
output.append(".. code-block:: %s" % command.get_target_language())
output.append("")
output.append(indent(command.get_content()))
output.append("")
else:
output.append("```%s" % command.get_target_language())
output.append(command.get_content())
output.append("```")
output.append("")
else:
statement = command.get_statement(include_comment=False, include_register=False, include_stop=False)
if statement is not None:
line = command.comment.replace("#", "")
output.append("%s:" % line.capitalize())
output.append("")
if args.docs == "plain":
output.append("---")
output.append(statement)
output.append("---")
output.append("")
elif args.docs == "rst":
output.append(".. code-block:: bash")
output.append("")
output.append(indent(statement))
output.append("")
else:
output.append("```bash")
output.append(statement)
output.append("```")
output.append("")
if args.docs == "html":
_output = markdown("\n".join(output), extensions=['fenced_code'])
else:
_output = "\n".join(output)
print(_output)
if args.output_file:
write_file(args.output_file, _output)
else:
commands = command_factory(loader)
output = list()
for command in commands:
# print("COMMAND", command)
# Explanations and screenshots don't produce usable statements but may be added as comments.
if command.name in ("explain", "screenshot"):
# commands.append("# %s" % command.content)
# commands.append("")
continue
statement = command.get_statement(include_comment=False)
if statement is not None:
output.append(statement)
output.append("")
if args.color_enabled:
print(highlight_code("\n".join(output), language="bash"))
else:
print("\n".join(output))
if args.output_file:
write_file(args.output_file, "\n".join(output))
exit(EXIT.OK)
def main_command(): def main_command():
"""Process script configurations.""" """Process script configurations."""

@ -510,8 +510,11 @@ class Template(object):
def __getattr__(self, item): def __getattr__(self, item):
return self.kwargs.get(item) return self.kwargs.get(item)
def __str__(self): # def __str__(self):
return "template" # return "template"
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.source)
def get_content(self): def get_content(self):
"""Parse the template. """Parse the template.

@ -26,6 +26,10 @@ 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. # The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default.
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS
venv = kwargs.pop("venv", None)
if venv is not None:
kwargs['prefix'] = "source %s/bin/activate" % venv
# Django's management commands can have a number of options. We need to filter out internal parameters so that these # 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. # are not used as options for the management command.
_kwargs = dict() _kwargs = dict()

@ -25,8 +25,6 @@ def pgsql(command, *args, host="localhost", excluded_kwargs=None, password=None,
# kwargs['comment'] = "run %s postgres command" % command # kwargs['comment'] = "run %s postgres command" % command
# Allow additional command line switches to pass through? # 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() _kwargs = dict()
for key in excluded_kwargs: for key in excluded_kwargs:
if key in kwargs: if key in kwargs:
@ -69,6 +67,22 @@ def pgsql_create(database, owner=None, template=None, **kwargs):
if template is not None: if template is not None:
kwargs['template'] = template kwargs['template'] = template
# psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '<your db name>'" | grep -q 1 | psql -U postgres -c "CREATE DATABASE <your db name>"
# first = pgsql("psql", **kwargs)
#
# first_query = "SELECT 1 FROM pg_database WHERE datname = '%s'" % database
# first_statement = '%s -tc "%s" | grep -q 1' % (first.statement, first_query)
#
# kwargs_without_password = kwargs.copy()
# if 'password' in kwargs_without_password:
# kwargs_without_password.pop("password")
#
# second = pgsql("psql", **kwargs_without_password)
# second_statement = '%s -c "CREATE DATABASE %s"' % (second.statement, database)
#
# final_statement = "%s | %s" % (first_statement, second_statement)
# return Command(final_statement, **kwargs)
return pgsql("createdb", database, **kwargs) return pgsql("createdb", database, **kwargs)

@ -120,12 +120,16 @@ def directory(path, group=None, mode=None, owner=None, recursive=True, **kwargs)
if recursive: if recursive:
statement.append("-p") statement.append("-p")
statement.append(path)
if group: if group:
if recursive: if recursive:
statement.append("&& chgrp -R %s" % group) statement.append("&& chgrp -R %s" % group)
else: else:
statement.append("&& chgrp %s" % group) statement.append("&& chgrp %s" % group)
statement.append(path)
if owner: if owner:
if recursive: if recursive:
statement.append("&& chown -R %s" % owner) statement.append("&& chown -R %s" % owner)
@ -381,6 +385,13 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
# rsync -e "ssh -i $(SSH_KEY) -p $(SSH_PORT)" -P -rvzc --delete # rsync -e "ssh -i $(SSH_KEY) -p $(SSH_PORT)" -P -rvzc --delete
# $(OUTPUTH_PATH) $(SSH_USER)@$(SSH_HOST):$(UPLOAD_PATH) --cvs-exclude; # $(OUTPUTH_PATH) $(SSH_USER)@$(SSH_HOST):$(UPLOAD_PATH) --cvs-exclude;
# ansible:
# /usr/bin/rsync --delay-updates -F --compress --delete-after --copy-links --archive --rsh='/usr/bin/ssh -S none -
# i /home/shawn/.ssh/sharedservices_group -o Port=4894 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
# --rsync-path='sudo -u root rsync'
# --exclude-from=/home/shawn/Work/app_sharedservices_group/deploy/roles/project/rsync.txt
# --out-format='<<CHANGED>>%i %n%L'
tokens = list() tokens = list()
tokens.append("rsync") tokens.append("rsync")
tokens.append("--cvs-exclude") tokens.append("--cvs-exclude")
@ -576,7 +587,7 @@ POSIX_MAPPINGS = {
'remove': remove, 'remove': remove,
'replace': replace, 'replace': replace,
'run': run, 'run': run,
'rysnc': rsync, 'rsync': rsync,
'scopy': scopy, 'scopy': scopy,
'sync': sync, 'sync': sync,
'touch': touch, 'touch': touch,

@ -28,6 +28,32 @@ def python_pip(name, op="install", upgrade=False, venv=None, version=3, **kwargs
return Command(statement, **kwargs) return Command(statement, **kwargs)
def python_pip_file(path, venv=None, version=3, **kwargs):
"""Install Python packages from a pip file.
:param path: The path to the file.
:type path: str
:param venv: The name (and/or path) of the virtual environment.
:type venv: str
:param version: The pip version to use.
"""
manager = "pip"
if version == 3:
manager = "pip3"
if venv is not None:
kwargs['prefix'] = "source %s/bin/activate" % venv
kwargs.setdefault("comment", "install packages from pip file %s" % path)
statement = "%s -r %s" % (manager, path)
return Command(statement, **kwargs)
def python_virtualenv(name, **kwargs): def python_virtualenv(name, **kwargs):
"""Create a Python virtual environment. """Create a Python virtual environment.
@ -41,5 +67,7 @@ def python_virtualenv(name, **kwargs):
PYTHON_MAPPINGS = { PYTHON_MAPPINGS = {
'pip': python_pip, 'pip': python_pip,
'pipf': python_pip_file,
'pip_file': python_pip_file,
'virtualenv': python_virtualenv, 'virtualenv': python_virtualenv,
} }

@ -276,10 +276,10 @@ def user(name, groups=None, home=None, op="add", password=None, **kwargs):
for c in commands: for c in commands:
a.append(c.get_statement(include_comment=True)) a.append(c.get_statement(include_comment=True))
return Command("\n".join(a), **kwargs) return Command("\n".join(a), name="user_add", **kwargs)
elif op == "remove": elif op == "remove":
kwargs.setdefault("comment", "remove a user named %s" % name) kwargs.setdefault("comment", "remove a user named %s" % name)
return Command("deluser %s" % name, **kwargs) return Command("deluser %s" % name, name="user_remove", **kwargs)
else: else:
raise NameError("Unsupported or unrecognized operation: %s" % op) raise NameError("Unsupported or unrecognized operation: %s" % op)

@ -58,6 +58,10 @@ def command_factory(loader, excluded_kwargs=None, mappings=None, profile=PROFILE
command = get_command(_mappings, command_name, *args, locations=loader.locations, **kwargs) command = get_command(_mappings, command_name, *args, locations=loader.locations, **kwargs)
if command is not None: if command is not None:
command.number = number command.number = number
if command.name == "template":
command.context = loader.get_context()
commands.append(command) commands.append(command)
number += 1 number += 1

@ -8,7 +8,6 @@ import logging
import os import os
from ...constants import EXCLUDED_KWARGS from ...constants import EXCLUDED_KWARGS
from ..contexts import Variable from ..contexts import Variable
from ..snippets.mappings import MAPPINGS
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -149,6 +148,8 @@ class BaseLoader(File):
self.context = context self.context = context
self.is_loaded = False self.is_loaded = False
self.locations = locations or list() self.locations = locations or list()
self.profile = kwargs.pop("profile", "ubuntu")
self.options = kwargs self.options = kwargs
super().__init__(path) super().__init__(path)

@ -1,5 +1,5 @@
DATE = "2021-01-26" DATE = "2022-06-14"
VERSION = "6.8.2" VERSION = "6.8.15"
MAJOR = 6 MAJOR = 6
MINOR = 8 MINOR = 8
PATCH = 3 PATCH = 15

@ -58,7 +58,7 @@ setup(
test_suite='runtests.runtests', test_suite='runtests.runtests',
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'tease = script_tease.cli:main_command', 'tease = scripttease.cli:main_command',
], ],
}, },
) )

Loading…
Cancel
Save