Compare commits

..

11 Commits

  1. 6
      Makefile
  2. 2
      RELEASE.txt
  3. 2
      VERSION.txt
  4. BIN
      docs/source/_static/images/mattermost-1.png
  5. BIN
      docs/source/_static/images/mattermost-2.png
  6. BIN
      docs/source/_static/images/mattermost-3.png
  7. BIN
      docs/source/_static/images/slack-1.jpg
  8. BIN
      docs/source/_static/images/slack-2.jpg
  9. BIN
      docs/source/_static/images/slack-3.jpg
  10. BIN
      docs/source/_static/images/twist-1.png
  11. BIN
      docs/source/_static/images/twist-2.png
  12. 2
      help/en/docs/cli.md
  13. 46
      help/en/docs/topics/steps-file.md
  14. 97
      help/en/docs/usage/apache.md
  15. 10
      help/en/docs/usage/centos.md
  16. 5
      help/en/docs/usage/django.md
  17. BIN
      help/en/docs/usage/images/mattermost-1.png
  18. BIN
      help/en/docs/usage/images/mattermost-2.png
  19. BIN
      help/en/docs/usage/images/mattermost-3.png
  20. BIN
      help/en/docs/usage/images/slack-1.jpg
  21. BIN
      help/en/docs/usage/images/slack-2.jpg
  22. BIN
      help/en/docs/usage/images/slack-3.jpg
  23. BIN
      help/en/docs/usage/images/twist-1.png
  24. BIN
      help/en/docs/usage/images/twist-2.png
  25. 82
      help/en/docs/usage/messages.md
  26. 4
      help/en/docs/usage/mysql.md
  27. 45
      help/en/docs/usage/packages.md
  28. 5
      help/en/docs/usage/pgsql.md
  29. 1
      help/en/docs/usage/php.md
  30. 16
      help/en/docs/usage/posix.md
  31. 2
      help/en/docs/usage/python.md
  32. 60
      help/en/docs/usage/system.md
  33. 10
      help/en/docs/usage/ubuntu.md
  34. 41
      help/en/docs/usage/users.md
  35. 6
      help/en/mkdocs.yml
  36. 4
      scripttease/constants.py
  37. 6
      scripttease/data/inventory/matomo/meta.ini
  38. 91
      scripttease/data/inventory/matomo/steps.ini
  39. 6
      scripttease/data/inventory/matomo/templates/crontab.txt
  40. 12
      scripttease/data/inventory/matomo/templates/http.conf
  41. 52
      scripttease/data/inventory/matomo/templates/https.conf
  42. 43
      scripttease/data/inventory/matomo/variables.ini
  43. 13
      scripttease/lib/commands/base.py
  44. 24
      scripttease/lib/commands/messages.py
  45. 19
      scripttease/lib/commands/pgsql.py
  46. 6
      scripttease/lib/commands/posix.py
  47. 4
      scripttease/lib/factories.py
  48. 8
      scripttease/lib/loaders/base.py
  49. 10
      scripttease/version.py
  50. 12
      tests/test_lib_commands_messages.py
  51. 8
      tests/test_lib_commands_pgsql.py
  52. 2
      tests/test_lib_factories.py

@ -51,7 +51,7 @@ docs: lines
cd docs && make html; cd docs && make html;
cd docs && make coverage; cd docs && make coverage;
open docs/build/coverage/python.txt; open docs/build/coverage/python.txt;
open docs/build/html/index.html; firefox docs/build/html/index.html;
#> clean - Remove pyc files and documentation builds. #> clean - Remove pyc files and documentation builds.
clean: clean:
@ -85,11 +85,11 @@ support:
cp meta.ini $(DOCS_PATH)/config/$(PROJECT_NAME).ini; cp meta.ini $(DOCS_PATH)/config/$(PROJECT_NAME).ini;
cd $(DOCS_WWW)/en/$(PROJECT_NAME) && ln -fs $(RELEASE) latest; cd $(DOCS_WWW)/en/$(PROJECT_NAME) && ln -fs $(RELEASE) latest;
cd $(DOCS_WWW)/en/$(PROJECT_NAME) && ln -fs $(RELEASE) stable; # if product is stable cd $(DOCS_WWW)/en/$(PROJECT_NAME) && ln -fs $(RELEASE) stable; # if product is stable
cd $(DOCS_PATH) && make build; #cd $(DOCS_PATH) && make build;
#> tests - Run unit tests and generate coverage report. #> tests - Run unit tests and generate coverage report.
tests: tests:
coverage run --source=. -m pytest; coverage run --source=. -m pytest;
coverage html --directory=$(COVERAGE_PATH); coverage html --directory=$(COVERAGE_PATH);
open $(COVERAGE_PATH)/index.html; firefox $(COVERAGE_PATH)/index.html;

@ -1 +1 @@
7.0.0-a 7.1.5-a

@ -1 +1 @@
7.0.0 7.1.5

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

