diff --git a/test_wildfly.py b/test_wildfly.py new file mode 100644 index 0000000..d3c7a1d --- /dev/null +++ b/test_wildfly.py @@ -0,0 +1,137 @@ +''' +Unit tests for wildfly module +''' + +import unittest +from six import string_types +from wildfly import WildflyPath, format_attr, config_query, wildfly_batch + +class TestWildflyPath(unittest.TestCase): + ''' + Tests for WildflyPath class. + ''' + + def setUp(self): + test_input = r''' + /subsystem=undertow/server=default-server/host=default-host + /location=\/myapp\/multimedia + ''' + self.instance = WildflyPath(test_input) + + def test_wildfly_path_str(self): + self.assertEqual( + str(self.instance), + r'/subsystem=undertow/server=default-server/host=default-host' + r'/location=\/myapp\/multimedia' + ) + + def test_wildfly_path_len(self): + self.assertEqual(len(self.instance), 8) + + def test_wildfly_path_lpop(self): + self.instance.lpop() + self.instance.lpop() + self.assertEqual( + str(self.instance), + r'/server=default-server/host=default-host' + r'/location=\/myapp\/multimedia' + ) + + +class TestFormatAttr(unittest.TestCase): + ''' + Tests for format_attr function. + ''' + + def test_format_attr_return_type(self): + for input_ in (None, True, 1234, 1234.5, ['123',243], + 'hello', {'a': 123}): + self.assertIsInstance(format_attr(input_), string_types) + + def test_format_attr_true(self): + self.assertEqual(format_attr(True), 'True') + + def test_format_attr_false(self): + self.assertEqual(format_attr(False), 'False') + + def test_format_attr_none(self): + self.assertEqual(format_attr(None), 'undefined') + + def test_format_attr_float(self): + self.assertEqual(format_attr(1234.5), '1234.500000') + + def test_format_attr_int(self): + self.assertEqual(format_attr(1234), '1234') + + def test_format_attr_bigint(self): + self.assertEqual(format_attr(1e10), '10000000000.000000') + + def test_format_attr_string(self): + self.assertEqual(format_attr('hello'), '"hello"') + + def test_format_attr_stripquotes(self): + self.assertEqual(format_attr('"hello"'), '"hello"') + + +class TestConfigQueryFunction(unittest.TestCase): + ''' + Tests for config_query function. + ''' + + def setUp(self): + self.config0 = {'core-service': {'management': {'security-realm': { + 'ManagementRealm': {'plug-in': None, 'server-identity': None}}}}} + self.path0 = ('/core-service=management/security-realm=ManagementRealm' + '/server-identity=ssl') + self.config1 = {'core-service': {'management': {'security-realm': { + 'ManagementRealm': {'server-identity': {'ssl': { + 'alias': 'wildfly', + 'key-password': 'changeit', + 'keystore-password': 'changeit', + 'keystore-path': 'server-admin.keystore', + 'keystore-relative-to': 'jboss.server.config.dir', + }}}}}}} + + def test_config_query_none(self): + self.assertEqual(config_query(self.config0, self.path0), None) + + +class TestBatch(unittest.TestCase): + ''' + Tests for wildfly_batch function. + ''' + + def setUp(self): + self.config0 = {'core-service': {'management': {'security-realm': { + 'ManagementRealm': {'plug-in': None, 'server-identity': None}}}}} + self.path0 = ('/core-service=management/security-realm=ManagementRealm' + '/server-identity=ssl') + self.attrs0 = { + 'alias': 'wildfly', + 'key-password': 'changeit', + 'keystore-password': 'changeit', + 'keystore-path': 'server-admin.keystore', + 'keystore-relative-to': 'jboss.server.config.dir', + } + self.batch0 = '''\ +/core-service=management/security-realm=ManagementRealm\ +/server-identity=ssl:add(alias="wildfly",key-password="changeit",\ +keystore-password="changeit",keystore-path="server-admin.keystore",\ +keystore-relative-to="jboss.server.config.dir") +''' + self.config1 = {'core-service': {'management': {'security-realm': { + 'ManagementRealm': {'plug-in': None, 'server-identity': {'ssl': { + 'alias': 'wildfly', + 'key-password': 'changeit', + 'keystore-password': 'changeit', + 'keystore-path': 'server-admin.keystore', + 'keystore-relative-to': 'jboss.server.config.dir', + }}}}}}} + + def test_batch_add_node(self): + self.assertEqual(wildfly_batch(self.config0, self.path0, self.attrs0, + wrap=False), self.batch0) + + +if __name__ == '__main__': + unittest.main() diff --git a/wildfly.py b/wildfly.py index 4b73007..94daf07 100644 --- a/wildfly.py +++ b/wildfly.py @@ -6,23 +6,30 @@ Ansible action plugin for configuring Wildfly ''' from __future__ import (absolute_import, division, print_function) -__metaclass__ = type + +import re +import json from ansible.plugins.action import ActionBase from ansible.utils.display import Display from ansible.utils.vars import merge_hash -display = Display() -import json -import re from six import string_types +display = Display() + + class WildflyError(Exception): - pass + ''' + Basic error class for this module. + ''' class ActionModule(ActionBase): + ''' + The action module. + ''' def run(self, tmp=None, task_vars=None): super(ActionModule, self).run(tmp, task_vars) @@ -36,7 +43,6 @@ class ActionModule(ActionBase): cli_command, config, dry_run = read_args(task_args, check_mode) # read current config - # if not self.prev: cur_result = self.execute_command( '{} --output-json --command="/:read-resource(recursive)"'.format( cli_command), @@ -47,10 +53,10 @@ class ActionModule(ActionBase): try: cur_state = json.loads(cur_result.get('stdout')).get('result') - except Exception as e: + except Exception as ex: return merge_hash(cur_result, dict( failed=True, - msg='Error parsing JSON from Wildfly CLI: {}'.format(str(e)) + msg='Error parsing JSON from Wildfly CLI: {}'.format(str(ex)) )) # generate batch script @@ -61,25 +67,27 @@ class ActionModule(ActionBase): cur_state, item.get('root', '/'), item.get('config', {}), - item.get('state', 'present') - , False) + item.get('state', 'present'), + False) - except Exception as e: + except Exception as ex: return merge_hash(cur_result, dict( failed=True, - msg='Error while generating Wildfly batch: {}'.format(str(e)), + msg='Error while generating Wildfly batch: {}'.format( + str(ex)), stdout=None, stdout_lines=[], config_item=item, batch_script=batch_script )) - result = dict(changed=False,batch_script=batch_script) + result = dict(changed=False, batch_script=batch_script, + previous_state=cur_state) # apply changes if batch_script: batch_script = 'batch\n{}run-batch\n'.format(batch_script) - result = dict(changed=True,batch_script=batch_script) + result = merge_hash(result, dict(changed=True)) if not dry_run: self.execute_command( @@ -90,7 +98,6 @@ class ActionModule(ActionBase): return result - def execute_command(self, command, stdin=None, check_mode_unsafe=True): ''' Execute command module and return result @@ -107,7 +114,6 @@ class ActionModule(ActionBase): ) - def read_args(task_args, check_mode): ''' Read and validate invocation arguments @@ -125,9 +131,9 @@ def read_args(task_args, check_mode): 'Please use "config" argument instead.') # validate config argument - if type(config) is list: + if isinstance(config, list): pass - elif type(config) is dict: + elif isinstance(config, dict): config = [dict( config=config, root=task_args.get('root'), @@ -142,7 +148,6 @@ def read_args(task_args, check_mode): return cli_command, config, dry_run - def wildfly_batch(prev, root, attrs, state='present', wrap=True): ''' Generate Wildfly batch to assert state of configuration item under @@ -166,7 +171,7 @@ def wildfly_batch(prev, root, attrs, state='present', wrap=True): output = '' # check current config - cur = wildfly_config_query(prev, root) + cur = config_query(prev, root) display.v("current config: {}".format(cur)) @@ -174,7 +179,7 @@ def wildfly_batch(prev, root, attrs, state='present', wrap=True): if cur: # node exists, add missing attrs - for k in attrs: + for k in sorted(attrs): if k not in cur or cur[k] != attrs[k]: output = '{}{}:write-attribute(name={},value={})\n'.format( output, root, k, format_attr(attrs[k])) @@ -185,21 +190,22 @@ def wildfly_batch(prev, root, attrs, state='present', wrap=True): # node doesn't exist, add it output = '{}{}:add({})\n'.format( output, root, - ','.join(['{}={}'.format(k,format_attr(attrs[k])) for k in attrs]) + ','.join(['{}={}'.format( + k, format_attr(attrs[k])) for k in sorted(attrs)]) ) - wildfly_config_add_node(prev, root, attrs) + # config_add_node(prev, root, attrs) if state.lower() == 'absent' and cur: # remove existing node output = '{}{}:remove()\n'.format(output, root) - if len(output) and wrap: + if len(output) > 0 and wrap: output = 'batch\n{}\nrun-batch\n'.format(output) return output -def wildfly_config_query(config, path): +def config_query(config, path): ''' Given Wildfly configuration as a dict, follow requested path and return corresponding entry. @@ -207,39 +213,18 @@ def wildfly_config_query(config, path): ''' path = WildflyPath(path) ptr = config - while len(path): + while len(path) > 0: try: qry = path.lpop() ptr = ptr[qry] except (KeyError, TypeError): # either ptr is none (Type), or ptr[qry] not found (Key) return None - except Exception as e: - raise WildflyError( "config query error: {}".format(str(e))) + except Exception as ex: + raise WildflyError("config query error: {}".format(str(ex))) return ptr -def wildfly_config_add_node(config, path, node): - ''' - Given Wildfly configuration as a dict, follow - requested path and add given node. - - ''' - path = WildflyPath(path) - ptr = config - while len(path): - try: - qry = path.lpop() - ptr = ptr[qry] - except TypeError: - ptr = dict() - except KeyError: - ptr[qry] = dict() - except Exception as e: - raise Exception( "unknown error: {}".format(str(e))) - ptr = merge_hash(ptr,node) - - def format_attr(attr): ''' Return valid text representation for attribute as understood by @@ -251,19 +236,27 @@ def format_attr(attr): if attr is None: return 'undefined' + if isinstance(attr, (float)): + return '{:f}'.format(attr) + + if isinstance(attr, (int, bool)): + return str(attr) + # list => '[ format(item), ... ]' - if isinstance(attr,list): + if isinstance(attr, (list,tuple)): return '[' + ', '.join( - [ '{}'.format( 'undefined' if k_ is None else format_attr(k_)) for k_ in attr ] + ['{}'.format( + 'undefined' if k_ is None + else format_attr(k_)) for k_ in attr] ) + ']' # dict => '{ "key" => format(value), ... }' - if isinstance(attr,dict): + if isinstance(attr, dict): return '{' + ', '.join( - [ '"{}" => {}'.format( - k_, - 'undefined' if attr[k_] is None else format_attr(attr[k_]) - ) for k_ in list(attr.keys()) ] + ['"{}" => {}'.format( + k_, 'undefined' if attr[k_] is None + else format_attr(attr[k_]) + ) for k_ in list(attr.keys())] ) + '}' # wrap strings with double quotes @@ -283,14 +276,14 @@ class WildflyPath: def __init__(self, path): if isinstance(path, string_types): self.parts = [ - i.replace('\\','').strip() + i.replace('\\', '').strip() for i in re.split(r'((?