# -*- coding: utf-8 -*- # Copyright (c) 2017 Citrix Systems # # This code is part of Ansible, but is an independent component. # This particular file snippet, and this file snippet only, is BSD licensed. # Modules you write using this snippet, which is embedded dynamically by Ansible # still belong to the author of the module, and may assign their own license # to the complete work. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import json import re from ansible.module_utils.basic import env_fallback class ConfigProxy(object): def __init__(self, actual, client, attribute_values_dict, readwrite_attrs, transforms={}, readonly_attrs=[], immutable_attrs=[], json_encodes=[]): # Actual config object from nitro sdk self.actual = actual # nitro client self.client = client # ansible attribute_values_dict self.attribute_values_dict = attribute_values_dict self.readwrite_attrs = readwrite_attrs self.readonly_attrs = readonly_attrs self.immutable_attrs = immutable_attrs self.json_encodes = json_encodes self.transforms = transforms self.attribute_values_processed = {} for attribute, value in self.attribute_values_dict.items(): if attribute in transforms: for transform in self.transforms[attribute]: if transform == 'bool_yes_no': value = 'YES' if value is True else 'NO' elif transform == 'bool_on_off': value = 'ON' if value is True else 'OFF' elif callable(transform): value = transform(value) else: raise Exception('Invalid transform %s' % transform) self.attribute_values_processed[attribute] = value self._copy_attributes_to_actual() def _copy_attributes_to_actual(self): for attribute in self.readwrite_attrs: if attribute in self.attribute_values_processed: attribute_value = self.attribute_values_processed[attribute] if attribute_value is None: continue # Fallthrough if attribute in self.json_encodes: attribute_value = json.JSONEncoder().encode(attribute_value).strip('"') setattr(self.actual, attribute, attribute_value) def __getattr__(self, name): if name in self.attribute_values_dict: return self.attribute_values_dict[name] else: raise AttributeError('No attribute %s found' % name) def add(self): self.actual.__class__.add(self.client, self.actual) def update(self): return self.actual.__class__.update(self.client, self.actual) def delete(self): self.actual.__class__.delete(self.client, self.actual) def get(self, *args, **kwargs): result = self.actual.__class__.get(self.client, *args, **kwargs) return result def has_equal_attributes(self, other): if self.diff_object(other) == {}: return True else: return False def diff_object(self, other): diff_dict = {} for attribute in self.attribute_values_processed: # Skip readonly attributes if attribute not in self.readwrite_attrs: continue # Skip attributes not present in module arguments if self.attribute_values_processed[attribute] is None: continue # Check existence if hasattr(other, attribute): attribute_value = getattr(other, attribute) else: diff_dict[attribute] = 'missing from other' continue # Compare values param_type = self.attribute_values_processed[attribute].__class__ if param_type(attribute_value) != self.attribute_values_processed[attribute]: str_tuple = ( type(self.attribute_values_processed[attribute]), self.attribute_values_processed[attribute], type(attribute_value), attribute_value, ) diff_dict[attribute] = 'difference. ours: (%s) %s other: (%s) %s' % str_tuple return diff_dict def get_actual_rw_attributes(self, filter='name'): if self.actual.__class__.count_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) == 0: return {} server_list = self.actual.__class__.get_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) actual_instance = server_list[0] ret_val = {} for attribute in self.readwrite_attrs: if not hasattr(actual_instance, attribute): continue ret_val[attribute] = getattr(actual_instance, attribute) return ret_val def get_actual_ro_attributes(self, filter='name'): if self.actual.__class__.count_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) == 0: return {} server_list = self.actual.__class__.get_filtered(self.client, '%s:%s' % (filter, self.attribute_values_dict[filter])) actual_instance = server_list[0] ret_val = {} for attribute in self.readonly_attrs: if not hasattr(actual_instance, attribute): continue ret_val[attribute] = getattr(actual_instance, attribute) return ret_val def get_missing_rw_attributes(self): return list(set(self.readwrite_attrs) - set(self.get_actual_rw_attributes().keys())) def get_missing_ro_attributes(self): return list(set(self.readonly_attrs) - set(self.get_actual_ro_attributes().keys())) def get_immutables_intersection(config_proxy, keys): immutables_set = set(config_proxy.immutable_attrs) keys_set = set(keys) # Return list of sets' intersection return list(immutables_set & keys_set) def ensure_feature_is_enabled(client, feature_str): enabled_features = client.get_enabled_features() if feature_str not in enabled_features: client.enable_features(feature_str) client.save_config() def get_nitro_client(module): from nssrc.com.citrix.netscaler.nitro.service.nitro_service import nitro_service client = nitro_service(module.params['nsip'], module.params['nitro_protocol']) client.set_credential(module.params['nitro_user'], module.params['nitro_pass']) client.timeout = float(module.params['nitro_timeout']) client.certvalidation = module.params['validate_certs'] return client netscaler_common_arguments = dict( nsip=dict( required=True, fallback=(env_fallback, ['NETSCALER_NSIP']), ), nitro_user=dict( required=True, fallback=(env_fallback, ['NETSCALER_NITRO_USER']), no_log=True ), nitro_pass=dict( required=True, fallback=(env_fallback, ['NETSCALER_NITRO_PASS']), no_log=True ), nitro_protocol=dict( choices=['http', 'https'], fallback=(env_fallback, ['NETSCALER_NITRO_PROTOCOL']), default='http' ), validate_certs=dict( default=True, type='bool' ), nitro_timeout=dict(default=310, type='float'), state=dict( choices=[ 'present', 'absent', ], default='present', ), save_config=dict( type='bool', default=True, ), ) loglines = [] def complete_missing_attributes(actual, attrs_list, fill_value=None): for attribute in attrs_list: if not hasattr(actual, attribute): setattr(actual, attribute, fill_value) def log(msg): loglines.append(msg) def get_ns_version(client): from nssrc.com.citrix.netscaler.nitro.resource.config.ns.nsversion import nsversion result = nsversion.get(client) m = re.match(r'^.*NS(\d+)\.(\d+).*$', result[0].version) if m is None: return None else: return int(m.group(1)), int(m.group(2)) def monkey_patch_nitro_api(): from nssrc.com.citrix.netscaler.nitro.resource.base.Json import Json def new_resource_to_string_convert(self, resrc): try: # Line below is the actual patch dict_valid_values = dict((k.replace('_', '', 1), v) for k, v in resrc.__dict__.items() if v) return json.dumps(dict_valid_values) except Exception as e: raise e Json.resource_to_string_convert = new_resource_to_string_convert from nssrc.com.citrix.netscaler.nitro.util.nitro_util import nitro_util @classmethod def object_to_string_new(cls, obj): try: str_ = "" flds = obj.__dict__ # Line below is the actual patch flds = dict((k.replace('_', '', 1), v) for k, v in flds.items() if v) if (flds): for k, v in flds.items(): str_ = str_ + "\"" + k + "\":" if type(v) is unicode: v = v.encode('utf8') if type(v) is bool: str_ = str_ + v elif type(v) is str: str_ = str_ + "\"" + v + "\"" elif type(v) is int: str_ = str_ + "\"" + str(v) + "\"" if str_: str_ = str_ + "," return str_ except Exception as e: raise e @classmethod def object_to_string_withoutquotes_new(cls, obj): try: str_ = "" flds = obj.__dict__ # Line below is the actual patch flds = dict((k.replace('_', '', 1), v) for k, v in flds.items() if v) i = 0 if (flds): for k, v in flds.items(): str_ = str_ + k + ":" if type(v) is unicode: v = v.encode('utf8') if type(v) is bool: str_ = str_ + v elif type(v) is str: str_ = str_ + cls.encode(v) elif type(v) is int: str_ = str_ + str(v) i = i + 1 if i != (len(flds.items())) and str_: str_ = str_ + "," return str_ except Exception as e: raise e nitro_util.object_to_string = object_to_string_new nitro_util.object_to_string_withoutquotes = object_to_string_withoutquotes_new