@ -33,7 +33,7 @@ NOTES
A nice benefit of using configuration for commands is that the information may be used to output documentation. This allows the automatic creation of an install guide or tutorial using exactly the same commands that would be used for the actual work. A nice benefit of using configuration for commands is that the information may be used to output documentation. This allows the automatic creation of an install guide or tutorial using exactly the same commands that would be used for the actual work.
Additionally, Script Tease provides the [explain](commands/messages.md#explain) and [screenshot](commands/messages.md#screenshot) commands that help provide extra content for documentary output. Additionally, Script Tease provides the [explain](usage/messages.md#explain) and [screenshot](usage/messages.md#screenshot) commands that help provide extra content for documentary output.
```text ```text
usage: tease docs [-h] [-o= {html,md,plain,rst}] [-C= VARIABLES] [-i= STEPS_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}] usage: tease docs [-h] [-o= {html,md,plain,rst}] [-C= VARIABLES] [-i= STEPS_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}]

@ -76,11 +76,6 @@ The comment comes from the section name (INI) or list name (YAML). It is include
; ... ; ...
``` ```
```yaml
- this becomes the comment:
# ...
```
### env ### env
The `env` option indicates the target environment (or environments) in which the statement should run. This is not used in command generation, but may be used for filtering. The `env` option indicates the target environment (or environments) in which the statement should run. This is not used in command generation, but may be used for filtering.
@ -136,13 +131,6 @@ shell: /bin/bash
A `yes` indicates processing should stop if the statement fails to execute with success. If provided, it is included by default when the statement is generated, but may be suppressed. Additionally, when [register](#register) is defined, this option will use the result of the command to determine success. This option is also useful for programmatic execution. A `yes` indicates processing should stop if the statement fails to execute with success. If provided, it is included by default when the statement is generated, but may be suppressed. Additionally, when [register](#register) is defined, this option will use the result of the command to determine success. This option is also useful for programmatic execution.
```yaml
- check apache configuration:
apache: test
register: apache_ok
stop: yes
```
!!! warning !!! warning
Some commands do not provide a zero or non-zero exit code on success or failure. You should verify that the `stop` will actually be used. Some commands do not provide a zero or non-zero exit code on success or failure. You should verify that the `stop` will actually be used.
@ -163,39 +151,7 @@ sudo: yes
### tags ### tags
`tags` is a comma separated list (INI) or list (YAML) of tag names. These may be used for filtering. `tags` is a comma separated list of tag names. These may be used for filtering.
```yaml
- install apache:
install: apache2
tags: [apache, web]
- enable wsgi:
apache.enable: mod_wsgi
tags: [apache, web]
- restart apache:
apache.restart:
tags: [apache, web]
- run django checks:
django: check
tags: [django, python]
- apply database migrations:
django: migrate
tags: [django, python]
```
With YAML, tags may also be specified like so:
```yaml
- install apache:
install: apache2
tags:
- apache
- web
```
## Ad Hoc Parameters ## Ad Hoc Parameters

@ -0,0 +1,97 @@
# Apache
Summary: Work with Apache.
## Available Commands
### apache.disable_module
Disable an Apache module. NOT SUPPORTED ACROSS ALL OPERATING SYSTEMS.
```ini
[disable ifenv]
apache.disable_module: ifenv
sudo: yes
```
### apache.disable_site
Disable an Apache site (virtual host configuration). NOT SUPPORTED ACROSS ALL OPERATING SYSTEMS.
```ini
[disable the default apache site]
apache.disable_site: 000-default
sudo: yes
```
### apache.enable_module
Enable an Apache module. NOT SUPPORTED ACROSS ALL OPERATING SYSTEMS.
```ini
[enable apache modules]
apache.enable_module: $item
items: ssl, rewrite, wsgi
sudo: yes
```
### apache.enable_site
Enable an Apache site (virtual host configuration). NOT SUPPORTED ACROSS ALL OPERATING SYSTEMS.
```ini
[enable the application configuration]
apache.enable_site: example.app
sudo: yes
```
### apache.reload
Reload Apache configuration.
```ini
[reload apache]
apache.reload:
sudo: yes
```
### apache.restart
Restart the Apache service.
```ini
[restart apache]
apache.restart:
sudo: yes
```
### apache.start
Start the Apache service.
```ini
[start apache]
apache.start:
sudo: yes
```
### apache.stop
Stop the Apache service.
```ini
[stop apache]
apache.stop:
sudo: yes
```
### apache.test
Test Apache configuration.
```ini
[test apache configuration]
apache.test:
sudo: yes
stop: yes
```

@ -2,7 +2,15 @@
## Available Commands ## Available Commands
The `centos` profile incorporates commands from [Django](../commands/django.md), [messages](../commands/messages.md), [MySQL](../commands/mysql.md), [PHP](../commands/php.md), [POSIX](../commands/posix.md), [Postgres](../commands/pgsql.md), and [Python](../commands/python.md). The `centos` profile incorporates commands from
- [Django](../commands/django.md)
- [messages](../commands/messages.md)
- [MySQL](../commands/mysql.md)
- [PHP](../commands/php.md)
- [POSIX](../commands/posix.md)
- [Postgres](../commands/pgsql.md)
- [Python](../commands/python.md)
### apache ### apache

@ -28,7 +28,6 @@ django.dump: projects.Category
indent: 4 indent: 4
natural_foreign: yes natural_foreign: yes
natural_primary: yes natural_primary: yes
``` ```
`settings` becomes "--settings=tenants.example_com.settings". `indent` becomes "--indent=4". `natural_foreign` and `natural_primary` become "--natural-foreign" and "--natural-primary" respectively. `settings` becomes "--settings=tenants.example_com.settings". `indent` becomes "--indent=4". `natural_foreign` and `natural_primary` become "--natural-foreign" and "--natural-primary" respectively.
@ -41,7 +40,6 @@ natural_primary: yes
[run django checks] [run django checks]
django.check: django.check:
stop: yes stop: yes
``` ```
### collectstatic ### collectstatic
@ -82,7 +80,6 @@ django.dump: projects
[dump project categories] [dump project categories]
django.dump: projects.Category django.dump: projects.Category
path: local/projects/fixtures/default-categories.json path: local/projects/fixtures/default-categories.json
``` ```
### loaddata ### loaddata
@ -99,7 +96,6 @@ Load fixture data.
[load project categories] [load project categories]
django.load: projects django.load: projects
path: local/projects/fixtures/default-categories.json path: local/projects/fixtures/default-categories.json
``` ```
### migrate ### migrate
@ -128,5 +124,4 @@ This will generate a statement like:
```bash ```bash
./manage.py command_name --option-one="asdf" --option-two=1234 --option-three ./manage.py command_name --option-one="asdf" --option-two=1234 --option-three
``` ```

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

@ -17,11 +17,6 @@ Use the dialog CLI to display a message.
dialog: "This is a message." dialog: "This is a message."
``` ```
```yaml
- send some feedback:
dialog: "This is a message."
```
!!! warning !!! warning
The dialog command line utility must be installed. The dialog command line utility must be installed.
@ -47,11 +42,34 @@ Display a simple message.
echo: "This is a message." echo: "This is a message."
``` ```
```yaml ### mattermost
- send some feedback:
echo: "This is a message." Send a message via Mattermost.
- `url` (str): Required. The URL to which the message should be sent.
```ini
[send some feedback]
mattermost: "This is a message."
url: https://mattermostdomain.com/path/to/your/channel/webhook
``` ```
Using the mattermost command requires setup from your Mattermost admin. The procedure will be something like the following:
**1.** Click on Integrations from the admin menu.
![integrations menu](images/mattermost-1.png)
**2.** Click Incoming Webhooks and then Add New Webhook.
![add new webhook](images/mattermost-2.png)
**3.** Fill in the information and click Save.
![webhook form](images/mattermost-3.png)
**4.** Copy and past the webhook URL to your configuration.
### slack ### slack
Send a message via Slack. Send a message via Slack.
@ -64,14 +82,26 @@ slack: "This is a message."
url: https://subdomain.slack.com/path/to/your/integration url: https://subdomain.slack.com/path/to/your/integration
``` ```
```yaml
- send some feedback:
slack: "This is a message."
url: https://subdomain.slack.com/path/to/your/integration
```
!!! note !!! note
You could easily define a variable for the Slack URL and set ``url: {{ slack_url }}`` to save some typing. See [variables](../config/variables.md). You could easily define a variable for the Slack URL and set ``url: {{ slack_url }}`` to save some typing. See [variables](../topics/variables.md).
Using the slack command requires setup from your Slack admin. The procedure will be something like the following:
**1.** Log in to Slack and go to [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.
![toggle activation](images/slack-1.jpg)
**4.** Next click Add new Webhook to Workspace and select the channel to which the message will be posted.
![incoming webhooks](images/slack-2.jpg)
![select channel](images/slack-3.jpg)
**5.** Copy the URL for the new webhook to use as the ``url`` parameter for the Slack command.
### screenshot ### screenshot
@ -105,12 +135,20 @@ twist: "This is a message."
url: https://subdomain.twist.com/path/to/your/integration url: https://subdomain.twist.com/path/to/your/integration
``` ```
```yaml
- send some feedback:
twist: "This is a message."
url: https://subdomain.twist.com/path/to/your/integration
```
!!! note !!! note
As with Slack, you could easily define a variable for the Twist URL and set ``url: {{ twist_url }}``. See [variables](../config/variables.md). As with Slack, you could easily define a variable for the Twist URL and set ``url: {{ twist_url }}``. See [variables](../topics/variables.md).
Using the twist command requires setup on your Twist account. The procedure will be something like the following:
**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.
![provide info about your integration](images/twist-1.png)
**3.** After submitting this info, go to Installation. Select a channel and who to notify. Then click "Install integration".
![install integration](images/twist-2.png)
**4.** Copy the "Post content manually" URL for use in your configuration file.

@ -36,7 +36,6 @@ Dump the database schema. Argument is the database name.
[create a soft backup of the database] [create a soft backup of the database]
mysql.dump: example_app mysql.dump: example_app
path: /tmp/example_app.sql path: /tmp/example_app.sql
``` ```
### mysql.exists ### mysql.exists
@ -46,10 +45,8 @@ Determine if a database exists. Argument is the database name.
```ini ```ini
[determine if the database exists] [determine if the database exists]
mysql.exists: example_app mysql.exists: example_app
``` ```
### mysql.grant ### mysql.grant
Grant privileges to a user. Grant privileges to a user.
@ -61,7 +58,6 @@ Grant privileges to a user.
[grant select privileges to bob] [grant select privileges to bob]
mysql.grant: bob mysql.grant: bob
privileges: select privileges: select
``` ```
### mysql.user ### mysql.user

@ -0,0 +1,45 @@
# Packages
Summary: Work with system packages.
## Available Commands
### install
Install a package.
```ini
[install vim]
install: vim
sudo: yes
```
### uninstall
Uninstall a package.
```ini
[uninstall vim]
uninstall: vim
sudo: yes
```
### update
Update package information.
```ini
[update package info]
update:
sudo: yes
```
### upgrade
Upgrade system packages.
```ini
[upgrade system packages]
upgrade:
sudo: yes
```

@ -18,7 +18,6 @@ Options provided in the steps file are automatically converted to command line s
pgsql.dump: example_app pgsql.dump: example_app
schema_only: yes schema_only: yes
path: /tmp/example_app.sql path: /tmp/example_app.sql
``` ```
`schema_only` becomes "--schema-only". `schema_only` becomes "--schema-only".
@ -43,7 +42,6 @@ Drop a database. Argument is the database name.
```ini ```ini
[drop the testing database] [drop the testing database]
pgsql.drop: testing_example_app pgsql.drop: testing_example_app
``` ```
### pgsql.dump ### pgsql.dump
@ -57,7 +55,6 @@ Dump the database schema. Argument is the database name.
pgsql.dump: example_app pgsql.dump: example_app
column_inserts: yes column_inserts: yes
path: /tmp/example_app.sql path: /tmp/example_app.sql
``` ```
### pgsql.exists ### pgsql.exists
@ -67,7 +64,6 @@ Determine if a database exists. Argument is the database name.
```ini ```ini
[determine if the database exists] [determine if the database exists]
pgsql.exists: example_app pgsql.exists: example_app
``` ```
### pgsql.grant ### pgsql.grant
@ -88,7 +84,6 @@ pgsql.grant: bob
database: example_app database: example_app
privileges: select privileges: select
schema: public schema: public
``` ```
### pgsql.user ### pgsql.user

@ -11,5 +11,4 @@ Enable a PHP module. Argument is the module name.
```ini ```ini
[enable postgres for PHP] [enable postgres for PHP]
php.module: pdo_pgsql php.module: pdo_pgsql
``` ```

@ -1,6 +1,6 @@
# POSIX # POSIX
Summary: Work with common POSIX-compliant commands.. Summary: Work with common POSIX-compliant commands.
## Available Commands ## Available Commands
@ -32,7 +32,6 @@ Create an archive (tarball). Argument is the target file or directory.
archive: /path/to/file_or_directory archive: /path/to/file_or_directory
file_name: testing.tgz file_name: testing.tgz
to: /tmp to: /tmp
``` ```
### certbot ### certbot
@ -48,7 +47,6 @@ Use Let's Encrypt (certbot) to acquire an SSL certificate. Argument is the domai
[get an SSL cert] [get an SSL cert]
ssl: example.app ssl: example.app
email: webmaster@example.app email: webmaster@example.app
``` ```
### copy ### copy
@ -63,7 +61,6 @@ Copy a file or directory. First argument is the target file/directory. Second ar
copy: /path/to/directory /path/to/new_directory copy: /path/to/directory /path/to/new_directory
overwrite: yes overwrite: yes
recursive: yes recursive: yes
``` ```
### dir ### dir
@ -82,7 +79,6 @@ group: www-data
mode: 755 mode: 755
owner: deploy owner: deploy
recursive: yes recursive: yes
``` ```
### extract ### extract
@ -98,7 +94,6 @@ Extract an archive (tarball). Argument is the path to the archive file.
```ini ```ini
[extract an archive] [extract an archive]
extract: /path/to/archive.tgz extract: /path/to/archive.tgz
``` ```
### link ### link
@ -114,7 +109,6 @@ link: /path/to/project/releases/1.0
cd: /path/to/project cd: /path/to/project
force: yes force: yes
target: current target: current
``` ```
### move ### move
@ -124,7 +118,6 @@ Move a file or directory. First argument is the target. Second argument is the d
```ini ```ini
[move a file] [move a file]
move: /path/to/file.txt /new/path/to/file.txt move: /path/to/file.txt /new/path/to/file.txt
``` ```
### perms ### perms
@ -143,7 +136,6 @@ group: www-data
mode: 775 mode: 775
owner: deploy owner: deploy
recursive: yes recursive: yes
``` ```
### push ### push
@ -167,7 +159,6 @@ push: /path/to/project /path/on/server
key_file: ~/.ssh/example_app key_file: ~/.ssh/example_app
host: example.app host: example.app
user: deploy user: deploy
``` ```
### remove ### remove
@ -181,7 +172,7 @@ Remove a file or directory. Argument is the path.
[remove a directory] [remove a directory]
remove: /path/to/directory remove: /path/to/directory
force: yes force: yes
recusrive: yes recursive: yes
``` ```
### replace ### replace
@ -214,7 +205,6 @@ Copy a file to a remote server. First argument is the local file name. Second ar
scopy: /path/to/local.txt path/to/remove.txt scopy: /path/to/local.txt path/to/remove.txt
host: example.app host: example.app
user: deploy user: deploy
``` ```
### sync ### sync
@ -229,7 +219,6 @@ Sync (rsync) local files and directories. First argument is the target. Second a
```ini ```ini
[syncrhonize files on the local machine] [syncrhonize files on the local machine]
sync: /path/to/project /path/to/sync/directory sync: /path/to/project /path/to/sync/directory
``` ```
### touch ### touch
@ -260,5 +249,4 @@ Write to a file. Argument is the path.
[replace an existing file] [replace an existing file]
write: /path/to/file.txt write: /path/to/file.txt
content: This whole file has been replaced. content: This whole file has been replaced.
``` ```

@ -34,7 +34,6 @@ Install packages from a pip file.
pip_file: deploy/packages/testing.pip pip_file: deploy/packages/testing.pip
cd: path/to/project cd: path/to/project
venv: python venv: python
``` ```
### virtualenv ### virtualenv
@ -45,5 +44,4 @@ Create a python virtual environment. Argument is the environment name.
[create the virtual environment] [create the virtual environment]
virtualenv: python virtualenv: python
cd: /path/to/project cd: /path/to/project
``` ```

@ -0,0 +1,60 @@
# System
Summary: Work with the system.
## Available Commands
### reboot
Reboot the system.
```ini
[reboot the system]
reboot:
sudo: yes
```
### reload
Reload a service.
```ini
[reload postgres]
reload: postgresql
```
### restart
Restart a service:
```ini
[restart postgres]
restart: postgresql
```
### run
Run any shell command.
```ini
[run a useless listing command]
run: "ls -ls"
```
### start
Start a service:
```ini
[start postgres]
start: postgresql
```
### stop
Stop a service:
```ini
[stop postgres]
stop: postgresql
```

@ -2,7 +2,15 @@
## Available Commands ## Available Commands
The `ubuntu` profile incorporates commands from [Django](../commands/django.md), [messages](../commands/messages.md), [MySQL](../commands/mysql.md), [PHP](../commands/php.md), [POSIX](../commands/posix.md), [Postgres](../commands/pgsql.md), and [Python](../commands/python.md). The `ubuntu` profile incorporates commands from
- [Django](../commands/django.md)
- [messages](../commands/messages.md)
- [MySQL](../commands/mysql.md)
- [PHP](../commands/php.md)
- [POSIX](../commands/posix.md)
- [Postgres](../commands/pgsql.md)
- [Python](../commands/python.md)
### apache ### apache

@ -0,0 +1,41 @@
# Users
Summary: Work with system users and groups.
## Available Commands
### group
NOT YET IMPLEMENTED.
### user
Add or remove a user.
Adding a user:
```ini
[create bob's user account]
user: bob
groups: sudo
sudo: yes
```
To override the home directory:
```ini
[create the deploy user]
user: deploy
groups: sudo, www-data
home: /var/www/example_app
sudo: yes
```
Removing a user:
```ini
[remove bob's user account because he's screwing up the system]
user: bob
op: remove
sudo: yes
```

@ -22,15 +22,17 @@ nav:
- Post a Message to Twist: how-to/post-message-twist.md - Post a Message to Twist: how-to/post-message-twist.md
- Use Script Tease with Common Kit: how-to/use-with-commonkit.md - Use Script Tease with Common Kit: how-to/use-with-commonkit.md
- Usage: - Usage:
- CentOS: usage/centos.md - Apache: usage/apache.md
- Django: usage/django.md - Django: usage/django.md
- Messages: usage/messages.md - Messages: usage/messages.md
- MySQL: usage/mysql.md - MySQL: usage/mysql.md
- PHP: usage/php.md - PHP: usage/php.md
- Packages: usage/packages.md
- Postgres: usage/pgsql.md - Postgres: usage/pgsql.md
- POSIX: usage/posix.md - POSIX: usage/posix.md
- Python: usage/python.md - Python: usage/python.md
- Ubuntu: usage/ubuntu.md - System: usage/system.md
- Users: usage/users.md
- CLI: cli.md - CLI: cli.md
repo_name: Git Traction repo_name: Git Traction
repo_url: https://gittraction.com/diff6/python-scripttease repo_url: https://gittraction.com/diff6/python-scripttease

@ -19,4 +19,8 @@ class PROFILE:
"""Supported operating system profiles.""" """Supported operating system profiles."""
CENTOS = "centos" CENTOS = "centos"
DEBIAN = "debian"
FEDORA = "fedora"
REDHAT = "redhat"
POP_OS = "pop_os"
UBUNTU = "ubuntu" UBUNTU = "ubuntu"

@ -0,0 +1,6 @@
[package]
description = Install Matomo.
docs = https://matomo.com
tags = stats
title = Matomo
version = 0.1.0-d

@ -0,0 +1,91 @@
[install dependencies]
install: $item
sudo: yes
items: apache2 mariadb-server php libapache2-mod-php php-cli php-fpm php-json php-common php-mysql php-zip php-gd php-mbstring php-curl php-xml php-pear phpbcmath curl unzip
[make sure a maintenance root exists]
dir: /var/www/maint/www
group: {{ apache_group }}
owner: {{ apache_user }}
recursive: yes
sudo: yes
[disable the default site]
apache.disable_site: 000-default
sudo: yes
[install certbot]
install: certbot
sudo: yes
[enable apache modules]
apache.enable_module: $item
items: rewrite headers enc dif mime setenvif ssl
sudo: yes
; Is this really necessary?
[enable php]
run: systemctl enable --now php7.4-fpm
sudo: yes
[create virtual host without SSL]
template: http.conf /etc/apache2/sites-available/{{ domain_name }}.conf
sudo: yes
[enable the non-SSL site]
apache.enable_site: {{ domain_name }}
sudo: yes
[reload apache with non-SSL site in place]
apache.reload:
sudo: yes
[create the database]
mysql.create: {{ database_name }}
host: {{ database_host }}
[create the database user]
mysql.user: {{ database_user }}
host: {{ database_host }}
password: {{ database_password }}
[set privileges for database user]
mysql.grant: {{ database_user }}
database: {{ database_name }}
host: {{ database_host }}
[download the latest copy of matomo]
run: wget http://builds.matomo.org/matomo-latest.zip
cd: /tmp
[unzip the matomo package]
run: unzip matomo-latest.zip
cd: /tmp
[move the matomo package]
move: /tmp/matomo {{ install_path }}/{{ domain_tld }}
sudo: yes
[set permissions on the matomo directory]
perms: {{ install_path }}/{{ domain_tld }}
owner: {{ apache_user }}
group: {{ apache_group }}
mode: 755
recursive: yes
sudo: yes
{% if not file_exists(letsencrypt_file, host=current_host) %}
[acquire SSL certificate]
certbot: {{ domain_name }}
email: {{ webmaster_email }}
webroot: /var/www/maint/www
sudo: yes
{% endif %}
[create virtual host with SSL]
template: https.conf /etc/apache2/sites-available/{{ domain_name }}.conf
sudo: yes
[reload apache with SSL in place]
apache.reload:
sudo: yes

@ -0,0 +1,6 @@
# 12:15am EST is 4:15am UTC.
# Cron times are in UTC.
# See https://savvytime.com/converter/est-to-utc
# See https://crontab.guru/
5 * * * * {{ apache_user }} /usr/bin/php {{ deploy.current_path }}/console core:archive --url=https://{{ domain_name }}/ > {{ deploy.current_path }}/archive.log

@ -0,0 +1,12 @@
# The port 80 host is required for renewing Let's Encrypt certificates. By default document root is shared by all sites
# requiring SSL support, but this may be changed to {{ deploy.shared_path }}/maint/www in the deployment template if
# a custom maintenance site is required.
<VirtualHost *:80>
ServerName {{ domain_name }}
ServerAlias *.{{ domain_name }}
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/.well-known [NC]
RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L]
DocumentRoot /var/www/maint/www
</VirtualHost>

@ -0,0 +1,52 @@
# The port 80 host is required for renewing Let's Encrypt certificates. By default document root is shared by all sites
# requiring SSL support, but this may be changed to {{ deploy.shared_path }}/maint/www in the deployment template if
# a custom maintenance site is required.
<VirtualHost *:80>
ServerName {{ domain_name }}
ServerAlias *.{{ domain_name }}
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/.well-known [NC]
RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L]
DocumentRoot /var/www/maint/www
</VirtualHost>
# The 443 host is where the application is actually served.
<VirtualHost *:443>
ServerName {{ domain_name }}
DocumentRoot {{ install_path }}/{{ domain_tld }}
<Directory {{ install_path }}/{{ domain_tld }}>
Options FollowSymLinks
Allowoverride All
Require all granted
</Directory>
<Files "console">
Options None
Require all denied
</Files>
<Directory {{ install_path }}/{{ domain_tld }}/misc/user>
Options None
Required all granted
</Directory>
<Directory {{ install_path }}/{{ domain_tld }}/misc>
Options None
Required all granted
</Directory>
<Directory {{ install_path }}/{{ domain_tld }}/vendor>
Options None
Required all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/matomo_error.log
CustomLog ${APACHE_LOG_DIR}/matomo_access.log combined
SSLEngine on
SSLCertificateKeyFile /etc/letsencrypt/live/{{ domain_name }}/privkey.pem
SSLCertificateFile /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem
</VirtualHost>

