def remove(path): ''' Get matches for path expression CLI Example: .. code-block:: bash salt '*' augeas.remove /files/etc/sysctl.conf/net.ipv4.conf.all.log_martians ''' aug = _Augeas() ret = {'retval': False} try: count = aug.remove(path) aug.save() if count == -1: ret['error'] = 'Invalid node' else: ret['retval'] = True except (RuntimeError, IOError) as err: ret['error'] = str(err) ret['count'] = count return ret
def get(path, value=''): ''' Get a value for a specific augeas path CLI Example: .. code-block:: bash salt '*' augeas.get /files/etc/hosts/1/ ipaddr ''' aug = _Augeas() ret = {} path = path.rstrip('/') if value: path += '/{0}'.format(value.strip('/')) try: _match = aug.match(path) except RuntimeError as err: return {'error': str(err)} if _match: ret[path] = aug.get(path) else: ret[path] = '' # node does not exist return ret
def tree(path, load_path=None): """ Returns recursively the complete tree of a node CLI Example: .. code-block:: bash salt '*' augeas.tree /files/etc/ path The base of the recursive listing .. versionadded:: 2016.3.0 load_path A colon-spearated list of directories that modules should be searched in. This is in addition to the standard load path and the directories in AUGEAS_LENS_LIB. """ load_path = _check_load_paths(load_path) aug = _Augeas(loadpath=load_path) path = path.rstrip("/") + "/" match_path = path return dict([i for i in _recurmatch(match_path, aug)])
def ls(path, load_path=None): # pylint: disable=C0103 """ List the direct children of a node CLI Example: .. code-block:: bash salt '*' augeas.ls /files/etc/passwd path The path to list .. versionadded:: 2016.3.0 load_path A colon-spearated list of directories that modules should be searched in. This is in addition to the standard load path and the directories in AUGEAS_LENS_LIB. """ def _match(path): """ Internal match function """ try: matches = aug.match(salt.utils.stringutils.to_str(path)) except RuntimeError: return {} ret = {} for _ma in matches: ret[_ma] = aug.get(_ma) return ret load_path = _check_load_paths(load_path) aug = _Augeas(loadpath=load_path) path = path.rstrip("/") + "/" match_path = path + "*" matches = _match(match_path) ret = {} for key, value in six.iteritems(matches): name = _lstrip_word(key, path) if _match(key + "/*"): ret[name + "/"] = value # has sub nodes, e.g. directory else: ret[name] = value return ret
def tree(path): ''' Returns recursively the complete tree of a node CLI Example: .. code-block:: bash salt '*' augeas.tree /files/etc/ ''' aug = _Augeas() path = path.rstrip('/') + '/' match_path = path return dict([i for i in _recurmatch(match_path, aug)])
def get(path, value="", load_path=None): """ Get a value for a specific augeas path CLI Example: .. code-block:: bash salt '*' augeas.get /files/etc/hosts/1/ ipaddr path The path to get the value of value The optional value to get .. versionadded:: 2016.3.0 load_path A colon-spearated list of directories that modules should be searched in. This is in addition to the standard load path and the directories in AUGEAS_LENS_LIB. """ load_path = _check_load_paths(load_path) aug = _Augeas(loadpath=load_path) ret = {} path = path.rstrip("/") if value: path += "/{0}".format(value.strip("/")) try: _match = aug.match(path) except RuntimeError as err: return {"error": six.text_type(err)} if _match: ret[path] = aug.get(path) else: ret[path] = "" # node does not exist return ret
def match(path, value="", load_path=None): """ Get matches for path expression CLI Example: .. code-block:: bash salt '*' augeas.match /files/etc/services/service-name ssh path The path to match value The value to match on .. versionadded:: 2016.3.0 load_path A colon-spearated list of directories that modules should be searched in. This is in addition to the standard load path and the directories in AUGEAS_LENS_LIB. """ load_path = _check_load_paths(load_path) aug = _Augeas(loadpath=load_path) ret = {} try: matches = aug.match(path) except RuntimeError: return ret for _match in matches: if value and aug.get(_match) == value: ret[_match] = value elif not value: ret[_match] = aug.get(_match) return ret
def remove(path, load_path=None): """ Get matches for path expression CLI Example: .. code-block:: bash salt '*' augeas.remove \\ /files/etc/sysctl.conf/net.ipv4.conf.all.log_martians path The path to remove .. versionadded:: 2016.3.0 load_path A colon-spearated list of directories that modules should be searched in. This is in addition to the standard load path and the directories in AUGEAS_LENS_LIB. """ load_path = _check_load_paths(load_path) aug = _Augeas(loadpath=load_path) ret = {"retval": False} try: count = aug.remove(path) aug.save() if count == -1: ret["error"] = "Invalid node" else: ret["retval"] = True except (RuntimeError, IOError) as err: ret["error"] = six.text_type(err) ret["count"] = count return ret
def remove(path, load_path=None): ''' Get matches for path expression CLI Example: .. code-block:: bash salt '*' augeas.remove \\ /files/etc/sysctl.conf/net.ipv4.conf.all.log_martians path The path to remove .. versionadded:: Boron load_path A colon-spearated list of directories that modules should be searched in. This is in addition to the standard load path and the directories in AUGEAS_LENS_LIB. ''' load_path = _check_load_paths(load_path) aug = _Augeas(loadpath=load_path) ret = {'retval': False} try: count = aug.remove(path) aug.save() if count == -1: ret['error'] = 'Invalid node' else: ret['retval'] = True except (RuntimeError, IOError) as err: ret['error'] = str(err) ret['count'] = count return ret
def ls(path): # pylint: disable=C0103 ''' List the direct children of a node CLI Example: .. code-block:: bash salt '*' augeas.ls /files/etc/passwd ''' def _match(path): ''' Internal match function ''' try: matches = aug.match(path) except RuntimeError: return {} ret = {} for _ma in matches: ret[_ma] = aug.get(_ma) return ret aug = _Augeas() path = path.rstrip('/') + '/' match_path = path + '*' matches = _match(match_path) ret = {} for key, value in six.iteritems(matches): name = _lstrip_word(key, path) if _match(key + '/*'): ret[name + '/'] = value # has sub nodes, e.g. directory else: ret[name] = value return ret
def match(path, value=''): ''' Get matches for path expression CLI Example: .. code-block:: bash salt '*' augeas.match /files/etc/services/service-name ssh ''' aug = _Augeas() ret = {} try: matches = aug.match(path) except RuntimeError: return ret for _match in matches: if value and aug.get(_match) == value: ret[_match] = value elif not value: ret[_match] = aug.get(_match) return ret
def execute(context=None, lens=None, commands=()): ''' Execute Augeas commands .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' augeas.execute /files/etc/redis/redis.conf commands='["set bind 0.0.0.0", "set maxmemory 1G"]' ''' ret = {'retval': False} method_map = { 'set': 'set', 'mv': 'move', 'move': 'move', 'ins': 'insert', 'insert': 'insert', 'rm': 'remove', 'remove': 'remove', } flags = _Augeas.NO_MODL_AUTOLOAD if lens else _Augeas.NONE aug = _Augeas(flags=flags) if lens: aug.add_transform(lens, re.sub('^/files', '', context)) aug.load() for command in commands: # first part up to space is always the command name (i.e.: set, move) cmd, arg = command.split(' ', 1) if cmd not in method_map: ret['error'] = 'Command {0} is not supported (yet)'.format(cmd) return ret method = method_map[cmd] try: if method == 'set': path, value, remainder = re.split('([^\'" ]+|"[^"]+"|\'[^\']+\')$', arg, 1) if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) value = value.strip('"').strip("'") args = {'path': path, 'value': value} elif method == 'move': path, dst = arg.split(' ', 1) if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) args = {'src': path, 'dst': dst} elif method == 'insert': label, where, path = re.split(' (before|after) ', arg) if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) args = {'path': path, 'label': label, 'before': where == 'before'} elif method == 'remove': path = arg if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) args = {'path': path} except ValueError as err: log.error(str(err)) ret['error'] = 'Invalid formatted command, ' \ 'see debug log for details: {0}'.format(arg) return ret log.debug('{0}: {1}'.format(method, args)) func = getattr(aug, method) func(**args) try: aug.save() ret['retval'] = True except IOError as err: ret['error'] = str(err) if lens and not lens.endswith('.lns'): ret['error'] += '\nLenses are normally configured as "name.lns". ' \ 'Did you mean "{0}.lns"?'.format(lens) aug.close() return ret
def execute(context=None, lens=None, commands=()): ''' Execute Augeas commands .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' augeas.execute /files/etc/redis/redis.conf commands='["set bind 0.0.0.0", "set maxmemory 1G"]' ''' ret = {'retval': False} arg_map = { 'set': (1, 2), 'setm': (2, 3), 'move': (2,), 'insert': (3,), 'remove': (1,), } def make_path(path): ''' Return correct path ''' if not context: return path if path.lstrip('/'): if path.startswith(context): return path path = path.lstrip('/') return os.path.join(context, path) else: return context flags = _Augeas.NO_MODL_AUTOLOAD if lens and context else _Augeas.NONE aug = _Augeas(flags=flags) if lens and context: aug.add_transform(lens, re.sub('^/files', '', context)) aug.load() for command in commands: try: # first part up to space is always the command name (i.e.: set, move) cmd, arg = command.split(' ', 1) if cmd not in METHOD_MAP: ret['error'] = 'Command {0} is not supported (yet)'.format(cmd) return ret method = METHOD_MAP[cmd] nargs = arg_map[method] parts = salt.utils.shlex_split(arg) if len(parts) not in nargs: err = '{0} takes {1} args: {2}'.format(method, nargs, parts) raise ValueError(err) if method == 'set': path = make_path(parts[0]) value = parts[1] if len(parts) == 2 else None args = {'path': path, 'value': value} elif method == 'setm': base = make_path(parts[0]) sub = parts[1] value = parts[2] if len(parts) == 3 else None args = {'base': base, 'sub': sub, 'value': value} elif method == 'move': path = make_path(parts[0]) dst = parts[1] args = {'src': path, 'dst': dst} elif method == 'insert': label, where, path = parts if where not in ('before', 'after'): raise ValueError('Expected "before" or "after", not {0}'.format(where)) path = make_path(path) args = {'path': path, 'label': label, 'before': where == 'before'} elif method == 'remove': path = make_path(parts[0]) args = {'path': path} except ValueError as err: log.error(str(err)) # if command.split fails arg will not be set if 'arg' not in locals(): arg = command ret['error'] = 'Invalid formatted command, ' \ 'see debug log for details: {0}'.format(arg) return ret log.debug('{0}: {1}'.format(method, args)) func = getattr(aug, method) func(**args) try: aug.save() ret['retval'] = True except IOError as err: ret['error'] = str(err) if lens and not lens.endswith('.lns'): ret['error'] += '\nLenses are normally configured as "name.lns". ' \ 'Did you mean "{0}.lns"?'.format(lens) aug.close() return ret
def execute(context=None, lens=None, commands=(), load_path=None): """ Execute Augeas commands .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' augeas.execute /files/etc/redis/redis.conf \\ commands='["set bind 0.0.0.0", "set maxmemory 1G"]' context The Augeas context lens The Augeas lens to use commands The Augeas commands to execute .. versionadded:: 2016.3.0 load_path A colon-spearated list of directories that modules should be searched in. This is in addition to the standard load path and the directories in AUGEAS_LENS_LIB. """ ret = {"retval": False} arg_map = { "set": (1, 2), "setm": (2, 3), "move": (2,), "insert": (3,), "remove": (1,), } def make_path(path): """ Return correct path """ if not context: return path if path.lstrip("/"): if path.startswith(context): return path path = path.lstrip("/") return os.path.join(context, path) else: return context load_path = _check_load_paths(load_path) flags = _Augeas.NO_MODL_AUTOLOAD if lens and context else _Augeas.NONE aug = _Augeas(flags=flags, loadpath=load_path) if lens and context: aug.add_transform(lens, re.sub("^/files", "", context)) aug.load() for command in commands: try: # first part up to space is always the # command name (i.e.: set, move) cmd, arg = command.split(" ", 1) if cmd not in METHOD_MAP: ret["error"] = "Command {0} is not supported (yet)".format(cmd) return ret method = METHOD_MAP[cmd] nargs = arg_map[method] parts = salt.utils.args.shlex_split(arg) if len(parts) not in nargs: err = "{0} takes {1} args: {2}".format(method, nargs, parts) raise ValueError(err) if method == "set": path = make_path(parts[0]) value = parts[1] if len(parts) == 2 else None args = {"path": path, "value": value} elif method == "setm": base = make_path(parts[0]) sub = parts[1] value = parts[2] if len(parts) == 3 else None args = {"base": base, "sub": sub, "value": value} elif method == "move": path = make_path(parts[0]) dst = parts[1] args = {"src": path, "dst": dst} elif method == "insert": label, where, path = parts if where not in ("before", "after"): raise ValueError( 'Expected "before" or "after", not {0}'.format(where) ) path = make_path(path) args = {"path": path, "label": label, "before": where == "before"} elif method == "remove": path = make_path(parts[0]) args = {"path": path} except ValueError as err: log.error(err) # if command.split fails arg will not be set if "arg" not in locals(): arg = command ret["error"] = ( "Invalid formatted command, " "see debug log for details: {0}".format(arg) ) return ret args = salt.utils.data.decode(args, to_str=True) log.debug("%s: %s", method, args) func = getattr(aug, method) func(**args) try: aug.save() ret["retval"] = True except IOError as err: ret["error"] = six.text_type(err) if lens and not lens.endswith(".lns"): ret["error"] += ( '\nLenses are normally configured as "name.lns". ' 'Did you mean "{0}.lns"?'.format(lens) ) aug.close() return ret
def setvalue(*args): """ Set a value for a specific augeas path CLI Example: .. code-block:: bash salt '*' augeas.setvalue /files/etc/hosts/1/canonical localhost This will set the first entry in /etc/hosts to localhost CLI Example: .. code-block:: bash salt '*' augeas.setvalue /files/etc/hosts/01/ipaddr 192.168.1.1 \\ /files/etc/hosts/01/canonical test Adds a new host to /etc/hosts the ip address 192.168.1.1 and hostname test CLI Example: .. code-block:: bash salt '*' augeas.setvalue prefix=/files/etc/sudoers/ \\ "spec[user = '******']/user" "%wheel" \\ "spec[user = '******']/host_group/host" 'ALL' \\ "spec[user = '******']/host_group/command[1]" 'ALL' \\ "spec[user = '******']/host_group/command[1]/tag" 'PASSWD' \\ "spec[user = '******']/host_group/command[2]" '/usr/bin/apt-get' \\ "spec[user = '******']/host_group/command[2]/tag" NOPASSWD Ensures that the following line is present in /etc/sudoers:: %wheel ALL = PASSWD : ALL , NOPASSWD : /usr/bin/apt-get , /usr/bin/aptitude """ load_path = None load_paths = [x for x in args if six.text_type(x).startswith("load_path=")] if load_paths: if len(load_paths) > 1: raise SaltInvocationError("Only one 'load_path=' value is permitted") else: load_path = load_paths[0].split("=", 1)[1] load_path = _check_load_paths(load_path) aug = _Augeas(loadpath=load_path) ret = {"retval": False} tuples = [ x for x in args if not six.text_type(x).startswith("prefix=") and not six.text_type(x).startswith("load_path=") ] prefix = [x for x in args if six.text_type(x).startswith("prefix=")] if prefix: if len(prefix) > 1: raise SaltInvocationError("Only one 'prefix=' value is permitted") else: prefix = prefix[0].split("=", 1)[1] if len(tuples) % 2 != 0: raise SaltInvocationError("Uneven number of path/value arguments") tuple_iter = iter(tuples) for path, value in zip(tuple_iter, tuple_iter): target_path = path if prefix: target_path = os.path.join(prefix.rstrip("/"), path.lstrip("/")) try: aug.set(target_path, six.text_type(value)) except ValueError as err: ret["error"] = "Multiple values: {0}".format(err) try: aug.save() ret["retval"] = True except IOError as err: ret["error"] = six.text_type(err) return ret
def setvalue(*args): ''' Set a value for a specific augeas path CLI Example: .. code-block:: bash salt '*' augeas.setvalue /files/etc/hosts/1/canonical localhost This will set the first entry in /etc/hosts to localhost CLI Example: .. code-block:: bash salt '*' augeas.setvalue /files/etc/hosts/01/ipaddr 192.168.1.1 \\ /files/etc/hosts/01/canonical test Adds a new host to /etc/hosts the ip address 192.168.1.1 and hostname test CLI Example: .. code-block:: bash salt '*' augeas.setvalue prefix=/files/etc/sudoers/ \\ "spec[user = '******']/user" "%wheel" \\ "spec[user = '******']/host_group/host" 'ALL' \\ "spec[user = '******']/host_group/command[1]" 'ALL' \\ "spec[user = '******']/host_group/command[1]/tag" 'PASSWD' \\ "spec[user = '******']/host_group/command[2]" '/usr/bin/apt-get' \\ "spec[user = '******']/host_group/command[2]/tag" NOPASSWD Ensures that the following line is present in /etc/sudoers:: %wheel ALL = PASSWD : ALL , NOPASSWD : /usr/bin/apt-get , /usr/bin/aptitude ''' aug = _Augeas() ret = {'retval': False} tuples = [x for x in args if not str(x).startswith('prefix=')] prefix = [x for x in args if str(x).startswith('prefix=')] if prefix: if len(prefix) > 1: raise SaltInvocationError( 'Only one \'prefix=\' value is permitted' ) else: prefix = prefix[0].split('=', 1)[1] if len(tuples) % 2 != 0: raise SaltInvocationError('Uneven number of path/value arguments') tuple_iter = iter(tuples) for path, value in zip(tuple_iter, tuple_iter): target_path = path if prefix: target_path = os.path.join(prefix.rstrip('/'), path.lstrip('/')) try: aug.set(target_path, str(value)) except ValueError as err: ret['error'] = 'Multiple values: {0}'.format(err) try: aug.save() ret['retval'] = True except IOError as err: ret['error'] = str(err) return ret
def setvalue(*args): ''' Set a value for a specific augeas path CLI Example: .. code-block:: bash salt '*' augeas.setvalue /files/etc/hosts/1/canonical localhost This will set the first entry in /etc/hosts to localhost CLI Example: .. code-block:: bash salt '*' augeas.setvalue /files/etc/hosts/01/ipaddr 192.168.1.1 \\ /files/etc/hosts/01/canonical test Adds a new host to /etc/hosts the ip address 192.168.1.1 and hostname test CLI Example: .. code-block:: bash salt '*' augeas.setvalue prefix=/files/etc/sudoers/ \\ "spec[user = '******']/user" "%wheel" \\ "spec[user = '******']/host_group/host" 'ALL' \\ "spec[user = '******']/host_group/command[1]" 'ALL' \\ "spec[user = '******']/host_group/command[1]/tag" 'PASSWD' \\ "spec[user = '******']/host_group/command[2]" '/usr/bin/apt-get' \\ "spec[user = '******']/host_group/command[2]/tag" NOPASSWD Ensures that the following line is present in /etc/sudoers:: %wheel ALL = PASSWD : ALL , NOPASSWD : /usr/bin/apt-get , /usr/bin/aptitude ''' aug = _Augeas() ret = {'retval': False} tuples = [x for x in args if not str(x).startswith('prefix=')] prefix = [x for x in args if str(x).startswith('prefix=')] if prefix: if len(prefix) > 1: raise SaltInvocationError( 'Only one \'prefix=\' value is permitted') else: prefix = prefix[0].split('=', 1)[1] if len(tuples) % 2 != 0: raise SaltInvocationError('Uneven number of path/value arguments') tuple_iter = iter(tuples) for path, value in zip(tuple_iter, tuple_iter): target_path = path if prefix: target_path = os.path.join(prefix.rstrip('/'), path.lstrip('/')) try: aug.set(target_path, str(value)) except ValueError as err: ret['error'] = 'Multiple values: {0}'.format(err) try: aug.save() ret['retval'] = True except IOError as err: ret['error'] = str(err) return ret
def execute(context=None, lens=None, commands=()): ''' Execute Augeas commands .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' augeas.execute /files/etc/redis/redis.conf commands='["set bind 0.0.0.0", "set maxmemory 1G"]' ''' ret = {'retval': False} method_map = { 'set': 'set', 'mv': 'move', 'move': 'move', 'ins': 'insert', 'insert': 'insert', 'rm': 'remove', 'remove': 'remove', } flags = _Augeas.NO_MODL_AUTOLOAD if lens else _Augeas.NONE aug = _Augeas(flags=flags) if lens: aug.add_transform(lens, re.sub('^/files', '', context)) aug.load() for command in commands: # first part up to space is always the command name (i.e.: set, move) cmd, arg = command.split(' ', 1) if cmd not in method_map: ret['error'] = 'Command {0} is not supported (yet)'.format(cmd) return ret method = method_map[cmd] try: if method == 'set': path, value, remainder = re.split( '([^\'" ]+|"[^"]+"|\'[^\']+\')$', arg, 1) if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) value = value.strip('"').strip("'") args = {'path': path, 'value': value} elif method == 'move': path, dst = arg.split(' ', 1) if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) args = {'src': path, 'dst': dst} elif method == 'insert': path, where, label = re.split(' (before|after) ', arg) if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) args = { 'path': path, 'label': label, 'before': where == 'before' } elif method == 'remove': path = arg if context: path = os.path.join(context.rstrip('/'), path.lstrip('/')) args = {'path': path} except ValueError as err: log.error(str(err)) ret['error'] = 'Invalid formatted command, ' \ 'see debug log for details: {0}'.format(arg) return ret log.debug('{0}: {1}'.format(method, args)) func = getattr(aug, method) func(**args) try: aug.save() ret['retval'] = True except IOError as err: ret['error'] = str(err) if lens and not lens.endswith('.lns'): ret['error'] += '\nLenses are normally configured as "name.lns". ' \ 'Did you mean "{0}.lns"?'.format(lens) aug.close() return ret
def execute(context=None, lens=None, commands=()): ''' Execute Augeas commands .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' augeas.execute /files/etc/redis/redis.conf commands='["set bind 0.0.0.0", "set maxmemory 1G"]' ''' ret = {'retval': False} method_map = { 'set': 'set', 'setm': 'setm', 'mv': 'move', 'move': 'move', 'ins': 'insert', 'insert': 'insert', 'rm': 'remove', 'remove': 'remove', } arg_map = { 'set': (1, 2), 'setm': (2, 3), 'move': (2,), 'insert': (3,), 'remove': (1,), } def make_path(path): if not context: return path path = path.lstrip('/') if path: return os.path.join(context, path) else: return context flags = _Augeas.NO_MODL_AUTOLOAD if lens else _Augeas.NONE aug = _Augeas(flags=flags) if lens: aug.add_transform(lens, re.sub('^/files', '', context)) aug.load() for command in commands: # first part up to space is always the command name (i.e.: set, move) cmd, arg = command.split(' ', 1) if cmd not in method_map: ret['error'] = 'Command {0} is not supported (yet)'.format(cmd) return ret method = method_map[cmd] nargs = arg_map[method] try: parts = salt.utils.shlex_split(arg) if len(parts) not in nargs: err = '{0} takes {1} args: {2}'.format(method, nargs, parts) raise ValueError(err) if method == 'set': path = make_path(parts[0]) value = parts[1] if len(parts) == 2 else None args = {'path': path, 'value': value} elif method == 'setm': base = make_path(parts[0]) sub = parts[1] value = parts[2] if len(parts) == 3 else None args = {'base': base, 'sub': sub, 'value': value} elif method == 'move': path = make_path(parts[0]) dst = parts[1] args = {'src': path, 'dst': dst} elif method == 'insert': label, where, path = parts if where not in ('before', 'after'): raise ValueError('Expected "before" or "after", not {0}'.format(where)) path = make_path(path) args = {'path': path, 'label': label, 'before': where == 'before'} elif method == 'remove': path = make_path(parts[0]) args = {'path': path} except ValueError as err: log.error(str(err)) ret['error'] = 'Invalid formatted command, ' \ 'see debug log for details: {0}'.format(arg) return ret log.debug('{0}: {1}'.format(method, args)) func = getattr(aug, method) func(**args) try: aug.save() ret['retval'] = True except IOError as err: ret['error'] = str(err) if lens and not lens.endswith('.lns'): ret['error'] += '\nLenses are normally configured as "name.lns". ' \ 'Did you mean "{0}.lns"?'.format(lens) aug.close() return ret