Updated docs.

development
Shawn Davis 1 year ago
parent 0d93018a00
commit 29c40cf1f1
  1. 4
      docs/Makefile
  2. 139
      docs/generate_command_signatures.py
  3. 5
      docs/source/_data/cloc.csv
  4. 6
      docs/source/_includes/dependencies.rst
  5. 1294
      docs/source/_includes/overlays.rst
  6. 12
      docs/source/_includes/tests.rst
  7. BIN
      docs/source/_static/images/architecture-diagram.png
  8. BIN
      docs/source/_static/images/slack-1.jpg
  9. BIN
      docs/source/_static/images/slack-2.jpg
  10. BIN
      docs/source/_static/images/slack-3.jpg
  11. BIN
      docs/source/_static/images/twist-1.png
  12. BIN
      docs/source/_static/images/twist-2.png
  13. 86
      docs/source/commands.rst
  14. 4
      docs/source/contact.rst
  15. 77
      docs/source/contributing.rst
  16. 32
      docs/source/getting-started.rst
  17. 236
      docs/source/how-to.rst
  18. 22
      docs/source/index.rst
  19. 125
      docs/source/introduction.rst
  20. 140
      docs/source/project.rst
  21. 56
      docs/source/reference.rst
  22. 29
      docs/source/releases.rst
  23. 115
      docs/source/topics-configuration.rst
  24. 24
      docs/source/topics-overlays.rst
  25. 11
      docs/source/topics.rst
  26. 67
      scripttease/lib/commands/base.py
  27. 52
      scripttease/lib/commands/centos.py
  28. 20
      scripttease/lib/commands/django.py
  29. 69
      scripttease/lib/commands/messages.py
  30. 93
      scripttease/lib/commands/mysql.py
  31. 82
      scripttease/lib/commands/pgsql.py
  32. 6
      scripttease/lib/commands/php.py
  33. 296
      scripttease/lib/commands/posix.py
  34. 22
      scripttease/lib/commands/python.py
  35. 62
      scripttease/lib/commands/ubuntu.py
  36. 3
      scripttease/lib/loaders/base.py

@ -54,8 +54,8 @@ clean:
.PHONY: html .PHONY: html
html: html:
resrc pkg docs -I ../packages.ini -O rst --no-heading > source/_includes/project-dependencies.rst; #resrc pkg docs -I ../packages.ini -O rst --no-heading > source/_includes/project-dependencies.rst;
./generate_command_signatures.py > source/_includes/overlays.rst #./generate_command_signatures.py > source/_includes/overlays.rst
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo @echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

@ -1,139 +0,0 @@
#! /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)

@ -1,3 +1,4 @@
files,language,blank,comment,code files,language,blank,comment,code
22,Python,1124,981,1847 23,Python,1215,1163,2070
22,SUM,1124,981,1847 12,INI,54,83,174
35,SUM,1269,1246,2244

1 files language blank comment code
2 22 23 Python 1124 1215 981 1163 1847 2070
3 22 12 SUM INI 1124 54 981 83 1847 174
4 35 SUM 1269 1246 2244

@ -1,10 +1,6 @@
Requirements
------------
jinja2 jinja2
------ ------
**Install** **Install**
.. code-block:: bash .. code-block:: bash
@ -14,7 +10,6 @@ jinja2
pygments pygments
-------- --------
**Install** **Install**
.. code-block:: bash .. code-block:: bash
@ -24,7 +19,6 @@ pygments
python-commonkit python-commonkit
---------------- ----------------
**URLs** **URLs**
- `Source Code <https://github.com/develmaycare/python-commonkit>`_ - `Source Code <https://github.com/develmaycare/python-commonkit>`_

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
Coverage Requirements Coverage Requirements
--------------------- .....................
100% coverage is required for the ``master`` branch. We always strive for 100% coverage in the ``master`` branch. The CLI is currently ignored.
See `current coverage report <coverage/index.html>`_. See `current coverage report <coverage/index.html>`_.
@ -9,7 +9,7 @@ See `current coverage report <coverage/index.html>`_.
:file: ../_data/cloc.csv :file: ../_data/cloc.csv
Set Up for Testing Set Up for Testing
------------------ ..................
Install requirements: Install requirements:
@ -35,16 +35,16 @@ Run a specific test:
.. code-block:: bash .. code-block:: bash
python -m pytest tests/units/path/to/test.py python -m pytest tests/path/to/test.py
To allow output from print statements within a test method, add the ``-s`` switch: To allow output from print statements within a test method, add the ``-s`` switch:
.. code-block:: bash .. code-block:: bash
python -m pytest -s tests/units/path/to/test.py python -m pytest -s tests/path/to/test.py
Reference Reference
--------- .........
- `coverage <https://coverage.readthedocs.io/en/v4.5.x/>`_ - `coverage <https://coverage.readthedocs.io/en/v4.5.x/>`_
- `pytest <https://pytest.org>`_ - `pytest <https://pytest.org>`_

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

@ -1,86 +0,0 @@
.. _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.

@ -10,14 +10,14 @@ General Contact
Please use the `contact form at develmaycare.com`_ to inquire about training, commercial development or support, to Please use the `contact form at develmaycare.com`_ to inquire about training, commercial development or support, to
report security issues, and other questions or communications. report security issues, and other questions or communications.
.. _contact form at develmaycare.com: https://develmaycare.com/contact/?product=ScriptTease .. _contact form at develmaycare.com: https://develmaycare.com/contact/?product=python-scripttease
Issue Tracker Issue Tracker
============= =============
Bugs and feature requests are logged with the `issue tracker`_. Do *not* log security issues here. Bugs and feature requests are logged with the `issue tracker`_. Do *not* log security issues here.
.. _issue tracker: https://github.com/develmaycare/python-scripttease/issues .. _issue tracker: https://gittraction.com/diff6/python-scripttease/issues
Support Support
======= =======

@ -0,0 +1,77 @@
************
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

@ -1,32 +0,0 @@
.. _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

@ -1,236 +0,0 @@
.. _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.

@ -1,22 +1,20 @@
Script Tease Script Tease Developer Documentation
============ ====================================
.. raw:: html Resources for turning plain text instructions (INI, YAML) into Bash scripting statements.
<p> .. note::
<a href="https://develmaycare.com/products/python/script-tease/">Project Home Page</a> This documentation is for developers interested in working with the Script Tease library to create a custom implementation, or to contribute to the product. For usage information, please refer to the `user help page`_.
</p>
.. _user help page: https://docs.diff6.com/en/python-scripttease
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
Introduction <introduction> Introduction <introduction>
Getting Started <getting-started> Code Reference <reference>
How To <how-to> Releases <releases>
Commands <commands> Contributing <contributing>
Topics <topics>
Reference <reference>
Project <project>
Contact <contact> Contact <contact>
Indices and tables Indices and tables

