Add support for OpenBSD and SmartOS to timezone module

pull/20312/head
Jasper Lievisse Adriaanse 8 years ago committed by Brian Coca
parent a560a2d512
commit e9038d8dc1

@ -19,7 +19,11 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import platform
import random
import re
import string
from ansible.module_utils.basic import AnsibleModule, get_platform
from ansible.module_utils.six import iteritems
@ -34,12 +38,15 @@ module: timezone
short_description: Configure timezone setting
description:
- This module configures the timezone setting, both of the system clock
and of the hardware clock. I(Currently only the Linux platform is supported).
and of the hardware clock. I(Currently only Linux, OpenBSD and SmartOS
instances are supported.)
It is recommended to restart C(crond) after changing the timezone,
otherwise the jobs may run at the wrong time.
It uses the C(timedatectl) command if available. Otherwise, it edits
C(/etc/sysconfig/clock) or C(/etc/timezone) for the system clock,
On Linux it uses the C(timedatectl) command if available. Otherwise,
it edits C(/etc/sysconfig/clock) or C(/etc/timezone) for the system clock,
and uses the C(hwclock) command for the hardware clock.
On SmartOS the C(sm-set-timezone) utility is used to set the zone timezone,
and on OpenBSD C(/etc/localtime) is modified.
If you want to set up the NTP, use M(service) module.
version_added: "2.2"
options:
@ -56,9 +63,12 @@ options:
Note that this option is recommended not to change and may fail
to configure, especially on virtual environments such as AWS.
B(At least one of name and hwclock are required.)
I(Only used on Linux.)
required: false
aliases: ['rtc']
author: "Shinichi TAMURA (@tmshn)"
author:
- "Shinichi TAMURA (@tmshn)"
- "Jasper Lievisse Adriaanse (@jasperla)"
'''
RETURN = '''
@ -93,8 +103,8 @@ class Timezone(object):
def __new__(cls, module):
"""Return the platform-specific subclass.
It does not use load_platform_subclass() because it need to judge based
on whether the `timedatectl` command exists and available.
It does not use load_platform_subclass() because it needs to judge based
on whether the `timedatectl` command exists and is available.
Args:
module: The AnsibleModule.
@ -105,6 +115,14 @@ class Timezone(object):
return super(Timezone, SystemdTimezone).__new__(SystemdTimezone)
else:
return super(Timezone, NosystemdTimezone).__new__(NosystemdTimezone)
elif re.match('^joyent_.*Z', platform.version()):
# get_platform() returns SunOS, which is too broad. So look at the
# platform version instead.
return super(Timezone, SmartOSTimezone).__new__(SmartOSTimezone)
elif re.match('^OpenBSD', platform.platform()):
# This might be too specific for now, however it can then serve as
# a generic base for /etc/localtime honoring Unix-like systems.
return super(Timezone, OpenBSDTimezone).__new__(OpenBSDTimezone)
else:
# Not supported yet
return super(Timezone, Timezone).__new__(Timezone)
@ -430,12 +448,114 @@ class NosystemdTimezone(Timezone):
self.abort('unknown parameter "%s"' % key)
class SmartOSTimezone(Timezone):
"""This is a Timezone manipulation class for SmartOS instances.
It uses the C(sm-set-timezone) utility to set the timezone, and
inspects C(/etc/default/init) to determine the current timezone.
NB: A zone needs to be rebooted in order for the change to be
activated.
"""
def __init__(self, module):
super(SmartOSTimezone, self).__init__(module)
self.settimezone = self.module.get_bin_path('sm-set-timezone', required=True)
def get(self, key, phase):
"""Lookup the current timezone name in `/etc/default/init`. If anything else
is requested, or if the TZ field is not set we fail.
"""
if key == 'name':
try:
f = open('/etc/default/init', 'r')
for line in f:
m = re.match('^TZ=(.*)$', line.strip())
if m:
return m.groups()[0]
except:
self.module.fail_json(msg='Failed to read /etc/default/init')
else:
self.module.fail_json(msg='{0} is not a supported option on target platform'.format(key))
def set(self, key, value):
"""Set the requested timezone through sm-set-timezone, an invalid timezone name
will be rejected and we have no further input validation to perform.
"""
if key == 'name':
cmd = 'sm-set-timezone {0}'.format(value)
(rc, stdout, stderr) = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg=stderr)
# sm-set-timezone knows no state and will always set the timezone.
# XXX: https://github.com/joyent/smtools/pull/2
m = re.match('^\* Changed (to)? timezone (to)? ({0}).*'.format(value), stdout.splitlines()[1])
if not (m and m.groups()[-1] == value):
self.module.fail_json(msg='Failed to set timezone')
else:
self.module.fail_json(msg='{0} is not a supported option on target platform'.
format(key))
class OpenBSDTimezone(Timezone):
"""This is the timezone implementation for OpenBSD which works simply through
updating the `/etc/localtime` symlink to point to a valid timezone name under
`/usr/share/zoneinfo`.
"""
def __init__(self, module):
super(OpenBSDTimezone, self).__init__(module)
def get(self, key, phase):
"""Lookup the current timezone by resolving `/etc/localtime`."""
if key == 'name':
try:
tz = os.readlink('/etc/localtime')
return tz.replace('/usr/share/zoneinfo/', '')
except:
self.module.fail_json(msg='Could not read /etc/localtime')
else:
self.module.fail_json(msg='{0} is not a supported option on target platform'.
format(key))
def set(self, key, value):
if key == 'name':
# First determine if the requested timezone is valid by looking in
# the zoneinfo directory.
zonefile = '/usr/share/zoneinfo/' + value
try:
if not os.path.isfile(zonefile):
self.module.fail_json(msg='{0} is not a recognized timezone'.format(value))
except:
self.module.fail_json(msg='Failed to stat {0}'.format(zonefile))
# Now (somewhat) atomically update the symlink by creating a new
# symlink and move it into place. Otherwise we have to remove the
# original symlink and create the new symlink, however that would
# create a race condition in case another process tries to read
# /etc/localtime between removal and creation.
suffix = "".join([random.choice(string.ascii_letters + string.digits) for x in range(0, 10)])
new_localtime = '/etc/localtime.' + suffix
try:
os.symlink(zonefile, new_localtime)
os.rename(new_localtime, '/etc/localtime')
except:
os.remove(new_localtime)
self.module.fail_json(msg='Could not update /etc/localtime')
else:
self.module.fail_json(msg='{0} is not a supported option on target platform'.format(key))
def main():
# Construct 'module' and 'tz'
module = AnsibleModule(
argument_spec=dict(
hwclock=dict(default=None, choices=['UTC', 'local'], aliases=['rtc']),
name=dict(default=None),
hwclock=dict(choices=['UTC', 'local'], aliases=['rtc']),
name=dict(),
),
required_one_of=[['hwclock', 'name']],
supports_check_mode=True

Loading…
Cancel
Save