Reformat everything.

This commit is contained in:
Felix Fontein
2025-11-01 12:08:41 +01:00
parent 3f2213791a
commit 340ff8586d
1008 changed files with 61301 additions and 58309 deletions

View File

@@ -153,45 +153,66 @@ import xml.etree.ElementTree as ET
class spectrum_model_attrs:
def __init__(self, module):
self.module = module
self.url = module.params['url']
self.url = module.params["url"]
# If the user did not define a full path to the restul space in url:
# params, add what we believe it to be.
if not re.search('\\/.+', self.url.split('://')[1]):
if not re.search("\\/.+", self.url.split("://")[1]):
self.url = f"{self.url.rstrip('/')}/spectrum/restful"
# Align these with what is defined in OneClick's UI under:
# Locator -> Devices -> By Model Name -> <enter any model> ->
# Attributes tab.
self.attr_map = dict(App_Manufacturer=hex(0x230683),
CollectionsModelNameString=hex(0x12adb),
Condition=hex(0x1000a),
Criticality=hex(0x1290c),
DeviceType=hex(0x23000e),
isManaged=hex(0x1295d),
Model_Class=hex(0x11ee8),
Model_Handle=hex(0x129fa),
Model_Name=hex(0x1006e),
Modeltype_Handle=hex(0x10001),
Modeltype_Name=hex(0x10000),
Network_Address=hex(0x12d7f),
Notes=hex(0x11564),
ServiceDesk_Asset_ID=hex(0x12db9),
TopologyModelNameString=hex(0x129e7),
sysDescr=hex(0x10052),
sysName=hex(0x10b5b),
Vendor_Name=hex(0x11570),
Description=hex(0x230017))
self.attr_map = dict(
App_Manufacturer=hex(0x230683),
CollectionsModelNameString=hex(0x12ADB),
Condition=hex(0x1000A),
Criticality=hex(0x1290C),
DeviceType=hex(0x23000E),
isManaged=hex(0x1295D),
Model_Class=hex(0x11EE8),
Model_Handle=hex(0x129FA),
Model_Name=hex(0x1006E),
Modeltype_Handle=hex(0x10001),
Modeltype_Name=hex(0x10000),
Network_Address=hex(0x12D7F),
Notes=hex(0x11564),
ServiceDesk_Asset_ID=hex(0x12DB9),
TopologyModelNameString=hex(0x129E7),
sysDescr=hex(0x10052),
sysName=hex(0x10B5B),
Vendor_Name=hex(0x11570),
Description=hex(0x230017),
)
self.search_qualifiers = [
"and", "or", "not", "greater-than", "greater-than-or-equals",
"less-than", "less-than-or-equals", "equals", "equals-ignore-case",
"does-not-equal", "does-not-equal-ignore-case", "has-prefix",
"does-not-have-prefix", "has-prefix-ignore-case",
"does-not-have-prefix-ignore-case", "has-substring",
"does-not-have-substring", "has-substring-ignore-case",
"does-not-have-substring-ignore-case", "has-suffix",
"does-not-have-suffix", "has-suffix-ignore-case",
"does-not-have-suffix-ignore-case", "has-pcre",
"has-pcre-ignore-case", "has-wildcard", "has-wildcard-ignore-case",
"is-derived-from", "not-is-derived-from"]
"and",
"or",
"not",
"greater-than",
"greater-than-or-equals",
"less-than",
"less-than-or-equals",
"equals",
"equals-ignore-case",
"does-not-equal",
"does-not-equal-ignore-case",
"has-prefix",
"does-not-have-prefix",
"has-prefix-ignore-case",
"does-not-have-prefix-ignore-case",
"has-substring",
"does-not-have-substring",
"has-substring-ignore-case",
"does-not-have-substring-ignore-case",
"has-suffix",
"does-not-have-suffix",
"has-suffix-ignore-case",
"does-not-have-suffix-ignore-case",
"has-pcre",
"has-pcre-ignore-case",
"has-wildcard",
"has-wildcard-ignore-case",
"is-derived-from",
"not-is-derived-from",
]
self.resp_namespace = dict(ca="http://www.ca.com/spectrum/restful/schema/response")
@@ -266,23 +287,26 @@ class spectrum_model_attrs:
# None values should be converted to empty strings
val = ""
val = self.urlencode(str(val))
if not update_url.endswith('?'):
if not update_url.endswith("?"):
update_url += "&"
update_url += f"attr={self.attr_id(name) or name}&val={val}"
# POST to /model to update the attributes, or fail.
resp, info = fetch_url(self.module, update_url, method="PUT",
headers={"Content-Type": "application/json",
"Accept": "application/json"},
use_proxy=self.module.params['use_proxy'])
resp, info = fetch_url(
self.module,
update_url,
method="PUT",
headers={"Content-Type": "application/json", "Accept": "application/json"},
use_proxy=self.module.params["use_proxy"],
)
status_code = info["status"]
if status_code >= 400:
body = info['body']
body = info["body"]
else:
body = "" if resp is None else resp.read()
if status_code != 200:
self.result['msg'] = f"HTTP PUT error {status_code}: {update_url}: {body}"
self.result["msg"] = f"HTTP PUT error {status_code}: {update_url}: {body}"
self.module.fail_json(**self.result)
# Load and parse the JSON response and either fail or set results.
@@ -293,17 +317,17 @@ class spectrum_model_attrs:
Example failure response:
{'model-update-response-list': {'model-responses': {'model': {'@error': 'PartialFailure', '@mh': '0x1010e76', 'attribute': {'@error-message': 'brn0vlappua001: You do not have permission to set attribute Network_Address for this model.', '@error': 'Error', '@id': '0x12d7f'}}}}}
""" # noqa
model_resp = json_resp['model-update-response-list']['model-responses']['model']
if model_resp['@error'] != "Success":
model_resp = json_resp["model-update-response-list"]["model-responses"]["model"]
if model_resp["@error"] != "Success":
# I'm not 100% confident on the expected failure structure so just
# dump all of ['attribute'].
self.result['msg'] = str(model_resp['attribute'])
self.result["msg"] = str(model_resp["attribute"])
self.module.fail_json(**self.result)
# Should be OK if we get to here, set results.
self.result['msg'] = self.success_msg
self.result['changed_attrs'].update(attrs)
self.result['changed'] = True
self.result["msg"] = self.success_msg
self.result["changed_attrs"].update(attrs)
self.result["changed"] = True
def find_model(self, search_criteria, ret_attrs=None):
"""
@@ -319,7 +343,7 @@ class spectrum_model_attrs:
# If no return attributes were asked for, return Model_Handle.
if ret_attrs is None:
ret_attrs = ['Model_Handle']
ret_attrs = ["Model_Handle"]
# Set the XML <rs:requested-attribute id=<id>> tags. If no hex ID
# is found for the name, assume it is already in hex. {name: hex ID}
@@ -347,44 +371,52 @@ xsi:schemaLocation="http://www.ca.com/spectrum/restful/schema/request ../../../x
# POST to /models and fail on errors.
url = self.build_url("/models")
resp, info = fetch_url(self.module, url, data=xml, method="POST",
use_proxy=self.module.params['use_proxy'],
headers={"Content-Type": "application/xml",
"Accept": "application/xml"})
resp, info = fetch_url(
self.module,
url,
data=xml,
method="POST",
use_proxy=self.module.params["use_proxy"],
headers={"Content-Type": "application/xml", "Accept": "application/xml"},
)
status_code = info["status"]
if status_code >= 400:
body = info['body']
body = info["body"]
else:
body = "" if resp is None else resp.read()
if status_code != 200:
self.result['msg'] = f"HTTP POST error {status_code}: {url}: {body}"
self.result["msg"] = f"HTTP POST error {status_code}: {url}: {body}"
self.module.fail_json(**self.result)
# Parse through the XML response and fail on any detected errors.
root = ET.fromstring(body)
total_models = int(root.attrib['total-models'])
error = root.attrib['error']
model_responses = root.find('ca:model-responses', self.resp_namespace)
total_models = int(root.attrib["total-models"])
error = root.attrib["error"]
model_responses = root.find("ca:model-responses", self.resp_namespace)
if total_models < 1:
self.result['msg'] = f"No models found matching search criteria `{search_criteria}'"
self.result["msg"] = f"No models found matching search criteria `{search_criteria}'"
self.module.fail_json(**self.result)
elif total_models > 1:
self.result['msg'] = f"More than one model found ({total_models}): `{ET.tostring(model_responses, encoding='unicode')}'"
self.result["msg"] = (
f"More than one model found ({total_models}): `{ET.tostring(model_responses, encoding='unicode')}'"
)
self.module.fail_json(**self.result)
if error != "EndOfResults":
self.result['msg'] = f"Unexpected search response `{error}': {ET.tostring(model_responses, encoding='unicode')}"
self.result["msg"] = (
f"Unexpected search response `{error}': {ET.tostring(model_responses, encoding='unicode')}"
)
self.module.fail_json(**self.result)
model = model_responses.find('ca:model', self.resp_namespace)
attrs = model.findall('ca:attribute', self.resp_namespace)
model = model_responses.find("ca:model", self.resp_namespace)
attrs = model.findall("ca:attribute", self.resp_namespace)
if not attrs:
self.result['msg'] = "No attributes returned."
self.result["msg"] = "No attributes returned."
self.module.fail_json(**self.result)
# XML response should be successful. Iterate and set each returned
# attribute ID/name and value for return.
ret = dict()
for attr in attrs:
attr_id = attr.get('id')
attr_id = attr.get("id")
attr_name = self.attr_name(attr_id)
# Note: all values except empty strings (None) are strings only!
attr_val = attr.text
@@ -410,7 +442,7 @@ xsi:schemaLocation="http://www.ca.com/spectrum/restful/schema/request ../../../x
# If no return attributes were asked for, return Model_Handle.
if ret_attrs is None:
ret_attrs = ['Model_Handle']
ret_attrs = ["Model_Handle"]
"""This is basically as follows:
<filtered-models>
@@ -428,58 +460,51 @@ xsi:schemaLocation="http://www.ca.com/spectrum/restful/schema/request ../../../x
"""
# Parent filter tag
filtered_models = ET.Element('filtered-models')
filtered_models = ET.Element("filtered-models")
# Logically and
_and = ET.SubElement(filtered_models, 'and')
_and = ET.SubElement(filtered_models, "and")
# Model Name
MN_equals = ET.SubElement(_and, 'equals')
Model_Name = ET.SubElement(MN_equals, 'attribute',
{'id': self.attr_map['Model_Name']})
MN_value = ET.SubElement(Model_Name, 'value')
MN_equals = ET.SubElement(_and, "equals")
Model_Name = ET.SubElement(MN_equals, "attribute", {"id": self.attr_map["Model_Name"]})
MN_value = ET.SubElement(Model_Name, "value")
MN_value.text = mname
# Model Type Name
MTN_equals = ET.SubElement(_and, 'equals')
Modeltype_Name = ET.SubElement(MTN_equals, 'attribute',
{'id': self.attr_map['Modeltype_Name']})
MTN_value = ET.SubElement(Modeltype_Name, 'value')
MTN_equals = ET.SubElement(_and, "equals")
Modeltype_Name = ET.SubElement(MTN_equals, "attribute", {"id": self.attr_map["Modeltype_Name"]})
MTN_value = ET.SubElement(Modeltype_Name, "value")
MTN_value.text = mtype
return self.find_model(ET.tostring(filtered_models,
encoding='unicode'),
ret_attrs)
return self.find_model(ET.tostring(filtered_models, encoding="unicode"), ret_attrs)
def ensure_model_attrs(self):
# Get a list of all requested attribute names/IDs plus Model_Handle and
# use them to query the values currently set. Store finding in a
# dictionary.
req_attrs = []
for attr in self.module.params['attributes']:
req_attrs.append(attr['name'])
if 'Model_Handle' not in req_attrs:
req_attrs.append('Model_Handle')
for attr in self.module.params["attributes"]:
req_attrs.append(attr["name"])
if "Model_Handle" not in req_attrs:
req_attrs.append("Model_Handle")
# Survey attributes currently set and store in a dict.
cur_attrs = self.find_model_by_name_type(self.module.params['name'],
self.module.params['type'],
req_attrs)
cur_attrs = self.find_model_by_name_type(self.module.params["name"], self.module.params["type"], req_attrs)
# Iterate through the requested attributes names/IDs values pair and
# compare with those currently set. If different, attempt to change.
Model_Handle = cur_attrs.pop("Model_Handle")
for attr in self.module.params['attributes']:
req_name = attr['name']
req_val = attr['value']
for attr in self.module.params["attributes"]:
req_name = attr["name"]
req_val = attr["value"]
if req_val == "":
# The API will return None on empty string
req_val = None
if cur_attrs[req_name] != req_val:
if self.module.check_mode:
self.result['changed_attrs'][req_name] = req_val
self.result['msg'] = self.success_msg
self.result['changed'] = True
self.result["changed_attrs"][req_name] = req_val
self.result["msg"] = self.success_msg
self.result["changed"] = True
continue
resp = self.update_model(Model_Handle, {req_name: req_val})
@@ -488,21 +513,19 @@ xsi:schemaLocation="http://www.ca.com/spectrum/restful/schema/request ../../../x
def run_module():
argument_spec = dict(
url=dict(type='str', required=True),
url_username=dict(type='str', required=True, aliases=['username']),
url_password=dict(type='str', required=True, aliases=['password'],
no_log=True),
validate_certs=dict(type='bool', default=True),
use_proxy=dict(type='bool', default=True),
name=dict(type='str', required=True),
type=dict(type='str', required=True),
attributes=dict(type='list',
required=True,
elements='dict',
options=dict(
name=dict(type='str', required=True),
value=dict(type='str', required=True)
)),
url=dict(type="str", required=True),
url_username=dict(type="str", required=True, aliases=["username"]),
url_password=dict(type="str", required=True, aliases=["password"], no_log=True),
validate_certs=dict(type="bool", default=True),
use_proxy=dict(type="bool", default=True),
name=dict(type="str", required=True),
type=dict(type="str", required=True),
attributes=dict(
type="list",
required=True,
elements="dict",
options=dict(name=dict(type="str", required=True), value=dict(type="str", required=True)),
),
)
module = AnsibleModule(
supports_check_mode=True,