@ -0,0 +1,43 @@
[apache_user]
comment = The name of the user that runs Apache.
value = www-data
[apache_group]
comment = The name of the group to which the Apache user is assigned.
value = www-data
[database_host]
comment = The server name hosting the database.
value = localhost
[database_name]
comment = The name of the database.
value = matomo_example_com
[database_password]
comment = The password used to access the database.
value = change_this_to_something_secure!
[database_user]
comment = The user name accessing the database.
value = matomo_example_com
[domain_name]
comment = The domain name to use for the Matomo host.
value = matomo.example.com
[domain_tld]
comment = The domain name as a directory.
value = matomo_example_com
[letsencrypt_file]
comment = The path to the SSL cert.
value = /etc/letsencrypt/live/%(domain_name)/cert.pem
[webmaster_email]
comment = The webmaster's email address. Used when setting up SSL.
value = webmaster@example.com
[install_path]
comment = The path to document root where Matomo will be installed.
value = /var/www

@ -791,3 +791,16 @@ class Template(object):
def is_itemized(self): def is_itemized(self):
# return "$item" in self.target # return "$item" in self.target
return False return False
'''
class UnsupportedCommand(Command):
"""A command class that may be used when a command is not supported on the target operating system."""
def __init__(self, statement, **kwargs):
"""Initialize the command."""
self._original_statement = statement
super().__init__(statement, **kwargs)
def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True):
return "The %s command is not supported by the target operating system: %s"
'''

