WuttaMess 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wuttamess-0.1.0/.gitignore +4 -0
- wuttamess-0.1.0/CHANGELOG.md +5 -0
- wuttamess-0.1.0/PKG-INFO +40 -0
- wuttamess-0.1.0/README.md +6 -0
- wuttamess-0.1.0/docs/Makefile +20 -0
- wuttamess-0.1.0/docs/_static/.keepme +0 -0
- wuttamess-0.1.0/docs/api/wuttamess.apt.rst +6 -0
- wuttamess-0.1.0/docs/api/wuttamess.rst +6 -0
- wuttamess-0.1.0/docs/api/wuttamess.sync.rst +6 -0
- wuttamess-0.1.0/docs/conf.py +39 -0
- wuttamess-0.1.0/docs/index.rst +34 -0
- wuttamess-0.1.0/docs/make.bat +35 -0
- wuttamess-0.1.0/docs/narr/install.rst +14 -0
- wuttamess-0.1.0/docs/narr/usage.rst +209 -0
- wuttamess-0.1.0/pyproject.toml +57 -0
- wuttamess-0.1.0/src/wuttamess/__init__.py +27 -0
- wuttamess-0.1.0/src/wuttamess/_version.py +6 -0
- wuttamess-0.1.0/src/wuttamess/apt.py +81 -0
- wuttamess-0.1.0/src/wuttamess/sync.py +91 -0
- wuttamess-0.1.0/tasks.py +24 -0
- wuttamess-0.1.0/tests/__init__.py +0 -0
- wuttamess-0.1.0/tests/files/foo +1 -0
- wuttamess-0.1.0/tests/test_apt.py +41 -0
- wuttamess-0.1.0/tests/test_sync.py +67 -0
- wuttamess-0.1.0/tox.ini +17 -0
wuttamess-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: WuttaMess
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fabric Automation Helpers
|
|
5
|
+
Project-URL: Homepage, https://wuttaproject.org/
|
|
6
|
+
Project-URL: Repository, https://forgejo.wuttaproject.org/wutta/wuttamess
|
|
7
|
+
Project-URL: Changelog, https://forgejo.wuttaproject.org/wutta/wuttamess/src/branch/master/CHANGELOG.md
|
|
8
|
+
Author-email: Lance Edgar <lance@wuttaproject.org>
|
|
9
|
+
License: GNU GPL v3+
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Requires-Dist: fabric
|
|
26
|
+
Requires-Dist: fabsync
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
Requires-Dist: furo; extra == 'docs'
|
|
29
|
+
Requires-Dist: sphinx; extra == 'docs'
|
|
30
|
+
Provides-Extra: tests
|
|
31
|
+
Requires-Dist: pytest-cov; extra == 'tests'
|
|
32
|
+
Requires-Dist: tox; extra == 'tests'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# WuttaMess
|
|
37
|
+
|
|
38
|
+
Fabric Automation Helpers
|
|
39
|
+
|
|
40
|
+
See docs at https://rattailproject.org/docs/wuttamess/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Minimal makefile for Sphinx documentation
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
# You can set these variables from the command line, and also
|
|
5
|
+
# from the environment for the first two.
|
|
6
|
+
SPHINXOPTS ?=
|
|
7
|
+
SPHINXBUILD ?= sphinx-build
|
|
8
|
+
SOURCEDIR = .
|
|
9
|
+
BUILDDIR = _build
|
|
10
|
+
|
|
11
|
+
# Put it first so that "make" without argument is like "make help".
|
|
12
|
+
help:
|
|
13
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
14
|
+
|
|
15
|
+
.PHONY: help Makefile
|
|
16
|
+
|
|
17
|
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
18
|
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
19
|
+
%: Makefile
|
|
20
|
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Configuration file for the Sphinx documentation builder.
|
|
2
|
+
#
|
|
3
|
+
# For the full list of built-in configuration values, see the documentation:
|
|
4
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
5
|
+
|
|
6
|
+
# -- Project information -----------------------------------------------------
|
|
7
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import version as get_version
|
|
10
|
+
|
|
11
|
+
project = 'WuttaMess'
|
|
12
|
+
copyright = '2024, Lance Edgar'
|
|
13
|
+
author = 'Lance Edgar'
|
|
14
|
+
release = get_version('WuttaMess')
|
|
15
|
+
|
|
16
|
+
# -- General configuration ---------------------------------------------------
|
|
17
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
18
|
+
|
|
19
|
+
extensions = [
|
|
20
|
+
'sphinx.ext.autodoc',
|
|
21
|
+
'sphinx.ext.intersphinx',
|
|
22
|
+
'sphinx.ext.viewcode',
|
|
23
|
+
'sphinx.ext.todo',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
templates_path = ['_templates']
|
|
27
|
+
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
28
|
+
|
|
29
|
+
intersphinx_mapping = {
|
|
30
|
+
'fabsync': ('https://fabsync.ignorare.dev/', None),
|
|
31
|
+
'invoke': ('https://docs.pyinvoke.org/en/stable/', None),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# -- Options for HTML output -------------------------------------------------
|
|
36
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
37
|
+
|
|
38
|
+
html_theme = 'furo'
|
|
39
|
+
html_static_path = ['_static']
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
WuttaMess
|
|
3
|
+
=========
|
|
4
|
+
|
|
5
|
+
This package provides various helpers for use with `Fabric
|
|
6
|
+
<https://www.fabfile.org>`_ automation.
|
|
7
|
+
|
|
8
|
+
It can be used to deploy custom apps built with the `Wutta framework
|
|
9
|
+
<https://wuttaproject.org>`_, but is not specific to that use case.
|
|
10
|
+
It can also be used for general server setup etc.
|
|
11
|
+
|
|
12
|
+
However it is only intended for use with Linux (and similar) for the
|
|
13
|
+
target machine.
|
|
14
|
+
|
|
15
|
+
Good documentation and 100% `test coverage`_ are priorities for this
|
|
16
|
+
project.
|
|
17
|
+
|
|
18
|
+
.. _test coverage: https://buildbot.rattailproject.org/coverage/wuttamess/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
.. toctree::
|
|
22
|
+
:maxdepth: 2
|
|
23
|
+
:caption: Documentation:
|
|
24
|
+
|
|
25
|
+
narr/install
|
|
26
|
+
narr/usage
|
|
27
|
+
|
|
28
|
+
.. toctree::
|
|
29
|
+
:maxdepth: 1
|
|
30
|
+
:caption: Package API:
|
|
31
|
+
|
|
32
|
+
api/wuttamess
|
|
33
|
+
api/wuttamess.apt
|
|
34
|
+
api/wuttamess.sync
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@ECHO OFF
|
|
2
|
+
|
|
3
|
+
pushd %~dp0
|
|
4
|
+
|
|
5
|
+
REM Command file for Sphinx documentation
|
|
6
|
+
|
|
7
|
+
if "%SPHINXBUILD%" == "" (
|
|
8
|
+
set SPHINXBUILD=sphinx-build
|
|
9
|
+
)
|
|
10
|
+
set SOURCEDIR=.
|
|
11
|
+
set BUILDDIR=_build
|
|
12
|
+
|
|
13
|
+
%SPHINXBUILD% >NUL 2>NUL
|
|
14
|
+
if errorlevel 9009 (
|
|
15
|
+
echo.
|
|
16
|
+
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
|
17
|
+
echo.installed, then set the SPHINXBUILD environment variable to point
|
|
18
|
+
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
|
19
|
+
echo.may add the Sphinx directory to PATH.
|
|
20
|
+
echo.
|
|
21
|
+
echo.If you don't have Sphinx installed, grab it from
|
|
22
|
+
echo.https://www.sphinx-doc.org/
|
|
23
|
+
exit /b 1
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if "%1" == "" goto help
|
|
27
|
+
|
|
28
|
+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
29
|
+
goto end
|
|
30
|
+
|
|
31
|
+
:help
|
|
32
|
+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
33
|
+
|
|
34
|
+
:end
|
|
35
|
+
popd
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
Installation
|
|
3
|
+
============
|
|
4
|
+
|
|
5
|
+
Just the usual for a Python package:
|
|
6
|
+
|
|
7
|
+
.. code-block:: sh
|
|
8
|
+
|
|
9
|
+
pip install WuttaMess
|
|
10
|
+
|
|
11
|
+
Note that this will also install these dependencies:
|
|
12
|
+
|
|
13
|
+
* `fabric <https://pypi.org/project/fabric/>`_
|
|
14
|
+
* `fabsync <https://pypi.org/project/fabsync/>`_
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
|
|
2
|
+
Usage
|
|
3
|
+
=====
|
|
4
|
+
|
|
5
|
+
The expected use case is as follows:
|
|
6
|
+
|
|
7
|
+
Let's say you have a Linux machine "myserver" and you want to setup
|
|
8
|
+
these software systems on it:
|
|
9
|
+
|
|
10
|
+
* `Postfix <https://www.postfix.org/>`_
|
|
11
|
+
* `PostgreSQL <https://www.postgresql.org/>`_
|
|
12
|
+
* `collectd <https://www.collectd.org/>`_
|
|
13
|
+
|
|
14
|
+
Create a folder to contain the ``fabfile.py`` etc. Let's also assume
|
|
15
|
+
you will have other machines to setup, and you want to commit all this
|
|
16
|
+
to source control.
|
|
17
|
+
|
|
18
|
+
Recommended project structure is like:
|
|
19
|
+
|
|
20
|
+
.. code-block:: none
|
|
21
|
+
|
|
22
|
+
myproject
|
|
23
|
+
└── machines
|
|
24
|
+
└── myserver
|
|
25
|
+
├── fabfile.py
|
|
26
|
+
├── files
|
|
27
|
+
│ └── etc
|
|
28
|
+
│ ├── collectd
|
|
29
|
+
│ │ └── collectd.conf
|
|
30
|
+
│ └── postfix
|
|
31
|
+
│ └── main.cf
|
|
32
|
+
└── Vagrantfile
|
|
33
|
+
|
|
34
|
+
More details on these below.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
.. _fabfile-example:
|
|
38
|
+
|
|
39
|
+
``fabfile.py``
|
|
40
|
+
--------------
|
|
41
|
+
|
|
42
|
+
This is a "typical" fabfile, to the extent there is such a thing.
|
|
43
|
+
This file contains Fabric "tasks" which may be executed on the target
|
|
44
|
+
machine via SSH. For more on that concept see
|
|
45
|
+
:ref:`invoke:defining-and-running-task-functions`.
|
|
46
|
+
|
|
47
|
+
In this example we define "bootstrap" tasks for the setup, but that is
|
|
48
|
+
merely a personal convention. You can define tasks however you need::
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
Fabric script for myserver
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from fabric import task
|
|
55
|
+
from wuttamess import apt, sync
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# nb. this is used below, for file sync
|
|
59
|
+
root = sync.make_root('files')
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@task
|
|
63
|
+
def bootstrap_all(c):
|
|
64
|
+
"""
|
|
65
|
+
Bootstrap all aspects of the server
|
|
66
|
+
"""
|
|
67
|
+
bootstrap_base(c)
|
|
68
|
+
bootstrap_postgresql(c)
|
|
69
|
+
bootstrap_collectd(c)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@task
|
|
73
|
+
def bootstrap_base(c):
|
|
74
|
+
"""
|
|
75
|
+
Bootstrap the base system
|
|
76
|
+
"""
|
|
77
|
+
apt.dist_upgrade(c)
|
|
78
|
+
|
|
79
|
+
# postfix
|
|
80
|
+
apt.install(c, 'postfix')
|
|
81
|
+
if sync.check_isync(c, root, 'etc/postfix'):
|
|
82
|
+
c.run('systemctl restart postfix')
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@task
|
|
86
|
+
def bootstrap_postgresql(c):
|
|
87
|
+
"""
|
|
88
|
+
Bootstrap the PostgreSQL service
|
|
89
|
+
"""
|
|
90
|
+
apt.install(c, 'postgresql', 'libpq-dev')
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@task
|
|
94
|
+
def bootstrap_collectd(c):
|
|
95
|
+
"""
|
|
96
|
+
Bootstrap the collectd service
|
|
97
|
+
"""
|
|
98
|
+
apt.install(c, 'collectd')
|
|
99
|
+
if sync.check_isync(c, root, 'etc/collectd'):
|
|
100
|
+
c.run('systemctl restart collectd')
|
|
101
|
+
|
|
102
|
+
Above you can see how WuttaMess is actually used; it simply provides
|
|
103
|
+
convenience functions which can be called from a Fabric task.
|
|
104
|
+
|
|
105
|
+
But `Fabric <https://www.fabfile.org>`_ (and `fabsync
|
|
106
|
+
<https://fabsync.ignorare.dev/>`_ for file sync operations) are doing
|
|
107
|
+
the heavy lifting. The goal for WuttaMess is to further abstract
|
|
108
|
+
common operations and keep the task logic as "clean" as possible.
|
|
109
|
+
|
|
110
|
+
See also these functions which are used above:
|
|
111
|
+
|
|
112
|
+
* :func:`wuttamess.apt.dist_upgrade()`
|
|
113
|
+
* :func:`wuttamess.apt.install()`
|
|
114
|
+
* :func:`wuttamess.sync.make_root()`
|
|
115
|
+
* :func:`wuttamess.sync.check_isync()`
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
``files``
|
|
119
|
+
---------
|
|
120
|
+
|
|
121
|
+
This folder contains all files which must be synced to the target
|
|
122
|
+
machine as part of setup. As shown in the example above, the
|
|
123
|
+
``files`` structure should "mirror" the target machine file system.
|
|
124
|
+
|
|
125
|
+
The :func:`~wuttamess.sync.check_isync()` function may be called with
|
|
126
|
+
a "subpath" to sync just a portion of the file system. It returns
|
|
127
|
+
``True`` if any files were modified, so we can check for that and
|
|
128
|
+
avoid restarting services if nothing changed.
|
|
129
|
+
|
|
130
|
+
Note that in global module scope, we create the "root" object for use
|
|
131
|
+
with file sync. This is then passed to the various sync functions.
|
|
132
|
+
|
|
133
|
+
This uses the ``fabsync`` library under the hood; for more on how that
|
|
134
|
+
works see :doc:`fabsync:index`.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
``Vagrantfile``
|
|
138
|
+
---------------
|
|
139
|
+
|
|
140
|
+
This file is optional but may be useful for testing deployment on a
|
|
141
|
+
local VM using `Vagrant <https://www.vagrantup.com/>`_. For example:
|
|
142
|
+
|
|
143
|
+
.. code-block:: ruby
|
|
144
|
+
|
|
145
|
+
Vagrant.configure("2") do |config|
|
|
146
|
+
|
|
147
|
+
# live machine runs Debian 12 "bookworm"
|
|
148
|
+
config.vm.box = "debian/bookworm64"
|
|
149
|
+
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
For more info see docs for `Vagrantfile
|
|
153
|
+
<https://developer.hashicorp.com/vagrant/docs/vagrantfile>`_.
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
.. _running-tasks:
|
|
157
|
+
|
|
158
|
+
Running Tasks via CLI
|
|
159
|
+
---------------------
|
|
160
|
+
|
|
161
|
+
With the above setup, first make sure you are in the right working
|
|
162
|
+
directory (wherever ``fabfile.py`` lives):
|
|
163
|
+
|
|
164
|
+
.. code-block:: sh
|
|
165
|
+
|
|
166
|
+
cd myproject/machines/myserver
|
|
167
|
+
|
|
168
|
+
Then run whichever tasks you need, specifying the connection info for
|
|
169
|
+
target machine like so:
|
|
170
|
+
|
|
171
|
+
.. code-block:: sh
|
|
172
|
+
|
|
173
|
+
fab -e -H root@myserver.example.com bootstrap-all
|
|
174
|
+
|
|
175
|
+
Fabric uses SSH to connect to the target machine
|
|
176
|
+
(myserver.example.com) and runs the specified task on that machine.
|
|
177
|
+
|
|
178
|
+
Testing with a Vagrant VM will likely require a more "complicated"
|
|
179
|
+
command line. See output from ``vagrant ssh-config`` for details
|
|
180
|
+
specific to your VM, but the command may be something like:
|
|
181
|
+
|
|
182
|
+
.. code-block:: sh
|
|
183
|
+
|
|
184
|
+
fab -e -H root@192.168.121.42 -i .vagrant/machines/default/libvirt/private_key bootstrap-all
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
Troubleshooting SSH
|
|
188
|
+
-------------------
|
|
189
|
+
|
|
190
|
+
In some cases troubleshooting the SSH connection can be tricky. A rule of
|
|
191
|
+
thumb is to first make sure it works without Fabric.
|
|
192
|
+
|
|
193
|
+
Try a basic connection with the same args using SSH only:
|
|
194
|
+
|
|
195
|
+
.. code-block:: sh
|
|
196
|
+
|
|
197
|
+
ssh root@myserver.example.com
|
|
198
|
+
|
|
199
|
+
Or for a Vagrant VM:
|
|
200
|
+
|
|
201
|
+
.. code-block:: sh
|
|
202
|
+
|
|
203
|
+
ssh root@192.168.121.42 -i .vagrant/machines/default/libvirt/private_key
|
|
204
|
+
|
|
205
|
+
You may want to edit your ``~/.ssh/config`` file as needed. However
|
|
206
|
+
this usually is done for "normal" machines only, not for Vagrant VM.
|
|
207
|
+
|
|
208
|
+
Once that works, then the ``fab`` command *should* also work using the
|
|
209
|
+
same args...
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["hatchling"]
|
|
4
|
+
build-backend = "hatchling.build"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "WuttaMess"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "Fabric Automation Helpers"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
|
13
|
+
license = {text = "GNU GPL v3+"}
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
19
|
+
"Natural Language :: English",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.8",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Topic :: System :: Systems Administration",
|
|
28
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
|
+
]
|
|
30
|
+
requires-python = ">= 3.8"
|
|
31
|
+
dependencies = [
|
|
32
|
+
"fabric",
|
|
33
|
+
"fabsync",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
docs = ["Sphinx", "furo"]
|
|
39
|
+
tests = ["pytest-cov", "tox"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://wuttaproject.org/"
|
|
44
|
+
Repository = "https://forgejo.wuttaproject.org/wutta/wuttamess"
|
|
45
|
+
Changelog = "https://forgejo.wuttaproject.org/wutta/wuttamess/src/branch/master/CHANGELOG.md"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
[tool.commitizen]
|
|
49
|
+
version_provider = "pep621"
|
|
50
|
+
tag_format = "v$version"
|
|
51
|
+
update_changelog_on_bump = true
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.sdist]
|
|
55
|
+
exclude = [
|
|
56
|
+
"htmlcov/",
|
|
57
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
################################################################################
|
|
3
|
+
#
|
|
4
|
+
# WuttaMess -- Fabric Automation Helpers
|
|
5
|
+
# Copyright © 2024 Lance Edgar
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Wutta Framework.
|
|
8
|
+
#
|
|
9
|
+
# Wutta Framework is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU General Public License as published by the Free
|
|
11
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
12
|
+
# later version.
|
|
13
|
+
#
|
|
14
|
+
# Wutta Framework is distributed in the hope that it will be useful, but
|
|
15
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
17
|
+
# more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the GNU General Public License along with
|
|
20
|
+
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
#
|
|
22
|
+
################################################################################
|
|
23
|
+
"""
|
|
24
|
+
WuttaMess - base package
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from ._version import __version__
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
################################################################################
|
|
3
|
+
#
|
|
4
|
+
# WuttaMess -- Fabric Automation Helpers
|
|
5
|
+
# Copyright © 2024 Lance Edgar
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Wutta Framework.
|
|
8
|
+
#
|
|
9
|
+
# Wutta Framework is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU General Public License as published by the Free
|
|
11
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
12
|
+
# later version.
|
|
13
|
+
#
|
|
14
|
+
# Wutta Framework is distributed in the hope that it will be useful, but
|
|
15
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
17
|
+
# more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the GNU General Public License along with
|
|
20
|
+
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
#
|
|
22
|
+
################################################################################
|
|
23
|
+
"""
|
|
24
|
+
APT package management
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def dist_upgrade(c, frontend='noninteractive'):
|
|
29
|
+
"""
|
|
30
|
+
Run a full dist-upgrade for APT. Essentially this runs:
|
|
31
|
+
|
|
32
|
+
.. code-block:: sh
|
|
33
|
+
|
|
34
|
+
apt update
|
|
35
|
+
apt dist-upgrade
|
|
36
|
+
"""
|
|
37
|
+
update(c)
|
|
38
|
+
upgrade(c, dist_upgrade=True, frontend=frontend)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def install(c, *packages, **kwargs):
|
|
42
|
+
"""
|
|
43
|
+
Install some package(s) via APT. Essentially this runs:
|
|
44
|
+
|
|
45
|
+
.. code-block:: sh
|
|
46
|
+
|
|
47
|
+
apt install PKG [PKG ...]
|
|
48
|
+
"""
|
|
49
|
+
frontend = kwargs.pop('frontend', 'noninteractive')
|
|
50
|
+
packages = ' '.join(packages)
|
|
51
|
+
return c.run(f'DEBIAN_FRONTEND={frontend} apt-get --assume-yes install {packages}')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def update(c):
|
|
55
|
+
"""
|
|
56
|
+
Update the APT package lists. Essentially this runs:
|
|
57
|
+
|
|
58
|
+
.. code-block:: sh
|
|
59
|
+
|
|
60
|
+
apt update
|
|
61
|
+
"""
|
|
62
|
+
c.run('apt-get update')
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def upgrade(c, dist_upgrade=False, frontend='noninteractive'):
|
|
66
|
+
"""
|
|
67
|
+
Upgrade packages via APT. Essentially this runs:
|
|
68
|
+
|
|
69
|
+
.. code-block:: sh
|
|
70
|
+
|
|
71
|
+
apt upgrade
|
|
72
|
+
|
|
73
|
+
# ..or..
|
|
74
|
+
|
|
75
|
+
apt dist-upgrade
|
|
76
|
+
"""
|
|
77
|
+
options = ''
|
|
78
|
+
if frontend == 'noninteractive':
|
|
79
|
+
options = '--option Dpkg::Options::="--force-confdef" --option Dpkg::Options::="--force-confold"'
|
|
80
|
+
upgrade = 'dist-upgrade' if dist_upgrade else 'upgrade'
|
|
81
|
+
c.run(f'DEBIAN_FRONTEND={frontend} apt-get --assume-yes {options} {upgrade}')
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
################################################################################
|
|
3
|
+
#
|
|
4
|
+
# WuttaMess -- Fabric Automation Helpers
|
|
5
|
+
# Copyright © 2024 Lance Edgar
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Wutta Framework.
|
|
8
|
+
#
|
|
9
|
+
# Wutta Framework is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU General Public License as published by the Free
|
|
11
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
12
|
+
# later version.
|
|
13
|
+
#
|
|
14
|
+
# Wutta Framework is distributed in the hope that it will be useful, but
|
|
15
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
17
|
+
# more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the GNU General Public License along with
|
|
20
|
+
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
#
|
|
22
|
+
################################################################################
|
|
23
|
+
"""
|
|
24
|
+
Synchronize Files
|
|
25
|
+
|
|
26
|
+
See :doc:`/narr/usage` for a basic example.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import fabsync
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def make_root(path, dest='/'):
|
|
33
|
+
"""
|
|
34
|
+
Make and return a "root" object for use with future sync calls.
|
|
35
|
+
|
|
36
|
+
This is a convenience wrapper around
|
|
37
|
+
:func:`fabsync:fabsync.load()`.
|
|
38
|
+
|
|
39
|
+
:param path: Path to local file tree. Usually this is relative to
|
|
40
|
+
the ``fabfile.py`` location, otherwise should be absolute.
|
|
41
|
+
|
|
42
|
+
:param dest: Path for target file tree.
|
|
43
|
+
"""
|
|
44
|
+
return fabsync.load(path, dest)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def isync(c, root, selector=None, echo=True, **kwargs):
|
|
48
|
+
"""
|
|
49
|
+
Sync files, yielding the result for each as it goes.
|
|
50
|
+
|
|
51
|
+
This is a convenience wrapper around
|
|
52
|
+
:func:`fabsync:fabsync.isync()`.
|
|
53
|
+
|
|
54
|
+
:param c: Connection object.
|
|
55
|
+
|
|
56
|
+
:param root: File tree "root" object as obtained from
|
|
57
|
+
:func:`make_root()`.
|
|
58
|
+
|
|
59
|
+
:param selector: This can be a simple "subpath" string, indicating
|
|
60
|
+
a section of the file tree. For instance: ``'etc/postfix'``
|
|
61
|
+
|
|
62
|
+
:param echo: Flag indicating whether the path for each file synced
|
|
63
|
+
should be echoed to stdout. Generally thought to be useful but
|
|
64
|
+
may be disabled.
|
|
65
|
+
|
|
66
|
+
:param \**kwargs: Any remaining kwargs are passed as-is to
|
|
67
|
+
:func:`fabsync:fabsync.isync()`.
|
|
68
|
+
"""
|
|
69
|
+
if selector:
|
|
70
|
+
if not isinstance(selector, fabsync.ItemSelector):
|
|
71
|
+
selector = fabsync.ItemSelector.new(selector)
|
|
72
|
+
kwargs['selector'] = selector
|
|
73
|
+
|
|
74
|
+
for result in fabsync.isync(c, root, **kwargs):
|
|
75
|
+
if echo:
|
|
76
|
+
print(f"{result.path}{' [modified]' if result.modified else ''}")
|
|
77
|
+
yield result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def check_isync(c, root, selector=None, **kwargs):
|
|
81
|
+
"""
|
|
82
|
+
Sync all files and return boolean indicating whether any actual
|
|
83
|
+
modifications were made.
|
|
84
|
+
|
|
85
|
+
Arguments are the same as for :func:`isync()`, which this calls.
|
|
86
|
+
|
|
87
|
+
:returns: ``True`` if any sync result indicates a file was
|
|
88
|
+
modified; otherwise ``False``.
|
|
89
|
+
"""
|
|
90
|
+
return any([result.modified
|
|
91
|
+
for result in isync(c, root, selector, **kwargs)])
|
wuttamess-0.1.0/tasks.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
"""
|
|
3
|
+
Tasks for WuttaMess
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
|
|
9
|
+
from invoke import task
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@task
|
|
13
|
+
def release(c, skip_tests=False):
|
|
14
|
+
"""
|
|
15
|
+
Release a new version of WuttaMess
|
|
16
|
+
"""
|
|
17
|
+
if not skip_tests:
|
|
18
|
+
c.run('pytest')
|
|
19
|
+
|
|
20
|
+
if os.path.exists('dist'):
|
|
21
|
+
shutil.rmtree('dist')
|
|
22
|
+
|
|
23
|
+
c.run('python -m build --sdist')
|
|
24
|
+
c.run('twine upload dist/*')
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
foo
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
|
|
3
|
+
from unittest import TestCase
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
|
|
6
|
+
from wuttamess import apt as mod
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestDistUpgrade(TestCase):
|
|
10
|
+
|
|
11
|
+
def test_basic(self):
|
|
12
|
+
c = MagicMock()
|
|
13
|
+
with patch.object(mod, 'update') as update:
|
|
14
|
+
with patch.object(mod, 'upgrade') as upgrade:
|
|
15
|
+
mod.dist_upgrade(c, frontend='whatever')
|
|
16
|
+
update.assert_called_once_with(c)
|
|
17
|
+
upgrade.assert_called_once_with(c, dist_upgrade=True, frontend='whatever')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestInstall(TestCase):
|
|
21
|
+
|
|
22
|
+
def test_basic(self):
|
|
23
|
+
c = MagicMock()
|
|
24
|
+
mod.install(c, 'postfix')
|
|
25
|
+
c.run.assert_called_once_with('DEBIAN_FRONTEND=noninteractive apt-get --assume-yes install postfix')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestUpdate(TestCase):
|
|
29
|
+
|
|
30
|
+
def test_basic(self):
|
|
31
|
+
c = MagicMock()
|
|
32
|
+
mod.update(c)
|
|
33
|
+
c.run.assert_called_once_with('apt-get update')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestUpgrade(TestCase):
|
|
37
|
+
|
|
38
|
+
def test_basic(self):
|
|
39
|
+
c = MagicMock()
|
|
40
|
+
mod.upgrade(c)
|
|
41
|
+
c.run.assert_called_once_with('DEBIAN_FRONTEND=noninteractive apt-get --assume-yes --option Dpkg::Options::="--force-confdef" --option Dpkg::Options::="--force-confold" upgrade')
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from unittest import TestCase
|
|
5
|
+
from unittest.mock import patch, MagicMock
|
|
6
|
+
|
|
7
|
+
from fabsync import SyncedRoot, ItemSelector
|
|
8
|
+
|
|
9
|
+
from wuttamess import sync as mod
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestMakeRoot(TestCase):
|
|
13
|
+
|
|
14
|
+
def test_basic(self):
|
|
15
|
+
root = mod.make_root('files')
|
|
16
|
+
self.assertIsInstance(root, SyncedRoot)
|
|
17
|
+
self.assertEqual(root.src, Path('files'))
|
|
18
|
+
self.assertEqual(root.dest, Path('/'))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestIsync(TestCase):
|
|
22
|
+
|
|
23
|
+
def test_basic(self):
|
|
24
|
+
c = MagicMock()
|
|
25
|
+
root = mod.make_root('files')
|
|
26
|
+
with patch.object(mod, 'fabsync') as fabsync:
|
|
27
|
+
fabsync.ItemSelector = ItemSelector
|
|
28
|
+
|
|
29
|
+
# nothing to sync
|
|
30
|
+
fabsync.isync.return_value = []
|
|
31
|
+
results = list(mod.isync(c, root))
|
|
32
|
+
self.assertEqual(results, [])
|
|
33
|
+
fabsync.isync.assert_called_once_with(c, root)
|
|
34
|
+
|
|
35
|
+
# sync one file
|
|
36
|
+
fabsync.isync.reset_mock()
|
|
37
|
+
result = MagicMock(path='/foo', modified=True)
|
|
38
|
+
fabsync.isync.return_value = [result]
|
|
39
|
+
results = list(mod.isync(c, root))
|
|
40
|
+
self.assertEqual(results, [result])
|
|
41
|
+
fabsync.isync.assert_called_once_with(c, root)
|
|
42
|
+
|
|
43
|
+
# sync with selector
|
|
44
|
+
fabsync.isync.reset_mock()
|
|
45
|
+
result = MagicMock(path='/foo', modified=True)
|
|
46
|
+
fabsync.isync.return_value = [result]
|
|
47
|
+
results = list(mod.isync(c, root, 'foo'))
|
|
48
|
+
self.assertEqual(results, [result])
|
|
49
|
+
fabsync.isync.assert_called_once_with(c, root, selector=fabsync.ItemSelector.new('foo'))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestCheckIsync(TestCase):
|
|
53
|
+
|
|
54
|
+
def test_basic(self):
|
|
55
|
+
c = MagicMock()
|
|
56
|
+
root = mod.make_root('files')
|
|
57
|
+
with patch.object(mod, 'isync') as isync:
|
|
58
|
+
|
|
59
|
+
# file(s) modified
|
|
60
|
+
result = MagicMock(path='/foo', modified=True)
|
|
61
|
+
isync.return_value = [result]
|
|
62
|
+
self.assertTrue(mod.check_isync(c, root))
|
|
63
|
+
|
|
64
|
+
# not modified
|
|
65
|
+
result = MagicMock(path='/foo', modified=False)
|
|
66
|
+
isync.return_value = [result]
|
|
67
|
+
self.assertFalse(mod.check_isync(c, root))
|
wuttamess-0.1.0/tox.ini
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
[tox]
|
|
3
|
+
envlist = py38, py39, py310, py311
|
|
4
|
+
|
|
5
|
+
[testenv]
|
|
6
|
+
extras = tests
|
|
7
|
+
commands = pytest {posargs}
|
|
8
|
+
|
|
9
|
+
[testenv:coverage]
|
|
10
|
+
basepython = python3.11
|
|
11
|
+
commands = pytest --cov=wuttamess --cov-report=html --cov-fail-under=100
|
|
12
|
+
|
|
13
|
+
[testenv:docs]
|
|
14
|
+
basepython = python3.11
|
|
15
|
+
extras = docs
|
|
16
|
+
changedir = docs
|
|
17
|
+
commands = sphinx-build -b html -d {envtmpdir}/doctrees -W -T . {envtmpdir}/docs
|