Compare commits
No commits in common. 'b2d43c01894d11c37c401e8993200cdb5293ec5d' and '7a64eeff094eb9dee5cda49f7d1fe57aa7c78ef9' have entirely different histories.
b2d43c0189
...
7a64eeff09
@ -1,13 +1,7 @@ |
|||||||
# Python Script Tease |
# Python Script Tease |
||||||
|
|
||||||
![](https://img.shields.io/badge/status-active-green.svg) |
![](https://img.shields.io/badge/status-active-green.svg) |
||||||
![](https://img.shields.io/badge/stage-stable-green.svg) |
![](https://img.shields.io/badge/stage-development-blue.svg) |
||||||
![](https://img.shields.io/badge/coverage-100%25-green.svg) |
![](https://img.shields.io/badge/coverage-100%25-green.svg) |
||||||
|
|
||||||
A collection of classes and commands for automated command line scripting using Python. |
A collection of classes and commands for automated command line scripting using Python. |
||||||
|
|
||||||
## Install |
|
||||||
|
|
||||||
```bash |
|
||||||
pip install python-scripttease; |
|
||||||
``` |
|
@ -1 +1 @@ |
|||||||
7.0.0-a |
7.0.0-a |
||||||
|
@ -0,0 +1,139 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
|
||||||
|
# Imports |
||||||
|
|
||||||
|
from collections import OrderedDict |
||||||
|
import inspect |
||||||
|
import sys |
||||||
|
|
||||||
|
# Set path before importing overlays. |
||||||
|
sys.path.append("../") |
||||||
|
|
||||||
|
# Import overlays |
||||||
|
from scripttease.library.overlays.common import COMMON_MAPPINGS |
||||||
|
from scripttease.library.overlays.centos import MAPPINGS as CENTOS_MAPPINGS |
||||||
|
from scripttease.library.overlays.django import DJANGO_MAPPINGS |
||||||
|
from scripttease.library.overlays.mysql import MYSQL_MAPPINGS |
||||||
|
from scripttease.library.overlays.pgsql import PGSQL_MAPPINGS |
||||||
|
from scripttease.library.overlays.posix import POSIX_MAPPINGS |
||||||
|
from scripttease.library.overlays.ubuntu import MAPPINGS as UBUNTU_MAPPINGS |
||||||
|
|
||||||
|
# Functions |
||||||
|
|
||||||
|
|
||||||
|
# https://stackoverflow.com/a/52003056/241720 |
||||||
|
def get_signature(fn): |
||||||
|
params = inspect.signature(fn).parameters |
||||||
|
args = [] |
||||||
|
kwargs = OrderedDict() |
||||||
|
for p in params.values(): |
||||||
|
if p.default is p.empty: |
||||||
|
args.append(p.name) |
||||||
|
else: |
||||||
|
kwargs[p.name] = p.default |
||||||
|
return args, kwargs |
||||||
|
|
||||||
|
|
||||||
|
def print_description(text): |
||||||
|
print(text) |
||||||
|
print("") |
||||||
|
|
||||||
|
|
||||||
|
def print_heading(title): |
||||||
|
print(title) |
||||||
|
print("=" * len(title)) |
||||||
|
print("") |
||||||
|
|
||||||
|
|
||||||
|
def print_mapping(commands, excludes=None): |
||||||
|
keys = list(commands.keys()) |
||||||
|
keys.sort() |
||||||
|
|
||||||
|
_excludes = excludes or dict() |
||||||
|
|
||||||
|
for key in keys: |
||||||
|
if key in _excludes: |
||||||
|
continue |
||||||
|
|
||||||
|
func = commands[key] |
||||||
|
|
||||||
|
docstring = func.__doc__ |
||||||
|
if not docstring: |
||||||
|
continue |
||||||
|
|
||||||
|
print(key) |
||||||
|
print("-" * len(key)) |
||||||
|
print("") |
||||||
|
for i in docstring.split("\n"): |
||||||
|
print(i.strip()) |
||||||
|
# print("") |
||||||
|
|
||||||
|
print(".. code-block:: ini") |
||||||
|
print("") |
||||||
|
print(" [run %s command]" % key) |
||||||
|
|
||||||
|
args, kwargs = get_signature(func) |
||||||
|
|
||||||
|
line = list() |
||||||
|
for a in args: |
||||||
|
if a != "kwargs": |
||||||
|
line.append(a) |
||||||
|
|
||||||
|
print(" %s: %s" % (key, " ".join(line))) |
||||||
|
|
||||||
|
for option, value in kwargs.items(): |
||||||
|
if value is True: |
||||||
|
_value = "yes" |
||||||
|
elif value is False: |
||||||
|
_value = "no" |
||||||
|
else: |
||||||
|
_value = value |
||||||
|
|
||||||
|
print(" %s: %s" % (option, value)) |
||||||
|
|
||||||
|
print("") |
||||||
|
|
||||||
|
|
||||||
|
# Overlay output. |
||||||
|
print(".. generated by generate_command_signatures.py") |
||||||
|
print("") |
||||||
|
|
||||||
|
print_heading("Common") |
||||||
|
print_description("Common commands are available to all overlays.") |
||||||
|
print_mapping(COMMON_MAPPINGS) |
||||||
|
|
||||||
|
print_heading("Django") |
||||||
|
print_description("Django commands are available to all overlays.") |
||||||
|
print_mapping(DJANGO_MAPPINGS) |
||||||
|
|
||||||
|
print_heading("MySQL") |
||||||
|
print_description("MySQL commands.") |
||||||
|
print_mapping(MYSQL_MAPPINGS) |
||||||
|
|
||||||
|
print_heading("Postgres") |
||||||
|
print_description("Postgres commands.") |
||||||
|
print_mapping(PGSQL_MAPPINGS) |
||||||
|
|
||||||
|
print_heading("POSIX") |
||||||
|
print_description("Posix commands form the basis of overlays for *nix platforms.") |
||||||
|
print_mapping(POSIX_MAPPINGS, excludes=["func"]) |
||||||
|
|
||||||
|
exclude_from_centos = COMMON_MAPPINGS.copy() |
||||||
|
exclude_from_centos.update(DJANGO_MAPPINGS) |
||||||
|
exclude_from_centos.update(MYSQL_MAPPINGS) |
||||||
|
exclude_from_centos.update(PGSQL_MAPPINGS) |
||||||
|
exclude_from_centos.update(POSIX_MAPPINGS) |
||||||
|
print_heading("Cent OS") |
||||||
|
print_description("The Cent OS overlay incorporates commands specific to that platform as well as commands from " |
||||||
|
"common, Django, MySQL, Postgres, and POSIX.") |
||||||
|
print_mapping(CENTOS_MAPPINGS, excludes=exclude_from_centos) |
||||||
|
|
||||||
|
exclude_from_ubuntu = COMMON_MAPPINGS.copy() |
||||||
|
exclude_from_ubuntu.update(DJANGO_MAPPINGS) |
||||||
|
exclude_from_ubuntu.update(MYSQL_MAPPINGS) |
||||||
|
exclude_from_ubuntu.update(PGSQL_MAPPINGS) |
||||||
|
exclude_from_ubuntu.update(POSIX_MAPPINGS) |
||||||
|
print_heading("Ubuntu") |
||||||
|
print_description("The Ubuntu overlay incorporates commands specific to that platform as well as commands from " |
||||||
|
"common, Django, MySQL, Postgres, and POSIX.") |
||||||
|
print_mapping(UBUNTU_MAPPINGS, excludes=exclude_from_ubuntu) |
|
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
Before Width: | Height: | Size: 393 KiB After Width: | Height: | Size: 393 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 181 KiB |
@ -0,0 +1,86 @@ |
|||||||
|
.. _commands: |
||||||
|
|
||||||
|
******** |
||||||
|
Commands |
||||||
|
******** |
||||||
|
|
||||||
|
.. code-block:: text |
||||||
|
|
||||||
|
usage: tease [-h] [-c] [-C= VARIABLES] [-d] [-D] [-f= FILTERS] [-O= OPTIONS] [-s] [-T= TEMPLATE_LOCATIONS] [-w= OUTPUT_FILE] [-V= VARIABLES_FILE] |
||||||
|
[-v] [--version] |
||||||
|
[path] |
||||||
|
|
||||||
|
positional arguments: |
||||||
|
path The path to the configuration file. |
||||||
|
|
||||||
|
optional arguments: |
||||||
|
-h, --help show this help message and exit |
||||||
|
-c, --color Enable code highlighting for terminal output. |
||||||
|
-C= VARIABLES, --context= VARIABLES |
||||||
|
Context variables for use in pre-parsing the config and templates. In the form of: name:value |
||||||
|
-d, --docs Output documentation instead of code. |
||||||
|
-D, --debug Enable debug output. |
||||||
|
-f= FILTERS, --filter= FILTERS |
||||||
|
Filter the commands in the form of: attribute:value |
||||||
|
-O= OPTIONS, --option= OPTIONS |
||||||
|
Common command options in the form of: name:value |
||||||
|
-s, --script Output commands as a script. |
||||||
|
-T= TEMPLATE_LOCATIONS, --template-path= TEMPLATE_LOCATIONS |
||||||
|
The location of template files that may be used with the template command. |
||||||
|
-w= OUTPUT_FILE, --write= OUTPUT_FILE |
||||||
|
Write the output to disk. |
||||||
|
-V= VARIABLES_FILE, --variables-file= VARIABLES_FILE |
||||||
|
Load variables from a file. |
||||||
|
-v Show version number and exit. |
||||||
|
--version Show verbose version information and exit. |
||||||
|
|
||||||
|
NOTES |
||||||
|
|
||||||
|
This command is used to parse configuration files and output the commands. |
||||||
|
|
||||||
|
Using the Tease Command |
||||||
|
======================= |
||||||
|
|
||||||
|
The ``tease`` command may be used to parse a configuration file, providing additional utilities for working with commands. |
||||||
|
|
||||||
|
The ``path`` argument defaults to ``commands.ini``. |
||||||
|
|
||||||
|
Context Variables May be Provided on the Command Line |
||||||
|
----------------------------------------------------- |
||||||
|
|
||||||
|
To supply context variables on the command line: |
||||||
|
|
||||||
|
.. code-block:: bash |
||||||
|
|
||||||
|
tease -C domain_name:example.com -C domain_tld:example_com |
||||||
|
|
||||||
|
Loading Context Variables from a File |
||||||
|
------------------------------------- |
||||||
|
|
||||||
|
Context variables may be loaded from a file: |
||||||
|
|
||||||
|
.. code-block:: ini |
||||||
|
|
||||||
|
[domain] |
||||||
|
name = example.com |
||||||
|
tld = example_com |
||||||
|
|
||||||
|
The variables above are available as ``section_key``. For example, ``domain_name`` is ``example.com``. |
||||||
|
|
||||||
|
.. code-block:: bash |
||||||
|
|
||||||
|
tease -V variables.ini |
||||||
|
|
||||||
|
Setting Common Options for All Commands |
||||||
|
--------------------------------------- |
||||||
|
|
||||||
|
Rather than include a common parameter in the configuration file, it is possible to specify a common option on the command line. |
||||||
|
|
||||||
|
.. code-block:: bash |
||||||
|
|
||||||
|
tease -O sudo:yes |
||||||
|
|
||||||
|
The Difference Between Variables and Options |
||||||
|
-------------------------------------------- |
||||||
|
|
||||||
|
Variables are used to pre-process configuration files as templates, while common options are passed to *all* command instances. |
@ -1,77 +0,0 @@ |
|||||||
************ |
|
||||||
Contributing |
|
||||||
************ |
|
||||||
|
|
||||||
We welcome contributions to this and any of `our open source projects`_. There are a number of ways to participate and contribute. See :ref:`contact`. |
|
||||||
|
|
||||||
.. _our open source projects: https://docs.diff6.com |
|
||||||
|
|
||||||
Issue Management |
|
||||||
================ |
|
||||||
|
|
||||||
Reporting Issues |
|
||||||
---------------- |
|
||||||
|
|
||||||
If you have found a bug or error in the documentation, please submit a request. See :ref:`contact`. |
|
||||||
|
|
||||||
.. important:: |
|
||||||
Do **not** report security issues using the issue tracker. Instead, send an email to security@develmaycare.com with details on the issue you've discovered. |
|
||||||
|
|
||||||
Submitting Feature Requests |
|
||||||
--------------------------- |
|
||||||
|
|
||||||
Although we reserve the right to decline new features, we welcome all feature requests. See :ref:`contact`. |
|
||||||
|
|
||||||
Testing and Quality Control |
|
||||||
--------------------------- |
|
||||||
|
|
||||||
Testing involves using Script Tease in real life or in development. Feel free to report any issues you find, or to improve the unit tests. |
|
||||||
|
|
||||||
Pull Requests |
|
||||||
------------- |
|
||||||
|
|
||||||
Pull requests are welcome. Such requests should be associated with an issue. We may ignore pull requests that do not have a corresponding issue, so create an issue if one does not already exist. |
|
||||||
|
|
||||||
Promotion |
|
||||||
========= |
|
||||||
|
|
||||||
You may help spread awareness of Script Tease by writing blog posts. We are happy to link out to reviews and tutorials from our web site. `Let us know if you've created a blog post`_ that we can share. Be sure to include a link to the post. |
|
||||||
|
|
||||||
You may also provide us with a guest post to be included on our blog. |
|
||||||
|
|
||||||
.. _Let us know if you've created a blog post: https://develmaycare.com/contact/?product=Script%20Tease |
|
||||||
|
|
||||||
.. note:: |
|
||||||
We reserve the right to proof and approve or decline all content posted on our web site. |
|
||||||
|
|
||||||
Development |
|
||||||
=========== |
|
||||||
|
|
||||||
Setting Up For Development |
|
||||||
-------------------------- |
|
||||||
|
|
||||||
1. Clone the repo at https://github.com/develmaycare/python-scripttease |
|
||||||
2. Create a virtual environment and install the requirements from ``requirements.pip`` |
|
||||||
|
|
||||||
Style Guide |
|
||||||
----------- |
|
||||||
|
|
||||||
Script Tease follows `PEP8`_ and (where appropriate) the `Django style guide`_ and `JavaScript Standard Style`_. |
|
||||||
|
|
||||||
.. _Django style guide: https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/ |
|
||||||
.. _JavaScript Standard Style: https://standardjs.com |
|
||||||
.. _PEP8: https://www.python.org/dev/peps/pep-0008/ |
|
||||||
|
|
||||||
We *do* make a few exceptions and provide additional guidance which is documented in our `developer docs`_. |
|
||||||
|
|
||||||
.. _developer docs: https://docs.develmaycare.com/en/developer/ |
|
||||||
|
|
||||||
Dependencies |
|
||||||
------------ |
|
||||||
|
|
||||||
.. include:: _includes/dependencies.rst |
|
||||||
|
|
||||||
Testing |
|
||||||
------- |
|
||||||
|
|
||||||
.. include:: _includes/tests.rst |
|
@ -0,0 +1,32 @@ |
|||||||
|
.. _getting-started: |
||||||
|
|
||||||
|
*************** |
||||||
|
Getting Started |
||||||
|
*************** |
||||||
|
|
||||||
|
System Requirements |
||||||
|
=================== |
||||||
|
|
||||||
|
Python 3.6 or greater is required. |
||||||
|
|
||||||
|
Install |
||||||
|
======= |
||||||
|
|
||||||
|
To install: |
||||||
|
|
||||||
|
.. code-block:: bash |
||||||
|
|
||||||
|
pip install python-scripttease; |
||||||
|
|
||||||
|
Configuration |
||||||
|
============= |
||||||
|
|
||||||
|
See :ref:`topics-configuration` for creating a command configuration file. |
||||||
|
|
||||||
|
FAQs |
||||||
|
==== |
||||||
|
|
||||||
|
Have a question? `Just ask`_! |
||||||
|
|
||||||
|
.. _Just ask: https://develmaycare.com/contact/?product=Script%20Tease |
||||||
|
|
@ -0,0 +1,236 @@ |
|||||||
|
.. _how-to: |
||||||
|
|
||||||
|
****** |
||||||
|
How To |
||||||
|
****** |
||||||
|
|
||||||
|
Create a New Command Overlay |
||||||
|
============================ |
||||||
|
|
||||||
|
:ref:`topics-overlays` are used to define the commands supported by a given application, service, or operating system. Commands are defined as a function. |
||||||
|
|
||||||
|
1) Define a Module |
||||||
|
------------------ |
||||||
|
|
||||||
|
The first step is to create a new module in which functions will be defined. |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
# module_name.py |
||||||
|
from ..commands import Command |
||||||
|
|
||||||
|
For overlays that represent an operating system, the ``command_exists()`` function is required: |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
def command_exists(name): |
||||||
|
return name in MAPPINGS |
||||||
|
|
||||||
|
|
||||||
|
2) Define Command Function |
||||||
|
-------------------------- |
||||||
|
|
||||||
|
The purpose of each function is to provide an interface for instantiating a :py:class:`scripttease.library.commands.base.Command` instance. The example below is taken from the ``posix`` module. |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
# module_name.py |
||||||
|
# ... |
||||||
|
|
||||||
|
def mkdir(path, mode=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") |
||||||
|
|
||||||
|
statement.append(path) |
||||||
|
|
||||||
|
return Command(" ".join(statement), **kwargs) |
||||||
|
|
||||||
|
The arguments and any specific keyword arguments are automatically used by the parser, but also serve as a simple interface for programmatic use. |
||||||
|
|
||||||
|
Each function *must* also accept ``**kwargs`` and should set a default for ``comment`` as above. |
||||||
|
|
||||||
|
.. important:: |
||||||
|
Rather than the usual Spinx-based documentation, define the docstring as shown above. This is used to automatically create the documentation for the command. |
||||||
|
|
||||||
|
3) Add Functions to the Mapping |
||||||
|
------------------------------- |
||||||
|
|
||||||
|
The final step adds the function to the mapping. This makes it available to the command factory. |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
# module_name.py |
||||||
|
# ... |
||||||
|
|
||||||
|
MAPPINGS = { |
||||||
|
'mkdir': mkdir, |
||||||
|
} |
||||||
|
|
||||||
|
For overlays that represent an operating system, ``MAPPINGS`` is required -- in addition to ``command_exists()`` above. For commands that are specific to service or application, the name of the dictionary may be anything that is appropriate. For example, ``DJANGO_MAPPINGS``. |
||||||
|
|
||||||
|
Additionally, for an operating system overlay, you may wish to import other mappings and incorporate them into ``MAPPINGS``. |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
# module_name.py |
||||||
|
from ..commands import Command |
||||||
|
from .common import COMMON_MAPPINGS |
||||||
|
from .django import DJANGO_MAPPINGS |
||||||
|
from .pgsql import PGSQL_MAPPINGS |
||||||
|
|
||||||
|
MAPPINGS = { |
||||||
|
# ... |
||||||
|
} |
||||||
|
|
||||||
|
MAPPINGS.update(COMMON_MAPPINGS) |
||||||
|
MAPPINGS.update(DJANGO_MAPPINGS) |
||||||
|
MAPPINGS.update(PGSQL_MAPPINGS) |
||||||
|
|
||||||
|
4) Update Documentation |
||||||
|
----------------------- |
||||||
|
|
||||||
|
Add the command mappings to the ``docs/generate_command_signatures.py`` file. See the script for more details. |
||||||
|
|
||||||
|
Export Commands as a Script |
||||||
|
=========================== |
||||||
|
|
||||||
|
You can export commands as a read-to-use script. For example: |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
config = Config("commands.ini") |
||||||
|
if not config.load(): |
||||||
|
print("Bummer!") |
||||||
|
exit() |
||||||
|
|
||||||
|
script = config.as_script() |
||||||
|
print(script) |
||||||
|
|
||||||
|
Post a Message to Slack |
||||||
|
======================= |
||||||
|
|
||||||
|
The slack function may be used to send a message to a Slack channel. This uses the Incoming Webhooks feature, which requires some additional setup. |
||||||
|
|
||||||
|
.. note:: |
||||||
|
The following steps were accurate as of September 2020. |
||||||
|
|
||||||
|
**1.** Log in to Slack and go to `Your Apps`_. |
||||||
|
|
||||||
|
.. _Your Apps: https://api.slack.com/apps |
||||||
|
|
||||||
|
**2.** Create a new Slack app. |
||||||
|
|
||||||
|
**3.** On the next page, select Incoming Webhooks and then toggle activation. |
||||||
|
|
||||||
|
.. image:: /_static/images/slack-1.jpg |
||||||
|
|
||||||
|
**4.** Next click Add new Webhook to Workspace and select the channel to which the message will be posted. |
||||||
|
|
||||||
|
.. image:: /_static/images/slack-2.jpg |
||||||
|
|
||||||
|
.. image:: /_static/images/slack-3.jpg |
||||||
|
|
||||||
|
**5.** Copy the URL for the new webhook to use as the ``url`` parameter for the Slack command. |
||||||
|
|
||||||
|
.. code-block:: ini |
||||||
|
|
||||||
|
[send a message to slack] |
||||||
|
slack: "This is a test message." |
||||||
|
url: the URL you created goes here |
||||||
|
|
||||||
|
Post a Message to Twist |
||||||
|
======================= |
||||||
|
|
||||||
|
The twist function may be used to send a message to Twist, which requires some additional setup. |
||||||
|
|
||||||
|
.. note:: |
||||||
|
The following steps were accurate as of September 2020. |
||||||
|
|
||||||
|
**1.** Log in to Twist and from the profile menu go to Add Integrations. Then click on Build and "Add a new integration". |
||||||
|
|
||||||
|
**2.** Provide the requested info. |
||||||
|
|
||||||
|
.. image:: _static/images/twist-1.png |
||||||
|
|
||||||
|
**3.** After submitting this info, go to Installation. Select a channel and who to notify. Then click "Install integration". |
||||||
|
|
||||||
|
.. image:: _static/images/twist-2.png |
||||||
|
|
||||||
|
**4.** Copy the "Post content manually" URL for use in your configuration file. |
||||||
|
|
||||||
|
.. code-block:: ini |
||||||
|
|
||||||
|
[post a message to twist] |
||||||
|
twist: "This is a test message." |
||||||
|
url: the URL you created goes here |
||||||
|
|
||||||
|
Use Script Tease With Common Kit |
||||||
|
================================ |
||||||
|
|
||||||
|
Since the focus of Script Tease is to convert plain text instructions into valid command line statements, it does *not* provide support for executing those statements either locally or remotely. However, The shell component of `python-commonkit`_ *does* provide support for executing commands in local POSIX environments. |
||||||
|
|
||||||
|
.. _python-commonkit: https://docs.develmaycare.com/en/python-commonkit/stable/components/#module-commonkit.shell |
||||||
|
|
||||||
|
Here is an example of how to use these packages together: |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
from commonkit.shell import Command |
||||||
|
from scripttease.parsers.utils import load_commands |
||||||
|
|
||||||
|
def execute(step): |
||||||
|
command = Command( |
||||||
|
step.statement, |
||||||
|
comment=step.comment, |
||||||
|
path=step.cd, |
||||||
|
prefix=step.prefix, |
||||||
|
shell=step.shell |
||||||
|
) |
||||||
|
|
||||||
|
# Sudo is a different class, but identical in behavior. |
||||||
|
command.sudo = step.sudo |
||||||
|
|
||||||
|
if command.run(): |
||||||
|
print("[success] %s" % step.comment) |
||||||
|
else: |
||||||
|
print("[failure] %s" % step.comment) |
||||||
|
if step.stop: |
||||||
|
print("I can't go on: %s" % command.error) |
||||||
|
exit(command.code) |
||||||
|
|
||||||
|
# Load SCRIPT TEASE commands from an INI file. These are instances of either Command or ItemizedCommand found in |
||||||
|
# scripttease.library.commands |
||||||
|
steps = load_commands("path/to/steps.ini") |
||||||
|
|
||||||
|
# A failure to load results in None. |
||||||
|
if steps is None: |
||||||
|
print("Failed to load steps.") |
||||||
|
exit(1) |
||||||
|
|
||||||
|
# Iterate through each step to create a COMMON KIT command. |
||||||
|
for step in steps: |
||||||
|
|
||||||
|
# To preview ... |
||||||
|
# print(step.get_statement(cd=True)) |
||||||
|
|
||||||
|
if step.is_itemized: |
||||||
|
for substep in step.get_commands(): |
||||||
|
execute(substep) |
||||||
|
else: |
||||||
|
execute(step) |
||||||
|
|
||||||
|
Common Kit is already a dependency of Script Tease so it is installed by default. The ``execute()`` function is a shortcut that helps deal with itemized commands. The path (``step.cd``) is automatically handled by Common Kit's Command class. |
||||||
|
|
@ -0,0 +1,140 @@ |
|||||||
|
******* |
||||||
|
Project |
||||||
|
******* |
||||||
|
|
||||||
|
Contributing |
||||||
|
============ |
||||||
|
|
||||||
|
We welcome contributions to this and any of `our open source projects`_. There are a number of ways to participate and contribute. See :ref:`contact`. |
||||||
|
|
||||||
|
.. _our open source projects: https://develmaycare.com/products/ |
||||||
|
|
||||||
|
Reporting Issues |
||||||
|
---------------- |
||||||
|
|
||||||
|
Perhaps the easiest way to contribute is to submit an issue. If you have found a bug or error in the documentation, please submit a request. See :ref:`contact`. |
||||||
|
|
||||||
|
.. important:: |
||||||
|
Do **not** report security issues using the issue tracker. Instead, send an email to security@develmaycare.com with details on the issue you've discovered. |
||||||
|
|
||||||
|
Submitting Feature Requests |
||||||
|
--------------------------- |
||||||
|
|
||||||
|
Although we reserve the right to decline new features, we welcome all feature requests. See :ref:`contact`. |
||||||
|
|
||||||
|
Testing and Quality Control |
||||||
|
--------------------------- |
||||||
|
|
||||||
|
Testing involves using Script Tease in real life or in development. Feel free to report any issues you find, or to improve the unit tests. |
||||||
|
|
||||||
|
Pull Requests |
||||||
|
------------- |
||||||
|
|
||||||
|
Pull requests are welcome. Such requests should be associated with an issue. We may ignore pull requests that do not have a corresponding issue, so create an issue if one does not already exist. |
||||||
|
|
||||||
|
Blogging |
||||||
|
-------- |
||||||
|
|
||||||
|
You may help spread awareness of Script Tease by writing blog posts. We are happy to link out to reviews and tutorials from our web site. `Let us know if you've created a blog post`_ that we can share. Be sure to include a link to the post. |
||||||
|
|
||||||
|
You may also provide us with a guest post to be included on our blog. |
||||||
|
|
||||||
|
.. _Let us know if you've created a blog post: https://develmaycare.com/contact/?product=Script%20Tease |
||||||
|
|
||||||
|
.. note:: |
||||||
|
We reserve the right to proof and approve or decline all content posted on our web site. |
||||||
|
|
||||||
|
Development |
||||||
|
=========== |
||||||
|
|
||||||
|
Setting Up For Development |
||||||
|
-------------------------- |
||||||
|
|
||||||
|
1. Clone the repo at https://github.com/develmaycare/python-scripttease |
||||||
|
2. Create a virtual environment and install the requirements from ``requirements.pip`` |
||||||
|
3. See :ref:`how-to`. |
||||||
|
|
||||||
|
Style Guide |
||||||
|
----------- |
||||||
|
|
||||||
|
Script Tease follows `PEP8`_ and (where appropriate) the `Django style guide`_ and `JavaScript Standard Style`_. |
||||||
|
|
||||||
|
.. _Django style guide: https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/ |
||||||
|
.. _JavaScript Standard Style: https://standardjs.com |
||||||
|
.. _PEP8: https://www.python.org/dev/peps/pep-0008/ |
||||||
|
|
||||||
|
We *do* make a few exceptions and provide additional guidance which is documented in our `developer docs`_. |
||||||
|
|
||||||
|
.. _developer docs: https://docs.develmaycare.com/en/developer/ |
||||||
|
|
||||||
|
Dependencies |
||||||
|
============ |
||||||
|
|
||||||
|
.. include:: _includes/project-dependencies.rst |
||||||
|
|
||||||
|
Tests |
||||||
|
===== |
||||||
|
|
||||||
|
.. include:: _includes/project-tests.rst |
||||||
|
|
||||||
|
Releasing |
||||||
|
========= |
||||||
|
|
||||||
|
Versioning |
||||||
|
---------- |
||||||
|
|
||||||
|
Script Tease follows a loose form of `semantic versioning`_. The use of semantic versioning makes it clear when deprecation occurs and backward compatibility is removed. Documented incompatibilities may still exist where deprecation is not feasible (technically or financially). |
||||||
|
|
||||||
|
.. _semantic versioning: https://semver.org/ |
||||||
|
|
||||||
|
Cadence |
||||||
|
------- |
||||||
|
|
||||||
|
New features (and especially new overlays) are planned for release every 3 months. Patch-level changes (to fix bugs or security issues) are always released as needed. |
||||||
|
|
||||||
|
Long-Term Support |
||||||
|
----------------- |
||||||
|
|
||||||
|
Some releases may be designated as long-term support (LTS) releases. Such releases will have security and critical bug fixes applied for 6 months. |
||||||
|
|
||||||
|
Deprecation Policy |
||||||
|
------------------ |
||||||
|
|
||||||
|
Minor releases may deprecate features from a previous minor release. For example, if a feature is deprecated in release 1.1, it will continue to work in the 1.2 release, though warnings may be raised. However, the deprecated feature may be removed in release 1.3 and may not function as previously expected or will raise errors. |
||||||
|
|
||||||
|
Major releases may *always* remove deprecated features. |
||||||
|
|
||||||
|
Patch-level releases *never* remove deprecated features. |
||||||
|
|
||||||
|
Legal |
||||||
|
===== |
||||||
|
|
||||||
|
.. code-block:: text |
||||||
|
|
||||||
|
Copyright (c) Pleasant Tents, LLC |
||||||
|
|
||||||
|
All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, |
||||||
|
are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer. |
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer in the documentation |
||||||
|
and/or other materials provided with the distribution. |
||||||
|
* Neither the name of Pleasant Tents, LLC nor the names of its contributors |
||||||
|
may be used to endorse or promote products derived from this software |
||||||
|
without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -1,29 +0,0 @@ |
|||||||
******** |
|
||||||
Releases |
|
||||||
******** |
|
||||||
|
|
||||||
Versioning |
|
||||||
========== |
|
||||||
|
|
||||||
Script Tease follows a loose form of `semantic versioning`_. The use of semantic versioning makes it clear when deprecation occurs and backward compatibility is removed. Documented incompatibilities may still exist where deprecation is not feasible (technically or financially). |
|
||||||
|
|
||||||
.. _semantic versioning: https://semver.org/ |
|
||||||
|
|
||||||
Cadence |
|
||||||
======= |
|
||||||
|
|
||||||
New features (and especially new overlays) are planned for release every 3 months. Patch-level changes (to fix bugs or security issues) are always released as needed. |
|
||||||
|
|
||||||
Long-Term Support |
|
||||||
================= |
|
||||||
|
|
||||||
Some releases may be designated as long-term support (LTS) releases. Such releases will have security and critical bug fixes applied for 6 months. |
|
||||||
|
|
||||||
Deprecation Policy |
|
||||||
================== |
|
||||||
|
|
||||||
Minor releases may deprecate features from a previous minor release. For example, if a feature is deprecated in release 1.1, it will continue to work in the 1.2 release, though warnings may be raised. However, the deprecated feature may be removed in release 1.3 and may not function as previously expected or will raise errors. |
|
||||||
|
|
||||||
Major releases may *always* remove deprecated features. |
|
||||||
|
|
||||||
Patch-level releases *never* remove deprecated features. |
|
@ -0,0 +1,115 @@ |
|||||||
|
.. _topics-configuration: |
||||||
|
|
||||||
|
************* |
||||||
|
Configuration |
||||||
|
************* |
||||||
|
|
||||||
|
Generating Commands From a File |
||||||
|
=============================== |
||||||
|
|
||||||
|
The :py:class:`scripttease.parsers.ini.Config` class may instantiate commands by loading a configuration file. |
||||||
|
|
||||||
|
.. note:: |
||||||
|
Additional formats such as JSON or YAML may be supported in the future. |
||||||
|
|
||||||
|
An example file: |
||||||
|
|
||||||
|
.. code-block:: ini |
||||||
|
|
||||||
|
[install apache] |
||||||
|
install: apache2 |
||||||
|
|
||||||
|
[create the web site directory] |
||||||
|
mkdir: /var/www/domains/example_com/www |
||||||
|
recursive: yes |
||||||
|
|
||||||
|
[set permissions on the website directory] |
||||||
|
perms: /var/www/domains/example_com/www |
||||||
|
group: www-data |
||||||
|
mode: 775 |
||||||
|
owner: www-data |
||||||
|
|
||||||
|
Notes regarding this format: |
||||||
|
|
||||||
|
- This is the standard format for Python's ConfigParser. If you prefer, you may use ``=`` instead of ``:``. |
||||||
|
- The first part of each command is the INI section and is used as the default comment. |
||||||
|
- The command name *must* be the *first* option in the section. |
||||||
|
- The arguments for the command appear as the value of the first option in the section. Arguments are separated by a |
||||||
|
space. |
||||||
|
- Arguments that should be treated as a single value should be enclosed in double quotes. |
||||||
|
- ``yes`` and ``no`` are interpreted as boolean values. |
||||||
|
- List values, where required, are separated by commas. |
||||||
|
|
||||||
|
.. _topics-configuration-common-parameters: |
||||||
|
|
||||||
|
Common Parameters |
||||||
|
----------------- |
||||||
|
|
||||||
|
All commands support the following common parameters: |
||||||
|
|
||||||
|
- ``comment``: A comment regarding the command. |
||||||
|
- ``condition``: A condition for execution. For example, ``! -f /path/to/some/file.txt`` |
||||||
|
- ``cd``: The path from which a command should be executed. |
||||||
|
- ``environments``: A string or list of comma-separated strings indicating the operational environments in which the command runs. This is *not* used by default, but may be used to programmatically filter commands for a specific environment. For example, development versus live. |
||||||
|
- ``prefix``: A statement to be added prior to executing the command. |
||||||
|
- ``register``: A variable name to which the the success or failure (exit code) of the statement is captured. |
||||||
|
- ``shell``: The shell used to run the commands. For example, ``/bin/bash``. This is generally not important, but can be a problem when attempting to execute some commands (such as Django management commands). |
||||||
|
- ``stop``: ``True`` indicates no other commands should be executed if the given command fails. |
||||||
|
- ``sudo``: ``True`` indicates the command should be automatically prefixed with ``sudo``. If provided as a string, the command is also prefixed with a specific user name. |
||||||
|
- ``tags``: A list of tags used to classify the command. |
||||||
|
|
||||||
|
Defining an "Itemized" Command |
||||||
|
------------------------------ |
||||||
|
|
||||||
|
Certain command definitions may be repeated by defining a list of items. |
||||||
|
|
||||||
|
Example of an "itemized" command: |
||||||
|
|
||||||
|
.. code-block:: ini |
||||||
|
|
||||||
|
[create multiple directories] |
||||||
|
mkdir: /var/www/domains/example_com/$item |
||||||
|
items: www, www/assets, www/content |
||||||
|
recursive: yes |
||||||
|
|
||||||
|
[touch a bunch of files] |
||||||
|
touch: /var/www/domains/example_com/www/$item |
||||||
|
items: index.html, assets/index.html, content/index.html |
||||||
|
|
||||||
|
.. note:: |
||||||
|
Command itemization may vary with the command type. |
||||||
|
|
||||||
|
Pre-Parsing Command Files as Templates |
||||||
|
====================================== |
||||||
|
|
||||||
|
Configuration file may be pre-processed as a Jinja2 template by providing a context dictionary: |
||||||
|
|
||||||
|
.. code-block:: ini |
||||||
|
|
||||||
|
[install apache] |
||||||
|
install: apache |
||||||
|
|
||||||
|
[create the website directory] |
||||||
|
mkdir: /var/www/domains/{{ domain_tld }}/www |
||||||
|
recursive: yes |
||||||
|
|
||||||
|
[set permissions on the website directory] |
||||||
|
perms: /var/www/domains/{{ domain_tld }}/www |
||||||
|
group: www-data |
||||||
|
mode: 775 |
||||||
|
owner: www-data |
||||||
|
|
||||||
|
Then with a config instance: |
||||||
|
|
||||||
|
.. code-block:: python |
||||||
|
|
||||||
|
context = { |
||||||
|
'domain_tld': "example_com", |
||||||
|
} |
||||||
|
|
||||||
|
config = Config("commands.ini", context=context) |
||||||
|
config.load() |
||||||
|
|
||||||
|
for command in config.get_commands(): |
||||||
|
print(command.get_statement(cd=True)) |
||||||
|
print("") |
@ -0,0 +1,24 @@ |
|||||||
|
.. _topics-overlays: |
||||||
|
|
||||||
|
******** |
||||||
|
Overlays |
||||||
|
******** |
||||||
|
|
||||||
|
An overlay is a collection of functions that provide an interface to command creation. An overlay allows configuration files to specify commands in a generic way. When the file is loaded, an overlay may be specified which Script Tease uses to generate commands that are specific to a given operating system. |
||||||
|
|
||||||
|
There are currently four (5) general and re-usable overlays: |
||||||
|
|
||||||
|
- common |
||||||
|
- django |
||||||
|
- mysql |
||||||
|
- pgsql |
||||||
|
- posix |
||||||
|
|
||||||
|
And two (2) overlays that are specific to operating systems: |
||||||
|
|
||||||
|
- centos |
||||||
|
- ubuntu |
||||||
|
|
||||||
|
The examples that follow instantiate command instances from an INI file. Each example is shown with the defaults. All commands support a number of :ref:`topics-configuration-common-parameters`. |
||||||
|
|
||||||
|
.. include:: _includes/overlays.rst |
@ -0,0 +1,11 @@ |
|||||||
|
.. _topics: |
||||||
|
|
||||||
|
****** |
||||||
|
Topics |
||||||
|
****** |
||||||
|
|
||||||
|
.. toctree:: |
||||||
|
:maxdepth: 2 |
||||||
|
|
||||||
|
Configuration <topics-configuration> |
||||||
|
Overlays <topics-overlays> |
@ -0,0 +1,118 @@ |
|||||||
|
# Django |
||||||
|
|
||||||
|
Summary: Work with Django management commands. |
||||||
|
|
||||||
|
## Common Options for Django Commands |
||||||
|
|
||||||
|
You will generally want to include `cd` to change to the project directory and `prefix` to load the virtual environment. |
||||||
|
|
||||||
|
```yaml |
||||||
|
- collect static files: |
||||||
|
django: static |
||||||
|
cd: /path/to/project/source |
||||||
|
prefix: source ../python/bin/activate |
||||||
|
``` |
||||||
|
|
||||||
|
## Automatic Conversion of Django Command Switches |
||||||
|
|
||||||
|
Options provided in the command configuration file are automatically converted to command line switches. |
||||||
|
|
||||||
|
```yaml |
||||||
|
- run database migrations: |
||||||
|
django: migrate |
||||||
|
settings: tenants.example_com.settings |
||||||
|
|
||||||
|
- dump some data: |
||||||
|
django: dumpdata |
||||||
|
indent: 4 |
||||||
|
natural_foreign: yes |
||||||
|
natural_primary: yes |
||||||
|
``` |
||||||
|
|
||||||
|
## Available Commands |
||||||
|
|
||||||
|
### check |
||||||
|
|
||||||
|
```ini |
||||||
|
[run django checks] |
||||||
|
django: check |
||||||
|
``` |
||||||
|
|
||||||
|
```yaml |
||||||
|
- run django checks: |
||||||
|
django: check |
||||||
|
``` |
||||||
|
|
||||||
|
### dumpdata |
||||||
|
|
||||||
|
Dump fixture data. |
||||||
|
|
||||||
|
- app: Required. The name of the app. |
||||||
|
- model: Optional. A model name within the app. |
||||||
|
- path: The path to the JSON file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`. |
||||||
|
|
||||||
|
```ini |
||||||
|
[dump project data] |
||||||
|
django: dumpdata |
||||||
|
app: projects |
||||||
|
|
||||||
|
[dump project categories] |
||||||
|
django: dumpdata |
||||||
|
app: projects |
||||||
|
model: Category |
||||||
|
path: local/projects/fixtures/default-categories.json |
||||||
|
``` |
||||||
|
|
||||||
|
### loaddata |
||||||
|
|
||||||
|
Load fixture data. |
||||||
|
|
||||||
|
- path: The path to the JSON file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`. |
||||||
|
|
||||||
|
### migrate |
||||||
|
|
||||||
|
Run database migrations. |
||||||
|
|
||||||
|
```ini |
||||||
|
[run database migrations] |
||||||
|
django: migrate |
||||||
|
``` |
||||||
|
|
||||||
|
```yaml |
||||||
|
- run database migrations: |
||||||
|
django: migrate |
||||||
|
``` |
||||||
|
|
||||||
|
### static |
||||||
|
|
||||||
|
Collect static files. |
||||||
|
|
||||||
|
```ini |
||||||
|
[collect static files] |
||||||
|
django: static |
||||||
|
``` |
||||||
|
|
||||||
|
```yaml |
||||||
|
- collect static files: |
||||||
|
django: static |
||||||
|
``` |
||||||
|
|
||||||
|
## Custom or Ad Hoc Commands |
||||||
|
|
||||||
|
It is possible to work with any Django management command provided the parameters may be specified as a switch. |
||||||
|
|
||||||
|
```ini |
||||||
|
[run any django command] |
||||||
|
django: command_name |
||||||
|
first_option_name: asdf |
||||||
|
second_option_name: 1234 |
||||||
|
third_option_name: yes |
||||||
|
``` |
||||||
|
|
||||||
|
```yaml |
||||||
|
- run any django command: |
||||||
|
django: command_name |
||||||
|
first_option_name: asdf |
||||||
|
second_option_name: 1234 |
||||||
|
third_option_name: yes |
||||||
|
``` |
@ -0,0 +1,57 @@ |
|||||||
|
# PostgreSQL |
||||||
|
|
||||||
|
Summary: Work with Postgres databases. |
||||||
|
|
||||||
|
## Common Options |
||||||
|
|
||||||
|
- `admin_pass`: The password off the admin-authorized user. |
||||||
|
- `admin_user`: The user name of the admin-authorized user. Default: `postgres` |
||||||
|
- `host`: The host name. Default: `localhost` |
||||||
|
- `port`: The TCP port. Default: `5432` |
||||||
|
|
||||||
|
## Available Commands |
||||||
|
|
||||||
|
### pgsql.create |
||||||
|
|
||||||
|
Create a database. Argument is the database name. |
||||||
|
|
||||||
|
- `owner`: The user name that owns the database. |
||||||
|
|
||||||
|
```ini |
||||||
|
[create the database] |
||||||
|
pgsql.create: database_name |
||||||
|
``` |
||||||
|
|
||||||
|
### pgsql.drop |
||||||
|
|
||||||
|
Drop a database. Argument is the database name. |
||||||
|
|
||||||
|
### pgsql.dump |
||||||
|
|
||||||
|
Dump the database schema. Argument is the database name. |
||||||
|
|
||||||
|
- `path`: The path to the dump file. Default: `dump.sql` |
||||||
|
|
||||||
|
### pgsql.exec |
||||||
|
|
||||||
|
Execute an SQL statement. Argument is the SQL statement. |
||||||
|
|
||||||
|
- `database`: The name of the database where the statement will be executed. Default: `default` |
||||||
|
|
||||||
|
### pgsql.exists |
||||||
|
|
||||||
|
Determine if a database exists. Argument is the database name. |
||||||
|
|
||||||
|
### pgsql.user.create |
||||||
|
|
||||||
|
Create a user. Argument is the user name. |
||||||
|
|
||||||
|
- `password`: The user's password. |
||||||
|
|
||||||
|
### pgsql.user.drop |
||||||
|
|
||||||
|
Remove a user. Argument is the user name. |
||||||
|
|
||||||
|
### pgsql.user.exists |
||||||
|
|
||||||
|
Determine if a user exists. Argument is the user name. |
@ -0,0 +1,148 @@ |
|||||||
|
# POSIX |
||||||
|
|
||||||
|
Summary: Work with common POSIX-compliant commands.. |
||||||
|
|
||||||
|
## Available Commands |
||||||
|
|
||||||
|
### append |
||||||
|
|
||||||
|
Append content to a file. Argument is the file name. |
||||||
|
|
||||||
|
- `content`: The content to be appended. |
||||||
|
|
||||||
|
### archive |
||||||
|
|
||||||
|
Create an archive (tarball). Argument is the target file or directory. |
||||||
|
|
||||||
|
- `absolute`: Don't strip leading slashes from file names. |
||||||
|
- `view`: View the progress. |
||||||
|
- `exclude`: Exclude file name patterns. |
||||||
|
- `strip`: Strip component paths to the given depth (integer). |
||||||
|
- `to`: The path to where the archive will be created. |
||||||
|
|
||||||
|
### copy |
||||||
|
|
||||||
|
Copy a file or directory. First argument is the target file/directory. Second argument is the destination. |
||||||
|
|
||||||
|
- `overwrite`: Overwrite an existing target. |
||||||
|
- `recursive`: Copy directories recursively. |
||||||
|
|
||||||
|
### dir |
||||||
|
|
||||||
|
Create a directory. Argument is the path. |
||||||
|
|
||||||
|
- `group`: Set the group to the given group name. |
||||||
|
- `mode`: Set the mode on the path. |
||||||
|
- `owner`: Set the owner to the given owner name. |
||||||
|
- `recursive`: Create the full path even if intermediate directories do not exist. |
||||||
|
|
||||||
|
### extract |
||||||
|
|
||||||
|
Extract an archive (tarball). Argument is the path to the archive file. |
||||||
|
|
||||||
|
- `absolute`: Strip leading slashes from file names. |
||||||
|
- `view`: View the progress. |
||||||
|
- `exclude`: Exclude file name patterns. |
||||||
|
- `strip`: Strip component paths to the given depth (integer). |
||||||
|
- `to`: The path to where the archive will be extracted. Defaults to the current working directory. |
||||||
|
|
||||||
|
### file |
||||||
|
|
||||||
|
Create a file. Argument is the path. |
||||||
|
|
||||||
|
- `content`: The content of the file. Otherwise, an empty file is created. |
||||||
|
- `group`: Set the group to the given group name. |
||||||
|
- `mode`: Set the mode on the path. |
||||||
|
- `owner`: Set the owner to the given owner name. |
||||||
|
|
||||||
|
### link |
||||||
|
|
||||||
|
Create a symlink. First argument is the target. Second argument is the destination. |
||||||
|
|
||||||
|
- `force`: Force creation of the link. |
||||||
|
|
||||||
|
### move |
||||||
|
|
||||||
|
Move a file or directory. First argument is the target. Second argument is the desitnation. |
||||||
|
|
||||||
|
### perms |
||||||
|
|
||||||
|
Set permissions on a file or directory. Argument is the path. |
||||||
|
|
||||||
|
- `group`: Set the group to the given group name. |
||||||
|
- `mode`: Set the mode on the path. |
||||||
|
- `owner`: Set the owner to the given owner name. |
||||||
|
- `recursive`: Apply permission recursively (directories only). |
||||||
|
|
||||||
|
### push |
||||||
|
|
||||||
|
Push (rsync) a path to a remote server. First argument is the local path. Second argument is the remote path. |
||||||
|
|
||||||
|
- `delete`: Delete existing files/directories. |
||||||
|
- `host`: The host name. Required. |
||||||
|
- `key_file`: Use the given SSL (private) key. Required. |
||||||
|
- `links`: Copy symlinks. |
||||||
|
- `exclude`: Exclude patterns from the given (local) file. |
||||||
|
- `port`: The TCP port on the host. Default: `22` |
||||||
|
- `recursive`: Operate recursively on directories. |
||||||
|
- `user`: The user name. Required. |
||||||
|
|
||||||
|
### remove |
||||||
|
|
||||||
|
Remove a file or directory. Argument is the path. |
||||||
|
|
||||||
|
- `force`: Force the removal. |
||||||
|
- `recursive`: Remove (directories) rescurisvely. |
||||||
|
|
||||||
|
### rename |
||||||
|
|
||||||
|
Rename a file or directory. First argument is the target. Second argument is the destination. |
||||||
|
|
||||||
|
### replace |
||||||
|
|
||||||
|
Replace something in a file. First argument is the path. |
||||||
|
|
||||||
|
- `backup`: Create a backup. |
||||||
|
- `delimiiter`: The sed delimiter. Default: `/` |
||||||
|
- `find`: The text to be found. Required. |
||||||
|
- `sub`: The text to be replaced. Required. |
||||||
|
|
||||||
|
### scopy |
||||||
|
|
||||||
|
Copy a file to a remote server. First argument is the local file name. Second argument is the remote destination. |
||||||
|
|
||||||
|
- `key_file`: The private key file to use for the connection. |
||||||
|
- `host`: The host name. Required. |
||||||
|
- `port`: The TCP port. Default: `22` |
||||||
|
- `user`: The user name. Required. |
||||||
|
|
||||||
|
### ssl |
||||||
|
|
||||||
|
Use Let's Encrypt (certbot) to acquire an SSL certificate. Argument is the domain name. |
||||||
|
|
||||||
|
- `email`: The email address for "agree tos". Default: `webmaster@domain_name` |
||||||
|
- `webroot`: The webroot to use. Default: `/var/www/maint/www` |
||||||
|
|
||||||
|
### sync |
||||||
|
|
||||||
|
Sync (rsync) local files and directories. First argument is the target. Second argument is the destination. |
||||||
|
|
||||||
|
- `delete`: Delete existing files/directories. |
||||||
|
- `links`: Copy symlinks. |
||||||
|
- `exclude`: Exclude patterns from the given (local) file. |
||||||
|
- `recursive`: Operate recursively on directories. |
||||||
|
|
||||||
|
### touch |
||||||
|
|
||||||
|
Touch a file, whether it exists or not. Argument is the path. |
||||||
|
|
||||||
|
### wait |
||||||
|
|
||||||
|
Wait for n number of seconds before continuing. Argument is the number of seconds. |
||||||
|
|
||||||
|
### write |
||||||
|
|
||||||
|
Write to a file. Argument is the path. |
||||||
|
|
||||||
|
- `content`: The content to write to the file. Replaces existing content. |
||||||
|
|
@ -0,0 +1,20 @@ |
|||||||
|
# Python |
||||||
|
|
||||||
|
Summary: Work with Python. |
||||||
|
|
||||||
|
## Available Commands |
||||||
|
|
||||||
|
### pip |
||||||
|
|
||||||
|
Use the pip command. Argument is the package name. |
||||||
|
|
||||||
|
- `op`: The operation; `install` (the default), `remove`, or `updgrade`. |
||||||
|
- `venv`: The name of the virtual environment to use. |
||||||
|
|
||||||
|
### pip3 |
||||||
|
|
||||||
|
Use Python3 pip. See pip above. |
||||||
|
|
||||||
|
### virtualenv |
||||||
|
|
||||||
|
Create a python virtual environment. Argument is the environment name. |
@ -0,0 +1,58 @@ |
|||||||
|
# Python Script Tease |
||||||
|
|
||||||
|
## Overview |
||||||
|
|
||||||
|
Script Tease is a library and command line tool for generating Bash commands programmatically and (especially) using configuration files. |
||||||
|
|
||||||
|
The primary focus (and limit) is to convert plain text instructions (in INI or YAML format) into valid command line statements for a given platform. It does *not* provide support for executing those statements. |
||||||
|
|
||||||
|
## Concepts |
||||||
|
|
||||||
|
### Command Generation |
||||||
|
|
||||||
|
Script Tease may be used in two (2) ways: |
||||||
|
|
||||||
|
1. Using the library to programmatically define commands and export them as command line statements. |
||||||
|
2. Using the `tease` command to generate commands from a configuration file. See [command file configuration](config/command-file.md). |
||||||
|
|
||||||
|
This documentation focuses on the second method, but the developer docs may be used in your own implementation. |
||||||
|
|
||||||
|
### Self-Documenting |
||||||
|
|
||||||
|
The format of INI and YAML files is self-documenting. The command comment is this section (INI) or start of a list item (YAML). This ensures that all commands have a basic description of their purpose or intent. |
||||||
|
|
||||||
|
### Snippets |
||||||
|
|
||||||
|
An *snippet* is simply a tokenized command that may be customized based on the instructions found in a command file. Related snippets are collected into groups and then merged into a larger set that define the capabilities of a specific operating system. |
||||||
|
|
||||||
|
!!! note |
||||||
|
At present, the only fully defined operating systems are for Cent OS and Ubuntu. |
||||||
|
|
||||||
|
Snippets are defined in Python dictionaries. These include a "canonical" command name as the key and either a string or list which define the command. In both cases, the contents are parsed as Jinja templates. There are various approaches to evaluating a snippet. |
||||||
|
|
||||||
|
First: The snippet is a simple mapping of command name and command snippet. This is easy. Find the command name in the dictionary, and we have the snippet to be used. For example the `append` command in the `posix` dictionary. |
||||||
|
|
||||||
|
Second: The snippet is a mapping of command name and a list of snippets to be combined. Find the command name in the dictionary, and iterate through the snippets. For example, many of the commands in the `posix` dictionary takes this form. Command identification is the same as the first condition. |
||||||
|
|
||||||
|
Third: The command is a mapping to informal sub-commands. Examples include `apache` and `system` in the `ubuntu` dictionary. There are a couple of ways to handle this in the config file: |
||||||
|
|
||||||
|
- Use the outer command as the command with the inner command as the first (and perhaps only) argument. For example `apache: reload` or `system: upgrade`. |
||||||
|
- Use a "dotted path" to find the command. For example: `apache.reload: (implicity True)` or `system.upgrade: (implicitly True)`. Or `apache.enable_site: example.com`. |
||||||
|
|
||||||
|
The first approach complicates things when detecting actual sub-commands (below). Script Tease supports both of these approaches. |
||||||
|
|
||||||
|
Fourth: The command also expects a sub-command. In some cases, the sub-command may be implicit, like `pip install`. In other cases, a number of sub-commands may be pre-defined, but ad hoc sub-commands should also be supported as with Django commands. |
||||||
|
|
||||||
|
Fifth: Builds upon the third and fourth conditions where the commands have lots of options, some of which may be defined at runtime. Postgres and MySQL may use be presented as informal sub-commands, but there are lots of options and challenges in building the final command. Django management commands have a number of standard options, specific options, and must also support ad hoc commands. |
||||||
|
|
||||||
|
## Terms and Definitions |
||||||
|
|
||||||
|
command |
||||||
|
: When used in Script Tease documentation, this is a command instance which contains the properties and parameters for a command line statement. |
||||||
|
|
||||||
|
statement |
||||||
|
: A specific statement (string) to be executed. A *statement* is contained within a *command*. |
||||||
|
|
||||||
|
## License |
||||||
|
|
||||||
|
Python Script Tease is released under the BSD 3 clause license. |
@ -1,171 +0,0 @@ |
|||||||
--- |
|
||||||
title: CLI |
|
||||||
--- |
|
||||||
|
|
||||||
The `tease` command may be used to parse a steps file, providing additional utilities for working with commands. |
|
||||||
|
|
||||||
## Getting Help |
|
||||||
|
|
||||||
Use `tease -h` to get started. There are three (3) sub-commands: [docs](#the-docs-sub-command), [inventory](#the-inventory-sub-command), and [script](#the-script-sub-command). |
|
||||||
|
|
||||||
```text |
|
||||||
usage: tease [-h] [-v] [--version] docs, inventory, script ... |
|
||||||
|
|
||||||
positional arguments: |
|
||||||
docs, inventory, script |
|
||||||
Commands |
|
||||||
docs Output documentation instead of code. |
|
||||||
inventory (inv) Copy an inventory item to a local directory. |
|
||||||
script Output the commands. |
|
||||||
|
|
||||||
optional arguments: |
|
||||||
-h, --help show this help message and exit |
|
||||||
-v Show version number and exit. |
|
||||||
--version Show verbose version information and exit. |
|
||||||
|
|
||||||
NOTES |
|
||||||
|
|
||||||
This command is used to parse configuration files and output the commands. |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
## The Docs Sub-Command |
|
||||||
|
|
||||||
A nice benefit of using configuration for commands is that the information may be used to output documentation. This allows the automatic creation of an install guide or tutorial using exactly the same commands that would be used for the actual work. |
|
||||||
|
|
||||||
Additionally, Script Tease provides the [explain](commands/messages.md#explain) and [screenshot](commands/messages.md#screenshot) commands that help provide extra content for documentary output. |
|
||||||
|
|
||||||
```text |
|
||||||
usage: tease docs [-h] [-o= {html,md,plain,rst}] [-C= VARIABLES] [-i= STEPS_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}] |
|
||||||
[-T= TEMPLATE_LOCATIONS] [-w= OUTPUT_FILE] [-V= VARIABLES_FILE] [-D] [-p] |
|
||||||
|
|
||||||
optional arguments: |
|
||||||
-h, --help show this help message and exit |
|
||||||
-o= {html,md,plain,rst}, --output-format= {html,md,plain,rst} |
|
||||||
The output format; HTML, Markdown, plain text, or ReStructuredText. |
|
||||||
-C= VARIABLES, --context= VARIABLES |
|
||||||
Context variables for use in pre-parsing the config and templates. In the form of: name:value |
|
||||||
-i= STEPS_FILE, --input-file= STEPS_FILE |
|
||||||
The path to the configuration file. |
|
||||||
-O= OPTIONS, --option= OPTIONS |
|
||||||
Common command options in the form of: name:value |
|
||||||
-P= {centos,ubuntu}, --profile= {centos,ubuntu} |
|
||||||
The OS profile to use. |
|
||||||
-T= TEMPLATE_LOCATIONS, --template-path= TEMPLATE_LOCATIONS |
|
||||||
The location of template files that may be used with the template command. |
|
||||||
-w= OUTPUT_FILE, --write= OUTPUT_FILE |
|
||||||
Write the output to disk. |
|
||||||
-V= VARIABLES_FILE, --variables-file= VARIABLES_FILE |
|
||||||
Load variables from a file. |
|
||||||
-D, --debug Enable debug mode. Produces extra output. |
|
||||||
-p Preview mode. |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
## The Inventory Sub-Command |
|
||||||
|
|
||||||
Script Tease ships with few pre-defined configurations that may be copied to a local directory. |
|
||||||
|
|
||||||
Use `tease inv ?` to list available inventory items. |
|
||||||
|
|
||||||
```text |
|
||||||
usage: tease inventory [-h] [-P= TO_PATH] [-D] [-p] name |
|
||||||
|
|
||||||
positional arguments: |
|
||||||
name The name of the inventory item. Use ? to list available items. |
|
||||||
|
|
||||||
optional arguments: |
|
||||||
-h, --help show this help message and exit |
|
||||||
-P= TO_PATH, --path= TO_PATH |
|
||||||
The path to where the item should be copied. Defaults to the current working directory. |
|
||||||
-D, --debug Enable debug mode. Produces extra output. |
|
||||||
-p Preview mode. |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
!!! note "Road Map" |
|
||||||
A future release will include support for multiple inventory locations that may be defined by the user. |
|
||||||
|
|
||||||
## The Script Sub-Command |
|
||||||
|
|
||||||
The `script` sub-command exports command configuration to actual Bash statements. Minimum usage is |
|
||||||
|
|
||||||
```bash |
|
||||||
tease -i steps.ini |
|
||||||
``` |
|
||||||
|
|
||||||
This will output the statements represented in the specified configuration file. There are quite a few other parameters. |
|
||||||
|
|
||||||
```text |
|
||||||
usage: tease script [-h] [-c] [-s] [-C= VARIABLES] [-i= STEPS_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}] [-T= TEMPLATE_LOCATIONS] |
|
||||||
[-w= OUTPUT_FILE] [-V= VARIABLES_FILE] [-D] [-p] |
|
||||||
|
|
||||||
optional arguments: |
|
||||||
-h, --help show this help message and exit |
|
||||||
-c, --color Enable code highlighting for terminal output. |
|
||||||
-s, --shebang Add the shebang to the beginning of the output. |
|
||||||
-C= VARIABLES, --context= VARIABLES |
|
||||||
Context variables for use in pre-parsing the config and templates. In the form of: name:value |
|
||||||
-i= STEPS_FILE, --input-file= STEPS_FILE |
|
||||||
The path to the configuration file. |
|
||||||
-O= OPTIONS, --option= OPTIONS |
|
||||||
Common command options in the form of: name:value |
|
||||||
-P= {centos,ubuntu}, --profile= {centos,ubuntu} |
|
||||||
The OS profile to use. |
|
||||||
-T= TEMPLATE_LOCATIONS, --template-path= TEMPLATE_LOCATIONS |
|
||||||
The location of template files that may be used with the template command. |
|
||||||
-w= OUTPUT_FILE, --write= OUTPUT_FILE |
|
||||||
Write the output to disk. |
|
||||||
-V= VARIABLES_FILE, --variables-file= VARIABLES_FILE |
|
||||||
Load variables from a file. |
|
||||||
-D, --debug Enable debug mode. Produces extra output. |
|
||||||
-p Preview mode. |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### Context Variables May be Provided on the Command Line |
|
||||||
|
|
||||||
To supply context variables on the command line: |
|
||||||
|
|
||||||
```bash |
|
||||||
tease -C domain_name:example.com -C domain_tld:example_com |
|
||||||
``` |
|
||||||
|
|
||||||
!!! important |
|
||||||
Variables provided on the command will always override those provided in a file. |
|
||||||
|
|
||||||
### Loading Context Variables from a File |
|
||||||
|
|
||||||
Context variables may be loaded from a file: |
|
||||||
|
|
||||||
```ini |
|
||||||
[domain_name] |
|
||||||
value = example.com |
|
||||||
|
|
||||||
[domain_tld] |
|
||||||
value = example_com |
|
||||||
``` |
|
||||||
|
|
||||||
The variables above are available as template variables in a steps file. |
|
||||||
|
|
||||||
For example, ``{{ domain_name }}`` becomes ``example.com``. |
|
||||||
|
|
||||||
To load the variables file, use the `-V` switch: |
|
||||||
|
|
||||||
```bash |
|
||||||
tease -i steps.ini -V variables.ini |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### Setting Common Options for All Commands |
|
||||||
|
|
||||||
Rather than include a common parameter in the configuration file, it is possible to specify a common option on the command line. |
|
||||||
|
|
||||||
```bash |
|
||||||
tease -O sudo:yes |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### The Difference Between Variables and Options |
|
||||||
|
|
||||||
Variables are used to pre-process configuration files as templates, while common options are passed to *all* command instances. |
|
@ -1,21 +0,0 @@ |
|||||||
# Getting Started |
|
||||||
|
|
||||||
## System Requirements |
|
||||||
|
|
||||||
Python 3.6 or greater is required. |
|
||||||
|
|
||||||
## Install |
|
||||||
|
|
||||||
To install: |
|
||||||
|
|
||||||
```bash |
|
||||||
pip install python-scripttease; |
|
||||||
``` |
|
||||||
|
|
||||||
## Configuration |
|
||||||
|
|
||||||
See [Steps File](topics/steps-file.md) for creating a configuration file. |
|
||||||
|
|
||||||
## FAQs |
|
||||||
|
|
||||||
**Does Script Tease execute commands?** No. Script Tease generates statements that may be [saved to a file for examination and execution](how-to/create-executable-script.md). Or a [custom implementation](how-to/use-with-commonkit.md) may be created to execute the generated statements. |
|
@ -1,14 +0,0 @@ |
|||||||
# Create an Executable Script |
|
||||||
|
|
||||||
To create a script that is ready to execute: |
|
||||||
|
|
||||||
```bash |
|
||||||
tease -i commands.ini -s -w script.sh |
|
||||||
chmod +x script.sh |
|
||||||
``` |
|
||||||
|
|
||||||
The `-s` switch causes the [shebang](https://linuxhandbook.com/shebang/) to be included at the top of the output. This defaults to: |
|
||||||
|
|
||||||
`#! /usr/bin/env bash` |
|
||||||
|
|
||||||
The script is now ready to run: `./script.sh` |
|
@ -1,51 +0,0 @@ |
|||||||
# Define A Custom Command |
|
||||||
|
|
||||||
!!! note |
|
||||||
It is not currently possible to define a custom command which may be used with the `tease` command. |
|
||||||
|
|
||||||
## 1) Create A Function |
|
||||||
|
|
||||||
Create a function that does what you want: |
|
||||||
|
|
||||||
```python |
|
||||||
# mycommands.py |
|
||||||
from scripttease.lib.commands.base import Command |
|
||||||
|
|
||||||
def do_something_impressive(arg1, **kwargs): |
|
||||||
return Command("ls -ls %s" % arg1, **kwargs) |
|
||||||
``` |
|
||||||
|
|
||||||
!!! important |
|
||||||
kwargs are *always* required. |
|
||||||
|
|
||||||
## 2) Create A Mapping |
|
||||||
|
|
||||||
```python |
|
||||||
# mycommands.py |
|
||||||
MAPPINGS = { |
|
||||||
'impressive': do_something_impressive, |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
`impressive` is now mapped to the function which creates the command. |
|
||||||
|
|
||||||
## 3) Supply The Mapping to the Command Factory |
|
||||||
|
|
||||||
```python |
|
||||||
from scripttease.lib.factories import command_factory |
|
||||||
from scripttease.lib.loaders import INILoader |
|
||||||
from .mycommands import MAPPINGS |
|
||||||
|
|
||||||
ini = INILoader("path/to/commands.ini") |
|
||||||
ini.load() |
|
||||||
|
|
||||||
commands = command_factory(ini, mappings=MAPPINGS) |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
## 4) Include the Custom Command |
|
||||||
|
|
||||||
```ini |
|
||||||
[this is my custom command] |
|
||||||
impressive: testing |
|
||||||
``` |
|
@ -1,41 +0,0 @@ |
|||||||
# Post a Message to Slack |
|
||||||
|
|
||||||
The `slack` command may be used to send a message to a Slack channel. This uses the Incoming Webhooks feature, which requires some additional setup. |
|
||||||
|
|
||||||
!!! note |
|
||||||
The following steps were accurate as of September 2020. |
|
||||||
|
|
||||||
## 1) Log in to Slack |
|
||||||
|
|
||||||
Log in to Slack and go to [Your Apps](https://api.slack.com/apps). |
|
||||||
|
|
||||||
## 2) Create New Slack App |
|
||||||
|
|
||||||
Create a new Slack app. |
|
||||||
|
|
||||||
## 3) Select Incoming Webhooks |
|
||||||
|
|
||||||
On the next page, select Incoming Webhooks and then toggle activation. |
|
||||||
|
|
||||||
![incoming webhooks](images/slack-1.jpg) |
|
||||||
|
|
||||||
## 4) Select Channel |
|
||||||
|
|
||||||
Next, click "Add new Webhook to Workspace" and select the channel to which the message will be posted. |
|
||||||
|
|
||||||
![slack 2](images/slack-2.jpg) |
|
||||||
|
|
||||||
![slack 3](images/slack-3.jpg) |
|
||||||
|
|
||||||
## 5) Copy the URL |
|
||||||
|
|
||||||
Copy the URL for the new webhook to use as the ``url`` parameter for the Slack command. |
|
||||||
|
|
||||||
```ini |
|
||||||
[send a message to slack] |
|
||||||
slack: "This is a test message." |
|
||||||
url: the URL you created goes here |
|
||||||
``` |
|
||||||
|
|
||||||
!!! tip |
|
||||||
Define this URL in a `variables.ini` file if you need to send multiple messages to the same channel. |
|
@ -1,35 +0,0 @@ |
|||||||
# Post a Message to Twist |
|
||||||
|
|
||||||
The `twist` command may be used to send a message to Twist, which requires some additional setup. |
|
||||||
|
|
||||||
!!! note |
|
||||||
The following steps were accurate as of September 2020. |
|
||||||
|
|
||||||
## 1) Log In To Twist |
|
||||||
|
|
||||||
Log in to Twist and from the profile menu go to Add Integrations. Then click on Build and "Add a new integration". |
|
||||||
|
|
||||||
## 2) Provide Requested Information |
|
||||||
|
|
||||||
Provide the requested info. |
|
||||||
|
|
||||||
![twist 1](images/twist-1.png) |
|
||||||
|
|
||||||
## 3) Install Integration |
|
||||||
|
|
||||||
After submitting this info, go to Installation. Select a channel and who to notify. Then click "Install integration". |
|
||||||
|
|
||||||
![twist 2](images/twist-2.png) |
|
||||||
|
|
||||||
## 4) Copy the URL |
|
||||||
|
|
||||||
Copy the "Post content manually" URL for use in your configuration file. |
|
||||||
|
|
||||||
```ini |
|
||||||
[post a message to twist] |
|
||||||
twist: "This is a test message." |
|
||||||
url: the URL you created goes here |
|
||||||
``` |
|
||||||
|
|
||||||
!!! tip |
|
||||||
Define this URL in a `variables.ini` file if you need to send multiple messages to the same channel. |
|
@ -1,56 +0,0 @@ |
|||||||
# Use Script Tease With Common Kit |
|
||||||
|
|
||||||
Since the focus of Script Tease is to convert plain text instructions into valid command line statements, it does *not* provide support for executing those statements either locally or remotely. However, the shell component of [python-commonkit](https://docs.develmaycare.com/en/python-commonkit/stable/components/#module-commonkit.shell) *does* provide support for executing commands in local POSIX environments. |
|
||||||
|
|
||||||
Here is an example of how to use these packages together: |
|
||||||
|
|
||||||
```python |
|
||||||
from commonkit.shell import Command |
|
||||||
from scripttease.exceptions import InvalidInput |
|
||||||
from scripttease.lib.factories import command_factory |
|
||||||
from scripttease.lib.loaders import INILoader |
|
||||||
|
|
||||||
def execute(step): |
|
||||||
command = Command( |
|
||||||
step.statement, |
|
||||||
comment=step.comment, |
|
||||||
path=step.cd, |
|
||||||
prefix=step.prefix, |
|
||||||
shell=step.shell |
|
||||||
) |
|
||||||
|
|
||||||
# Sudo is a different class, but identical in behavior. |
|
||||||
command.sudo = step.sudo |
|
||||||
|
|
||||||
if command.run(): |
|
||||||
print("[success] %s" % step.comment) |
|
||||||
else: |
|
||||||
print("[failure] %s" % step.comment) |
|
||||||
if step.stop: |
|
||||||
print("I can't go on: %s" % command.error) |
|
||||||
exit(command.code) |
|
||||||
|
|
||||||
|
|
||||||
ini = INILoader("path/to/steps.ini") |
|
||||||
ini.load() |
|
||||||
|
|
||||||
try: |
|
||||||
steps = command_factory(ini) |
|
||||||
except InvalidInput as e: |
|
||||||
print("%s: I can't go on." % e) |
|
||||||
exit(1) |
|
||||||
|
|
||||||
# A failure to load results in None. |
|
||||||
if steps is None: |
|
||||||
print("Failed to load steps. Bummer.") |
|
||||||
exit(1) |
|
||||||
|
|
||||||
# Iterate through each step to create a Common Kit command instance. |
|
||||||
for step in steps: |
|
||||||
|
|
||||||
# To preview ... |
|
||||||
# print(step.get_statement(cd=True)) |
|
||||||
execute(step) |
|
||||||
``` |
|
||||||
|
|
||||||
Common Kit is already a dependency of Script Tease, so it is installed by default. The ``execute()`` function provides the interface between Script Tease command instances and Common Kit command instances. |
|
@ -1,66 +0,0 @@ |
|||||||
# Python Script Tease |
|
||||||
|
|
||||||
## Overview |
|
||||||
|
|
||||||
Script Tease is a library and command line tool for generating Bash commands programmatically and (especially) using configuration files. |
|
||||||
|
|
||||||
The primary focus (and limit) is to convert plain text instructions (in INI or YAML format) into valid command line statements for a given platform. It does *not* provide support for executing those statements. |
|
||||||
|
|
||||||
!!! warning |
|
||||||
YAML support is currently untested. |
|
||||||
|
|
||||||
## Concepts |
|
||||||
|
|
||||||
### Command Generation |
|
||||||
|
|
||||||
Script Tease may be used in three (3) ways: |
|
||||||
|
|
||||||
1. Using the library to programmatically define commands and export them as command line statements. |
|
||||||
2. Using the `tease` command to generate commands from a configuration file. See [steps file](topics/steps-file.md). |
|
||||||
3. Using the command file format and library to create a custom implementation. |
|
||||||
|
|
||||||
This documentation focuses on the second method, but the [developer docs](./reference) may be used to guide custom implementations for 1 and 3 above. |
|
||||||
|
|
||||||
### Self-Documenting |
|
||||||
|
|
||||||
The format of INI and YAML files is self-documenting. The command comment is this section (INI) or start of a list item (YAML). This ensures that all commands have a basic description of their purpose or intent. |
|
||||||
|
|
||||||
```ini |
|
||||||
[install apache] |
|
||||||
install: apache2 |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
```yaml |
|
||||||
- install apache |
|
||||||
install: apache2 |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### Representing Commands |
|
||||||
|
|
||||||
All commands are represented by simple, Python functions. These functions are responsible for accepting the arguments provided (usually by a command loader) and converting them into a common `Command` instance. This instance is then capable of generating a finished statement that may be used on the command line. |
|
||||||
|
|
||||||
#### Profiles |
|
||||||
|
|
||||||
Profiles contain command functions that are specific to an operating system. Not all operating systems support the same commands. |
|
||||||
|
|
||||||
!!! note |
|
||||||
At present, the only defined operating system profiles are for Cent OS and Ubuntu. |
|
||||||
|
|
||||||
Profiles import and appropriate all other available commands. |
|
||||||
|
|
||||||
## Terms and Definitions |
|
||||||
|
|
||||||
command |
|
||||||
: When used in Script Tease documentation, this is a command instance which contains the properties and parameters for assembling a command line statement. |
|
||||||
|
|
||||||
profile |
|
||||||
: A collection of commands that work for a specific operating system profile. |
|
||||||
|
|
||||||
statement |
|
||||||
: A specific statement (string) to be executed. A *statement* is generated from a *command*. |
|
||||||
|
|
||||||
## License |
|
||||||
|
|
||||||
Python Script Tease is released under the BSD 3 clause license. |
|
@ -1,14 +0,0 @@ |
|||||||
# Itemized Commands |
|
||||||
|
|
||||||
It is sometimes useful to create a single command entry that does the same thing with different input. Script Tease facilitates this with the "itemized" command. |
|
||||||
|
|
||||||
```ini |
|
||||||
[install common utilities] |
|
||||||
install: $item |
|
||||||
items: curl, git, lftp, wget |
|
||||||
``` |
|
||||||
|
|
||||||
In the example above, statements will be generated to install each of the packages named in `items`. |
|
||||||
|
|
||||||
!!! note |
|
||||||
Content commands (`explain` and `screenshot`) and Template commands cannot be itemized. |
|
@ -1,43 +0,0 @@ |
|||||||
# Templates |
|
||||||
|
|
||||||
Script Tease supports processing of template files using Jinja2. |
|
||||||
|
|
||||||
## Location of Templates |
|
||||||
|
|
||||||
By default, the location of template files are reckoned as relative to the steps file. |
|
||||||
|
|
||||||
```text |
|
||||||
package_name/ |
|
||||||
|-- steps.ini |
|
||||||
|-- templates |
|
||||||
| `-- httpd.conf |
|
||||||
``` |
|
||||||
|
|
||||||
Upon loading the `steps.ini` file, any reference to a template file is assumed to be in the `templates/` directory in the same location. |
|
||||||
|
|
||||||
```ini |
|
||||||
[create the apache config] |
|
||||||
template: httpd.conf /etc/apache2/sites-available/example.app.conf |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
!!! tip |
|
||||||
The `-T` switch of the `tease` command may be used to add template locations. |
|
||||||
|
|
||||||
## Template Context |
|
||||||
|
|
||||||
The context variables available to parse the template are: |
|
||||||
|
|
||||||
- Variables received on the command line are included in the context. |
|
||||||
- Variables loaded from a file. |
|
||||||
- Options passed to the loader from the command line are also included. |
|
||||||
|
|
||||||
Additionally, any unrecognized parameters specified in the command are included in the context. For example: |
|
||||||
|
|
||||||
```ini |
|
||||||
[create the httpd.conf file] |
|
||||||
template: httpd.conf /etc/apache/sites-available/example.com.conf |
|
||||||
ssl_enabled: yes |
|
||||||
``` |
|
||||||
|
|
||||||
`ssl_enabled` is not a normal parameter for the template command, so it is included in the context. |
|
@ -1,132 +0,0 @@ |
|||||||
# Django |
|
||||||
|
|
||||||
Summary: Work with Django management commands. |
|
||||||
|
|
||||||
## Common Options for Django Commands |
|
||||||
|
|
||||||
You will want to include `cd` to change to the project directory (where `manage.py` lives) and supply `venv` to load the virtual environment. |
|
||||||
|
|
||||||
```ini |
|
||||||
[collect static files] |
|
||||||
django.static: |
|
||||||
cd: /path/to/project/source |
|
||||||
venv: ../python |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
## Automatic Conversion of Django Command Switches |
|
||||||
|
|
||||||
Options provided in the steps file are automatically converted to command line switches. |
|
||||||
|
|
||||||
```ini |
|
||||||
[run database migrations] |
|
||||||
django.migrate: |
|
||||||
settings: tenants.example_com.settings |
|
||||||
|
|
||||||
[dump some data] |
|
||||||
django.dump: projects.Category |
|
||||||
indent: 4 |
|
||||||
natural_foreign: yes |
|
||||||
natural_primary: yes |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
`settings` becomes "--settings=tenants.example_com.settings". `indent` becomes "--indent=4". `natural_foreign` and `natural_primary` become "--natural-foreign" and "--natural-primary" respectively. |
|
||||||
|
|
||||||
## Available Commands |
|
||||||
|
|
||||||
### check |
|
||||||
|
|
||||||
```ini |
|
||||||
[run django checks] |
|
||||||
django.check: |
|
||||||
stop: yes |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### collectstatic |
|
||||||
|
|
||||||
Alias: static |
|
||||||
|
|
||||||
Collect static files. |
|
||||||
|
|
||||||
```ini |
|
||||||
[collect static files] |
|
||||||
django.static: |
|
||||||
``` |
|
||||||
|
|
||||||
### createsuperuser |
|
||||||
|
|
||||||
Create a superuser account. |
|
||||||
|
|
||||||
```ini |
|
||||||
[create the root user account] |
|
||||||
django.createsuperuser: root |
|
||||||
email: root@example.com |
|
||||||
``` |
|
||||||
|
|
||||||
### dumpdata |
|
||||||
|
|
||||||
Alias: dump |
|
||||||
|
|
||||||
Dump fixture data. |
|
||||||
|
|
||||||
- `target` (str): Required. The name of the app or `app.Model`. |
|
||||||
- `format` (str): `json` (default) or `xml`. |
|
||||||
- `path` (str): The path to the output file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`. |
|
||||||
|
|
||||||
```ini |
|
||||||
[dump project data] |
|
||||||
django.dump: projects |
|
||||||
|
|
||||||
[dump project categories] |
|
||||||
django.dump: projects.Category |
|
||||||
path: local/projects/fixtures/default-categories.json |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### loaddata |
|
||||||
|
|
||||||
Alias: load |
|
||||||
|
|
||||||
Load fixture data. |
|
||||||
|
|
||||||
- `target` (str): Required. The name of the app or `app.Model`. |
|
||||||
- `format` (str): `json` (default) or `xml` |
|
||||||
- `path` (str): The path to the JSON file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`. |
|
||||||
|
|
||||||
```ini |
|
||||||
[load project categories] |
|
||||||
django.load: projects |
|
||||||
path: local/projects/fixtures/default-categories.json |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### migrate |
|
||||||
|
|
||||||
Run database migrations. |
|
||||||
|
|
||||||
```ini |
|
||||||
[run database migrations] |
|
||||||
django.migrate: |
|
||||||
stop: yes |
|
||||||
``` |
|
||||||
|
|
||||||
## Custom or Ad Hoc Commands |
|
||||||
|
|
||||||
It is possible to work with any Django management command provided the parameters may be specified as a switch. |
|
||||||
|
|
||||||
```ini |
|
||||||
[run any django command] |
|
||||||
django: command_name |
|
||||||
option_one: asdf |
|
||||||
option_two: 1234 |
|
||||||
option_three: yes |
|
||||||
``` |
|
||||||
|
|
||||||
This will generate a statement like: |
|
||||||
|
|
||||||
```bash |
|
||||||
./manage.py command_name --option-one="asdf" --option-two=1234 --option-three |
|
||||||
|
|
||||||
``` |
|
@ -1,119 +0,0 @@ |
|||||||
# PostgreSQL |
|
||||||
|
|
||||||
Summary: Work with Postgres databases. |
|
||||||
|
|
||||||
## Common Options |
|
||||||
|
|
||||||
- `host` (str): The host name. Default: `localhost` |
|
||||||
- `password` (str): The password of the user executing the command. |
|
||||||
- `port` (int): The TCP port. Default: `5432` |
|
||||||
- `user` (str): The username of the user executing the command. Default: `postgres` |
|
||||||
|
|
||||||
## Automatic Conversion of Postgres Command Switches |
|
||||||
|
|
||||||
Options provided in the steps file are automatically converted to command line switches. For example: |
|
||||||
|
|
||||||
```ini |
|
||||||
[create a soft backup of the database schema] |
|
||||||
pgsql.dump: example_app |
|
||||||
schema_only: yes |
|
||||||
path: /tmp/example_app.sql |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
`schema_only` becomes "--schema-only". |
|
||||||
|
|
||||||
## Available Commands |
|
||||||
|
|
||||||
### pgsql.create |
|
||||||
|
|
||||||
Create a database. Argument is the database name. |
|
||||||
|
|
||||||
- `owner` (str): The username that owns the database. |
|
||||||
|
|
||||||
```ini |
|
||||||
[create the database] |
|
||||||
pgsql.create: database_name |
|
||||||
``` |
|
||||||
|
|
||||||
### pgsql.drop |
|
||||||
|
|
||||||
Drop a database. Argument is the database name. |
|
||||||
|
|
||||||
```ini |
|
||||||
[drop the testing database] |
|
||||||
pgsql.drop: testing_example_app |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### pgsql.dump |
|
||||||
|
|
||||||
Dump the database schema. Argument is the database name. |
|
||||||
|
|
||||||
- `path` (str): The path to the dump file. Default: `database_name.sql` |
|
||||||
|
|
||||||
```ini |
|
||||||
[create a soft backup of the database] |
|
||||||
pgsql.dump: example_app |
|
||||||
column_inserts: yes |
|
||||||
path: /tmp/example_app.sql |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### pgsql.exists |
|
||||||
|
|
||||||
Determine if a database exists. Argument is the database name. |
|
||||||
|
|
||||||
```ini |
|
||||||
[determine if the database exists] |
|
||||||
pgsql.exists: example_app |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### pgsql.grant |
|
||||||
|
|
||||||
Grant privileges to a user. Argument is the username. Database option is required. |
|
||||||
|
|
||||||
- `database` (str): The name of the database where the target object exists. |
|
||||||
- `privileges` (str): The privileges to be granted. Default `ALL` (see [Postgres docs](https://www.postgresql.org/docs/current/sql-grant.html)) |
|
||||||
- `schema` (str): The schema name to which the privileges apply. |
|
||||||
- `table` (str): The table name to which privileges apply. |
|
||||||
|
|
||||||
!!! note |
|
||||||
A schema name or table name is required. |
|
||||||
|
|
||||||
```ini |
|
||||||
[grant select access to bob] |
|
||||||
pgsql.grant: bob |
|
||||||
database: example_app |
|
||||||
privileges: select |
|
||||||
schema: public |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### pgsql.user |
|
||||||
|
|
||||||
Create a user. Argument is the user name. |
|
||||||
|
|
||||||
- `password` (str): The user's password. |
|
||||||
|
|
||||||
```ini |
|
||||||
[create a database user] |
|
||||||
pgsql.user: username |
|
||||||
``` |
|
||||||
|
|
||||||
Remove a user. |
|
||||||
|
|
||||||
```ini |
|
||||||
[remove a database user] |
|
||||||
pgsql.user: username |
|
||||||
op: remove |
|
||||||
``` |
|
||||||
|
|
||||||
Determine if a user exists. |
|
||||||
|
|
||||||
```ini |
|
||||||
[determine if database user exists] |
|
||||||
pgsql.user: username |
|
||||||
op: exists |
|
||||||
``` |
|
@ -1,264 +0,0 @@ |
|||||||
# POSIX |
|
||||||
|
|
||||||
Summary: Work with common POSIX-compliant commands.. |
|
||||||
|
|
||||||
## Available Commands |
|
||||||
|
|
||||||
### append |
|
||||||
|
|
||||||
Append content to a file. Argument is the file name. |
|
||||||
|
|
||||||
- `content` (str): The content to be appended. |
|
||||||
|
|
||||||
```ini |
|
||||||
[add to the log file] |
|
||||||
append: /path/to/file.log |
|
||||||
content: This is a test. |
|
||||||
``` |
|
||||||
|
|
||||||
### archive |
|
||||||
|
|
||||||
Create an archive (tarball). Argument is the target file or directory. |
|
||||||
|
|
||||||
- `absolute` (bool): Don't strip leading slashes from file names. Default `False` |
|
||||||
- `exclude` (str): Exclude file name patterns. |
|
||||||
- `file_name` (str): The name of the archive file. Default `archive.tgz` |
|
||||||
- `strip` (int): Strip component paths to the given depth. |
|
||||||
- `to_path` (str): The path to where the archive will be created. Default `.` |
|
||||||
- `view` (bool): View the progress. Default `False` |
|
||||||
|
|
||||||
```ini |
|
||||||
[create an archive of the site] |
|
||||||
archive: /path/to/file_or_directory |
|
||||||
file_name: testing.tgz |
|
||||||
to: /tmp |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### certbot |
|
||||||
|
|
||||||
Alias: ssl |
|
||||||
|
|
||||||
Use Let's Encrypt (certbot) to acquire an SSL certificate. Argument is the domain name. |
|
||||||
|
|
||||||
- `email`: The email address for "agree tos". Default: `webmaster@domain_name` |
|
||||||
- `webroot`: The webroot to use. Default: `/var/www/maint/www` |
|
||||||
|
|
||||||
```ini |
|
||||||
[get an SSL cert] |
|
||||||
ssl: example.app |
|
||||||
email: webmaster@example.app |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### copy |
|
||||||
|
|
||||||
Copy a file or directory. First argument is the target file/directory. Second argument is the destination. |
|
||||||
|
|
||||||
- `overwrite` (bool): Overwrite an existing target. |
|
||||||
- `recursive` (bool): Copy directories recursively. |
|
||||||
|
|
||||||
```ini |
|
||||||
[copy a directory] |
|
||||||
copy: /path/to/directory /path/to/new_directory |
|
||||||
overwrite: yes |
|
||||||
recursive: yes |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### dir |
|
||||||
|
|
||||||
Create a directory. Argument is the path. |
|
||||||
|
|
||||||
- `group` (str): Set the group to the given group name. |
|
||||||
- `mode` (str): Set the mode on the path. |
|
||||||
- `owner` (str): Set the owner to the given owner name. |
|
||||||
- `recursive` (str): Create the full path even if intermediate directories do not exist. |
|
||||||
|
|
||||||
```ini |
|
||||||
[create a directory] |
|
||||||
dir: /path/to/directory |
|
||||||
group: www-data |
|
||||||
mode: 755 |
|
||||||
owner: deploy |
|
||||||
recursive: yes |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### extract |
|
||||||
|
|
||||||
Extract an archive (tarball). Argument is the path to the archive file. |
|
||||||
|
|
||||||
- `absolute` (bool): Don't strip leading slashes from file names. Default `False` |
|
||||||
- `exclude` (str): Exclude file name patterns. |
|
||||||
- `strip` (int): Strip component paths to the given depth. |
|
||||||
- `to_path` (str): The path to where the archive will be created. Default `./` |
|
||||||
- `view` (bool): View the progress. Default `False` |
|
||||||
|
|
||||||
```ini |
|
||||||
[extract an archive] |
|
||||||
extract: /path/to/archive.tgz |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### link |
|
||||||
|
|
||||||
Create a symlink. First argument is the source. |
|
||||||
|
|
||||||
- `force` (bool): Force creation of the link. |
|
||||||
- `target` (str): The location of the link. Defaults to the current directory and the base name of the source. |
|
||||||
|
|
||||||
```ini |
|
||||||
[create a symlink] |
|
||||||
link: /path/to/project/releases/1.0 |
|
||||||
cd: /path/to/project |
|
||||||
force: yes |
|
||||||
target: current |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### move |
|
||||||
|
|
||||||
Move a file or directory. First argument is the target. Second argument is the desitnation. |
|
||||||
|
|
||||||
```ini |
|
||||||
[move a file] |
|
||||||
move: /path/to/file.txt /new/path/to/file.txt |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### perms |
|
||||||
|
|
||||||
Set permissions on a file or directory. Argument is the path. |
|
||||||
|
|
||||||
- `group` (str): Set the group to the given group name. |
|
||||||
- `mode` (str): Set the mode on the path. |
|
||||||
- `owner` (str): Set the owner to the given owner name. |
|
||||||
- `recursive` (bool): Apply permission recursively (directories only). |
|
||||||
|
|
||||||
```ini |
|
||||||
[set permissions on the shared directory] |
|
||||||
perms: /path/to/project/shared |
|
||||||
group: www-data |
|
||||||
mode: 775 |
|
||||||
owner: deploy |
|
||||||
recursive: yes |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### push |
|
||||||
|
|
||||||
Alias: rsync |
|
||||||
|
|
||||||
Push (rsync) a path to a remote server. First argument is the local path. Second argument is the remote path. |
|
||||||
|
|
||||||
- `delete` (bool): Delete existing files/directories. Default `False` |
|
||||||
- `host` (str): The host name. Required. |
|
||||||
- `key_file` (str): Use the given SSL (private) key. |
|
||||||
- `links` (bool): Copy symlinks. Default `True |
|
||||||
- `exclude` (str): Exclude patterns from the given (local) file. |
|
||||||
- `port` (int): The TCP port on the host. Default: `22` |
|
||||||
- `recursive` (bool): Operate recursively on directories. |
|
||||||
- `user` (str): The username. |
|
||||||
|
|
||||||
```ini |
|
||||||
[push the project to the server] |
|
||||||
push: /path/to/project /path/on/server |
|
||||||
key_file: ~/.ssh/example_app |
|
||||||
host: example.app |
|
||||||
user: deploy |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### remove |
|
||||||
|
|
||||||
Remove a file or directory. Argument is the path. |
|
||||||
|
|
||||||
- `force` (bool): Force the removal. Default `False` |
|
||||||
- `recursive` (bool): Remove all directories in the path. Default `False` |
|
||||||
|
|
||||||
```ini |
|
||||||
[remove a directory] |
|
||||||
remove: /path/to/directory |
|
||||||
force: yes |
|
||||||
recusrive: yes |
|
||||||
``` |
|
||||||
|
|
||||||
### replace |
|
||||||
|
|
||||||
Replace something in a file. First argument is the path. |
|
||||||
|
|
||||||
- `backup`: Backup file extension. Default `.b` |
|
||||||
- `delimiiter`: The sed delimiter. Default: `/` |
|
||||||
- `find`: The text to be found. Required. |
|
||||||
- `sub`: The text to be replaced. Required. |
|
||||||
|
|
||||||
```ini |
|
||||||
[replace text in a file] |
|
||||||
replace: /path/to/file.txt |
|
||||||
find: testing |
|
||||||
sub: 123 |
|
||||||
``` |
|
||||||
|
|
||||||
### scopy |
|
||||||
|
|
||||||
Copy a file to a remote server. First argument is the local file name. Second argument is the remote destination. |
|
||||||
|
|
||||||
- `key_file` (str): The private key file to use for the connection. |
|
||||||
- `host` (str): The host name. Required. |
|
||||||
- `port` (int): The TCP port. Default: `22` |
|
||||||
- `user` (str): The username. Required. |
|
||||||
|
|
||||||
```ini |
|
||||||
[copy a file to the server] |
|
||||||
scopy: /path/to/local.txt path/to/remove.txt |
|
||||||
host: example.app |
|
||||||
user: deploy |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### sync |
|
||||||
|
|
||||||
Sync (rsync) local files and directories. First argument is the target. Second argument is the destination. |
|
||||||
|
|
||||||
- `delete` (bool): Delete existing files/directories. |
|
||||||
- `links` (bool): Copy symlinks. |
|
||||||
- `exclude` (str): Exclude patterns from the given (local) file. |
|
||||||
- `recursive` (bool): Operate recursively on directories. |
|
||||||
|
|
||||||
```ini |
|
||||||
[syncrhonize files on the local machine] |
|
||||||
sync: /path/to/project /path/to/sync/directory |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### touch |
|
||||||
|
|
||||||
Touch a file, whether it exists or not. Argument is the path. |
|
||||||
|
|
||||||
```ini |
|
||||||
[touch a file] |
|
||||||
touch: /path/to/file.txt |
|
||||||
``` |
|
||||||
|
|
||||||
### wait |
|
||||||
|
|
||||||
Wait for n number of seconds before continuing. Argument is the number of seconds. |
|
||||||
|
|
||||||
```ini |
|
||||||
[wait just a minute] |
|
||||||
wait: 60 |
|
||||||
``` |
|
||||||
|
|
||||||
### write |
|
||||||
|
|
||||||
Write to a file. Argument is the path. |
|
||||||
|
|
||||||
- `content` (str): The content to write to the file. Replaces existing content. |
|
||||||
|
|
||||||
```ini |
|
||||||
[replace an existing file] |
|
||||||
write: /path/to/file.txt |
|
||||||
content: This whole file has been replaced. |
|
||||||
|
|
||||||
``` |
|
@ -1,49 +0,0 @@ |
|||||||
# Python |
|
||||||
|
|
||||||
Summary: Work with Python. |
|
||||||
|
|
||||||
## Available Commands |
|
||||||
|
|
||||||
### pip |
|
||||||
|
|
||||||
Use the pip command. Argument is the package name. |
|
||||||
|
|
||||||
- `op` (str): The operation; `install` (the default) or `remove`. |
|
||||||
- `upgrade` (bool): Upgrade the package. |
|
||||||
- `venv` (str): The name of the virtual environment to use. |
|
||||||
- `version` (int): The pip version to use. Default `3` |
|
||||||
|
|
||||||
```ini |
|
||||||
[install django] |
|
||||||
pip: django |
|
||||||
cd: /path/to/project |
|
||||||
venv: python |
|
||||||
``` |
|
||||||
|
|
||||||
### pip_file |
|
||||||
|
|
||||||
Alias: pipf |
|
||||||
|
|
||||||
Install packages from a pip file. |
|
||||||
|
|
||||||
- `venv` (str): The name of the virtual environment to use. |
|
||||||
- `version` (int): The pip version to use. Default `3` |
|
||||||
|
|
||||||
```ini |
|
||||||
[install dependencies] |
|
||||||
pip_file: deploy/packages/testing.pip |
|
||||||
cd: path/to/project |
|
||||||
venv: python |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
### virtualenv |
|
||||||
|
|
||||||
Create a python virtual environment. Argument is the environment name. |
|
||||||
|
|
||||||
```ini |
|
||||||
[create the virtual environment] |
|
||||||
virtualenv: python |
|
||||||
cd: /path/to/project |
|
||||||
|
|
||||||
``` |
|
@ -1,52 +0,0 @@ |
|||||||
site_name: Script Tease |
|
||||||
copyright: Copyright © Pleasant Tents, LLC. All rights reserved. |
|
||||||
markdown_extensions: |
|
||||||
- toc: |
|
||||||
permalink: True |
|
||||||
- admonition |
|
||||||
- attr_list |
|
||||||
- def_list |
|
||||||
- pymdownx.superfences |
|
||||||
nav: |
|
||||||
- Introduction: index.md |
|
||||||
- Getting Started: getting-started.md |
|
||||||
- Topics: |
|
||||||
- Steps File: topics/steps-file.md |
|
||||||
- Variables File: topics/variables.md |
|
||||||
- Itemized Commands: topics/itemized-commands.md |
|
||||||
- Templates: topics/templates.md |
|
||||||
- How-To: |
|
||||||
- Create an Executable Script: how-to/create-executable-script.md |
|
||||||
- Define a Custom Command: how-to/define-custom-command.md |
|
||||||
- Post a Message to Slack: how-to/post-message-slack.md |
|
||||||
- Post a Message to Twist: how-to/post-message-twist.md |
|
||||||
- Use Script Tease with Common Kit: how-to/use-with-commonkit.md |
|
||||||
- Usage: |
|
||||||
- CentOS: usage/centos.md |
|
||||||
- Django: usage/django.md |
|
||||||
- Messages: usage/messages.md |
|
||||||
- MySQL: usage/mysql.md |
|
||||||
- PHP: usage/php.md |
|
||||||
- Postgres: usage/pgsql.md |
|
||||||
- POSIX: usage/posix.md |
|
||||||
- Python: usage/python.md |
|
||||||
- Ubuntu: usage/ubuntu.md |
|
||||||
- CLI: cli.md |
|
||||||
repo_name: Git Traction |
|
||||||
repo_url: https://gittraction.com/diff6/python-scripttease |
|
||||||
theme: |
|
||||||
name: material |
|
||||||
palette: |
|
||||||
|
|
||||||
# Palette toggle for light mode |
|
||||||
- scheme: default |
|
||||||
toggle: |
|
||||||
icon: material/brightness-7 |
|
||||||
name: Switch to dark mode |
|
||||||
|
|
||||||
# Palette toggle for dark mode |
|
||||||
- scheme: slate |
|
||||||
toggle: |
|
||||||
icon: material/brightness-4 |
|
||||||
name: Switch to light mode |
|
||||||
|
|
@ -0,0 +1,26 @@ |
|||||||
|
site_name: Script Tease |
||||||
|
copyright: Copyright © Pleasant Tents, LLC. All rights reserved. |
||||||
|
markdown_extensions: |
||||||
|
- toc: |
||||||
|
permalink: True |
||||||
|
- admonition |
||||||
|
- attr_list |
||||||
|
- def_list |
||||||
|
nav: |
||||||
|
- Home: index.md |
||||||
|
- Configuration: |
||||||
|
- Command File: config/command-file.md |
||||||
|
- Variables: config/variables.md |
||||||
|
- Commands: |
||||||
|
- Django: commands/django.md |
||||||
|
- Messages: commands/messages.md |
||||||
|
- MySQL: commands/mysql.md |
||||||
|
- PHP: commands/php.md |
||||||
|
- Postgres: commands/pgsql.md |
||||||
|
- POSIX: commands/posix.md |
||||||
|
- Python: commands/python.md |
||||||
|
- Profiles: |
||||||
|
- Ubuntu: profiles/ubuntu.md |
||||||
|
# - Developer Reference: /developers/ |
||||||
|
repo_name: GitLab |
||||||
|
repo_url: https://git.sixgunsoftware.com/python-scripttease |
@ -0,0 +1,382 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
|
||||||
|
from argparse import ArgumentParser, RawDescriptionHelpFormatter |
||||||
|
from commonkit import highlight_code, indent, smart_cast, write_file |
||||||
|
from commonkit.logging import LoggingHelper |
||||||
|
from commonkit.shell import EXIT |
||||||
|
from markdown import markdown |
||||||
|
import os |
||||||
|
import sys |
||||||
|
|
||||||
|
sys.path.insert(0, "../") |
||||||
|
|
||||||
|
from scripttease.constants import LOGGER_NAME |
||||||
|
from scripttease.lib.contexts import Context |
||||||
|
from scripttease.lib.loaders import load_variables, INILoader, YMLLoader |
||||||
|
from scripttease.variables import PATH_TO_SCRIPT_TEASE |
||||||
|
from scripttease.version import DATE as VERSION_DATE, VERSION |
||||||
|
|
||||||
|
DEBUG = 10 |
||||||
|
|
||||||
|
logging = LoggingHelper(colorize=True, name=LOGGER_NAME) |
||||||
|
log = logging.setup() |
||||||
|
|
||||||
|
|
||||||
|
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 + "+new" |
||||||
|
|
||||||
|
# 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) |
||||||
|
|
||||||
|
# Validate snippets before continuing. |
||||||
|
valid = list() |
||||||
|
for snippet in loader.get_snippets(): |
||||||
|
if snippet.is_valid: |
||||||
|
valid.append(True) |
||||||
|
else: |
||||||
|
log.error("Invalid snippet: %s" % snippet.name) |
||||||
|
valid.append(False) |
||||||
|
|
||||||
|
if not all(valid): |
||||||
|
exit(EXIT.ERROR) |
||||||
|
|
||||||
|
# Generate output. |
||||||
|
if args.docs: |
||||||
|
output = list() |
||||||
|
for snippet in loader.get_snippets(): |
||||||
|
|
||||||
|
# Will this every happen? |
||||||
|
# if snippet is None: |
||||||
|
# continue |
||||||
|
|
||||||
|
if snippet.name == "explain": |
||||||
|
if snippet.header: |
||||||
|
if args.docs == "plain": |
||||||
|
output.append("***** %s *****" % snippet.name.title()) |
||||||
|
elif args.docs == "rst": |
||||||
|
output.append(snippet.name.title()) |
||||||
|
output.append("=" * len(snippet.name)) |
||||||
|
else: |
||||||
|
output.append("## %s" % snippet.name.title()) |
||||||
|
|
||||||
|
output.append("") |
||||||
|
|
||||||
|
output.append(snippet.content) |
||||||
|
output.append("") |
||||||
|
elif snippet.name == "screenshot": |
||||||
|
if args.docs == "html": |
||||||
|
b = list() |
||||||
|
b.append('<img src="%s"' % snippet.args[0]) |
||||||
|
b.append('alt="%s"' % snippet.caption or snippet.comment) |
||||||
|
|
||||||
|
if snippet.classes: |
||||||
|
b.append('class="%s"' % snippet.classes) |
||||||
|
|
||||||
|
if snippet.height: |
||||||
|
b.append('height="%s"' % snippet.height) |
||||||
|
|
||||||
|
if snippet.width: |
||||||
|
b.append('width="%s"' % snippet) |
||||||
|
|
||||||
|
output.append(" ".join(b) + ">") |
||||||
|
output.append("") |
||||||
|
elif args.docs == "plain": |
||||||
|
output.append(snippet.args[0]) |
||||||
|
output.append("") |
||||||
|
elif args.docs == "rst": |
||||||
|
output.append(".. figure:: %s" % snippet.args[0]) |
||||||
|
|
||||||
|
if snippet.caption: |
||||||
|
output.append(indent(":alt: %s" % snippet.caption or snippet.comment)) |
||||||
|
|
||||||
|
if snippet.height: |
||||||
|
output.append(indent(":height: %s" % snippet.height)) |
||||||
|
|
||||||
|
if snippet.width: |
||||||
|
output.append(indent(":width: %s" % snippet.width)) |
||||||
|
|
||||||
|
output.append("") |
||||||
|
else: |
||||||
|
output.append("![%s](%s)" % (snippet.caption or snippet.comment, snippet.args[0])) |
||||||
|
output.append("") |
||||||
|
elif snippet.name == "template": |
||||||
|
if args.docs == "plain": |
||||||
|
output.append("+++") |
||||||
|
output.append(snippet.get_content()) |
||||||
|
output.append("+++") |
||||||
|
elif args.docs == "rst": |
||||||
|
output.append(".. code-block:: %s" % snippet.get_target_language()) |
||||||
|
output.append("") |
||||||
|
output.append(indent(snippet.get_content())) |
||||||
|
output.append("") |
||||||
|
else: |
||||||
|
output.append("```%s" % snippet.get_target_language()) |
||||||
|
output.append(snippet.get_content()) |
||||||
|
output.append("```") |
||||||
|
output.append("") |
||||||
|
else: |
||||||
|
statement = snippet.get_statement(include_comment=False, include_register=False, include_stop=False) |
||||||
|
if statement is not None: |
||||||
|
line = snippet.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 = list() |
||||||
|
for snippet in loader.get_snippets(): |
||||||
|
# Explanations and screenshots don't produce usable statements but may be added as comments. |
||||||
|
if snippet.name in ("explain", "screenshot"): |
||||||
|
# commands.append("# %s" % snippet.content) |
||||||
|
# commands.append("") |
||||||
|
continue |
||||||
|
|
||||||
|
statement = snippet.get_statement() |
||||||
|
if statement is not None: |
||||||
|
commands.append(statement) |
||||||
|
commands.append("") |
||||||
|
|
||||||
|
if args.color_enabled: |
||||||
|
print(highlight_code("\n".join(commands), language="bash")) |
||||||
|
else: |
||||||
|
print("\n".join(commands)) |
||||||
|
|
||||||
|
if args.output_file: |
||||||
|
write_file(args.output_file, "\n".join(commands)) |
||||||
|
|
||||||
|
exit(EXIT.OK) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
execute() |
||||||
|
|
||||||
|
|
@ -1,374 +1,126 @@ |
|||||||
import logging |
# Imports |
||||||
|
|
||||||
from commonkit import smart_cast |
from commonkit import smart_cast |
||||||
from commonkit.shell import EXIT |
from configparser import ConfigParser |
||||||
import logging |
import logging |
||||||
import os |
import os |
||||||
from ..lib.loaders import load_variables, INILoader, YMLLoader |
from ..constants import LOGGER_NAME |
||||||
from ..variables import LOGGER_NAME, PATH_TO_SCRIPT_TEASE |
|
||||||
|
|
||||||
log = logging.getLogger(LOGGER_NAME) |
log = logging.getLogger(LOGGER_NAME) |
||||||
|
|
||||||
|
# Exports |
||||||
|
|
||||||
|
__all__ = ( |
||||||
|
"context_from_cli", |
||||||
|
"filters_from_cli", |
||||||
|
"options_from_cli", |
||||||
|
"variables_from_file", |
||||||
|
) |
||||||
|
|
||||||
|
# Functions |
||||||
|
|
||||||
|
|
||||||
|
def context_from_cli(variables): |
||||||
|
"""Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
||||||
|
|
||||||
|
:param variables: A list of strings of ``name:value`` pairs. |
||||||
|
:type variables: list[str] |
||||||
|
|
||||||
|
:rtype: dict |
||||||
|
|
||||||
|
The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
||||||
|
|
||||||
|
""" |
||||||
|
context = dict() |
||||||
|
for i in variables: |
||||||
|
key, value = i.split(":") |
||||||
|
context[key] = smart_cast(value) |
||||||
|
|
||||||
|
return context |
||||||
|
|
||||||
|
|
||||||
|
def filters_from_cli(filters): |
||||||
|
"""Takes a list of filters given in the form of ``name:value`` and converts them to a dictionary. |
||||||
|
|
||||||
|
:param filters: A list of strings of ``attribute:value`` pairs. |
||||||
|
:type filters: list[str] |
||||||
|
|
||||||
|
:rtype: dict |
||||||
|
|
||||||
|
""" |
||||||
|
_filters = dict() |
||||||
|
for i in filters: |
||||||
|
key, value = i.split(":") |
||||||
|
if key not in filters: |
||||||
|
_filters[key] = list() |
||||||
|
|
||||||
|
_filters[key].append(value) |
||||||
|
|
||||||
|
return _filters |
||||||
|
|
||||||
|
|
||||||
def options_from_cli(options): |
def options_from_cli(options): |
||||||
|
"""Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
||||||
|
|
||||||
|
:param options: A list of strings of ``name:value`` pairs. |
||||||
|
:type options: list[str] |
||||||
|
|
||||||
|
:rtype: dict |
||||||
|
|
||||||
|
The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
||||||
|
|
||||||
|
""" |
||||||
_options = dict() |
_options = dict() |
||||||
for token in options: |
for i in options: |
||||||
try: |
key, value = i.split(":") |
||||||
key, value = token.split(":") |
_options[key] = smart_cast(value) |
||||||
_options[key] = smart_cast(value) |
|
||||||
except ValueError: |
|
||||||
_options[token] = True |
|
||||||
|
|
||||||
return _options |
return _options |
||||||
|
|
||||||
|
|
||||||
def loader(path, context=None, options=None, template_locations=None): |
def variables_from_file(path): |
||||||
# The path may have been given as a file name (steps.ini), path, or an inventory name. |
"""Loads variables from a given INI file. |
||||||
input_locations = [ |
|
||||||
path, |
|
||||||
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", path, "steps.ini"), |
|
||||||
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", 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" % path) |
|
||||||
return None |
|
||||||
|
|
||||||
# Initialize the loader. |
:param path: The path to the INI file. |
||||||
if path.endswith(".ini"): |
:type path: str |
||||||
_loader = INILoader( |
|
||||||
path, |
|
||||||
context=context, |
|
||||||
locations=template_locations, |
|
||||||
**options |
|
||||||
) |
|
||||||
elif path.endswith(".yml"): |
|
||||||
_loader = YMLLoader( |
|
||||||
path, |
|
||||||
context=context, |
|
||||||
locations=template_locations, |
|
||||||
**options |
|
||||||
) |
|
||||||
else: |
|
||||||
log.error("Unsupported file format: %s" % path) |
|
||||||
return None |
|
||||||
|
|
||||||
# Load the commands. |
:rtype: dict | None |
||||||
if not _loader.load(): |
|
||||||
log.error("Failed to load the input file: %s" % path) |
The resulting dictionary flattens the sections and values. For example: |
||||||
return None |
|
||||||
|
.. code-block:: ini |
||||||
|
|
||||||
|
[copyright] |
||||||
|
name = ACME, Inc. |
||||||
|
year = 2020 |
||||||
|
|
||||||
return _loader |
[domain] |
||||||
|
name = example.com |
||||||
|
tld = example_com |
||||||
|
|
||||||
|
The dictionary would contain: |
||||||
|
|
||||||
def subcommands(subparsers): |
.. code-block:: python |
||||||
"""Initialize sub-commands. |
|
||||||
|
|
||||||
:param subparsers: The subparsers instance from argparse. |
{ |
||||||
|
'copyright_name': "ACME, Inc.", |
||||||
|
'copyright_year': 2020, |
||||||
|
'domain_name': "example.com", |
||||||
|
'domain_tld': "example_com", |
||||||
|
} |
||||||
|
|
||||||
""" |
""" |
||||||
sub = SubCommands(subparsers) |
if not os.path.exists(path): |
||||||
sub.docs() |
log.warning("Variables file does not exist: %s" % path) |
||||||
sub.inventory() |
return None |
||||||
sub.script() |
|
||||||
|
ini = ConfigParser() |
||||||
|
ini.read(path) |
||||||
def variables_from_cli(context, variables): |
|
||||||
for token in variables: |
variables = dict() |
||||||
try: |
for section in ini.sections(): |
||||||
key, value = token.split(":") |
for key, value in ini.items(section): |
||||||
context.add(key, smart_cast(value)) |
key = "%s_%s" % (section, key) |
||||||
except ValueError: |
variables[key] = smart_cast(value) |
||||||
context.add(token, True) |
|
||||||
|
return variables |
||||||
|
|
||||||
class SubCommands(object): |
|
||||||
|
|
||||||
def __init__(self, subparsers): |
|
||||||
self.subparsers = subparsers |
|
||||||
|
|
||||||
def docs(self): |
|
||||||
sub = self.subparsers.add_parser( |
|
||||||
"docs", |
|
||||||
help="Output documentation instead of code." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-o=", |
|
||||||
"--output-format=", |
|
||||||
choices=["html", "md", "plain", "rst"], |
|
||||||
default="md", |
|
||||||
dest="output_format", |
|
||||||
help="The output format; HTML, Markdown, plain text, or ReStructuredText." |
|
||||||
) |
|
||||||
|
|
||||||
self._add_script_options(sub) |
|
||||||
self._add_common_options(sub) |
|
||||||
|
|
||||||
def inventory(self): |
|
||||||
sub = self.subparsers.add_parser( |
|
||||||
"inventory", |
|
||||||
aliases=["inv"], |
|
||||||
help="Copy an inventory item to a local directory." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"name", |
|
||||||
help="The name of the inventory item. Use ? to list available items." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-P=", |
|
||||||
"--path=", |
|
||||||
dest="to_path", |
|
||||||
help="The path to where the item should be copied. Defaults to the current working directory." |
|
||||||
) |
|
||||||
|
|
||||||
self._add_common_options(sub) |
|
||||||
|
|
||||||
def script(self): |
|
||||||
sub = self.subparsers.add_parser( |
|
||||||
"script", |
|
||||||
help="Output the commands." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-c", |
|
||||||
"--color", |
|
||||||
action="store_true", |
|
||||||
dest="color_enabled", |
|
||||||
help="Enable code highlighting for terminal output." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-s", |
|
||||||
"--shebang", |
|
||||||
action="store_true", |
|
||||||
dest="include_shebang", |
|
||||||
help="Add the shebang to the beginning of the output." |
|
||||||
) |
|
||||||
|
|
||||||
self._add_script_options(sub) |
|
||||||
self._add_common_options(sub) |
|
||||||
|
|
||||||
def _add_common_options(self, sub): |
|
||||||
sub.add_argument( |
|
||||||
"-D", |
|
||||||
"--debug", |
|
||||||
action="store_true", |
|
||||||
dest="debug_enabled", |
|
||||||
help="Enable debug mode. Produces extra output." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-p", |
|
||||||
action="store_true", |
|
||||||
dest="preview_enabled", |
|
||||||
help="Preview mode." |
|
||||||
) |
|
||||||
|
|
||||||
def _add_script_options(self, sub): |
|
||||||
sub.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" |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-i=", |
|
||||||
"--input-file=", |
|
||||||
default="steps.ini", |
|
||||||
dest="steps_file", |
|
||||||
help="The path to the configuration file." |
|
||||||
) |
|
||||||
|
|
||||||
# sub.add_argument( |
|
||||||
# "-f=", |
|
||||||
# "--filter=", |
|
||||||
# action="append", |
|
||||||
# dest="filters", |
|
||||||
# help="Filter the commands in the form of: attribute:value" |
|
||||||
# ) |
|
||||||
|
|
||||||
# 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) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-O=", |
|
||||||
"--option=", |
|
||||||
action="append", |
|
||||||
dest="options", |
|
||||||
help="Common command options in the form of: name:value" |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-P=", |
|
||||||
"--profile=", |
|
||||||
choices=["centos", "ubuntu"], |
|
||||||
default="ubuntu", |
|
||||||
dest="profile", |
|
||||||
help="The OS profile to use." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-T=", |
|
||||||
"--template-path=", |
|
||||||
action="append", |
|
||||||
dest="template_locations", |
|
||||||
help="The location of template files that may be used with the template command." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-w=", |
|
||||||
"--write=", |
|
||||||
dest="output_file", |
|
||||||
help="Write the output to disk." |
|
||||||
) |
|
||||||
|
|
||||||
sub.add_argument( |
|
||||||
"-V=", |
|
||||||
"--variables-file=", |
|
||||||
dest="variables_file", |
|
||||||
help="Load variables from a file." |
|
||||||
) |
|
||||||
|
|
||||||
# # Imports |
|
||||||
# |
|
||||||
# from commonkit import smart_cast |
|
||||||
# from configparser import ConfigParser |
|
||||||
# import logging |
|
||||||
# import os |
|
||||||
# from ..constants import LOGGER_NAME |
|
||||||
# |
|
||||||
# log = logging.getLogger(LOGGER_NAME) |
|
||||||
# |
|
||||||
# # Exports |
|
||||||
# |
|
||||||
# __all__ = ( |
|
||||||
# "context_from_cli", |
|
||||||
# "filters_from_cli", |
|
||||||
# "options_from_cli", |
|
||||||
# "variables_from_file", |
|
||||||
# ) |
|
||||||
# |
|
||||||
# # Functions |
|
||||||
# |
|
||||||
# |
|
||||||
# def context_from_cli(variables): |
|
||||||
# """Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
|
||||||
# |
|
||||||
# :param variables: A list of strings of ``name:value`` pairs. |
|
||||||
# :type variables: list[str] |
|
||||||
# |
|
||||||
# :rtype: dict |
|
||||||
# |
|
||||||
# The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
|
||||||
# |
|
||||||
# """ |
|
||||||
# context = dict() |
|
||||||
# for i in variables: |
|
||||||
# key, value = i.split(":") |
|
||||||
# context[key] = smart_cast(value) |
|
||||||
# |
|
||||||
# return context |
|
||||||
# |
|
||||||
# |
|
||||||
# def filters_from_cli(filters): |
|
||||||
# """Takes a list of filters given in the form of ``name:value`` and converts them to a dictionary. |
|
||||||
# |
|
||||||
# :param filters: A list of strings of ``attribute:value`` pairs. |
|
||||||
# :type filters: list[str] |
|
||||||
# |
|
||||||
# :rtype: dict |
|
||||||
# |
|
||||||
# """ |
|
||||||
# _filters = dict() |
|
||||||
# for i in filters: |
|
||||||
# key, value = i.split(":") |
|
||||||
# if key not in filters: |
|
||||||
# _filters[key] = list() |
|
||||||
# |
|
||||||
# _filters[key].append(value) |
|
||||||
# |
|
||||||
# return _filters |
|
||||||
# |
|
||||||
# |
|
||||||
# def options_from_cli(options): |
|
||||||
# """Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
|
||||||
# |
|
||||||
# :param options: A list of strings of ``name:value`` pairs. |
|
||||||
# :type options: list[str] |
|
||||||
# |
|
||||||
# :rtype: dict |
|
||||||
# |
|
||||||
# The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
|
||||||
# |
|
||||||
# """ |
|
||||||
# _options = dict() |
|
||||||
# for i in options: |
|
||||||
# key, value = i.split(":") |
|
||||||
# _options[key] = smart_cast(value) |
|
||||||
# |
|
||||||
# return _options |
|
||||||
# |
|
||||||
# |
|
||||||
# def variables_from_file(path): |
|
||||||
# """Loads variables from a given INI file. |
|
||||||
# |
|
||||||
# :param path: The path to the INI file. |
|
||||||
# :type path: str |
|
||||||
# |
|
||||||
# :rtype: dict | None |
|
||||||
# |
|
||||||
# The resulting dictionary flattens the sections and values. For example: |
|
||||||
# |
|
||||||
# .. code-block:: ini |
|
||||||
# |
|
||||||
# [copyright] |
|
||||||
# name = ACME, Inc. |
|
||||||
# year = 2020 |
|
||||||
# |
|
||||||
# [domain] |
|
||||||
# name = example.com |
|
||||||
# tld = example_com |
|
||||||
# |
|
||||||
# The dictionary would contain: |
|
||||||
# |
|
||||||
# .. code-block:: python |
|
||||||
# |
|
||||||
# { |
|
||||||
# 'copyright_name': "ACME, Inc.", |
|
||||||
# 'copyright_year': 2020, |
|
||||||
# 'domain_name': "example.com", |
|
||||||
# 'domain_tld': "example_com", |
|
||||||
# } |
|
||||||
# |
|
||||||
# """ |
|
||||||
# if not os.path.exists(path): |
|
||||||
# log.warning("Variables file does not exist: %s" % path) |
|
||||||
# return None |
|
||||||
# |
|
||||||
# ini = ConfigParser() |
|
||||||
# ini.read(path) |
|
||||||
# |
|
||||||
# variables = dict() |
|
||||||
# for section in ini.sections(): |
|
||||||
# for key, value in ini.items(section): |
|
||||||
# key = "%s_%s" % (section, key) |
|
||||||
# variables[key] = smart_cast(value) |
|
||||||
# |
|
||||||
# return variables |
|
||||||
|
@ -1,181 +1,153 @@ |
|||||||
# Imports |
# Imports |
||||||
|
|
||||||
from commonkit import copy_tree, highlight_code, indent, write_file |
from commonkit import highlight_code |
||||||
from commonkit.shell import EXIT |
from commonkit.shell import EXIT |
||||||
from markdown import markdown |
from ..parsers import load_commands, load_config |
||||||
import os |
|
||||||
from ..lib.factories import command_factory |
|
||||||
from ..constants import PROFILE |
|
||||||
from ..variables import PATH_TO_SCRIPT_TEASE |
|
||||||
|
|
||||||
# Exports |
# Exports |
||||||
|
|
||||||
__all__ = ( |
__all__ = ( |
||||||
"copy_inventory", |
"output_commands", |
||||||
"generate_docs", |
"output_docs", |
||||||
"generate_script", |
"output_script", |
||||||
) |
) |
||||||
|
|
||||||
# Functions |
# Functions |
||||||
|
|
||||||
|
|
||||||
def copy_inventory(name, to_path=None): |
def output_commands(path, color_enabled=False, context=None, filters=None, locations=None, options=None): |
||||||
"""Copy an inventory item to a path. |
"""Output commands found in a given configuration file. |
||||||
|
|
||||||
:param name: The name of the inventory item. ``?`` will list available items. |
:param path: The path to the configuration file. |
||||||
:type name: str |
:type path: str |
||||||
|
|
||||||
:param to_path: The path to where the item should be copied. Defaults to the current working directory. |
:param color_enabled: Indicates the output should be colorized. |
||||||
:type to_path: str |
:type color_enabled: bool |
||||||
|
|
||||||
|
:param context: The context to be applied to the file before parsing it as configuration. |
||||||
|
:type context: dict |
||||||
|
|
||||||
|
:param filters: Output only those commands which match the given filters. |
||||||
|
:type filters: dict |
||||||
|
|
||||||
|
:param locations: The locations (paths) of additional resources. |
||||||
|
:type locations: list[str] |
||||||
|
|
||||||
|
:param options: Options to be applied to all commands. |
||||||
|
:type options: dict |
||||||
|
|
||||||
:rtype: int |
:rtype: int |
||||||
:returns: An exit code. |
:returns: An exit code. |
||||||
|
|
||||||
""" |
""" |
||||||
path = os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory") |
commands = load_commands( |
||||||
if name == "?": |
path, |
||||||
for d in os.listdir(path): |
context=context, |
||||||
print(d) |
filters=filters, |
||||||
|
locations=locations, |
||||||
|
options=options |
||||||
|
) |
||||||
|
if commands is None: |
||||||
|
return EXIT.ERROR |
||||||
|
|
||||||
return EXIT.OK |
output = list() |
||||||
|
for command in commands: |
||||||
|
statement = command.get_statement(cd=True) |
||||||
|
if statement is None: |
||||||
|
continue |
||||||
|
|
||||||
from_path = os.path.join(path, name) |
output.append(statement) |
||||||
|
output.append("") |
||||||
|
|
||||||
if to_path is None: |
if color_enabled: |
||||||
to_path = os.path.join(os.getcwd(), name) |
print(highlight_code("\n".join(output), language="bash")) |
||||||
os.makedirs(to_path) |
else: |
||||||
|
print("\n".join(output)) |
||||||
|
|
||||||
if copy_tree(from_path, to_path): |
return EXIT.OK |
||||||
return EXIT.OK |
|
||||||
|
|
||||||
return EXIT.ERROR |
|
||||||
|
|
||||||
|
def output_docs(path, context=None, filters=None, locations=None, options=None): |
||||||
|
"""Output documentation for commands found in a given configuration file. |
||||||
|
|
||||||
def generate_docs(loader, output_file=None, output_format="md", profile=PROFILE.UBUNTU): |
:param path: The path to the configuration file. |
||||||
"""Generate documentation from a commands file. |
:type path: str |
||||||
|
|
||||||
:param loader: The loader instance. |
:param context: The context to be applied to the file before parsing it as configuration. |
||||||
:type loader: BaseType[scripttease.lib.loaders.BaseLoader] |
:type context: dict |
||||||
|
|
||||||
:param output_file: The path to the output file. |
:param filters: Output only those commands which match the given filters. |
||||||
:type output_file: str |
:type filters: dict |
||||||
|
|
||||||
:param output_format: The output format; ``html``, ``md`` (Markdown, the default), ``plain`` (text), ``rst`` |
:param locations: The locations (paths) of additional resources. |
||||||
(ReStructuredText). |
:type locations: list[str] |
||||||
:type output_format: str |
|
||||||
|
|
||||||
:param profile: The operating system profile to use. |
:param options: Options to be applied to all commands. |
||||||
:type profile: str |
:type options: dict |
||||||
|
|
||||||
:rtype: int |
:rtype: int |
||||||
:returns: An exit code. |
:returns: An exit code. |
||||||
|
|
||||||
""" |
""" |
||||||
commands = command_factory(loader, profile=profile) |
commands = load_commands( |
||||||
|
path, |
||||||
|
context=context, |
||||||
|
filters=filters, |
||||||
|
locations=locations, |
||||||
|
options=options |
||||||
|
) |
||||||
if commands is None: |
if commands is None: |
||||||
return EXIT.ERROR |
return EXIT.ERROR |
||||||
|
|
||||||
|
count = 1 |
||||||
output = list() |
output = list() |
||||||
for command in commands: |
for command in commands: |
||||||
|
output.append("%s. %s" % (count, command.comment)) |
||||||
|
count += 1 |
||||||
|
|
||||||
if command.name in ("explain", "screenshot"): |
print("\n".join(output)) |
||||||
output.append(command.get_output(output_format)) |
|
||||||
elif command.name == "template": |
|
||||||
if output_format == "plain": |
|
||||||
output.append("+++") |
|
||||||
output.append(command.get_content()) |
|
||||||
output.append("+++") |
|
||||||
output.append("") |
|
||||||
elif output_format == "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 output_format == "plain": |
|
||||||
output.append("---") |
|
||||||
output.append(statement) |
|
||||||
output.append("---") |
|
||||||
output.append("") |
|
||||||
elif output_format == "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 output_format == "html": |
|
||||||
_output = markdown("\n".join(output), extensions=['fenced_code']) |
|
||||||
else: |
|
||||||
_output = "\n".join(output) |
|
||||||
|
|
||||||
print(_output) |
|
||||||
|
|
||||||
if output_file: |
|
||||||
write_file(output_file, _output) |
|
||||||
|
|
||||||
return EXIT.OK |
return EXIT.OK |
||||||
|
|
||||||
|
|
||||||
def generate_script(loader, color_enabled=False, include_shebang=False, output_file=None, profile=PROFILE.UBUNTU): |
def output_script(path, color_enabled=False, context=None, filters=None, locations=None, options=None): |
||||||
"""Generate statements from a commands file. |
"""Output a script of commands found in a given configuration file. |
||||||
|
|
||||||
:param loader: The loader instance. |
:param path: The path to the configuration file. |
||||||
:type loader: BaseType[scripttease.lib.loaders.BaseLoader] |
:type path: str |
||||||
|
|
||||||
:param color_enabled: Colorize the output. |
:param color_enabled: Indicates the output should be colorized. |
||||||
:type color_enabled: bool |
:type color_enabled: bool |
||||||
|
|
||||||
:param include_shebang: Add the shebang to the beginning of the output. |
:param context: The context to be applied to the file before parsing it as configuration. |
||||||
:type include_shebang: bool |
:type context: dict |
||||||
|
|
||||||
|
:param filters: Output only those commands which match the given filters. NOT IMPLEMENTED. |
||||||
|
:type filters: dict |
||||||
|
|
||||||
:param output_file: The path to the output file. |
:param locations: The locations (paths) of additional resources. |
||||||
:type output_file: str |
:type locations: list[str] |
||||||
|
|
||||||
:param profile: The operating system profile to use. |
:param options: Options to be applied to all commands. |
||||||
:type profile: str |
:type options: dict |
||||||
|
|
||||||
:rtype: int |
:rtype: int |
||||||
:returns: An exit code. |
:returns: An exit code. |
||||||
|
|
||||||
""" |
""" |
||||||
commands = command_factory(loader, profile=profile) |
config = load_config( |
||||||
if commands is None: |
path, |
||||||
|
context=context, |
||||||
|
locations=locations, |
||||||
|
options=options |
||||||
|
) |
||||||
|
if config is None: |
||||||
return EXIT.ERROR |
return EXIT.ERROR |
||||||
|
|
||||||
output = list() |
script = config.as_script() |
||||||
if include_shebang: |
|
||||||
output.append("#! /usr/bin/env bash") |
|
||||||
|
|
||||||
for command in commands: |
|
||||||
if command.name in ("explain", "screenshot"): |
|
||||||
continue |
|
||||||
|
|
||||||
statement = command.get_statement(include_comment=True) |
|
||||||
if statement is not None: |
|
||||||
output.append(statement) |
|
||||||
output.append("") |
|
||||||
|
|
||||||
if color_enabled: |
if color_enabled: |
||||||
print(highlight_code("\n".join(output), language="bash")) |
print(highlight_code(script.to_string(), language="bash")) |
||||||
else: |
else: |
||||||
print("\n".join(output)) |
print(script) |
||||||
|
|
||||||
if output_file: |
|
||||||
write_file(output_file, "\n".join(output)) |
|
||||||
|
|
||||||
return EXIT.OK |
return EXIT.OK |
||||||
|
@ -1,6 +0,0 @@ |
|||||||
[package] |
|
||||||
description = Install Nextcloud. |
|
||||||
docs = https://nextcloud.com |
|
||||||
tags = collaboration |
|
||||||
title = Nextcloud |
|
||||||
version = 0.1.0-d |
|
@ -1,6 +0,0 @@ |
|||||||
[package] |
|
||||||
description = Install PostgreSQL. |
|
||||||
docs = https://postgresql.org |
|
||||||
tags = database, postgres |
|
||||||
title = PostgreSQL |
|
||||||
version = 0.1.0-d |
|
@ -1,6 +0,0 @@ |
|||||||
[package] |
|
||||||
description = Set up an Ubuntu server. |
|
||||||
docs = https://ubuntu.com |
|
||||||
tags = operating system |
|
||||||
title = Ubuntu |
|
||||||
version = 0.1.0-d |
|
@ -1,9 +0,0 @@ |
|||||||
[create the deploy user] |
|
||||||
user: deploy |
|
||||||
groups: sudo, www-data |
|
||||||
home: /var/www |
|
||||||
sudo: yes |
|
||||||
|
|
||||||
[remove a user] |
|
||||||
user: bob |
|
||||||
op: remove |
|
@ -1,9 +0,0 @@ |
|||||||
from scripttease.lib.factories import command_factory |
|
||||||
from scripttease.lib.loaders import INILoader |
|
||||||
|
|
||||||
|
|
||||||
def test_user_commands(): |
|
||||||
ini = INILoader("tests/examples/users.ini") |
|
||||||
ini.load() |
|
||||||
commands = command_factory(ini) |
|
||||||
print(commands) |
|