diff --git a/src/wuttamess/util.py b/src/wuttamess/util.py index 075a4dd..619a34e 100644 --- a/src/wuttamess/util.py +++ b/src/wuttamess/util.py @@ -37,6 +37,22 @@ def exists(c, path): return not c.run(f'test -e {path}', warn=True).failed +def is_symlink(c, path): + """ + Check if the given path is a symlink. + + :param c: Fabric connection. + + :param path: Path to check, on target machine. + + :returns: ``True`` if path is a symlink, else ``False``. + """ + # nb. this function is derived from one copied from fabric v1 + cmd = 'test -L "$(echo %s)"' % path + result = c.run(cmd, warn=True) + return False if result.failed else True + + def mako_renderer(c, env={}): """ This returns a *function* suitable for use as a ``fabsync`` file @@ -67,3 +83,20 @@ def mako_renderer(c, env={}): return Template(filename=str(path)).render(**env) return render + + +def set_timezone(c, timezone): + """ + Set the system timezone. + + :param c: Fabric connection. + + :param timezone: Standard timezone name, + e.g. ``'America/Chicago'``. + """ + c.run(f"bash -c 'echo {timezone} > /etc/timezone'") + + if is_symlink(c, '/etc/localtime'): + c.run(f'ln -sf /usr/share/zoneinfo/{timezone} /etc/localtime') + else: + c.run(f'cp /usr/share/zoneinfo/{timezone} /etc/localtime') diff --git a/tests/test_util.py b/tests/test_util.py index af21f61..c85b0de 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -2,7 +2,7 @@ import os from unittest import TestCase -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch, call from wuttamess import util as mod @@ -15,6 +15,21 @@ class TestExists(TestCase): c.run.assert_called_once_with('test -e /foo', warn=True) +class TestIsSymlink(TestCase): + + def test_yes(self): + c = MagicMock() + c.run.return_value.failed = False + self.assertTrue(mod.is_symlink(c, '/foo')) + c.run.assert_called_once_with('test -L "$(echo /foo)"', warn=True) + + def test_no(self): + c = MagicMock() + c.run.return_value.failed = True + self.assertFalse(mod.is_symlink(c, '/foo')) + c.run.assert_called_once_with('test -L "$(echo /foo)"', warn=True) + + class TestMakoRenderer(TestCase): def test_basic(self): @@ -24,3 +39,26 @@ class TestMakoRenderer(TestCase): path = os.path.join(here, 'files', 'bar', 'baz') rendered = renderer(path, vars={}) self.assertEqual(rendered, 'machine_is_live = True') + + +class TestSetTimezone(TestCase): + + def test_symlink(self): + c = MagicMock() + with patch.object(mod, 'is_symlink') as is_symlink: + is_symlink.return_value = True + mod.set_timezone(c, 'America/Chicago') + c.run.assert_has_calls([ + call("bash -c 'echo America/Chicago > /etc/timezone'"), + ]) + c.run.assert_called_with('ln -sf /usr/share/zoneinfo/America/Chicago /etc/localtime') + + def test_not_symlink(self): + c = MagicMock() + with patch.object(mod, 'is_symlink') as is_symlink: + is_symlink.return_value = False + mod.set_timezone(c, 'America/Chicago') + c.run.assert_has_calls([ + call("bash -c 'echo America/Chicago > /etc/timezone'"), + ]) + c.run.assert_called_with('cp /usr/share/zoneinfo/America/Chicago /etc/localtime')