@ -7,30 +7,96 @@ Introduction
Overview Overview
======== ========
Script Tease is a library and command line tool for generating commands programmatically or (especially) using configuration files. The Script Tease package takes plain text instructions in INI or YAML (currently untested) format and converts them into valid command line statements for a given platform (currently CentOS and Ubuntu are supported). It does *not* provide support for executing these statements.
The primary focus (and limit) is to convert plain text instructions into valid command line statements for a given platform (see `Overlays`_). It does *not* provide support for executing those statements. Architecture
============
The provided command line interface (the ``tease`` command) loads configuration files containing instructions and may output these as a Bash script or as documentation.
However, it is possible to create your own implementation to provide input and gather output from the Script Tease library.
.. image:: _static/images/architecture-diagram.png
Concepts Concepts
======== ========
Generating Commands Loaders
------------------- -------
A Loader instance is responsible for reading configuration files and parsing the contents into a) a command name, b) arguments, c) keyword arguments.
Command instances are *not* created by the loader. This is the job of the :py:func:`scriptease.lib.factories.command_factory`.
Separating Command generation from loading configuration files makes the loader classes simpler, and keeps the overall system more flexible.
Command Functions
-----------------
Script Tease may be used in two (2) ways: Commands are represented by Python functions. All functions return either a :py:class:`scripttease.lib.commands.base.Command` instance, or in some cases an instance of :py:class:`scripttease.lib.commands.base.MultipleCommands`. (The Template command may also be returned; it is handled specially.)
1. Using the library to programmatically define commands and export them as command line statements. See :ref:`developer-reference`. .. code-block:: python
2. Using the ``tease`` command to generate commands from a configuration file. See :ref:`topics-configuration`.
Overlays def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
# ...
You could of course use this function directly:
.. code-block:: python
command = copy("/etc", "/tmp/", recursive=True)
print(command.get_statement())
But the point of Script Tease is to take plain text instructions and turn them into command line statements.
Mappings
-------- --------
An *overlay* is a set of command meta functions that define the capabilities of a specific operating system. So the `copy` function is mapped to a name that may be used in a configuration file:
.. code-block:: python
MAPPINGS = {
# ...
'copy': copy,
# ...
}
.. note:: Such mappings are used by the :py:func:`scriptease.lib.factories.command_factory` to match a command name in a configuration file to the Python function that instantiates the Command.
At present, the only fully defined operating system overlays are for Cent OS and Ubuntu.
See :ref:`topics-overlays`. Configuration File
------------------
The configuration that invokes this command would like like:
.. code-block:: ini
[create a copy of the etc directory]
copy: /etc /tmp/
recursive: yes
Note that the configuration is self-documenting, and in fact, it is possible to output commands as documentation rather than a script.
Variables File
--------------
Prior to a Loader processing commands, configuration files may be parsed as Jinja2 templates. This allows variables to be loaded from a file and passed to the loader as a Context instance.
.. code-block:: ini
# variables.ini
[db_name]
comment: The name of the database is the same as the domain name.
value: example_app
In the configuration file:
.. code-block:: ini
[create the database]
pgsql.create: {{ db_name }}
Variables are collected in a :py:class:`scripttease.lib.contexts.Context` instance and are passed to the Loader.
Terms and Definitions Terms and Definitions
===================== =====================
@ -38,10 +104,43 @@ Terms and Definitions
command command
When used in Script Tease documentation, this is a command instance which contains the properties and parameters for a command line statement. When used in Script Tease documentation, this is a command instance which contains the properties and parameters for a command line statement.
profile
A profile represents the commands available to a specific operating system.
statement statement
A specific statement (string) to be executed. A *statement* is contained within a *command*. A specific statement (string) to be executed. A *statement* is generated by a *command*.
License License
======= =======
Script Tease is released under the BSD 3 clause license. Script Tease is released under the BSD 3 clause license.
.. 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,140 +0,0 @@
*******
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,8 +1,8 @@
.. _developer-reference: .. _code-reference:
******************* **************
Developer Reference Code Reference
******************* **************
Constants Constants
========= =========
@ -12,11 +12,19 @@ Constants
:show-inheritance: :show-inheritance:
:special-members: __init__ :special-members: __init__
Library Exceptions
======= ==========
.. automodule:: scripttease.exceptions
:members:
:show-inheritance:
:special-members: __init__
Commands Commands
-------- ========
Base
----
.. automodule:: scripttease.lib.commands.base .. automodule:: scripttease.lib.commands.base
:members: :members:
@ -24,7 +32,7 @@ Commands
:special-members: __init__ :special-members: __init__
Centos Centos
...... ------
.. automodule:: scripttease.lib.commands.centos .. automodule:: scripttease.lib.commands.centos
:members: :members:
@ -32,7 +40,7 @@ Centos
:special-members: __init__ :special-members: __init__
Django Django
...... ------
.. automodule:: scripttease.lib.commands.django .. automodule:: scripttease.lib.commands.django
:members: :members:
@ -40,7 +48,7 @@ Django
:special-members: __init__ :special-members: __init__
Messages Messages
........ --------
.. automodule:: scripttease.lib.commands.messages .. automodule:: scripttease.lib.commands.messages
:members: :members:
@ -48,7 +56,7 @@ Messages
:special-members: __init__ :special-members: __init__
MySQL MySQL
...... -----
.. automodule:: scripttease.lib.commands.mysql .. automodule:: scripttease.lib.commands.mysql
:members: :members:
@ -56,7 +64,7 @@ MySQL
:special-members: __init__ :special-members: __init__
PHP PHP
... ---
.. automodule:: scripttease.lib.commands.php .. automodule:: scripttease.lib.commands.php
:members: :members:
@ -64,7 +72,7 @@ PHP
:special-members: __init__ :special-members: __init__
Postgres Postgres
........ --------
.. automodule:: scripttease.lib.commands.pgsql .. automodule:: scripttease.lib.commands.pgsql
:members: :members:
@ -72,7 +80,7 @@ Postgres
:special-members: __init__ :special-members: __init__
Posix Posix
..... -----
.. automodule:: scripttease.lib.commands.posix .. automodule:: scripttease.lib.commands.posix
:members: :members:
@ -80,7 +88,7 @@ Posix
:special-members: __init__ :special-members: __init__
Python Python
...... ------
.. automodule:: scripttease.lib.commands.python .. automodule:: scripttease.lib.commands.python
:members: :members:
@ -88,7 +96,7 @@ Python
:special-members: __init__ :special-members: __init__
Ubuntu Ubuntu
...... ------
.. automodule:: scripttease.lib.commands.ubuntu .. automodule:: scripttease.lib.commands.ubuntu
:members: :members:
@ -98,7 +106,7 @@ Ubuntu
Contexts Contexts
======== ========
.. automodule:: scripttease.contexts .. automodule:: scripttease.lib.contexts
:members: :members:
:show-inheritance: :show-inheritance:
:special-members: __init__ :special-members: __init__
@ -106,7 +114,7 @@ Contexts
Factories Factories
========= =========
.. automodule:: scripttease.factories .. automodule:: scripttease.lib.factories
:members: :members:
:show-inheritance: :show-inheritance:
:special-members: __init__ :special-members: __init__
@ -122,10 +130,18 @@ Base
:show-inheritance: :show-inheritance:
:special-members: __init__ :special-members: __init__
Config (INI) INI
------------ ---
.. automodule:: scripttease.lib.loaders.ini .. automodule:: scripttease.lib.loaders.ini
:members: :members:
:show-inheritance: :show-inheritance:
:special-members: __init__ :special-members: __init__
YAML
----
.. automodule:: scripttease.lib.loaders.yaml
:members:
:show-inheritance:
:special-members: __init__

@ -0,0 +1,29 @@
********
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.

@ -1,115 +0,0 @@
.. _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("")

@ -1,24 +0,0 @@
.. _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

@ -1,11 +0,0 @@
.. _topics:
******
Topics
******
.. toctree::
:maxdepth: 2
Configuration <topics-configuration>
Overlays <topics-overlays>

