def do_check(self, arg): """Toggle whether plays run with check mode""" if arg: self.options.check = C.mk_boolean(arg) display.v("check mode changed to %s" % self.options.check) else: display.display("Please specify check mode value, e.g. `check yes`")
def run(self): """Run the ansible command Subclasses must implement this method. It does the actual work of running an Ansible command. """ display.vv(to_text(self.parser.get_version())) if C.CONFIG_FILE: display.v(u"Using %s as config file" % to_text(C.CONFIG_FILE)) else: display.v(u"No config file found; using defaults") # warn about deprecated config options for deprecated in C.config.DEPRECATED: name = deprecated[0] why = deprecated[1]['why'] if 'alternatives' in deprecated[1]: alt = ', use %s instead' % deprecated[1]['alternatives'] else: alt = '' ver = deprecated[1]['version'] display.deprecated("%s option, %s %s" % (name, why, alt), version=ver) # warn about typing issues with configuration entries for unable in C.config.UNABLE: display.warning("Unable to set correct type for configuration entry: %s" % unable)
def do_become_method(self, arg): """Given a become_method, set the privilege escalation method when using become""" if arg: self.options.become_method = arg display.v("become_method changed to %s" % self.options.become_method) else: display.display("Please specify a become_method, e.g. `become_method su`")
def _parse_pkglist(self, pkglist, other=False): pkgs_dict = dict() repo_dict = dict() display.v("Handling package list: %s" % pkglist) with open(pkglist) as f: for line in f.readlines(): line = line.strip() if line.startswith('#'): m = self.INCLUDE_PATTERN.match(line) if not m: continue incf = m.group(1).strip() display.vvv("Including: %s" % incf) d1, d2 = self._parse_pkglist(incf, other) pkgs_dict.update(d1) if other: repo_dict.update(d2) else: if other: repo_dir = os.path.dirname(line) or '' if repo_dir: repo_dict[repo_dir] = 1 pkgname = os.path.basename(line) if pkgname.startswith('@ '): # it is a group, get rid of the space pkgname = "@%s" % pkgname[1:].strip() pkgs_dict[pkgname] = 1 return pkgs_dict, repo_dict
def run(self): """Run the ansible command Subclasses must implement this method. It does the actual work of running an Ansible command. """ display.vv(to_text(self.parser.get_version())) if C.CONFIG_FILE: display.v(u"Using %s as config file" % to_text(C.CONFIG_FILE)) else: display.v(u"No config file found; using defaults") # warn about deprecated config options for deprecated in C.config.DEPRECATED: name = deprecated[0] why = deprecated[1]['why'] if 'alternative' in deprecated[1]: alt = ', use %s instead' % deprecated[1]['alternative'] else: alt = '' ver = deprecated[1]['version'] display.deprecated("%s option, %s %s" % (name, why, alt), version=ver) # warn about typing issues with configuration entries for unable in C.config.UNABLE: display.warning( "Unable to set correct type for configuration entry: %s" % unable)
def _get_arg_or_var(self, name, default=None, is_required=True): ret = self._task.args.get(name, self._task_vars.get(name, default)) display.v("_get_arg_or_var %s, default: %s, required: %s, ret: %s" % (name, default, is_required, ret)) if is_required and not ret and ret != default: raise AnsibleOptionsError("parameter %s is required" % name) else: return ret
def do_diff(self, arg): """Toggle whether plays run with diff""" if arg: self.options.diff = C.mk_boolean(arg) display.v("diff mode changed to %s" % self.options.diff) else: display.display("Please specify a diff value , e.g. `diff yes`")
def do_verbosity(self, arg): """Set verbosity level""" if not arg: display.display('Usage: verbosity <number>') else: display.verbosity = int(arg) display.v('verbosity level set to %s' % arg)
def create_request(self, account_id, reason, period): display.v("Creating request for: %s (%s) " % (account_id, reason)) now = int(time()) payload = json.dumps( { "AccountID": account_id, "Reason": reason, "fromDate": now, "toDate": now + period, "hasTimeframe": True, "MultipleAccessRequired": True, }, indent=2, sort_keys=False) display.v(payload) response = self.request(api_endpoint='API/MyRequests', data=payload, method='POST') # if response.status != 201: # raise PWVRequestInvalid(0, "unexpected return code", "%s instead of 201" % response.status_code) response = json.loads(response.read()) if response['Status'] == 7: raise PWVRequestInvalid(response['RequestID'], response['StatusTitle'], response['StatusRequestReason']) return response['RequestID']
def run(self, tmp=None, task_vars=None): display.v("a log") display.vv("Kind of verbose") display.vvv("Verbose") display.vvvv("Lookout!") display.verbose("Super custom verbosity", caplevel=6) return {'msg': 'done'}
def do_become(self, arg): """Toggle whether plays run with become""" if arg: self.options.become = C.mk_boolean(arg) display.v("become changed to %s" % self.options.become) self.set_prompt() else: display.display("Please specify become value, e.g. `become yes`")
def do_become_user(self, arg): """Given a username, set the user that plays are run by when using become""" if arg: self.options.become_user = arg else: display.display("Please specify a user, e.g. `become_user jenkins`") display.v("Current user is %s" % self.options.become_user) self.set_prompt()
def _get_arg_or_var(self, name, default=None, is_required=True): display.v("%s, %s, %s" % (name, default, is_required)) ret = self._task.args.get(name, self._task_vars.get(name, default)) ret = self._templar.template(ret) if is_required and ret == None: raise AnsibleOptionsError("parameter %s is required" % name) else: return ret
def read_put_file(self, connection, in_path, out_path): self.put_index += 1 display.v('FIXTURE_PUT_INDEX: %s' % self.put_index) fixture_file = self.get_fixture_file('put', 'read', connection=connection) with open(fixture_file, 'r') as f: jdata = json.loads(f.read()) return (jdata['returncode'], jdata['stdout'], jdata['stderr'])
def orion_query(self, swis, query): """ Connect to Orion server and send query """ rsp = swis.query("{}".format(query)) if rsp['results'] == []: print("No results for: {}".format(query)) exit(-1) display.v("Query Orion Existing SerialNumber: {}\n".format( rsp['results'][0][rsp['results'][0].keys()[0]])) return rsp
def run(self, terms, variables=None, **kwargs): display.v("Netfilter terms: %s" % terms) try: if terms[0] == 'as_iif': # 1 == interface return self.as_if('i', terms[1]) if terms[0] == 'as_oif': # 1 == interface return self.as_if('o', terms[1]) if terms[0] == 'list_or_single': # 1 == list return self.as_list_or_single(terms[1]) raise AnsibleParserError("Непонятно что делать") except Exception as e: raise AnsibleError("Error in nft: %s (%s)" % (terms, e))
def run(self, tmp=None, task_vars=None): suffix_to_merge = self._task.args.get('suffix_to_merge', '') merged_var_name = self._task.args.get('merged_var_name', '') dedup = self._task.args.get('dedup', True) expected_type = self._task.args.get('expected_type') all_keys = task_vars.keys() # Validate args if expected_type not in ['dict', 'list']: raise AnsibleError("expected_type must be set ('dict' or 'list').") if not suffix_to_merge.endswith('__to_merge'): raise AnsibleError( "Merge suffix must end with '__to_merge', sorry!") if merged_var_name in all_keys: warning = "{} is already defined, are you sure you want to overwrite it?" display.warning(warning.format(merged_var_name)) display.v("The contents of {} are: {}".format( merged_var_name, task_vars[merged_var_name])) keys = [ key for key in task_vars.keys() if key.endswith(suffix_to_merge) ] display.v("Merging vars in this order: {}".format(keys)) merge_vals = [task_vars[key] for key in keys] # Dispatch based on type that we're merging if merge_vals == []: if expected_type == 'list': merged = [] else: merged = {} # pylint: disable=redefined-variable-type elif isinstance(merge_vals[0], list): merged = merge_list(merge_vals, dedup) elif isinstance(merge_vals[0], dict): merged = merge_dict(merge_vals) else: raise AnsibleError( "Don't know how to merge variables of type: {}".format( type(merge_vals[0]))) # We need to render any jinja in the merged var now, because once it # leaves this plugin, ansible will cleanse it by turning any jinja tags # into comments. merged = self._templar.template(merged) return { 'ansible_facts': { merged_var_name: merged }, 'changed': False, }
def run(self): """Run the ansible command Subclasses must implement this method. It does the actual work of running an Ansible command. """ display.vv(self.parser.get_version()) if C.CONFIG_FILE: display.v(u"Using %s as config file" % to_text(C.CONFIG_FILE)) else: display.v(u"No config file found; using defaults")
def hooks(hooks, type, run_once, groups): suffix = "once.yml" if run_once else "yml" hooks.append(cwd) display.v('Listing for: %s -> %s' % (hooks, groups)) groups.append('all') files = [] for hook in hooks: for group in groups: path = "%s/%s.%s.%s" % (hook, type, group, suffix) display.vv(path) if os.path.isfile(path): files.append(path) return files
def __init__(self, firewall_policy_path=None): self.policy = {} try: with open(firewall_policy_path, 'r') as stream: self.policy = yaml.load(stream) except yaml.YAMLError as exc: display.warning('%s badly formatted' % firewall_policy_path) raise except IOError: display.warning('%s missing, no firewall policy will be applied' % firewall_policy_path) pass else: display.v("firewall policy loaded: %s" % firewall_policy_path)
def _normalize_plugins(self, plugins): tmp = {} display.vv("_normalize_plugins: %s" % (plugins)) for plugin in plugins: display.v("_normalize_plugin: %s" % (plugin)) normalized_managed_plugin = { "version": plugin.get("version", None), "enabled": plugin.get("enabled", True), "pinned": plugin.get("pinned", False), "absent": plugin.get("absent", False), "latest": plugin.get("latest", False), } tmp[plugin.get('name')] = normalized_managed_plugin return tmp
def run(self, terms, variables=None, **kwargs): if not terms or len(terms) != 2: raise AnsibleError('Wrong request format') entry_path = terms[0].strip('/') entry_attr = terms[1] kp_dbx = variables.get('keepass_dbx', '') kp_dbx = os.path.realpath(os.path.expanduser(kp_dbx)) if os.path.isfile(kp_dbx): display.v(u"Keepass: database file %s" % kp_dbx) kp_soc = "%s.sock" % kp_dbx if os.path.exists(kp_soc): return self._fetch_socket(kp_soc, entry_path, entry_attr) kp_psw = variables.get('keepass_psw', '') kp_key = variables.get('keepass_key') return self._fetch_file(kp_dbx, str(kp_psw), kp_key, entry_path, entry_attr)
def run(self, terms, variables=None, **kwargs): if not terms or len(terms) < 2 or len(terms) > 3: raise AnsibleError('Wrong request format') entry_path = terms[0].strip('/') entry_attr = terms[1] enable_custom_attr = False if len(terms) == 3: enable_custom_attr = terms[2] kp_dbx = variables.get('keepass_dbx', '') kp_dbx = os.path.realpath(os.path.expanduser(kp_dbx)) if os.path.isfile(kp_dbx): display.v(u"Keepass: database file %s" % kp_dbx) kp_soc = "%s/ansible-keepass.sock" % tempfile.gettempdir() if os.path.exists(kp_soc): display.v(u"Keepass: fetch from socket") return self._fetch_socket(kp_soc, entry_path, entry_attr) kp_psw = variables.get('keepass_psw', '') kp_key = variables.get('keepass_key') display.v(u"Keepass: fetch from kdbx file") return self._fetch_file( kp_dbx, str(kp_psw), kp_key, entry_path, entry_attr, enable_custom_attr)
def run(self, terms, variables=None, **kwargs): display.v("Ship terms: %s" % terms) try: if terms[0] == 'ip': # 1 == номер return self.ip(int(terms[1])) if terms[0] == 'net': # 1 == версия ip return self._format_net(self._home_net(terms[1])) if terms[0] == 'delegated_net': return self._format_net(self._delegated_net()) if terms[0] == 'kis_ip': # 1 == номер return self.prov_ip('ipv4', 'kis', int(terms[1])) if terms[0] == 'kis_net': return self.prov_net('ipv4', 'kis') if terms[0] == 'henet_ip': # 1 == номер return self.prov_ip('ipv6', 'henet', int(terms[1])) if terms[0] == 'henet_net': return self.prov_net('ipv6', 'henet') if terms[0] == 'tor_ip': # 1 == версия ip, 2 == номер return self.prov_ip(terms[1], 'tor', int(terms[2])) if terms[0] == 'tor_net': # 1 == версия ip return self.prov_net(terms[1], 'tor') raise AnsibleParserError("Непонятно что делать") except Exception as e: raise AnsibleError("Error in ship: %s (%s)" % (terms, e))
def reject_task(self, task, task_vars): # is the task action capture by our policy? if task.action in self.policy: # is the entire action blocked? if not isinstance(self.policy[task.action], list) and not isinstance(self.policy[task.action], dict): raise TaskFirewallAnsibleError('firewall policy: module (%s) blocked' % task.action) display.v('firewall rule: module [%s]' % (self.policy[task.action])) # now check the action args for key in self.policy[task.action]: if key not in task.args: continue # is an entire arg of this action blocked? if not isinstance(self.policy[task.action][key], list): raise TaskFirewallAnsibleError('firewall policy: module (%s) arg (%s) blocked' % (task.action, key)) display.v('firewall rule passed: [%s:%s] against %s' % (self.policy[task.action], self.policy[task.action][key], task.args[key])) # check if the task arg contains a var that needs to be expanded if isinstance(task.args[key], str) and task.args[key].find('\{\{'): # TODO: resolve variables to actual values. This is pretty complicated for an Ansible outsider, # as I don't want to simply copy/paste/mod the task executor code into here. It's also # pretty essential to the concept of a firewall. pass # for each rule in the policy module:arg:[value] list, compare the current task arg for rule in self.policy[task.action][key]: # do we have the 'contains' verb option in policy if str(rule).startswith('contains'): if str(task.args[key]).find(rule[9:]) != -1: raise TaskFirewallAnsibleError('firewall policy: module (%s) arg (%s) (%s) blocked' % (task.action, key, rule)) # check if the policy arg is an exact match for the task arg elif task.args[key] == rule: raise TaskFirewallAnsibleError('firewall policy: module (%s) arg (%s) value (%s) blocked' % (task.action, key, rule)) display.v('firewall rule passed: [%s:%s %s] against %s' % (task.action, key, rule, task.args[key]))
def _calculate_present_plugins(self, managed_plugins, existing_plugins): tmp = {} for key in managed_plugins: managed_plugin = managed_plugins.get(key) try: managed_plugin_absent = managed_plugin.get("absent", False) managed_plugin_version = managed_plugin.get("version", None) managed_plugin_latest = managed_plugin.get("latest", False) except AnsibleOptionsError: managed_plugin_version = None managed_plugin_latest = False managed_plugin_absent = False if managed_plugin_absent is True: continue existing_plugin = existing_plugins.get(key, None) plugin_facts = False if existing_plugin is None: display.v("needs installation: [%s] %s" % (key, managed_plugin)) if managed_plugin_latest is True: # install plugin in lastest version plugin_facts = { "state": "latest" } elif managed_plugin_version is not None: # install plugin in specific version plugin_facts = { "state": "present", "version": managed_plugin_version } else: # no specific version, just make it present plugin_facts = { "state": "present", } else: display.v("already installed, checking versions: [%s] %s" % (key, managed_plugin)) existing_plugin_version = existing_plugin.get("version") existing_plugin_hasUpdate = existing_plugin.get("hasUpdate") # update plugin when possible if managed_plugin_latest is True and existing_plugin_hasUpdate is True: # update plugin to latest when update is available plugin_facts = { "state": "latest" } # install plugin with specific version elif managed_plugin_version is not None and managed_plugin_version != existing_plugin_version: # change plugin to specific version if defined plugin_facts = { "state": "present", "version": managed_plugin_version } if plugin_facts is not False: display.vv("Change required for plugin: [%s], plugin facts: %s, new: %s, old: %s" % (key, plugin_facts, managed_plugin, existing_plugin)) tmp[key] = plugin_facts return tmp
def run(self, terms, variables=None, **kwargs): npm_server = kwargs.get('npm_server', '172.19.128.111') column = kwargs.get('column', 'nodes.customproperties.serialNumber') user_id = kwargs.get('user_id', '**') passwd = kwargs.get('passwd', '**') update_flag = kwargs.get('update_flag', False) new_value = kwargs.get('new_value', '') # lookups in general are expected to both take a list as input terms[], and output a list, ret[] # this is done so they work with the looping construct 'with_'. ret = [] for hostname in terms: display.v("value to return %s" % column) query = "SELECT {} FROM Orion.Nodes WHERE Caption = '{}'".format( column, hostname) display.v("query: {}".format(query)) try: orion = self.orion_connect(npm_server, user_id, passwd) rsp = self.orion_query(orion, query) display.v("result: {}".format( rsp['results'][0][rsp['results'][0].keys()[0]])) if update_flag: display.v("update {} with {}".format(column, new_value)) uri_query = "SELECT URI from Orion.Nodes WHERE Caption = '{}'".format( hostname) uri = self.orion_query(orion, uri_query) url = "{}/{}".format(uri['results'][0]['URI'], column.split('.')[-2]) display.v("url: {}".format(url)) orion.update(url, **{column.split('.')[-1]: new_value}) except HTTPError as e: raise AnsibleError("Received HTTP error for %s : %s" % (hostname, str(e))) except URLError as e: raise AnsibleError("Failed lookup url for %s : %s" % (hostname, str(e))) except SSLValidationError as e: raise AnsibleError( "Error validating the server's certificate for %s: %s" % (hostname, str(e))) except ConnectionError as e: raise AnsibleError("Error connecting to %s: %s" % (hostname, str(e))) except Exception as e: raise AnsibleError("Error: %s: %s" % (hostname, str(e))) else: display.v("response {}".format(rsp['results'][0].keys()[0])) ret.append( to_text(rsp['results'][0][rsp['results'][0].keys()[0]])) return ret
def parse_source(self, source, cache=False): ''' Generate or update inventory for the source provided ''' parsed = False display.debug(u'Examining possible inventory source: %s' % source) # use binary for path functions b_source = to_bytes(source) # process directories as a collection of inventories if os.path.isdir(b_source): display.debug(u'Searching for inventory files in directory: %s' % source) for i in sorted(os.listdir(b_source)): display.debug(u'Considering %s' % i) # Skip hidden files and stuff we explicitly ignore if IGNORED.search(i): continue # recursively deal with directory entries fullpath = to_text(os.path.join(b_source, i), errors='surrogate_or_strict') parsed_this_one = self.parse_source(fullpath, cache=cache) display.debug(u'parsed %s as %s' % (fullpath, parsed_this_one)) if not parsed: parsed = parsed_this_one else: # left with strings or files, let plugins figure it out # set so new hosts can use for inventory_file/dir vasr self._inventory.current_source = source # get inventory plugins if needed, there should always be at least one generator if not self._inventory_plugins: self._setup_inventory_plugins() # try source with each plugin failures = [] for plugin in self._inventory_plugins: plugin_name = to_text( getattr(plugin, '_load_name', getattr(plugin, '_original_path', ''))) display.debug(u'Attempting to use plugin %s (%s)' % (plugin_name, plugin._original_path)) # initialize and figure out if plugin wants to attempt parsing this file try: plugin_wants = bool(plugin.verify_file(source)) except Exception: plugin_wants = False if plugin_wants: try: # in case plugin fails 1/2 way we dont want partial inventory plugin.parse(self._inventory, self._loader, source, cache=cache) parsed = True display.vvv( 'Parsed %s inventory source with %s plugin' % (source, plugin_name)) break except AnsibleParserError as e: display.debug('%s was not parsable by %s' % (source, plugin_name)) failures.append({ 'src': source, 'plugin': plugin_name, 'exc': e }) except Exception as e: display.debug('%s failed to parse %s' % (plugin_name, source)) failures.append({ 'src': source, 'plugin': plugin_name, 'exc': AnsibleError(e) }) else: display.v( '%s did not meet %s requirements, check plugin documentation if this is unexpected' % (source, plugin_name)) else: if not parsed and failures: # only if no plugin processed files should we show errors. for fail in failures: display.warning( u'\n* Failed to parse %s with %s plugin: %s' % (to_text(fail['src']), fail['plugin'], to_text(fail['exc']))) if hasattr(fail['exc'], 'tb'): display.vvv(to_text(fail['exc'].tb)) if C.INVENTORY_ANY_UNPARSED_IS_FAILED: raise AnsibleError( u'Completely failed to parse inventory source %s' % (source)) if not parsed: display.warning("Unable to parse %s as an inventory source" % source) # clear up, jic self._inventory.current_source = None return parsed
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() # uncomment to enable request debugging #try: # import http.client as http_client #except ImportError: # # Python 2 # import httplib as http_client # http_client.HTTPConnection.debuglevel = 1 ## #logLevel = logging.DEBUG #logging.basicConfig() #logging.getLogger().setLevel(logLevel) #requests_log = logging.getLogger("requests.packages.urllib3") #requests_log.setLevel(logLevel) #requests_log.propagate = True result = super(ActionModule, self).run(tmp, task_vars) self._task_vars = task_vars changed = False display.v("args: %s" % (self._task.args)) try: # Get the tasmota host tasmota_host = self._get_arg_or_var('tasmota_host', task_vars['ansible_host']) command = self._get_arg_or_var('command') incoming_value = self._get_arg_or_var('value') except Exception as err: display.v("got an exception: %s" % (err)) display.v("got an exception: " + err.message) return self._fail_result( result, "error during retrieving parameter '%s'" % (err.message)) endpoint_uri = "http://%s/cm" % (tasmota_host) status_params = {'cmnd': command} # execute command status_response = requests.get(url=endpoint_uri, params=status_params) # get response data data = status_response.json() display.v("data: %s" % (data)) existing_value = unicode(data.get(command)) if (command.startswith('Rule')): display.vv("rule found!") existing_once = data.get("Once") existing_rules = data.get("Rules") existing_rule = data.get(command) existing_stop_on_error = data.get("StopOnError") if incoming_value in ["0", "1", "2"]: display.vv("disable, enable, toggle rule found") existing_value = self._translateResultStr(existing_value) elif incoming_value in ["4", "5"]: display.vv("disable, enable oneshot") existing_value = self._translateResultStr( existing_once, "4", "5") elif incoming_value.startswith("on"): display.vv("rule value found") existing_value = existing_rules elif (command.startswith('SetOption')): existing_value = self._translateResultStr(existing_value) elif (command.startswith('PowerRetain')): existing_value = self._translateResultStr(existing_value) display.v( "[%s] command: %s, existing_value: '%s', incoming_value: '%s'" % (tasmota_host, command, existing_value, incoming_value)) display.v("[%s] existing_uri: %s" % (tasmota_host, endpoint_uri)) if existing_value != incoming_value: changed = True change_params = {'cmnd': ("%s %s" % (command, incoming_value))} change_response = requests.get(url=endpoint_uri, params=change_params) result["changed"] = changed result["command"] = command result["tasmota_host"] = tasmota_host result["raw_data"] = data result["endpoint_uri"] = endpoint_uri result["incoming_value"] = incoming_value result["existing_value"] = existing_value return result
def _fail_result(result, message): display.v("_fail_result") result['failed'] = True result['msg'] = message return result
def run(self, tmp=None, task_vars=None): suffix_to_merge = self._task.args.get('suffix_to_merge', '') merged_var_name = self._task.args.get('merged_var_name', '') dedup = self._task.args.get('dedup', True) expected_type = self._task.args.get('expected_type') recursive_dict_merge = bool( self._task.args.get('recursive_dict_merge', False)) if 'cacheable' in self._task.args.keys(): display.deprecated( "The `cacheable` option does not actually do anything, since Ansible 2.5. " "No matter what, the variable set by this plugin will be set in the fact " "cache if you have fact caching enabled. To get rid of this warning, " "remove the `cacheable` argument from your merge_vars task. This warning " "will be removed in a future version of this plugin.") # Validate args if expected_type not in ['dict', 'list']: raise AnsibleError("expected_type must be set ('dict' or 'list').") if not merged_var_name: raise AnsibleError("merged_var_name must be set") if not isidentifier(merged_var_name): raise AnsibleError( "merged_var_name '%s' is not a valid identifier" % merged_var_name) if not suffix_to_merge.endswith('__to_merge'): raise AnsibleError( "Merge suffix must end with '__to_merge', sorry!") keys = sorted( [key for key in task_vars.keys() if key.endswith(suffix_to_merge)]) display.v("Merging vars in this order: {}".format(keys)) # We need to render any jinja in the merged var now, because once it # leaves this plugin, ansible will cleanse it by turning any jinja tags # into comments. # And we need it done before merging the variables, # in case any structured data is specified with templates. merge_vals = [self._templar.template(task_vars[key]) for key in keys] # Dispatch based on type that we're merging if merge_vals == []: if expected_type == 'list': merged = [] else: merged = {} elif isinstance(merge_vals[0], list): merged = merge_list(merge_vals, dedup) elif isinstance(merge_vals[0], dict): merged = merge_dict(merge_vals, dedup, recursive_dict_merge) else: raise AnsibleError( "Don't know how to merge variables of type: {}".format( type(merge_vals[0]))) return { 'ansible_facts': { merged_var_name: merged }, 'changed': False, }
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() # uncomment to enable request debugging #try: # import http.client as http_client #except ImportError: # # Python 2 # import httplib as http_client # http_client.HTTPConnection.debuglevel = 1 ## #import logging #logLevel = logging.DEBUG #logging.basicConfig() #logging.getLogger().setLevel(logLevel) #requests_log = logging.getLogger("requests.packages.urllib3") #requests_log.setLevel(logLevel) #requests_log.propagate = True result = super(ActionModule, self).run(tmp, task_vars) self._task_vars = task_vars changed = False no_log = self._play_context.no_log if not no_log: display.v("args: %s" % (self._task.args)) check_mode = task_vars['ansible_check_mode'] display.v("check_mode: %s" % (check_mode)) try: # Get the tasmota host tasmota_host = self._get_arg_or_var('tasmota_host', task_vars['ansible_host']) command = self._get_arg_or_var('command') incoming_value = self._get_arg_or_var('value', None, False) if incoming_value is None: # early return when incoming_value is not provided result["changed"] = False result["skipped"] = True return result except Exception as err: display.v("got an exception: %s" % (err)) display.v("got an exception: " + err.message) return self._fail_result( result, "error during retrieving parameter '%s'" % (err.message)) if not no_log: display.v("incoming_value %s" % (incoming_value)) auth_params = {} try: user = self._get_arg_or_var("tasmota_user") password = self._get_arg_or_var('tasmota_password') auth_params = {'user': user, 'password': password} display.v("authentication parameters: %s" % (auth_params)) except: pass # Enable retries due to reboot of the devices session = requests.Session() session.mount("http://%s" % (tasmota_host), HTTPAdapter(Retry(total=5, backoff_factor=1.0))) endpoint_uri = "http://%s/cm" % (tasmota_host) status_params = copy.deepcopy(auth_params) status_params.update({'cmnd': command}) # execute command status_response = requests.get(url=endpoint_uri, params=status_params) # get response data data = status_response.json() display.v("data: %s, response code: %s" % (data, status_response.status_code)) warnings = [] resp_warn = data.get("WARNING") if resp_warn: # Prior to v8.2.3 authorization error has 200 ok status if status_response.status_code == 401 or resp_warn == "Need user=<username>&password=<password>": raise AnsibleAuthenticationFailure( "Missing/Invalid credentials") warnings.append(resp_warn) if status_response.status_code != 200: raise AnsibleRuntimeError("Unexpected response code: %s" % (status_response.status_code)) existing_value = unicode(data.get(command)) if (command.startswith('Rule')): display.vv("rule found!") existing_once = data.get("Once") existing_rules = data.get("Rules") existing_rule = data.get(command) existing_stop_on_error = data.get("StopOnError") if incoming_value in ["0", "1", "2"]: display.vv("disable, enable, toggle rule found") existing_value = self._translateResultStr(existing_value) elif incoming_value in ["4", "5"]: display.vv("disable, enable oneshot") existing_value = self._translateResultStr( existing_once, "4", "5") elif incoming_value.startswith("on"): display.vv("rule value found") existing_value = existing_rules elif (command.startswith('SetOption')): existing_value = self._translateResultStr(existing_value) elif (command.startswith('PowerRetain')): existing_value = self._translateResultStr(existing_value) elif (command == 'Module'): modules_ids = data.get(command).keys() existing_value = next(iter(modules_ids)) elif (command.startswith('Gpio')): gpios = data.get(command.upper()).keys() existing_value = next(iter(gpios)) elif (command == 'Template'): existing_value = data elif (command.startswith('Timers')): existing_value = self._translateResultStr(data.get('Timers')) elif (re.findall('Timer\d', command)): existing_value = data.get(command) elif (command == 'TimeStd' or command == 'TimeDst'): display.vv("TimeStd/TimeDst found!") existing_data = data.get(command) existing_day = existing_data.get("Day") existing_hemisphere = existing_data.get("Hemisphere") existing_hour = existing_data.get("Hour") existing_month = existing_data.get("Month") existing_offset = existing_data.get("Offset") existing_week = existing_data.get("Week") existing_value = "%s,%s,%s,%s,%s,%s" % ( existing_hemisphere, existing_week, existing_month, existing_day, existing_hour, existing_offset) elif (command == 'TuyaMCU'): # Return only relevant subset of fn/dp ids, ignoring the rest try: fn_id, dp_id = (int(x) for x in incoming_value.split(',')) except Exception as e: raise AnsibleOptionsError( "Invalid value '%s' for TuyaMCU: %s" % (incoming_value, e)) try: def our_entry(x): return fn_id == x['fnId'] or dp_id == x['dpId'] relevant_entries = list(filter(our_entry, data['TuyaMCU'])) relevant_entries = [ "%s,%s" % (x['fnId'], x['dpId']) for x in relevant_entries ] except KeyError as e: raise AnsibleRuntimeError("Invalid response: %s, error: %s" % (data, e)) if dp_id != 0: if len(relevant_entries) == 1: existing_value = relevant_entries[0] else: existing_value = relevant_entries else: if not relevant_entries: # Missing entries equals to disabled entry existing_value = incoming_value else: existing_value = relevant_entries elif (command == 'DimmerRange'): try: existing_value = "%s,%s" % (data[command]['Min'], data[command]['Max']) except Exception as e: raise AnsibleRuntimeError( "Invalid response payload: %s, error: %s" % (data, e)) display.v( "[%s] command: %s,\n\t existing_value: '%s',\n\t incoming_value: '%s'" % (tasmota_host, command, existing_value, incoming_value if not no_log else "")) display.v("[%s] existing_uri: %s" % (tasmota_host, endpoint_uri)) if existing_value != incoming_value: changed = True if not check_mode: change_params = copy.deepcopy(auth_params) # encode json if required if isinstance(incoming_value, dict): change_params.update({ 'cmnd': ("%s %s" % (command, json.dumps(incoming_value))) }) else: change_params.update( {'cmnd': ("%s %s" % (command, incoming_value))}) change_response = requests.get(url=endpoint_uri, params=change_params) if status_response.status_code != 200: raise AnsibleRuntimeError("Unexpected response code: %s" % (status_response.status_code)) if warnings: display.warning(warnings) result["warning"] = warnings result["changed"] = changed result["command"] = command result["tasmota_host"] = tasmota_host result["raw_data"] = data result["endpoint_uri"] = endpoint_uri result["incoming_value"] = incoming_value result["existing_value"] = existing_value return result