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.
@@ -0,0 +1,4 @@
1
+ *~
2
+ *.pyc
3
+ .coverage
4
+ docs/_build/
@@ -0,0 +1,5 @@
1
+ ## v0.1.0 (2024-09-10)
2
+
3
+ ### Feat
4
+
5
+ - initial package, with `apt` and `sync` modules
@@ -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,6 @@
1
+
2
+ # WuttaMess
3
+
4
+ Fabric Automation Helpers
5
+
6
+ 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,6 @@
1
+
2
+ ``wuttamess.apt``
3
+ =================
4
+
5
+ .. automodule:: wuttamess.apt
6
+ :members:
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttamess``
3
+ =============
4
+
5
+ .. automodule:: wuttamess
6
+ :members:
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttamess.sync``
3
+ ==================
4
+
5
+ .. automodule:: wuttamess.sync
6
+ :members:
@@ -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,6 @@
1
+ # -*- coding: utf-8; -*-
2
+
3
+ from importlib.metadata import version
4
+
5
+
6
+ __version__ = version('WuttaMess')
@@ -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)])
@@ -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))
@@ -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