@ -24,8 +24,47 @@ __all__ = (
class Command(object): class Command(object):
"""A command collects parameters and establishes the standard interface for creating a command line statement."""
def __init__(self, statement, cd=None, comment=None, condition=None, name=None, prefix=None, register=None, shell=None, stop=False, sudo=None, tags=None, **kwargs): def __init__(self, statement, cd=None, comment=None, condition=None, name=None, prefix=None, register=None, shell=None, stop=False, sudo=None, tags=None, **kwargs):
"""Initialize a command.
:param statement: The command statement.
:type statement: str
:param cd: Change directory to this path.
:type cd: str
:param comment: A comment on the command.
:type comment: str
:param condition: A condition to be met before the command should execute. This must be in the form of a Bash
conditional. For example, ``-f path/to/file.txt``
:type condition: str
:param name: The canonical name of the command. Provided automatically when using the command factory.
:type name: str
:param prefix: Any valid statement that should come before the command statement.
:type prefix: str
:param register: Register the result of the command to this variable name.
:type register: str
:param shell: The shell to use. Not used by Script Tease, but may be important to integrators.
:type shell: str
:param stop: Don't continue with additional commands if this one fails.
:type stop: bool
:param sudo: A username or boolean that indicates sudo should be used.
:type sudo: bool | str
:param tags: A list of tags that help classify the command.
:type tags: list[str]
kwargs are added to the Command instance as ``options`` and are dynamically accessible.
"""
self.cd = cd self.cd = cd
self.comment = comment self.comment = comment
self.condition = condition self.condition = condition
@ -124,6 +163,7 @@ class Command(object):
class Content(object): class Content(object):
"""A special command-like resource for capturing non-statement content such as explanations and screenshots."""
def __init__(self, content_type, caption=None, css=None, heading=None, height=None, image=None, message=None, def __init__(self, content_type, caption=None, css=None, heading=None, height=None, image=None, message=None,
width=None, **kwargs): width=None, **kwargs):
@ -564,12 +604,34 @@ class Sudo(object):
class Template(object): class Template(object):
"""A command-like resource that may be used to process template files."""
PARSER_JINJA = "jinja2" PARSER_JINJA = "jinja2"
PARSER_PYTHON = "python" PARSER_PYTHON = "python"
PARSER_SIMPLE = "simple" PARSER_SIMPLE = "simple"
def __init__(self, source, target, backup=True, parser=PARSER_JINJA, **kwargs): def __init__(self, source, target, backup=True, parser=PARSER_JINJA, **kwargs):
"""Initialize a template.
:param source: The path to the template file.
:type source: str
:param target: The path to where the rendered template should be saved.
:type target: str
:param backup: Indicates whether a backup of an existing target should be created.
:type backup: bool
:param parser: The parser to use; ``jinja``, ``python``, or ``simple``.
:type parser: str
kwargs are the same as that of :py:class:`scripttease.lib.commands.base.Command`
Python templates are parsed using string interpolation. For example, ``%(variable_name)s``.
Simple templates look for variables in the form of ``$variable_name$``.
"""
self.backup_enabled = backup self.backup_enabled = backup
self.cd = kwargs.pop("cd", None) self.cd = kwargs.pop("cd", None)
self.comment = kwargs.pop("comment", "create template %s" % target) self.comment = kwargs.pop("comment", "create template %s" % target)
@ -686,6 +748,11 @@ class Template(object):
return "\n".join(lines) return "\n".join(lines)
def get_target_language(self): def get_target_language(self):
"""Get the target language of the template output. Used when generating documentation.
:rtype: str
"""
if self.language is not None: if self.language is not None:
return self.language return self.language

@ -39,9 +39,11 @@ __all__ = (
def apache(op, **kwargs): def apache(op, **kwargs):
"""Execute an Apache-related command. """Execute an Apache-related command.
- op (str): The operation to perform; reload, restart, start, stop, test. :param op: The operation to perform; ``reload``, ``restart``, ``start``, ``stop``, ``test``.
:type op: str
""" """
# See https://unix.stackexchange.com/questions/258854/disable-and-enable-modules-in-apache-centos7
if op == "reload": if op == "reload":
return apache_reload(**kwargs) return apache_reload(**kwargs)
elif op == "restart": elif op == "restart":
@ -57,6 +59,7 @@ def apache(op, **kwargs):
def apache_reload(**kwargs): def apache_reload(**kwargs):
"""Reload the apache service."""
kwargs.setdefault("comment", "reload apache") kwargs.setdefault("comment", "reload apache")
kwargs.setdefault("register", "apache_reloaded") kwargs.setdefault("register", "apache_reloaded")
@ -64,6 +67,7 @@ def apache_reload(**kwargs):
def apache_restart(**kwargs): def apache_restart(**kwargs):
"""Restart the apache service."""
kwargs.setdefault("comment", "restart apache") kwargs.setdefault("comment", "restart apache")
kwargs.setdefault("register", "apache_restarted") kwargs.setdefault("register", "apache_restarted")
@ -71,6 +75,7 @@ def apache_restart(**kwargs):
def apache_start(**kwargs): def apache_start(**kwargs):
"""Start the apache service."""
kwargs.setdefault("comment", "start apache") kwargs.setdefault("comment", "start apache")
kwargs.setdefault("register", "apache_started") kwargs.setdefault("register", "apache_started")
@ -78,12 +83,14 @@ def apache_start(**kwargs):
def apache_stop(**kwargs): def apache_stop(**kwargs):
"""Stop the apache service."""
kwargs.setdefault("comment", "stop apache") kwargs.setdefault("comment", "stop apache")
return Command("apachectl –k stop", **kwargs) return Command("apachectl –k stop", **kwargs)
def apache_test(**kwargs): def apache_test(**kwargs):
"""Run a configuration test on apache."""
kwargs.setdefault("comment", "check apache configuration") kwargs.setdefault("comment", "check apache configuration")
kwargs.setdefault("register", "apache_checks_out") kwargs.setdefault("register", "apache_checks_out")
@ -93,7 +100,8 @@ def apache_test(**kwargs):
def service_reload(service, **kwargs): def service_reload(service, **kwargs):
"""Reload a service. """Reload a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "reload %s service" % service) kwargs.setdefault("comment", "reload %s service" % service)
@ -105,7 +113,8 @@ def service_reload(service, **kwargs):
def service_restart(service, **kwargs): def service_restart(service, **kwargs):
"""Restart a service. """Restart a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "restart %s service" % service) kwargs.setdefault("comment", "restart %s service" % service)
@ -117,7 +126,8 @@ def service_restart(service, **kwargs):
def service_start(service, **kwargs): def service_start(service, **kwargs):
"""Start a service. """Start a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "start %s service" % service) kwargs.setdefault("comment", "start %s service" % service)
@ -129,7 +139,8 @@ def service_start(service, **kwargs):
def service_stop(service, **kwargs): def service_stop(service, **kwargs):
"""Stop a service. """Stop a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "stop %s service" % service) kwargs.setdefault("comment", "stop %s service" % service)
@ -141,7 +152,8 @@ def service_stop(service, **kwargs):
def system(op, **kwargs): def system(op, **kwargs):
"""Perform a system operation. """Perform a system operation.
- op (str): The operation to perform; reboot, update, upgrade. :param op: The operation to perform; ``reboot``, ``update``, ``upgrade``.
:type op: str
""" """
if op == "reboot": if op == "reboot":
@ -157,7 +169,8 @@ def system(op, **kwargs):
def system_install(package, **kwargs): def system_install(package, **kwargs):
"""Install a system-level package. """Install a system-level package.
- name (str): The name of the package to install. :param package: The name of the package to install.
:type package: str
""" """
kwargs.setdefault("comment", "install system package %s" % package) kwargs.setdefault("comment", "install system package %s" % package)
@ -166,6 +179,7 @@ def system_install(package, **kwargs):
def system_reboot(**kwargs): def system_reboot(**kwargs):
"""Reboot the system."""
kwargs.setdefault("comment", "reboot the system") kwargs.setdefault("comment", "reboot the system")
return Command("reboot", **kwargs) return Command("reboot", **kwargs)
@ -174,7 +188,8 @@ def system_reboot(**kwargs):
def system_uninstall(package, **kwargs): def system_uninstall(package, **kwargs):
"""Uninstall a system-level package. """Uninstall a system-level package.
- name (str): The name of the package to uninstall. :param package: The name of the package to remove.
:type package: str
""" """
kwargs.setdefault("comment", "remove system package %s" % package) kwargs.setdefault("comment", "remove system package %s" % package)
@ -183,12 +198,14 @@ def system_uninstall(package, **kwargs):
def system_update(**kwargs): def system_update(**kwargs):
"""Update the system's package info."""
kwargs.setdefault("comment", "update system package info") kwargs.setdefault("comment", "update system package info")
return Command("yum check-update", **kwargs) return Command("yum check-update", **kwargs)
def system_upgrade(**kwargs): def system_upgrade(**kwargs):
"""updated the system."""
kwargs.setdefault("comment", "upgrade the system") kwargs.setdefault("comment", "upgrade the system")
return Command("yum update -y", **kwargs) return Command("yum update -y", **kwargs)
@ -197,11 +214,20 @@ def system_upgrade(**kwargs):
def user(name, groups=None, home=None, op="add", password=None, **kwargs): def user(name, groups=None, home=None, op="add", password=None, **kwargs):
"""Create or remove a user. """Create or remove a user.
- name (str): The user name. :param name: The username.
- groups (str | list): A list of groups to which the user should belong. :type name: str
- home (str): The path to the user's home directory.
- op (str); The operation to perform; ``add`` or ``remove``. :param groups: A list of groups to which the user should belong.
- password (str): The user's password. (NOT IMPLEMENTED) :type groups: list | str
:param home: The path to the user's home directory.
:type home: str
:param op: The operation to perform; ``add`` or ``remove``.
:type op:
:param password: The user's password. (NOT IMPLEMENTED)
:type password: str
""" """
if op == "add": if op == "add":

