Compare commits

..

2 commits

Author SHA1 Message Date
Lance Edgar e3b593d628 fix: add sync.make_selector() convenience function
wraps `fabsync.ItemSelector.new()`
2024-09-10 20:10:15 -05:00
Lance Edgar 2a83142d95 feat: add basic postfix config helpers 2024-09-10 14:05:13 -05:00
6 changed files with 173 additions and 5 deletions

View file

@ -0,0 +1,6 @@
``wuttamess.postfix``
=====================
.. automodule:: wuttamess.postfix
:members:

View file

@ -31,4 +31,5 @@ project.
api/wuttamess api/wuttamess
api/wuttamess.apt api/wuttamess.apt
api/wuttamess.postfix
api/wuttamess.sync api/wuttamess.sync

67
src/wuttamess/postfix.py Normal file
View file

@ -0,0 +1,67 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaMess -- Fabric Automation Helpers
# Copyright © 2024 Lance Edgar
#
# This file is part of Wutta Framework.
#
# Wutta Framework is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Wutta Framework is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Postfix mail service
"""
def set_config(c, setting, value):
"""
Configure the given setting with the given value.
"""
c.run(f"postconf -e '{setting}={value}'")
def set_myhostname(c, hostname):
"""
Configure the ``myhostname`` setting with the given string.
"""
set_config(c, 'myhostname', hostname)
def set_myorigin(c, origin):
"""
Configure the ``myorigin`` setting with the given string.
"""
set_config(c, 'myorigin', origin)
def set_mydestination(c, *destinations):
"""
Configure the ``mydestinations`` setting with the given strings.
"""
set_config(c, 'mydestination', ', '.join(destinations))
def set_mynetworks(c, *networks):
"""
Configure the ``mynetworks`` setting with the given strings.
"""
set_config(c, 'mynetworks', ' '.join(networks))
def set_relayhost(c, relayhost):
"""
Configure the ``relayhost`` setting with the given string
"""
set_config(c, 'relayhost', relayhost)

View file

@ -44,20 +44,41 @@ def make_root(path, dest='/'):
return fabsync.load(path, dest) return fabsync.load(path, dest)
def isync(c, root, selector=None, echo=True, **kwargs): def make_selector(subpath=None, **kwargs):
"""
Make and return an "item selector" for use with a sync call.
This is a convenience wrapper around
:meth:`fabsync:fabsync.ItemSelector.new()`.
:param subpath: (Optional) Relative subpath of the file tree to
sync, e.g. ``'etc/postfix'``.
:param tags: Optional iterable of tags to include; excluding any
files which are not so tagged. E.g. ``{'foo'}``
"""
return fabsync.ItemSelector.new(subpath, **kwargs)
def isync(c, root, selector=None, tags=None, echo=True, **kwargs):
""" """
Sync files, yielding the result for each as it goes. Sync files, yielding the result for each as it goes.
This is a convenience wrapper around This is a convenience wrapper around
:func:`fabsync:fabsync.isync()`. :func:`fabsync:fabsync.isync()`.
:param c: Connection object. :param c: Fabric connection.
:param root: File tree "root" object as obtained from :param root: File tree "root" object as obtained from
:func:`make_root()`. :func:`make_root()`.
:param selector: This can be a simple "subpath" string, indicating :param selector: This can be a simple "subpath" string, indicating
a section of the file tree. For instance: ``'etc/postfix'`` a section of the file tree (e.g. ``'etc/postfix'``). Or can be
a :class:`fabsync.ItemSelector` instance.
:param tags: Optional iterable of tags to select. If ``selector``
is a subpath string, and you specify ``tags`` then they will be
included when creating the actual selector.
:param echo: Flag indicating whether the path for each file synced :param echo: Flag indicating whether the path for each file synced
should be echoed to stdout. Generally thought to be useful but should be echoed to stdout. Generally thought to be useful but
@ -68,7 +89,10 @@ def isync(c, root, selector=None, echo=True, **kwargs):
""" """
if selector: if selector:
if not isinstance(selector, fabsync.ItemSelector): if not isinstance(selector, fabsync.ItemSelector):
selector = fabsync.ItemSelector.new(selector) kw = {}
if tags:
kw['tags'] = tags
selector = make_selector(selector, **kw)
kwargs['selector'] = selector kwargs['selector'] = selector
for result in fabsync.isync(c, root, **kwargs): for result in fabsync.isync(c, root, **kwargs):

54
tests/test_postfix.py Normal file
View file

@ -0,0 +1,54 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
from unittest.mock import MagicMock
from wuttamess import postfix as mod
class TestSetConfig(TestCase):
def test_basic(self):
c = MagicMock()
mod.set_config(c, 'foo', 'bar')
c.run.assert_called_once_with("postconf -e 'foo=bar'")
class TestSetMyhostname(TestCase):
def test_basic(self):
c = MagicMock()
mod.set_myhostname(c, 'test.example.com')
c.run.assert_called_once_with("postconf -e 'myhostname=test.example.com'")
class TestSetMyorigin(TestCase):
def test_basic(self):
c = MagicMock()
mod.set_myorigin(c, 'example.com')
c.run.assert_called_once_with("postconf -e 'myorigin=example.com'")
class TestSetMydestination(TestCase):
def test_basic(self):
c = MagicMock()
mod.set_mydestination(c, 'example.com', 'test.example.com', 'localhost')
c.run.assert_called_once_with("postconf -e 'mydestination=example.com, test.example.com, localhost'")
class TestSetMynetworks(TestCase):
def test_basic(self):
c = MagicMock()
mod.set_mynetworks(c, '127.0.0.0/8', '[::1]/128')
c.run.assert_called_once_with("postconf -e 'mynetworks=127.0.0.0/8 [::1]/128'")
class TestSetRelayhost(TestCase):
def test_basic(self):
c = MagicMock()
mod.set_relayhost(c, 'mail.example.com')
c.run.assert_called_once_with("postconf -e 'relayhost=mail.example.com'")

View file

@ -18,6 +18,14 @@ class TestMakeRoot(TestCase):
self.assertEqual(root.dest, Path('/')) self.assertEqual(root.dest, Path('/'))
class TestMakeSelector(TestCase):
def test_basic(self):
selector = mod.make_selector('etc/postfix')
self.assertIsInstance(selector, ItemSelector)
self.assertEqual(selector.subpath, Path('etc/postfix'))
class TestIsync(TestCase): class TestIsync(TestCase):
def test_basic(self): def test_basic(self):
@ -40,7 +48,7 @@ class TestIsync(TestCase):
self.assertEqual(results, [result]) self.assertEqual(results, [result])
fabsync.isync.assert_called_once_with(c, root) fabsync.isync.assert_called_once_with(c, root)
# sync with selector # sync with selector (subpath)
fabsync.isync.reset_mock() fabsync.isync.reset_mock()
result = MagicMock(path='/foo', modified=True) result = MagicMock(path='/foo', modified=True)
fabsync.isync.return_value = [result] fabsync.isync.return_value = [result]
@ -48,6 +56,14 @@ class TestIsync(TestCase):
self.assertEqual(results, [result]) self.assertEqual(results, [result])
fabsync.isync.assert_called_once_with(c, root, selector=fabsync.ItemSelector.new('foo')) fabsync.isync.assert_called_once_with(c, root, selector=fabsync.ItemSelector.new('foo'))
# sync with selector (subpath + tags)
fabsync.isync.reset_mock()
result = MagicMock(path='/foo', modified=True)
fabsync.isync.return_value = [result]
results = list(mod.isync(c, root, 'foo', tags={'bar'}))
self.assertEqual(results, [result])
fabsync.isync.assert_called_once_with(c, root, selector=fabsync.ItemSelector.new('foo', tags={'bar'}))
class TestCheckIsync(TestCase): class TestCheckIsync(TestCase):