feat: complete batch generation + apply
This commit is contained in:
parent
d85aac8715
commit
82f0ae24ef
239
wildfly.py
239
wildfly.py
@ -10,11 +10,18 @@ __metaclass__ = type
|
||||
|
||||
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
|
||||
|
||||
|
||||
class WildflyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
@ -24,26 +31,6 @@ class ActionModule(ActionBase):
|
||||
self._supports_check_mode = True
|
||||
self._supports_async = False
|
||||
|
||||
# module_args = self._task.args.copy()
|
||||
# module_return = self._execute_module(module_name='setup',
|
||||
# module_args=module_args,
|
||||
# task_vars=task_vars, tmp=tmp)
|
||||
# ret = dict()
|
||||
# remote_date = None
|
||||
# if not module_return.get('failed'):
|
||||
# for key, value in module_return['ansible_facts'].items():
|
||||
# if key == 'ansible_date_time':
|
||||
# remote_date = value['iso8601']
|
||||
|
||||
# if remote_date:
|
||||
# remote_date_obj = datetime.strptime(remote_date, '%Y-%m-%dT%H:%M:%SZ')
|
||||
# time_delta = datetime.now() - remote_date_obj
|
||||
# ret['delta_seconds'] = time_delta.seconds
|
||||
# ret['delta_days'] = time_delta.days
|
||||
# ret['delta_microseconds'] = time_delta.microseconds
|
||||
|
||||
# return dict(ansible_facts=dict(ret))
|
||||
|
||||
task_args = self._task.args.copy()
|
||||
check_mode = self._play_context.check_mode
|
||||
cli_command, config, dry_run = read_args(task_args, check_mode)
|
||||
@ -63,10 +50,46 @@ class ActionModule(ActionBase):
|
||||
except Exception as e:
|
||||
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(e))
|
||||
))
|
||||
|
||||
return cur_result
|
||||
# generate batch script
|
||||
batch_script = ''
|
||||
for item in config:
|
||||
try:
|
||||
batch_script = batch_script + wildfly_batch(
|
||||
cur_state,
|
||||
item.get('root', '/'),
|
||||
item.get('config', {}),
|
||||
item.get('state', 'present')
|
||||
, False)
|
||||
|
||||
except Exception as e:
|
||||
return merge_hash(cur_result, dict(
|
||||
failed=True,
|
||||
msg='Error while generating Wildfly batch: {}'.format(str(e)),
|
||||
stdout=None,
|
||||
stdout_lines=[],
|
||||
config_item=item,
|
||||
batch_script=batch_script
|
||||
))
|
||||
|
||||
result = dict(changed=False,batch_script=batch_script)
|
||||
|
||||
# apply changes
|
||||
if batch_script:
|
||||
batch_script = 'batch\n{}run-batch\n'.format(batch_script)
|
||||
result = dict(changed=True,batch_script=batch_script)
|
||||
|
||||
if not dry_run:
|
||||
self.execute_command(
|
||||
'{} --output-json'.format(cli_command),
|
||||
stdin=batch_script,
|
||||
check_mode_unsafe=True
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def execute_command(self, command, stdin=None, check_mode_unsafe=True):
|
||||
'''
|
||||
@ -111,9 +134,179 @@ def read_args(task_args, check_mode):
|
||||
state=task_args.get('state', 'present')
|
||||
)]
|
||||
else:
|
||||
raise Exception("config argument should be list or dict")
|
||||
raise WildflyError('config argument should be list or dict')
|
||||
|
||||
# dry run argument
|
||||
dry_run = (task_args.get('dry_run', False) or 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
|
||||
specified _root_, given current configuration in _prev_.
|
||||
|
||||
Arguments:
|
||||
- item: requested configuration tree under _root_
|
||||
- root: root path
|
||||
- prev: previous (current) state of configuration with root=/
|
||||
- state: required state: present or absent
|
||||
- wrap: if True, wrap script in batch ... run-batch
|
||||
instructions
|
||||
|
||||
Pitfalls (TODO):
|
||||
- Only node (i.e. not attribute) removal is supported
|
||||
- For hash attribute values, only first depth is supported
|
||||
|
||||
'''
|
||||
|
||||
# output batch script
|
||||
output = ''
|
||||
|
||||
# check current config
|
||||
cur = wildfly_config_query(prev, root)
|
||||
|
||||
display.v("current config: {}".format(cur))
|
||||
|
||||
if state.lower() == 'present':
|
||||
|
||||
if cur:
|
||||
# node exists, add missing attrs
|
||||
for k in 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]))
|
||||
# update configuration tree
|
||||
cur[k] = attrs[k]
|
||||
|
||||
else:
|
||||
# node doesn't exist, add it
|
||||
output = '{}{}:add({})\n'.format(
|
||||
output, root,
|
||||
','.join(['{}={}'.format(k,format_attr(attrs[k])) for k in attrs])
|
||||
)
|
||||
wildfly_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:
|
||||
output = 'batch\n{}\nrun-batch\n'.format(output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def wildfly_config_query(config, path):
|
||||
'''
|
||||
Given Wildfly configuration as a dict, follow
|
||||
requested path and return corresponding entry.
|
||||
|
||||
'''
|
||||
path = WildflyPath(path)
|
||||
ptr = config
|
||||
while len(path):
|
||||
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)))
|
||||
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
|
||||
Wildfly CLI.
|
||||
|
||||
'''
|
||||
|
||||
# None => 'undefined'
|
||||
if attr is None:
|
||||
return 'undefined'
|
||||
|
||||
# list => '[ format(item), ... ]'
|
||||
if isinstance(attr,list):
|
||||
return '[' + ', '.join(
|
||||
[ '{}'.format( 'undefined' if k_ is None else format_attr(k_)) for k_ in attr ]
|
||||
) + ']'
|
||||
|
||||
# dict => '{ "key" => format(value), ... }'
|
||||
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()) ]
|
||||
) + '}'
|
||||
|
||||
# wrap strings with double quotes
|
||||
return '"{}"'.format(str(attr).strip('"'))
|
||||
|
||||
|
||||
class WildflyPath:
|
||||
|
||||
'''
|
||||
Utility class for handling Wildfly configuration paths.
|
||||
Converts a path string into an array of components (parts).
|
||||
Supports whitespace around separator characters '=' and '/' and
|
||||
escaping them with backslashes.
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, path):
|
||||
if isinstance(path, string_types):
|
||||
self.parts = [
|
||||
i.replace('\\','').strip()
|
||||
for i in re.split(r'((?<!\\)[=/])', path)
|
||||
]
|
||||
elif isinstance(path, WildflyPath):
|
||||
self.parts = path.parts
|
||||
elif isinstance(path, list, tuple):
|
||||
self.parts = [
|
||||
i.replace('\\','').strip()
|
||||
for i in path
|
||||
]
|
||||
else:
|
||||
raise WildflyError('Bad path assignment')
|
||||
|
||||
def __str__(self):
|
||||
return ''.join([
|
||||
(i if i in ('','/','=') else i.replace('/','\\/'))
|
||||
for i in self.parts
|
||||
])
|
||||
|
||||
def __len__(self):
|
||||
return sum([1 for i in self.parts if i not in ('','=','/')])
|
||||
|
||||
def lpop(self):
|
||||
ret = self.parts.pop(0)
|
||||
while ret in ('','/','='):
|
||||
ret = self.parts.pop(0)
|
||||
return ret
|
||||
|
Loading…
x
Reference in New Issue
Block a user