Repository: ansible-collections/community.general

Issue:
idrac_redfish_config continue on invalid attribute
### Summary

The module idrac_redfish_config fails when an attribute doesn't exist. This means none of the attributes are set. I would like to change this so that it just skips the attribute and continues to set the valid ones. I can submit a pull request if this request makes sense. @felixfontein @sysadmind

The use case would be when certain iDRAC attributes are deprecated, this allows me to have one set of attributes that cover multiple versions. With the deprecated manager_attribute_name/manager_attribute_value I could loop through the attributes and it would just fail on the invalid ones. 

One example is TelemetryCUPS.1.EnableTelemetry was renamed to TelemetrySystemUsage.1.EnableTelemetry in iDRAC 4.40
Another example is PxeDev1Settings.PxeDev1Interface. In some cases you want to loop through the NICs to find the correct one to allow for PXE boot. This is the case on an FX2 chassis.

### Issue Type

Feature Idea

### Component Name

idrac_redfish_config

### Additional Information

This could be as simple as changing line 193 to continue instead of returning from the loop.
```python (paste below)
if attr_name not in data[u'Attributes']:
    continue
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/remote_management/redfish/idrac_redfish_config.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/remote_management/redfish/idrac_redfish_config.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @billdodd @mraineri @tomasg2012
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@TrevorSquillario That looks reasonable to me.
I would suggest/request that the same change be made to redfish_config.py at the same time.
(set_bios_attributes() in plugins/module_utils/redfish_utils.py)
(I don't think I can contribute to this discussion, as I'm not familiar with the modules.)
It might be better to issue a warning message instead of silently ignoring (that way the user knows if something is being discarded), but otherwise the general approach of allowing the setting to go through without producing a hard error is reasonable.
@mraineri
> It might be better to issue a warning message instead of silently ignoring (that way the user knows if something is being discarded), but otherwise the general approach of allowing the setting to go through without producing a hard error is reasonable.

What would be the proper way to handle the warning message? 

Should I just lump it into the `msg` variable or separate it out into it's own `warning` variable?
```
return {'ret': True, 'changed': True,
                'msg': "%s: Modified Manager attributes %s, Incorrect attributes %s" % (command, attrs_to_patch, attrs_bad)}