@ -1,23 +1,3 @@
"""
[run django checks]
django: check
[export fixtures]
django: dump lookups.Category
[import fixtures]
django: load lookups.Category
[migrate the database]
django: migrate
[collect static files]
django: static
[create super user (ad hoc command)]
django: createsuperuser root
"""
from ...constants import EXCLUDED_KWARGS from ...constants import EXCLUDED_KWARGS
from .base import Command from .base import Command

@ -3,6 +3,21 @@ from ...exceptions import InvalidInput
def dialog(message, height=15, title="Message", width=100, **kwargs): def dialog(message, height=15, title="Message", width=100, **kwargs):
"""Display a graphical feedback box to the user. Note that the ``dialog`` command must be available.
:param message: The message to be displayed.
:type message: str
:param height: The height of the dialog box.
:type height: int
:param title: The title displayed.
:type title: str
:param width: The width of the dialog box.
:type width: int
"""
statement = list() statement = list()
statement.append("dialog --clear") statement.append("dialog --clear")
statement.append('--backtitle "%s"' % title) statement.append('--backtitle "%s"' % title)
@ -13,18 +28,60 @@ def dialog(message, height=15, title="Message", width=100, **kwargs):
def echo(message, **kwargs): def echo(message, **kwargs):
"""Display a message.
:param message: The message to be displayed.
:type message: str
"""
return Command('echo "%s"' % message, **kwargs) return Command('echo "%s"' % message, **kwargs)
def explain(message, heading=None, **kwargs): def explain(message, heading=None, **kwargs):
"""Create an explanation for documentation.
:param message: The message to be displayed.
:type message: str
:param heading: Optional heading for the output.
:type heading: str
"""
return Content("explain", message=message, heading=heading, **kwargs) return Content("explain", message=message, heading=heading, **kwargs)
def screenshot(image, caption=None, css=None, height=None, width=None, **kwargs): def screenshot(image, caption=None, css=None, height=None, width=None, **kwargs):
"""Create a screenshot for documentation.
:param image: The URL or path to the image file.
:type image: str
:param caption: A caption for the image.
:type caption: str
:param css: CSS classes to be applied to the image tag.
:type css: str
:param height: The maximum height of the image.
:type height: int
:param width: The maximum widht of the image.
:type width: int
"""
return Content("screenshot", caption=caption, css=css, height=height, image=image, width=width, **kwargs) return Content("screenshot", caption=caption, css=css, height=height, image=image, width=width, **kwargs)
def slack(message, url=None, **kwargs): def slack(message, url=None, **kwargs):
"""Send a message to Slack.
:param message: The message to be displayed.
:type message: str
:param url: The channel URL.
:type url: str
"""
if url is None: if url is None:
raise InvalidInput("Slack command requires a url parameter.") raise InvalidInput("Slack command requires a url parameter.")
@ -37,6 +94,18 @@ def slack(message, url=None, **kwargs):
def twist(message, title="Notice", url=None, **kwargs): def twist(message, title="Notice", url=None, **kwargs):
"""Send a message to Twist.
:param message: The message to be displayed.
:type message: str
:param title: A title for the message.
:type title: str
:param url: The channel URL.
:type url: str
"""
if url is None: if url is None:
raise InvalidInput("Twist command requires a url parameter.") raise InvalidInput("Twist command requires a url parameter.")

@ -18,7 +18,27 @@ __all__ = (
) )
def mysql(command, *args, host="localhost", excluded_kwargs=None, password=None, port=3306, user="root", **kwargs): def mysql(command, *args, excluded_kwargs=None, host="localhost", password=None, port=3306, user="root", **kwargs):
"""Get a mysql-related command using commonly required parameters.
:param command: The name of the command.
:type command: str
:param excluded_kwargs: Keyword arguments to exclude from automatic switch creation.
:type excluded_kwargs: list[str]
:param host: The host name.
:type host: str
:param password: The password to use.
:type password: str
:param port: The TCP port number.
:type port: int
:param user: The username that will be used to execute the command.
"""
# The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default. # The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default.
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS
@ -63,6 +83,15 @@ def mysql(command, *args, host="localhost", excluded_kwargs=None, password=None,
def mysql_create(database, owner=None, **kwargs): def mysql_create(database, owner=None, **kwargs):
"""Create a MySQL database.
:param database: The database name.
:type database: str
:param owner: The owner (user/role name) of the new database.
:type owner: str
"""
kwargs.setdefault("comment", "create mysql database") kwargs.setdefault("comment", "create mysql database")
command = mysql("mysqladmin create", database, **kwargs) command = mysql("mysqladmin create", database, **kwargs)
@ -75,12 +104,27 @@ def mysql_create(database, owner=None, **kwargs):
def mysql_drop(database, **kwargs): def mysql_drop(database, **kwargs):
"""Remove a MySQL database.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "drop %s mysql database" % database) kwargs.setdefault("comment", "drop %s mysql database" % database)
return mysql("mysqladmin drop", database, **kwargs) return mysql("mysqladmin drop", database, **kwargs)
def mysql_dump(database, path=None, **kwargs): def mysql_dump(database, path=None, **kwargs):
"""Export a MySQL database.
:param database: The database name.
:type database: str
:param path: The name/path of the export file. Defaults the database name plus ``.sql``.
:type path: str
"""
kwargs.setdefault("comment", "dump mysql database") kwargs.setdefault("comment", "dump mysql database")
if path is None: if path is None:
@ -90,6 +134,12 @@ def mysql_dump(database, path=None, **kwargs):
def mysql_exists(database, **kwargs): def mysql_exists(database, **kwargs):
"""Determine if a MySQL database exists.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "determine if %s mysql database exists" % database) kwargs.setdefault("comment", "determine if %s mysql database exists" % database)
kwargs.setdefault("register", "%s_exists" % database) kwargs.setdefault("register", "%s_exists" % database)
@ -105,13 +155,13 @@ def mysql_exists(database, **kwargs):
def mysql_grant(to, database=None, privileges="ALL", **kwargs): def mysql_grant(to, database=None, privileges="ALL", **kwargs):
"""Grant privileges to a user. """Grant privileges to a user.
- to (str): The user name to which privileges are granted. :param to: The username to which privileges are granted.
- database (str): The database name.
- host (str): The database host name or IP address. :param database: The database name.
- password (str): The password for the user with sufficient access privileges to execute the command. :type database: str
- port (int): The TCP port number of the MySQL service running on the host.
- privileges (str): The privileges to be granted. :param privileges: The privileges to be granted.
- user (str): The name of the user with sufficient access privileges to execute the command. :type privileges: str
""" """
kwargs.setdefault("comment", "grant mysql privileges to %s" % to) kwargs.setdefault("comment", "grant mysql privileges to %s" % to)
@ -134,12 +184,39 @@ def mysql_grant(to, database=None, privileges="ALL", **kwargs):
def mysql_load(database, path, **kwargs): def mysql_load(database, path, **kwargs):
"""Load data into a MySQL database.
:param database: The database name.
:type database: str
:param path: The path to the file to be loaded.
:type path: str
"""
kwargs.setdefault("comment", "load data into a mysql database") kwargs.setdefault("comment", "load data into a mysql database")
return mysql("mysql", database, "< %s" % path, **kwargs) return mysql("mysql", database, "< %s" % path, **kwargs)
def mysql_user(name, admin_pass=None, admin_user="root", op="create", password=None, **kwargs): def mysql_user(name, admin_pass=None, admin_user="root", op="create", password=None, **kwargs):
"""Work with a MySQL user.
:param name: The username.
:type name: str
:param admin_pass: The password for the user with admin privileges.
:type admin_pass: str
:param admin_user: The username of the user with admin privileges.
:type admin_user: str
:param op: The operation to perform: ``create``, ``drop``, ``exists``.
:type op: str
:param password: The password for a new user.
:type password: str
"""
host = kwargs.get("host", "localhost") host = kwargs.get("host", "localhost")
if op == "create": if op == "create":