@ -50,6 +50,27 @@ def explain(message, heading=None, **kwargs):
return Content("explain", message=message, heading=heading, **kwargs) return Content("explain", message=message, heading=heading, **kwargs)
def mattermost(message, url=None, **kwargs):
"""Send a message to a Mattermost channel.
:param message: The message to be sent.
:type message: str
:param url: The URL of the Mattermost channel.
:type url: str
"""
if url is None:
raise InvalidInput("mattermost command requires a url parameter.")
statement = list()
statement.append("curl -X POST -H 'Content-type: application/json' --data")
statement.append('\'{"text": "%s"}\'' % message)
statement.append(url)
return Command(" ".join(statement), **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. """Create a screenshot for documentation.
@ -90,7 +111,7 @@ def slack(message, url=None, **kwargs):
statement.append('\'{"text": "%s"}\'' % message) statement.append('\'{"text": "%s"}\'' % message)
statement.append(url) statement.append(url)
return Command(statement, **kwargs) return Command(" ".join(statement), **kwargs)
def twist(message, title="Notice", url=None, **kwargs): def twist(message, title="Notice", url=None, **kwargs):
@ -121,6 +142,7 @@ MESSAGE_MAPPINGS = {
'dialog': dialog, 'dialog': dialog,
'echo': echo, 'echo': echo,
'explain': explain, 'explain': explain,
'mattermost': mattermost,
'screenshot': screenshot, 'screenshot': screenshot,
'slack': slack, 'slack': slack,
'twist': twist, 'twist': twist,

@ -14,6 +14,7 @@ __all__ = (
"pgsql_exists", "pgsql_exists",
"pgsql_grant", "pgsql_grant",
"pgsql_load", "pgsql_load",
"pgsql_sql",
"pgsql_user", "pgsql_user",
) )
@ -100,6 +101,9 @@ def pgsql_create(database, owner=None, template=None, **kwargs):
if template is not None: if template is not None:
kwargs['template'] = template kwargs['template'] = template
# SELECT 'CREATE DATABASE <db_name>'
# WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '<db_name>')\gexec
# psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '<your db name>'" | grep -q 1 | psql -U postgres -c "CREATE DATABASE <your db name>" # psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '<your db name>'" | grep -q 1 | psql -U postgres -c "CREATE DATABASE <your db name>"
# first = pgsql("psql", **kwargs) # first = pgsql("psql", **kwargs)
# #
@ -239,6 +243,17 @@ def pgsql_load(database, path, **kwargs):
return pgsql("psql", **kwargs) return pgsql("psql", **kwargs)
def pgsql_sql(statement, database="template1", **kwargs):
kwargs.setdefault("comment", "run SQL statement")
kwargs['dbname'] = database
command = pgsql("psql", **kwargs)
command.statement += ' -c "%s"' % statement
return command
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. """Work with a PostgreSQL user.
@ -275,7 +290,7 @@ def pgsql_user(name, admin_pass=None, admin_user="postgres", op="create", passwo
return pgsql("dropuser", name, password=admin_pass, user=admin_user, **kwargs) return pgsql("dropuser", name, password=admin_pass, user=admin_user, **kwargs)
elif op == "exists": elif op == "exists":
kwargs.setdefault("comment", "determine if %s postgres user exists" % name) kwargs.setdefault("comment", "determine if %s postgres user exists" % name)
kwargs.setdefault("register", "pgsql_use_exists") kwargs.setdefault("register", "pgsql_user_exists")
command = pgsql("psql", password=admin_pass, user=admin_user, **kwargs) command = pgsql("psql", password=admin_pass, user=admin_user, **kwargs)
@ -294,6 +309,6 @@ PGSQL_MAPPINGS = {
'pgsql.dump': pgsql_dump, 'pgsql.dump': pgsql_dump,
'pgsql.exists': pgsql_exists, 'pgsql.exists': pgsql_exists,
'pgsql.grant': pgsql_grant, 'pgsql.grant': pgsql_grant,
# 'pgsql.sql': pgsql_exec, 'pgsql.sql': pgsql_sql,
'pgsql.user': pgsql_user, 'pgsql.user': pgsql_user,
} }

@ -539,6 +539,7 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
tokens.append("--delete") tokens.append("--delete")
if exclude is not None: if exclude is not None:
# tokens.append("--exclude-from={'%s'}" % exclude)
tokens.append("--exclude-from=%s" % exclude) tokens.append("--exclude-from=%s" % exclude)
# --partial and --progress # --partial and --progress
@ -653,7 +654,8 @@ def sync(source, target, delete=False, exclude=None, links=True, recursive=True,
tokens = list() tokens = list()
tokens.append("rsync") tokens.append("rsync")
tokens.append("--cvs-exclude") # tokens.append("--cvs-exclude")
tokens.append("--exclude=.git")
tokens.append("--checksum") tokens.append("--checksum")
tokens.append("--compress") tokens.append("--compress")
@ -664,6 +666,7 @@ def sync(source, target, delete=False, exclude=None, links=True, recursive=True,
tokens.append("--delete") tokens.append("--delete")
if exclude is not None: if exclude is not None:
# tokens.append("--exclude-from={'%s'}" % exclude)
tokens.append("--exclude-from=%s" % exclude) tokens.append("--exclude-from=%s" % exclude)
# --partial and --progress # --partial and --progress
@ -741,6 +744,7 @@ POSIX_MAPPINGS = {
'move': move, 'move': move,
'perms': perms, 'perms': perms,
'prompt': prompt, 'prompt': prompt,
'push': rsync,
'remove': remove, 'remove': remove,
'replace': replace, 'replace': replace,
'run': run, 'run': run,

@ -33,9 +33,9 @@ def command_factory(loader, excluded_kwargs=None, mappings=None, profile=PROFILE
""" """
# Identify the command mappings to be used. # Identify the command mappings to be used.
if profile == PROFILE.CENTOS: if profile in (PROFILE.CENTOS, PROFILE.FEDORA, PROFILE.REDHAT):
_mappings = CENTOS_MAPPINGS _mappings = CENTOS_MAPPINGS
elif profile == PROFILE.UBUNTU: elif profile in (PROFILE.DEBIAN, PROFILE.POP_OS, PROFILE.UBUNTU):
_mappings = UBUNTU_MAPPINGS _mappings = UBUNTU_MAPPINGS
else: else:
log.error("Unsupported or unrecognized profile: %s" % profile) log.error("Unsupported or unrecognized profile: %s" % profile)

@ -221,7 +221,7 @@ class BaseLoader(File):
if type(value) in (list, tuple): if type(value) in (list, tuple):
_value = value _value = value
else: else:
_value = split_csv(value) _value = split_csv(value) if "," in value else split_csv(value, separator=" ")
elif key in ("func", "function"): elif key in ("func", "function"):
_key = "function" _key = "function"
_value = value _value = value
@ -230,19 +230,19 @@ class BaseLoader(File):
if type(value) in (list, tuple): if type(value) in (list, tuple):
_value = value _value = value
else: else:
_value = split_csv(value) _value = split_csv(value) if "," in value else split_csv(value, separator=" ")
elif key == "items": elif key == "items":
_key = "items" _key = "items"
if type(value) in (list, tuple): if type(value) in (list, tuple):
_value = value _value = value
else: else:
_value = split_csv(value) _value = split_csv(value) if "," in value else split_csv(value, separator=" ")
elif key == "tags": elif key == "tags":
_key = "tags" _key = "tags"
if type(value) in (list, tuple): if type(value) in (list, tuple):
_value = value _value = value
else: else:
_value = split_csv(value) _value = split_csv(value) if "," in value else split_csv(value, separator=" ")
else: else:
_key = key _key = key
_value = smart_cast(value) _value = smart_cast(value)

@ -1,5 +1,7 @@
DATE = "2023-03-30" DATE = "2024-04-11"
VERSION = "7.0.0" VERSION = "7.1.5"
MAJOR = 7 MAJOR = 7
MINOR = 0 MINOR = 1
PATCH = 0 PATCH = 5
STATUS = "a"

@ -22,6 +22,18 @@ def test_explain():
assert explain("this is a test") is not None assert explain("this is a test") is not None
def test_mattermost():
with pytest.raises(InvalidInput):
mattermost("This is a test.")
c = mattermost("This is a test.", url="https://example.mattermost.com/asdf/1234")
s = c.get_statement(include_comment=False)
assert "curl -X POST -H 'Content-type: application/json' --data" in s
assert "This is a test." in s
assert "https://example.mattermost.com/asdf/1234" in s
def test_screenshot(): def test_screenshot():
assert screenshot("static/images/testing.png") is not None assert screenshot("static/images/testing.png") is not None

@ -71,6 +71,14 @@ def test_pgsql_load():
assert '--bogus=1' in s # to test passing any key assert '--bogus=1' in s # to test passing any key
def test_pgsql_sql():
c = pgsql_sql("UPDATE etl_testing SET is_processed = 't'", database="example_app")
s = c.get_statement()
# psql -U postgres --host=localhost --port=5432 --dbname="example_app" -c "UPDATE etl_testing SET is_processed = 't'"
assert '--dbname="example_app"' in s
assert '-c "UPDATE etl_testing SET is_processed = \'t\'"' in s
def test_pgsql_user(): def test_pgsql_user():
c = pgsql_user("testing", password="secret") c = pgsql_user("testing", password="secret")
s = c.get_statement() s = c.get_statement()

@ -21,7 +21,7 @@ def test_command_factory():
ini = INILoader("tests/examples/kitchen_sink.ini") ini = INILoader("tests/examples/kitchen_sink.ini")
ini.load() ini.load()
commands = command_factory(ini) commands = command_factory(ini)
assert len(commands) == 48 assert len(commands) == 49
ini = INILoader("tests/examples/bad_command.ini") ini = INILoader("tests/examples/bad_command.ini")
ini.load() ini.load()

Loading…
Cancel
Save