From fde00327b043a4ef6c9b358f6a92dc32f38e5077 Mon Sep 17 00:00:00 2001 From: Patrik Lundin Date: Sat, 24 Nov 2012 22:52:18 +0100 Subject: [PATCH 1/2] Rework "enabled" implementation for FreeBSD. When trying to perform enabled=yes followed by enabled=no against FreeBSD the module would die with the following error: TypeError: sub() takes at most 4 arguments (5 given) The target FreeBSD client (8.2) is running python 2.6.6. It seems the extra 'flags' argument was added to re.sub() in 2.7. In fixing this issue I have attempted to create a general atomic method for modifying a rc.conf file. Hopefully this will make it easier to add other rc based platorms. The strip/split magic was inspired by the user module. --- library/service | 80 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/library/service b/library/service index f52a949bb8..355eb2be7c 100644 --- a/library/service +++ b/library/service @@ -73,6 +73,7 @@ examples: import platform import os import re +import tempfile class Service(object): """ @@ -108,6 +109,9 @@ class Service(object): self.svc_initctl = None self.enable_cmd = None self.arguments = module.params.get('arguments', '') + self.rcconf_file = None + self.rcconf_key = None + self.rcconf_value = None # select whether we dump additional debug info through syslog self.syslogging = False @@ -194,6 +198,60 @@ class Service(object): out = '' return rc, out, err + def service_enable_rcconf(self): + if self.rcconf_file is None or self.rcconf_key is None or self.rcconf_value is None: + self.module.fail_json(msg="service_enable_rcconf() requires rcconf_file, rcconf_key and rcconf_value") + + changed = None + entry = '%s="%s"\n' % (self.rcconf_key, self.rcconf_value) + RCFILE = open(self.rcconf_file, "r") + new_rc_conf = [] + + # Build a list containing the possibly modified file. + for rcline in RCFILE: + # Only parse non-comment and non-empty lines. + if not re.search('^(#.*)?$', rcline): + key = rcline.split('=')[0] + # We need to strip any newline and " signs from the value. + value = rcline.split('=')[1].strip('\n"') + if key == self.rcconf_key: + if value == self.rcconf_value: + # Since the proper entry already exists we can stop iterating. + changed = False + break + else: + # We found the key but the value is wrong, replace with new entry. + rcline = entry + changed = True + + # Add line to the list. + new_rc_conf.append(rcline) + + # We are done with reading the current rc.conf, close it. + RCFILE.close() + + # If we did not see any trace of our entry we need to add it. + if changed is None: + new_rc_conf.append(entry) + changed = True + + if changed is True: + # Create a temporary file next to the current rc.conf (so we stay on the same filesystem). + # This way the replacement operation is atomic. + rcconf_dir = os.path.dirname(self.rcconf_file) + rcconf_base = os.path.basename(self.rcconf_file) + (TMP_RCCONF, tmp_rcconf_file) = tempfile.mkstemp(dir=rcconf_dir, prefix="%s-" % rcconf_base) + + # Write out the contents of the list into our temporary file. + for rcline in new_rc_conf: + os.write(TMP_RCCONF, rcline) + + # Close temporary file. + os.close(TMP_RCCONF) + + # Replace previous rc.conf. + self.module.atomic_replace(tmp_rcconf_file, self.rcconf_file) + # =========================================== # Subclass: Linux @@ -368,28 +426,18 @@ class FreeBsdService(Service): def service_enable(self): if self.enable: - rc = "YES" + self.rcconf_value = "YES" else: - rc = "NO" + self.rcconf_value = "NO" rcfiles = [ '/etc/rc.conf','/usr/local/etc/rc.conf' ] for rcfile in rcfiles: if os.path.isfile(rcfile): - rcconf = rcfile + self.rcconf_file = rcfile + + self.rcconf_key = "%s_enable" % self.name - entry = "%s_enable" % self.name - full_entry = '%s="%s"' % (entry,rc) - rc = open(rcconf,"r+") - rctext = rc.read() - if re.search("^%s" % full_entry,rctext,re.M) is None: - if re.search("^%s" % entry,rctext,re.M) is None: - rctext += "\n%s" % full_entry - else: - rctext = re.sub("^%s.*" % entry,full_entry,rctext,1,re.M) - rc.truncate(0) - rc.seek(0) - rc.write(rctext) - rc.close() + return self.service_enable_rcconf() def service_control(self): if self.action is "start": From d4af9e4c5c40e0e20831255346effc5696611384 Mon Sep 17 00:00:00 2001 From: Patrik Lundin Date: Sun, 25 Nov 2012 03:24:49 +0100 Subject: [PATCH 2/2] Use shlex for rc.conf parsing. This makes the line parsing a lot more robust (and easier to read). Code supplied by @dhozac, thanks! Remove re import because this is not used anywhere. --- library/service | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/library/service b/library/service index 355eb2be7c..3889e4fefe 100644 --- a/library/service +++ b/library/service @@ -72,8 +72,8 @@ examples: import platform import os -import re import tempfile +import shlex class Service(object): """ @@ -209,11 +209,10 @@ class Service(object): # Build a list containing the possibly modified file. for rcline in RCFILE: - # Only parse non-comment and non-empty lines. - if not re.search('^(#.*)?$', rcline): - key = rcline.split('=')[0] - # We need to strip any newline and " signs from the value. - value = rcline.split('=')[1].strip('\n"') + # Parse line removing whitespaces, quotes, etc. + rcarray = shlex.split(rcline, comments=True) + if len(rcarray) >= 1 and '=' in rcarray[0]: + (key, value) = rcarray[0].split("=", 1) if key == self.rcconf_key: if value == self.rcconf_value: # Since the proper entry already exists we can stop iterating.