@ -17,7 +17,27 @@ __all__ = (
) )
def pgsql(command, *args, host="localhost", excluded_kwargs=None, password=None, port=5432, user="postgres", **kwargs): def pgsql(command, *args, excluded_kwargs=None, host="localhost", password=None, port=5432, user="postgres", **kwargs):
"""Get a postgres-related command using commonly required parameters.
:param command: The name of the command.
:type command: str
:param excluded_kwargs: Keyword arguments to exclude from automatic switch creation.
:type excluded_kwargs: list[str]
:param host: The host name.
:type host: str
:param password: The password to use.
:type password: str
:param port: The TCP port number.
:type port: int
:param user: The username that will be used to execute the command.
"""
# The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default. # The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default.
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS
@ -59,6 +79,18 @@ def pgsql(command, *args, host="localhost", excluded_kwargs=None, password=None,
def pgsql_create(database, owner=None, template=None, **kwargs): def pgsql_create(database, owner=None, template=None, **kwargs):
"""Create a PostgreSQL database.
:param database: The database name.
:type database: str
:param owner: The owner (user/role name) of the new database.
:type owner: str
:param template: The database template name to use, if any.
:type template: str
"""
kwargs.setdefault("comment", "create %s postgres database" % database) kwargs.setdefault("comment", "create %s postgres database" % database)
if owner is not None: if owner is not None:
@ -87,12 +119,27 @@ def pgsql_create(database, owner=None, template=None, **kwargs):
def pgsql_drop(database, **kwargs): def pgsql_drop(database, **kwargs):
"""Remove a PostgreSQL database.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "drop %s postgres database" % database) kwargs.setdefault("comment", "drop %s postgres database" % database)
return pgsql("dropdb", database, **kwargs) return pgsql("dropdb", database, **kwargs)
def pgsql_dump(database, path=None, **kwargs): def pgsql_dump(database, path=None, **kwargs):
"""Export a PostgreSQL database.
:param database: The database name.
:type database: str
:param path: The name/path of the export file. Defaults the database name plus ``.sql``.
:type path: str
"""
kwargs.setdefault("comment", "dump postgres database") kwargs.setdefault("comment", "dump postgres database")
kwargs.setdefault("column_inserts", True) kwargs.setdefault("column_inserts", True)
@ -106,6 +153,12 @@ def pgsql_dump(database, path=None, **kwargs):
def pgsql_exists(database, **kwargs): def pgsql_exists(database, **kwargs):
"""Determine if a PostgreSQL database exists.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "determine if %s postgres database exists" % database) kwargs.setdefault("comment", "determine if %s postgres database exists" % database)
kwargs.setdefault("register", "%s_exists" % database) kwargs.setdefault("register", "%s_exists" % database)
@ -116,6 +169,15 @@ def pgsql_exists(database, **kwargs):
def pgsql_load(database, path, **kwargs): def pgsql_load(database, path, **kwargs):
"""Load data into a PostgreSQL database.
:param database: The database name.
:type database: str
:param path: The path to the file to be loaded.
:type path: str
"""
kwargs.setdefault("comment", "load data into a postgres database") kwargs.setdefault("comment", "load data into a postgres database")
kwargs['dbname'] = database kwargs['dbname'] = database
@ -125,6 +187,24 @@ def pgsql_load(database, path, **kwargs):
def pgsql_user(name, admin_pass=None, admin_user="postgres", op="create", password=None, **kwargs): def pgsql_user(name, admin_pass=None, admin_user="postgres", op="create", password=None, **kwargs):
"""Work with a PostgreSQL user.
:param name: The username.
:type name: str
:param admin_pass: The password for the user with admin privileges.
:type admin_pass: str
:param admin_user: The username of the user with admin privileges.
:type admin_user: str
:param op: The operation to perform: ``create``, ``drop``, ``exists``.
:type op: str
:param password: The password for a new user.
:type password: str
"""
if op == "create": if op == "create":
kwargs.setdefault("comment", "create %s postgres user" % name) kwargs.setdefault("comment", "create %s postgres user" % name)

@ -2,6 +2,12 @@ from .base import Command
def php_module(name, **kwargs): def php_module(name, **kwargs):
"""Enable a PHP module.
:param name: The module name.
:type name: str
"""
statement = "phpenmod %s" % name statement = "phpenmod %s" % name
return Command(statement, **kwargs) return Command(statement, **kwargs)

