From 934ae283651996b9e749cddbc2f678bd888e26ed Mon Sep 17 00:00:00 2001 From: Shinichi TAMURA Date: Tue, 19 Sep 2017 08:04:58 +0900 Subject: [PATCH] Timezone modeule: Better env choice for Linux (#28229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * timezone module: fixed platform decision rule for Linux — For better handling of environments where timedatectl is unavailable * timezone module: allow absence of configuration files if specific commands are available * timezone module: remove duplicated line * timezone module: fixed docs to clarify returned diff * timezone module: fixed “undefined variable err” * Revert "timezone module: fixed docs to clarify returned diff" This reverts commit 4b783227f713eee9aa6717c0a8b9e697b939f471. * timezone module: revert platform decision rule; just warn instead of futher command checks * timezone module: [NosystemdTimezone] enhanced error message --- lib/ansible/modules/system/timezone.py | 55 ++++++++++++++++++++------ 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/lib/ansible/modules/system/timezone.py b/lib/ansible/modules/system/timezone.py index 671190ad0d..cc84ea1e30 100644 --- a/lib/ansible/modules/system/timezone.py +++ b/lib/ansible/modules/system/timezone.py @@ -70,6 +70,7 @@ EXAMPLES = ''' name: Asia/Tokyo ''' +import errno import os import platform import random @@ -99,8 +100,13 @@ class Timezone(object): """ if get_platform() == 'Linux': timedatectl = module.get_bin_path('timedatectl') - if timedatectl is not None and module.run_command(timedatectl)[0] == 0: - return super(Timezone, SystemdTimezone).__new__(SystemdTimezone) + if timedatectl is not None: + rc, stdout, stderr = module.run_command(timedatectl) + if rc == 0: + return super(Timezone, SystemdTimezone).__new__(SystemdTimezone) + else: + module.warn('timedatectl command was found but not usable: %s. using other method.' % stderr) + return super(Timezone, NosystemdTimezone).__new__(NosystemdTimezone) else: return super(Timezone, NosystemdTimezone).__new__(NosystemdTimezone) elif re.match('^joyent_.*Z', platform.version()): @@ -311,6 +317,8 @@ class NosystemdTimezone(Timezone): adjtime='/etc/adjtime' ) + allow_no_file = dict() + regexps = dict( name =None, # To be set in __init__ hwclock=re.compile(r'^UTC\s*=\s*([^\s]+)', re.MULTILINE), @@ -325,28 +333,41 @@ class NosystemdTimezone(Timezone): self.update_timezone = self.module.get_bin_path('cp', required=True) self.update_timezone += ' %s /etc/localtime' % tzfile self.update_hwclock = self.module.get_bin_path('hwclock', required=True) + self.allow_no_file['hwclock'] = True # Since this is only used for get values, file absense does not metter # Distribution-specific configurations if self.module.get_bin_path('dpkg-reconfigure') is not None: # Debian/Ubuntu self.update_timezone = self.module.get_bin_path('dpkg-reconfigure', required=True) self.update_timezone += ' --frontend noninteractive tzdata' self.conf_files['name'] = '/etc/timezone' + self.allow_no_file['name'] = True self.conf_files['hwclock'] = '/etc/default/rcS' self.regexps['name'] = re.compile(r'^([^\s]+)', re.MULTILINE) self.tzline_format = '%s\n' else: # RHEL/CentOS if self.module.get_bin_path('tzdata-update') is not None: - self.update_timezone = self.module.get_bin_path('tzdata-update', required=True) + self.update_timezone = self.module.get_bin_path('tzdata-update', required=True) + self.allow_no_file['name'] = True # else: - # self.update_timezone = 'cp ...' <- configured above + # self.update_timezone = 'cp ...' <- configured above + # self.allow_no_file['name'] = False <- this is default behavior self.conf_files['name'] = '/etc/sysconfig/clock' self.conf_files['hwclock'] = '/etc/sysconfig/clock' self.regexps['name'] = re.compile(r'^ZONE\s*=\s*"?([^"\s]+)"?', re.MULTILINE) self.tzline_format = 'ZONE="%s"\n' - self.update_hwclock = self.module.get_bin_path('hwclock', required=True) - def _edit_file(self, filename, regexp, value): + def _allow_ioerror(self, err, key): + # In some cases, even if the target file does not exist, + # simply creating it may solve the problem. + # In such cases, we should continue the configuration rather than aborting. + if err.errno != errno.ENOENT: + # If the error is not ENOENT ("No such file or directory"), + # (e.g., permission error, etc), we should abort. + return False + return self.allow_no_file.get(key, False) + + def _edit_file(self, filename, regexp, value, key): """Replace the first matched line with given `value`. If `regexp` matched more than once, other than the first line will be deleted. @@ -355,12 +376,16 @@ class NosystemdTimezone(Timezone): filename: The name of the file to edit. regexp: The regular expression to search with. value: The line which will be inserted. + key: For what key the file is being editted. """ # Read the file try: file = open(filename, 'r') - except IOError: - self.abort('cannot read "%s"' % filename) + except IOError as err: + if self._allow_ioerror(err, key): + lines = [] + else: + self.abort('tried to configure %s using a file "%s", but could not read it' % (key, filename)) else: lines = file.readlines() file.close() @@ -382,7 +407,7 @@ class NosystemdTimezone(Timezone): try: file = open(filename, 'w') except IOError: - self.abort('cannot write to "%s"' % filename) + self.abort('tried to configure %s using a file "%s", but could not write to it' % (key, filename)) else: file.writelines(lines) file.close() @@ -397,15 +422,18 @@ class NosystemdTimezone(Timezone): try: file = open(filename, mode='r') - except IOError: - self.abort('cannot read configuration file "%s" for %s' % (filename, key)) + except IOError as err: + if self._allow_ioerror(err, key): + return None + else: + self.abort('tried to configure %s using a file "%s", but could not read it' % (key, filename)) else: status = file.read() file.close() try: value = self.regexps[key].search(status).group(1) except AttributeError: - self.abort('cannot find the valid value from configuration file "%s" for %s' % (filename, key)) + self.abort('tried to configure %s using a file "%s", but could not find a valid value in it' % (key, filename)) else: if key == 'hwclock': # For key='hwclock'; convert yes/no -> UTC/local @@ -422,7 +450,8 @@ class NosystemdTimezone(Timezone): def set_timezone(self, value): self._edit_file(filename=self.conf_files['name'], regexp=self.regexps['name'], - value=self.tzline_format % value) + value=self.tzline_format % value, + key='name') self.execute(self.update_timezone) def set_hwclock(self, value):