diff --git a/kollacli/ansible/inventory.py b/kollacli/ansible/inventory.py index 8f90c17..6791a56 100644 --- a/kollacli/ansible/inventory.py +++ b/kollacli/ansible/inventory.py @@ -15,12 +15,9 @@ import json import jsonpickle import logging import os -import shutil import tempfile import traceback -from tempfile import mkstemp - from kollacli import exceptions from kollacli import utils @@ -322,8 +319,7 @@ class Inventory(object): data = '' try: if os.path.exists(inventory_path): - with open(inventory_path, 'rb') as inv_file: - data = inv_file.read() + data = utils.sync_read_file(inventory_path) if data.strip(): inventory = jsonpickle.decode(data) @@ -343,32 +339,14 @@ class Inventory(object): """Save the inventory in a pickle file""" inventory_path = os.path.join(utils.get_kollacli_etc(), INVENTORY_PATH) try: - # the file handle returned from mkstemp must be closed or else - # if this is called many times you will have an unpleasant - # file handle leak - tmp_filehandle, tmp_path = mkstemp() - # multiple trips thru json to render a readable inventory file data = jsonpickle.encode(inventory) data_str = json.loads(data) pretty_data = json.dumps(data_str, indent=4) - with open(tmp_path, 'w') as tmp_file: - tmp_file.write(pretty_data) - shutil.copyfile(tmp_path, inventory_path) - os.remove(tmp_path) + utils.sync_write_file(inventory_path, pretty_data) + except Exception as e: raise CommandError('saving inventory failed: %s' % e) - finally: - try: - os.close(tmp_filehandle) - except Exception: - pass - - if tmp_filehandle is not None: - try: - os.close(tmp_filehandle) - except Exception: - pass def _create_default_inventory(self): diff --git a/kollacli/ansible/playbook.py b/kollacli/ansible/playbook.py index 8813f88..ff63a85 100644 --- a/kollacli/ansible/playbook.py +++ b/kollacli/ansible/playbook.py @@ -96,9 +96,10 @@ class AnsiblePlaybook(object): stderr=subprocess.PIPE).communicate() self.log.debug(inv) - err_flag, _ = run_cmd(cmd, self.print_output) + err_flag, msg = run_cmd(cmd, self.print_output) if err_flag: - raise Exception('Failure') + raise CommandError('Ansible command failed \n%s\n%s' + % (cmd, msg)) self.log.info('Success') except CommandError as e: diff --git a/kollacli/ansible/properties.py b/kollacli/ansible/properties.py index a481ee9..40c871d 100644 --- a/kollacli/ansible/properties.py +++ b/kollacli/ansible/properties.py @@ -18,6 +18,7 @@ import yaml from kollacli.utils import change_property from kollacli.utils import get_kolla_etc from kollacli.utils import get_kolla_home +from kollacli.utils import sync_read_file ALLVARS_PATH = 'ansible/group_vars/all.yml' GLOBALS_FILENAME = 'globals.yml' @@ -85,15 +86,15 @@ class AnsibleProperties(object): try: self.globals_path = os.path.join(kolla_etc, GLOBALS_FILENAME) - with open(self.globals_path) as globals_file: - globals_contents = yaml.load(globals_file) - self.file_contents[self.globals_path] = globals_contents - globals_contents = self.filter_jinja2(globals_contents) - for key, value in globals_contents.items(): - ansible_property = AnsibleProperty(key, value, - GLOBALS_FILENAME) - self.properties.append(ansible_property) - self.unique_properties[key] = ansible_property + globals_data = sync_read_file(self.globals_path) + globals_contents = yaml.load(globals_data) + self.file_contents[self.globals_path] = globals_contents + globals_contents = self.filter_jinja2(globals_contents) + for key, value in globals_contents.items(): + ansible_property = AnsibleProperty(key, value, + GLOBALS_FILENAME) + self.properties.append(ansible_property) + self.unique_properties[key] = ansible_property except Exception as e: raise e diff --git a/kollacli/utils.py b/kollacli/utils.py index 479bbe0..1098eab 100644 --- a/kollacli/utils.py +++ b/kollacli/utils.py @@ -14,8 +14,11 @@ import logging import os import pexpect +import tempfile import yaml +from lockfile import LockFile + def get_kolla_home(): return os.environ.get("KOLLA_HOME", "/usr/share/kolla/") @@ -98,19 +101,22 @@ def run_cmd(cmd, print_output=True): If the command is an ansible playbook command, record the output in an ansible log file. """ + pwd_prompt = '[sudo] password' log = logging.getLogger(__name__) err_flag = False output = [] try: child = pexpect.spawn(cmd) - index = child.expect([pexpect.EOF, '[sudo] password']) - if index == 1: + sniff = child.read(len(pwd_prompt)) + if sniff == pwd_prompt: + output.append(sniff + '\n') raise Exception( 'Insufficient permissions to run command "%s"' % cmd) child.maxsize = 1 child.timeout = 86400 for line in child: - outline = line.rstrip() + outline = sniff + line.rstrip() + sniff = '' output.append(outline) if print_output: log.info(outline) @@ -140,29 +146,61 @@ def change_property(file_path, property_key, property_value, clear=False): If not clear, and key is found, edit property in place. """ try: - file_contents = [] - with open(file_path, 'r+') as property_file: - new_line = '%s: "%s"\n' % (property_key, property_value) - property_key_found = False - for line in property_file: - if line[0:len(property_key)] == property_key: - property_key_found = True - if clear: - # clear existing property - line = '' - else: - # edit existing property - line = new_line - file_contents.append(line) - if not property_key_found and not clear: - # add new property to file - file_contents.append(new_line) + new_contents = [] + read_data = sync_read_file(file_path) + lines = read_data.split('\n') + new_line = '%s: "%s"\n' % (property_key, property_value) + property_key_found = False + for line in lines: + if line[0:len(property_key)] == property_key: + property_key_found = True + if clear: + # clear existing property + line = '' + else: + # edit existing property + line = new_line + new_contents.append(line + '\n') + if not property_key_found and not clear: + # add new property to file + new_contents.append(new_line) - property_file.seek(0) - property_file.truncate() + write_data = ''.join(new_contents) + sync_write_file(file_path, write_data) - with open(file_path, 'w') as property_file: - for line in file_contents: - property_file.write(line) except Exception as e: raise e + + +def sync_read_file(path, mode='r'): + """synchronously read file + + return file data + """ + # lock is in /tmp to avoid permission issues + lpath = os.path.join(tempfile.gettempdir(), os.path.basename(path)) + lock = LockFile(lpath) + try: + lock.acquire(True) + with open(path, mode) as data_file: + data = data_file.read() + except Exception as e: + raise e + finally: + lock.release() + return data + + +def sync_write_file(path, data, mode='w'): + """synchronously write file""" + # lock is in /tmp to avoid permission issues + lpath = os.path.join(tempfile.gettempdir(), os.path.basename(path)) + lock = LockFile(lpath) + try: + lock.acquire(True) + with open(path, mode) as data_file: + data_file.write(data) + except Exception as e: + raise e + finally: + lock.release() diff --git a/tools/passwd_editor.py b/tools/passwd_editor.py index 89725a4..f982c92 100755 --- a/tools/passwd_editor.py +++ b/tools/passwd_editor.py @@ -21,15 +21,15 @@ from kollacli import utils def _print_pwd_keys(path): pwd_keys = '' prefix = '' - with open(path, 'r') as pwd_file: - for line in pwd_file: - if line.startswith('#'): - # skip commented lines - continue - if ':' in line: - pwd_key = line.split(':')[0] - pwd_keys = pwd_keys + prefix + pwd_key - prefix = ',' + pwd_data = utils.sync_read_file(path) + for line in pwd_data.split('\n'): + if line.startswith('#'): + # skip commented lines + continue + if ':' in line: + pwd_key = line.split(':')[0] + pwd_keys = ''.join([pwd_keys, prefix, pwd_key]) + prefix = ',' print(pwd_keys)