```
cc @renxulei @xmadsen
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Having it reported as its own `warning` variable I think would be better so it gets shown and tagged as a warning on the user's console. I think simply using `msg` might cause users to miss that sort of detail.
The standard mechanism in Ansible modules to report a warning is to call `module.warn()`. Returning `warning` is something that also works, but is more a legacy thing that shouldn't be done in new code (it's also not really flexible).

Resolving patch:
diff --git a/plugins/module_utils/redfish_utils.py b/plugins/module_utils/redfish_utils.py
--- a/plugins/module_utils/redfish_utils.py
+++ b/plugins/module_utils/redfish_utils.py
@@ -1671,19 +1671,31 @@ def set_bios_attributes(self, attributes):
 
         # Make a copy of the attributes dict
         attrs_to_patch = dict(attributes)
+        # List to hold attributes not found
+        attrs_bad = {}
 
         # Check the attributes
-        for attr in attributes:
-            if attr not in data[u'Attributes']:
-                return {'ret': False, 'msg': "BIOS attribute %s not found" % attr}
+        for attr_name, attr_value in attributes.items():
+            # Check if attribute exists
+            if attr_name not in data[u'Attributes']:
+                # Remove and proceed to next attribute if this isn't valid
+                attrs_bad.update({attr_name: attr_value})
+                del attrs_to_patch[attr_name]
+                continue
+
             # If already set to requested value, remove it from PATCH payload
-            if data[u'Attributes'][attr] == attributes[attr]:
-                del attrs_to_patch[attr]
+            if data[u'Attributes'][attr_name] == attributes[attr_name]:
+                del attrs_to_patch[attr_name]
+
+        warning = ""
+        if attrs_bad:
+            warning = "Incorrect attributes %s" % (attrs_bad)
 
         # Return success w/ changed=False if no attrs need to be changed
         if not attrs_to_patch:
             return {'ret': True, 'changed': False,
-                    'msg': "BIOS attributes already set"}
+                    'msg': "BIOS attributes already set",
+                    'warning': warning}
 
         # Get the SettingsObject URI
         set_bios_attr_uri = data["@Redfish.Settings"]["SettingsObject"]["@odata.id"]
@@ -1693,7 +1705,9 @@ def set_bios_attributes(self, attributes):
         response = self.patch_request(self.root_uri + set_bios_attr_uri, payload)
         if response['ret'] is False:
             return response
-        return {'ret': True, 'changed': True, 'msg': "Modified BIOS attribute"}
+        return {'ret': True, 'changed': True,
+                'msg': "Modified BIOS attributes %s" % (attrs_to_patch),
+                'warning': warning}
 
     def set_boot_order(self, boot_list):
         if not boot_list:
diff --git a/plugins/modules/remote_management/redfish/idrac_redfish_config.py b/plugins/modules/remote_management/redfish/idrac_redfish_config.py
--- a/plugins/modules/remote_management/redfish/idrac_redfish_config.py
+++ b/plugins/modules/remote_management/redfish/idrac_redfish_config.py
@@ -179,6 +179,7 @@ def set_manager_attributes(self, command):
 
         attrs_to_patch = {}
         attrs_skipped = {}
+        attrs_bad = {}  # Store attrs which were not found in the system
 
         # Search for key entry and extract URI from it
         response = self.get_request(self.root_uri + manager_uri + "/" + key)
@@ -189,13 +190,15 @@ def set_manager_attributes(self, command):
 
         if key not in data:
             return {'ret': False,
-                    'msg': "%s: Key %s not found" % (command, key)}
+                    'msg': "%s: Key %s not found" % (command, key),
+                    'warning': ""}
 
         for attr_name, attr_value in attributes.items():
             # Check if attribute exists
             if attr_name not in data[u'Attributes']:
-                return {'ret': False,
-                        'msg': "%s: Manager attribute %s not found" % (command, attr_name)}
+                # Skip and proceed to next attribute if this isn't valid
+                attrs_bad.update({attr_name: attr_value})
+                continue
 
             # Find out if value is already set to what we want. If yes, exclude
             # those attributes
@@ -204,16 +207,23 @@ def set_manager_attributes(self, command):
             else:
                 attrs_to_patch.update({attr_name: attr_value})
 
+        warning = ""
+        if attrs_bad:
+            warning = "Incorrect attributes %s" % (attrs_bad)
+
         if not attrs_to_patch:
             return {'ret': True, 'changed': False,
-                    'msg': "Manager attributes already set"}
+                    'msg': "No changes made. Manager attributes already set.",
+                    'warning': warning}
 
         payload = {"Attributes": attrs_to_patch}
         response = self.patch_request(self.root_uri + manager_uri + "/" + key, payload)
         if response['ret'] is False:
             return response
+
         return {'ret': True, 'changed': True,
-                'msg': "%s: Modified Manager attributes %s" % (command, attrs_to_patch)}
+                'msg': "%s: Modified Manager attributes %s" % (command, attrs_to_patch),
+                'warning': warning}
 
 
 CATEGORY_COMMANDS_ALL = {
@@ -221,6 +231,7 @@ def set_manager_attributes(self, command):
                 "SetSystemAttributes"]
 }
 
+
 # list of mutually exclusive commands for a category
 CATEGORY_COMMANDS_MUTUALLY_EXCLUSIVE = {
     "Manager": [["SetManagerAttributes", "SetLifecycleControllerAttributes",
@@ -308,6 +319,9 @@ def main():
 
     # Return data back or fail with proper message
     if result['ret'] is True:
+        if result.get('warning'):
+            module.warn(to_native(result['warning']))
+
         module.exit_json(changed=result['changed'], msg=to_native(result['msg']))
     else:
         module.fail_json(msg=to_native(result['msg']))
diff --git a/plugins/modules/remote_management/redfish/redfish_config.py b/plugins/modules/remote_management/redfish/redfish_config.py
--- a/plugins/modules/remote_management/redfish/redfish_config.py
+++ b/plugins/modules/remote_management/redfish/redfish_config.py
@@ -321,6 +321,9 @@ def main():
 
     # Return data back or fail with proper message
     if result['ret'] is True:
+        if result.get('warning'):
+            module.warn(to_native(result['warning']))
+
         module.exit_json(changed=result['changed'], msg=to_native(result['msg']))
     else:
         module.fail_json(msg=to_native(result['msg']))


Test patch: