Skip to content

Commit 113c7bd

Browse files
committed
Merge remote-tracking branch 'origin/master' into edge
2 parents 40d0185 + 7a95c72 commit 113c7bd

File tree

2 files changed

+106
-20
lines changed

2 files changed

+106
-20
lines changed

mig/shared/install.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,61 @@
6969
from mig.shared.url import urlparse
7070

7171

72+
def determine_timezone(_environ=os.environ, _path_exists=os.path.exists, _print=print):
73+
"""Attempt to detect the timezone in various known portable ways."""
74+
75+
sys_timezone = None
76+
77+
timezone_link = '/etc/localtime'
78+
timezone_cmd = ["/usr/bin/timedatectl", "status"]
79+
80+
env_timezone = _environ.get('TZ', None)
81+
if env_timezone:
82+
# Use TZ env value directly if set
83+
return env_timezone
84+
85+
if _path_exists(timezone_link):
86+
zoneinfo_absolute = os.path.realpath(timezone_link)
87+
# Convert /etc/localtime link to e.g. /.../zoneinfo/Europe/Rome
88+
# then remove leading directories leaving TZ e.g. Europe/Rome
89+
zoneinfo_path_parts = zoneinfo_absolute.split('/')
90+
try:
91+
zoneinfo_index = zoneinfo_path_parts.index('zoneinfo')
92+
# the last path parts are at least .../zoneinfo/ which
93+
# is good enough for us here - treat them as the timezone
94+
localtime_timezone = '/'.join(
95+
zoneinfo_path_parts[zoneinfo_index + 1:])
96+
return localtime_timezone
97+
except IndexError:
98+
pass
99+
100+
_print("WARNING: ignoring non-standard /etc/localtime")
101+
102+
if _path_exists(timezone_cmd[0]):
103+
# Parse Time zone: LOCATION (ALIAS, OFFSET) output of timedatectl
104+
# into just LOCATION
105+
try:
106+
timezone_proc = subprocess_popen(
107+
timezone_cmd, stdout=subprocess_pipe)
108+
for line in timezone_proc.stdout.readlines():
109+
line = ensure_native_string(line.strip())
110+
if not line.startswith("Time zone: "):
111+
continue
112+
timedatectl_parts = line.replace(
113+
"Time zone: ", "").split(" ", 1)
114+
return timedatectl_parts[0]
115+
except IndexError:
116+
pass
117+
except OSError as exc:
118+
# warn about any issues executing the command but continue
119+
_print("WARNING: failed to extract time zone with %s : %s" %
120+
(' '.join(timezone_cmd), exc))
121+
122+
# none of the standard extraction methods succeeded by this point
123+
_print("WARNING: failed to extract system time zone; defaulting to UTC")
124+
return 'UTC'
125+
126+
72127
def fill_template(template_file, output_file, settings, eat_trailing_space=[],
73128
additional=None):
74129
"""Fill a configuration template using provided settings dictionary"""
@@ -944,25 +999,7 @@ def generate_confs(
944999
user_dict['__DUPLICATI_PROTOCOLS__'] = ' '.join(prio_duplicati_protocols)
9451000

9461001
if timezone == keyword_auto:
947-
# attempt to detect the timezone
948-
sys_timezone = None
949-
try:
950-
timezone_cmd = ["/usr/bin/timedatectl", "status"]
951-
timezone_proc = subprocess_popen(
952-
timezone_cmd, stdout=subprocess_pipe)
953-
for line in timezone_proc.stdout.readlines():
954-
line = ensure_native_string(line.strip())
955-
if not line.startswith("Time zone: "):
956-
continue
957-
sys_timezone = line.replace("Time zone: ", "").split(" ", 1)[0]
958-
except OSError as exc:
959-
# warn about any issues executing the command but continue
960-
pass
961-
if sys_timezone is None:
962-
print("WARNING: failed to extract system time zone; defaulting to UTC")
963-
sys_timezone = 'UTC'
964-
965-
timezone = sys_timezone
1002+
timezone = determine_timezone()
9661003

9671004
user_dict['__SEAFILE_TIMEZONE__'] = timezone
9681005

tests/test_mig_shared_install.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737

3838
from support import MigTestCase, testmain, temppath, cleanpath, fixturepath
3939

40-
from mig.shared.install import generate_confs
40+
from mig.shared.install import determine_timezone, generate_confs
41+
4142

4243
class DummyPwInfo:
4344
"""Wrapper to assist in create_dummy_gpwnam"""
@@ -54,6 +55,54 @@ def create_dummy_gpwnam(pw_uid, pw_gid):
5455
return lambda _: dummy
5556

5657

58+
def noop(*args, **kwargs):
59+
pass
60+
61+
62+
class MigSharedInstall__determine_timezone(MigTestCase):
63+
"""Coverage of timezone determination."""
64+
65+
def test_determines_tz_utc_fallback(self):
66+
timezone = determine_timezone(
67+
_environ={}, _path_exists=lambda _: False, _print=noop)
68+
69+
self.assertEqual(timezone, 'UTC')
70+
71+
def test_determines_tz_via_environ(self):
72+
example_environ = {
73+
'TZ': 'Example/Enviromnent'
74+
}
75+
timezone = determine_timezone(_environ=example_environ)
76+
77+
self.assertEqual(timezone, 'Example/Enviromnent')
78+
79+
def test_determines_tz_via_localtime(self):
80+
def exists_localtime(value):
81+
saw_call = value == '/etc/localtime'
82+
exists_localtime.was_called = saw_call
83+
return saw_call
84+
exists_localtime.was_called = False
85+
86+
timezone = determine_timezone(
87+
_environ={}, _path_exists=exists_localtime)
88+
89+
self.assertTrue(exists_localtime.was_called)
90+
self.assertIsNotNone(timezone)
91+
92+
def test_determines_tz_via_timedatectl(self):
93+
def exists_timedatectl(value):
94+
saw_call = value == '/usr/bin/timedatectl'
95+
exists_timedatectl.was_called = saw_call
96+
return saw_call
97+
exists_timedatectl.was_called = False
98+
99+
timezone = determine_timezone(
100+
_environ={}, _path_exists=exists_timedatectl, _print=noop)
101+
102+
self.assertTrue(exists_timedatectl.was_called)
103+
self.assertIsNotNone(timezone)
104+
105+
57106
class MigSharedInstall__generate_confs(MigTestCase):
58107
"""Unit test helper for the migrid code pointed to in class name"""
59108

0 commit comments

Comments
 (0)