@ -5,8 +5,11 @@ from .base import Command, MultipleCommands, Prompt
def append(path, content=None, **kwargs): def append(path, content=None, **kwargs):
"""Append content to a file. """Append content to a file.
- path (str): The path to the file. :param path: The path to the file.
- content (str): The content to be appended. :type path: str
:param content: The content to be appended.
:type content: str
""" """
kwargs.setdefault("comment", "append to %s" % path) kwargs.setdefault("comment", "append to %s" % path)
@ -20,12 +23,26 @@ def archive(from_path, absolute=False, exclude=None, file_name="archive.tgz", st
**kwargs): **kwargs):
"""Create a file archive. """Create a file archive.
- from_path (str): The path that should be archived. :param from_path: The path that should be archived.
- absolute (bool): Set to ``True`` to preserve the leading slash. :type from_path: str
- exclude (str): A pattern to be excluded from the archive.
- strip (int): Remove the specified number of leading elements from the path. :param absolute: Set to ``True`` to preserve the leading slash.
- to_path (str): Where the archive should be created. This should *not* include the file name. :type absolute: str
- view (bool): View the output of the command as it happens.
:param exclude: A pattern to be excluded from the archive.
:type exclude: str
:param file_name: The name of the archive file.
:type file_name: str
:param strip: Remove the specified number of leading elements from the path.
:type strip: int
:param to_path: Where the archive should be created. This should *not* include the file name.
:type to_path: str
:param view: View the output of the command as it happens.
:type view: bool
""" """
tokens = ["tar"] tokens = ["tar"]
@ -56,9 +73,14 @@ def archive(from_path, absolute=False, exclude=None, file_name="archive.tgz", st
def certbot(domain_name, email=None, webroot=None, **kwargs): def certbot(domain_name, email=None, webroot=None, **kwargs):
"""Get new SSL certificate from Let's Encrypt. """Get new SSL certificate from Let's Encrypt.
- domain_name (str): The domain name for which the SSL certificate is requested. :param domain_name: The domain name for which the SSL certificate is requested.
- email (str): The email address of the requester sent to the certificate authority. Required. :type domain_name: str
- webroot (str): The directory where the challenge file will be created.
:param email: The email address of the requester sent to the certificate authority. Required.
:type email: str
:param webroot: The directory where the challenge file will be created.
:type webroot: str
""" """
_email = email or os.environ.get("SCRIPTTEASE_CERTBOT_EMAIL", None) _email = email or os.environ.get("SCRIPTTEASE_CERTBOT_EMAIL", None)
@ -80,10 +102,17 @@ def certbot(domain_name, email=None, webroot=None, **kwargs):
def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs): def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
"""Copy a file or directory. """Copy a file or directory.
- from_path (str): The file or directory to be copied. :param from_path: The file or directory to be copied.
- to_path (str): The location to which the file or directory should be copied. :type from_path: str
- overwrite (bool): Indicates files and directories should be overwritten if they exist.
- recursive (bool): Copy sub-directories. :param to_path: The location to which the file or directory should be copied.
:type to_path: str
:param overwrite: Indicates files and directories should be overwritten if they exist.
:type overwrite: bool
:param recursive: Copy sub-directories.
:type recursive: bool
""" """
kwargs.setdefault("comment", "copy %s to %s" % (from_path, to_path)) kwargs.setdefault("comment", "copy %s to %s" % (from_path, to_path))
@ -106,9 +135,14 @@ def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
def directory(path, group=None, mode=None, owner=None, recursive=False, **kwargs): def directory(path, group=None, mode=None, owner=None, recursive=False, **kwargs):
"""Create a directory. """Create a directory.
- path (str): The path to be created. :param path: The path to be created.
- mode (int | str): The access permissions of the new directory. :type path: str
- recursive (bool): Create all directories along the path.
:param mode: The access permissions of the new directory.
:type mode: int | str
:param recursive: Create all directories along the path.
:type recursive: bool
""" """
comment = kwargs.pop("comment", "create directory %s" % path) comment = kwargs.pop("comment", "create directory %s" % path)
@ -155,12 +189,23 @@ def directory(path, group=None, mode=None, owner=None, recursive=False, **kwargs
def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, view=False, **kwargs): def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, view=False, **kwargs):
"""Extract a file archive. """Extract a file archive.
- from_path (str): The path that should be archived. :param from_path: The path to be extracted.
- absolute (bool): Set to ``True`` to preserve the leading slash. :type from_path: str
- exclude (str): A pattern to be excluded from the archive.
- strip (int): Remove the specified number of leading elements from the path. :param absolute: Set to ``True`` to preserve the leading slash.
- to_path (str): Where the archive should be extracted. This should *not* include the file name. :type absolute: str
- view (bool): View the output of the command as it happens.
:param exclude: A pattern to be excluded from the extraction.
:type exclude: str
:param strip: Remove the specified number of leading elements from the path.
:type strip: int
:param to_path: Where the extraction should occur.
:type to_path: str
:param view: View the output of the command as it happens.
:type view: bool
""" """
_to_path = to_path or "./" _to_path = to_path or "./"
@ -192,9 +237,14 @@ def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, v
def link(source, force=False, target=None, **kwargs): def link(source, force=False, target=None, **kwargs):
"""Create a symlink. """Create a symlink.
- source (str): The source of the link. :param source: The source of the link.
- force (bool): Force the creation of the link. :type source: str
- target (str): The name or path of the target. Defaults to the base name of the source path.
:param force: Force the creation of the link.
:type force: bool
:param target: The name or path of the target. Defaults to the base name of the source path.
:type target: str
""" """
_target = target or os.path.basename(source) _target = target or os.path.basename(source)
@ -215,8 +265,11 @@ def link(source, force=False, target=None, **kwargs):
def move(from_path, to_path, **kwargs): def move(from_path, to_path, **kwargs):
"""Move a file or directory. """Move a file or directory.
- from_path (str): The current path. :param from_path: The current path.
- to_path (str): The new path. :type from_path: str
:param to_path: The new path.
:type to_path: str
""" """
kwargs.setdefault("comment", "move %s to %s" % (from_path, to_path)) kwargs.setdefault("comment", "move %s to %s" % (from_path, to_path))
@ -228,11 +281,20 @@ def move(from_path, to_path, **kwargs):
def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs): def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs):
"""Set permissions on a file or directory. """Set permissions on a file or directory.
- path (str): The path to be changed. :param path: The path to be changed.
- group (str): The name of the group to be applied. :type path: str
- mode (int | str): The access permissions of the file or directory.
- owner (str): The name of the user to be applied. :param group: The name of the group to be applied.
- recursive: Create all directories along the path. :type group: str
:param mode: The access permissions of the file or directory.
:type mode: int | str
:param owner: The name of the user to be applied.
:type owner: str
:param recursive: Update all files and directories along the path.
:type recursive: bool
""" """
comment = kwargs.pop("comment", "set permissions on %s" % path) comment = kwargs.pop("comment", "set permissions on %s" % path)
@ -292,13 +354,26 @@ def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs):
def prompt(name, back_title="Input", choices=None, default=None, dialog=False, help_text=None, label=None, **kwargs): def prompt(name, back_title="Input", choices=None, default=None, dialog=False, help_text=None, label=None, **kwargs):
"""Prompt the user for input. """Prompt the user for input.
- name (str): The programmatic name of the input. :param name: The programmatic name of the input.
- back_title (str): The back title used with the dialog command. :type name: str
- choices (str | list): A list of valid choices.
- default: The default value. :param back_title: The back title used with the dialog command.
- dialog (bool): Use a dialog command for the prompt. :type back_title: str
- help_text (str): The text to display with the dialog command.
- label (str): The label for the input. :param choices: A list of valid choices.
:type choices: list | str
:param default: The default value.
:type default: str
:param dialog: Use a dialog command for the prompt.
:type dialog: bool
:param help_text: The text to display with the dialog command.
:type help_text: str
:param label: The label for the input.
:type label: str
""" """
return Prompt( return Prompt(
@ -316,9 +391,14 @@ def prompt(name, back_title="Input", choices=None, default=None, dialog=False, h
def remove(path, force=False, recursive=False, **kwargs): def remove(path, force=False, recursive=False, **kwargs):
"""Remove a file or directory. """Remove a file or directory.
- path (str): The path to be removed. :param path: The path to be removed.
- force (bool): Force the removal. :type path: str
- recursive (bool): Remove all directories along the path.
:param force: Force the removal.
:type force: bool
:param recursive: Remove all directories along the path.
:type recursive: bool
""" """
kwargs.setdefault("comment", "remove %s" % path) kwargs.setdefault("comment", "remove %s" % path)
@ -339,11 +419,20 @@ def remove(path, force=False, recursive=False, **kwargs):
def replace(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs): def replace(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs):
"""Find and replace text in a file. """Find and replace text in a file.
- path (str): The path to the file to be edited. :param path: The path to the file to be edited.
- backup (str): The backup file extension to use. :type path: str
- delimiter (str): The pattern delimiter.
- find (str): The old text. Required. :param backup: The backup file extension to use.
- replace (str): The new text. Required. :type backup: str
:param delimiter: The pattern delimiter.
:type delimiter: str
:param find: The old text. Required.
:param find: str
:param replace: The new text. Required.
:type replace: str
""" """
@ -365,7 +454,12 @@ def replace(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs)
def run(statement, **kwargs): def run(statement, **kwargs):
"""Run any command.""" """Run any command.
:param statement: The statement to be executed.
:type statement: str
"""
kwargs.setdefault("comment", "run statement") kwargs.setdefault("comment", "run statement")
return Command(statement, **kwargs) return Command(statement, **kwargs)
@ -375,16 +469,35 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
recursive=True, user=None, **kwargs): recursive=True, user=None, **kwargs):
"""Synchronize a directory structure. """Synchronize a directory structure.
- source (str): The source directory. :param source: The source directory.
- target (str): The target directory. :type source: str
- delete (bool): Indicates target files that exist in source but not in target should be removed.
- exclude (str): The path to an exclude file. :param target: The target directory.
- host (str): The host name or IP address. This causes the command to run over SSH. :type target: str
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied.
- links (bool): Include symlinks in the sync. :param delete: Indicates target files that exist in source but not in target should be removed.
- port (int): The SSH port to use for remote connections. :type delete: bool
- recursive (bool): Indicates source contents should be recursively synchronized.
- user (str): The user name to use for remote connections. :param exclude: The path to an exclude file.
:type exclude: str
:param host: The host name or IP address.
:type host: str
:param key_file: The privacy SSH key (path) for remote connections. User expansion is automatically applied.
:type key_file: str
:param links: Include symlinks in the sync.
:type links: bool
:param port: The SSH port to use for remote connections.
:type port: int
:param recursive: Indicates source contents should be recursively synchronized.
:type recursive: bool
:param user: The username to use for remote connections.
:type user: str
""" """
# - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of # - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of
@ -455,12 +568,23 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
def scopy(from_path, to_path, host=None, key_file=None, port=22, user=None, **kwargs): def scopy(from_path, to_path, host=None, key_file=None, port=22, user=None, **kwargs):
"""Copy a file or directory to a remote server. """Copy a file or directory to a remote server.
- from_path (str): The source directory. :param from_path: The source of the copy.
- to_path (str): The target directory. :type from_path: str
- host (str): The host name or IP address. Required.
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied. :param to_path: The remote target of the copy.
- port (int): The SSH port to use for remote connections. :type to_path: str
- user (str): The user name to use for remote connections.
:param host: The host name or IP address. Required.
:type host: str
:param key_file: The privacy SSH key (path) for remote connections. User expansion is automatically applied.
:type key_file: str
:param port: The SSH port to use for remote connections.
:type port: int
:param user: The username to use for remote connections.
:type user: str
""" """
kwargs.setdefault("comment", "copy %s to remote %s" % (from_path, to_path)) kwargs.setdefault("comment", "copy %s to remote %s" % (from_path, to_path))
@ -491,16 +615,23 @@ def scopy(from_path, to_path, host=None, key_file=None, port=22, user=None, **kw
def sync(source, target, delete=False, exclude=None, links=True, recursive=True, **kwargs): def sync(source, target, delete=False, exclude=None, links=True, recursive=True, **kwargs):
"""Synchronize a local directory structure. """Synchronize a local directory structure.
- source (str): The source directory. :param source: The source directory.
- target (str): The target directory. :type source: str
- delete (bool): Indicates target files that exist in source but not in target should be removed.
- exclude (str): The path to an exclude file. :param target: The target directory.
- host (str): The host name or IP address. This causes the command to run over SSH. :type target: str
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied.
- links (bool): Include symlinks in the sync. :param delete: Indicates target files that exist in source but not in target should be removed.
- port (int): The SSH port to use for remote connections. :type delete: bool
- recursive (bool): Indicates source contents should be recursively synchronized.
- user (str): The user name to use for remote connections. :param exclude: The path to an exclude file.
:type exclude: str
:param links: Include symlinks in the sync.
:type links: bool
:param recursive: Indicates source contents should be recursively synchronized.
:type recursive: bool
""" """
# - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of # - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of
@ -552,7 +683,8 @@ def sync(source, target, delete=False, exclude=None, links=True, recursive=True,
def touch(path, **kwargs): def touch(path, **kwargs):
"""Touch a file or directory. """Touch a file or directory.
- path (str): The file or directory to touch. :param path: The file or directory to touch.
:type path: str
""" """
kwargs.setdefault("comment", "touch %s" % path) kwargs.setdefault("comment", "touch %s" % path)
@ -563,7 +695,8 @@ def touch(path, **kwargs):
def wait(seconds, **kwargs): def wait(seconds, **kwargs):
"""Pause execution for a number of seconds. """Pause execution for a number of seconds.
- seconds (int): The number of seconds to wait. :param seconds: The number of seconds to wait.
:type seconds: int
""" """
kwargs.setdefault("comment", "pause for %s seconds" % seconds) kwargs.setdefault("comment", "pause for %s seconds" % seconds)
@ -574,8 +707,11 @@ def wait(seconds, **kwargs):
def write(path, content=None, **kwargs): def write(path, content=None, **kwargs):
"""Write to a file. """Write to a file.
- path (str): The file to be written. :param path: The file to be written.
- content (str): The content to be written. Note: If omitted, this command is equivalent to ``touch``. :type path: str
:param content: The content to be written. Note: If omitted, this command is equivalent to ``touch``.
:type content: str
""" """
_content = content or "" _content = content or ""

@ -4,11 +4,20 @@ from .base import Command
def python_pip(name, op="install", upgrade=False, venv=None, version=3, **kwargs): def python_pip(name, op="install", upgrade=False, venv=None, version=3, **kwargs):
"""Use pip to install or uninstall a Python package. """Use pip to install or uninstall a Python package.
- name (str): The name of the package. :param name: The name of the package.
- op (str): The operation to perform; install, uninstall :type name: str
- upgrade (bool): Upgrade an installed package.
- venv (str): The name of the virtual environment to load. :param op: The operation to perform; ``install``, ``uninstall``
- version (int): The Python version to use, e.g. ``2`` or ``3``. :type op: str
:param upgrade: Upgrade an installed package.
:type upgrade: bool
:param venv: The name of the virtual environment to load.
:type venv: str
:param version: The Python version to use, e.g. ``2`` or ``3``.
:type version: int
""" """
manager = "pip" manager = "pip"
@ -57,7 +66,8 @@ def python_pip_file(path, venv=None, version=3, **kwargs):
def python_virtualenv(name, **kwargs): def python_virtualenv(name, **kwargs):
"""Create a Python virtual environment. """Create a Python virtual environment.
- name (str): The name of the environment to create. :param name: The name of the environment to create.
:type name: str
""" """
kwargs.setdefault("comment", "create %s virtual environment" % name) kwargs.setdefault("comment", "create %s virtual environment" % name)

@ -44,7 +44,8 @@ __all__ = (
def apache(op, **kwargs): def apache(op, **kwargs):
"""Execute an Apache-related command. """Execute an Apache-related command.
- op (str): The operation to perform; reload, restart, start, stop, test. :param op: The operation to perform; ``reload``, ``restart``, ``start``, ``stop``, ``test``.
:type op: str
""" """
if op == "reload": if op == "reload":
@ -64,7 +65,8 @@ def apache(op, **kwargs):
def apache_disable_module(module, **kwargs): def apache_disable_module(module, **kwargs):
"""Disable an Apache module. """Disable an Apache module.
- name (str): The module name. :param module: The name of the module.
:type module: str
""" """
kwargs.setdefault("comment", "disable %s apache module" % module) kwargs.setdefault("comment", "disable %s apache module" % module)
@ -75,7 +77,8 @@ def apache_disable_module(module, **kwargs):
def apache_disable_site(site, **kwargs): def apache_disable_site(site, **kwargs):
"""Disable an Apache site. """Disable an Apache site.
- name (str): The domain name. :param site: The site/domain name.
:type site: str
""" """
kwargs.setdefault("comment", "disable %s apache site" % site) kwargs.setdefault("comment", "disable %s apache site" % site)
@ -86,7 +89,8 @@ def apache_disable_site(site, **kwargs):
def apache_enable_module(module, **kwargs): def apache_enable_module(module, **kwargs):
"""Enable an Apache module. """Enable an Apache module.
- name (str): The module name. :param module: The name of the module.
:type module: str
""" """
kwargs.setdefault("comment", "enable %s apache module" % module) kwargs.setdefault("comment", "enable %s apache module" % module)
@ -97,6 +101,8 @@ def apache_enable_module(module, **kwargs):
def apache_enable_site(site, **kwargs): def apache_enable_site(site, **kwargs):
"""Enable an Apache site. """Enable an Apache site.
:param site: The site/domain name.
:type site: str
""" """
kwargs.setdefault("comment", "enable %s apache module" % site) kwargs.setdefault("comment", "enable %s apache module" % site)
@ -105,6 +111,7 @@ def apache_enable_site(site, **kwargs):
def apache_reload(**kwargs): def apache_reload(**kwargs):
"""Reload the apache service."""
kwargs.setdefault("comment", "reload apache") kwargs.setdefault("comment", "reload apache")
kwargs.setdefault("register", "apache_reloaded") kwargs.setdefault("register", "apache_reloaded")
@ -112,6 +119,7 @@ def apache_reload(**kwargs):
def apache_restart(**kwargs): def apache_restart(**kwargs):
"""Restart the apache service."""
kwargs.setdefault("comment", "restart apache") kwargs.setdefault("comment", "restart apache")
kwargs.setdefault("register", "apache_restarted") kwargs.setdefault("register", "apache_restarted")
@ -119,6 +127,7 @@ def apache_restart(**kwargs):
def apache_start(**kwargs): def apache_start(**kwargs):
"""Start the apache service."""
kwargs.setdefault("comment", "start apache") kwargs.setdefault("comment", "start apache")
kwargs.setdefault("register", "apache_started") kwargs.setdefault("register", "apache_started")
@ -126,12 +135,14 @@ def apache_start(**kwargs):
def apache_stop(**kwargs): def apache_stop(**kwargs):
"""Stop the apache service."""
kwargs.setdefault("comment", "stop apache") kwargs.setdefault("comment", "stop apache")
return Command("service apache2 stop", **kwargs) return Command("service apache2 stop", **kwargs)
def apache_test(**kwargs): def apache_test(**kwargs):
"""Run a configuration test on apache."""
kwargs.setdefault("comment", "check apache configuration") kwargs.setdefault("comment", "check apache configuration")
kwargs.setdefault("register", "apache_checks_out") kwargs.setdefault("register", "apache_checks_out")
@ -141,7 +152,8 @@ def apache_test(**kwargs):
def service_reload(service, **kwargs): def service_reload(service, **kwargs):
"""Reload a service. """Reload a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "reload %s service" % service) kwargs.setdefault("comment", "reload %s service" % service)
@ -153,7 +165,8 @@ def service_reload(service, **kwargs):
def service_restart(service, **kwargs): def service_restart(service, **kwargs):
"""Restart a service. """Restart a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "restart %s service" % service) kwargs.setdefault("comment", "restart %s service" % service)
@ -165,7 +178,8 @@ def service_restart(service, **kwargs):
def service_start(service, **kwargs): def service_start(service, **kwargs):
"""Start a service. """Start a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "start %s service" % service) kwargs.setdefault("comment", "start %s service" % service)
@ -177,7 +191,8 @@ def service_start(service, **kwargs):
def service_stop(service, **kwargs): def service_stop(service, **kwargs):
"""Stop a service. """Stop a service.
- name (str): The service name. :param service: The service name.
:type service: str
""" """
kwargs.setdefault("comment", "stop %s service" % service) kwargs.setdefault("comment", "stop %s service" % service)
@ -189,7 +204,8 @@ def service_stop(service, **kwargs):
def system(op, **kwargs): def system(op, **kwargs):
"""Perform a system operation. """Perform a system operation.
- op (str): The operation to perform; reboot, update, upgrade. :param op: The operation to perform; ``reboot``, ``update``, ``upgrade``.
:type op: str
""" """
if op == "reboot": if op == "reboot":
@ -205,7 +221,8 @@ def system(op, **kwargs):
def system_install(package, **kwargs): def system_install(package, **kwargs):
"""Install a system-level package. """Install a system-level package.
- name (str): The name of the package to install. :param package: The name of the package to install.
:type package: str
""" """
kwargs.setdefault("comment", "install system package %s" % package) kwargs.setdefault("comment", "install system package %s" % package)
@ -214,6 +231,7 @@ def system_install(package, **kwargs):
def system_reboot(**kwargs): def system_reboot(**kwargs):
"""Reboot the system."""
kwargs.setdefault("comment", "reboot the system") kwargs.setdefault("comment", "reboot the system")
return Command("reboot", **kwargs) return Command("reboot", **kwargs)
@ -222,7 +240,8 @@ def system_reboot(**kwargs):
def system_uninstall(package, **kwargs): def system_uninstall(package, **kwargs):
"""Uninstall a system-level package. """Uninstall a system-level package.
- name (str): The name of the package to uninstall. :param package: The name of the package to remove.
:type package: str
""" """
kwargs.setdefault("comment", "remove system package %s" % package) kwargs.setdefault("comment", "remove system package %s" % package)
@ -231,12 +250,14 @@ def system_uninstall(package, **kwargs):
def system_update(**kwargs): def system_update(**kwargs):
"""Update the system's package info."""
kwargs.setdefault("comment", "update system package info") kwargs.setdefault("comment", "update system package info")
return Command("apt update -y", **kwargs) return Command("apt update -y", **kwargs)
def system_upgrade(**kwargs): def system_upgrade(**kwargs):
"""updated the system."""
kwargs.setdefault("comment", "upgrade the system") kwargs.setdefault("comment", "upgrade the system")
return Command("apt upgrade -y", **kwargs) return Command("apt upgrade -y", **kwargs)
@ -245,11 +266,20 @@ def system_upgrade(**kwargs):
def user(name, groups=None, home=None, op="add", password=None, **kwargs): def user(name, groups=None, home=None, op="add", password=None, **kwargs):
"""Create or remove a user. """Create or remove a user.
- name (str): The user name. :param name: The username.
- groups (str | list): A list of groups to which the user should belong. :type name: str
- home (str): The path to the user's home directory.
- op (str); The operation to perform; ``add`` or ``remove``. :param groups: A list of groups to which the user should belong.
- password (str): The user's password. (NOT IMPLEMENTED) :type groups: list | str
:param home: The path to the user's home directory.
:type home: str
:param op: The operation to perform; ``add`` or ``remove``.
:type op:
:param password: The user's password. (NOT IMPLEMENTED)
:type password: str
""" """
if op == "add": if op == "add":

@ -125,8 +125,7 @@ class BaseLoader(File):
:param path: The path to the command file. :param path: The path to the command file.
:type path: str :type path: str
:param context: Global context that may be used when to parse the command file, snippets, and templates. This is :param context: Global context that may be used to parse the command file and templates.
converted to a ``dict`` when passed to a Snippet or Template.
:type context: scripttease.lib.contexts.Context :type context: scripttease.lib.contexts.Context
:param locations: A list of paths where templates and other external files may be found. The ``templates/`` :param locations: A list of paths where templates and other external files may be found. The ``templates/``

Loading…
Cancel
Save