Reporting Changes

The second of the two Changes classes that can be encountered in a module’s execution is the ReportableChanges class. The implementation of this class for the module being developed in this tutorial can be found here.

Purpose

As mentioned earlier, the general purpose of all the Changes classes is to serve as a place for massaging data. The ReportableChanges class is responsible for massaging data that is about to be sent back to the user.

This class implements an Adapter pattern, similar to the Adapter patterns that were implemented by the ApiParameters and ModuleParameters classes. Since you have already worked with those classes, you should be more than familiar with what needs to happen in this class.

Additionally, because this class is nothing more than another Adapter, its implementation is completely optional. It will exist as a stub in your module by default. It is your responsibility to implement it as needed.

What sort of data do you need to adapt at this point in the module?

Consider the implementation that is found in the module that is being studied here.

Implementation

Let’s look at one of the adapted properties in this class. The other is largely similar in purpose and function, so we’ll skip it. You should, however, implement it for completeness in your copy of the module.

@property
def actions(self):
    result = []
    if self._values['actions'] is None:
        return [dict(type='ignore')]
    for item in self._values['actions']:
        action = dict()
        if 'forward' in item:
            action.update(item)
            action['type'] = 'forward'
            del action['forward']
        elif 'enable' in item:
            action.update(item)
            action['type'] = 'enable'
            del action['enable']
        result.append(action)
    result = sorted(result, key=lambda x: x['name'])
    return result

Remember that earlier it was mentioned that the purpose of these classes is to adapt data immediately before it returns to the user. This module made use of an actions property that was observed in the different Parameters classes, and even the Difference class.

For the module to have done its work, it needed to create an internal representation of the data to do things like comparison. It did this in the Parameters classes. Now that the comparison is done though, it has to sent those updates to the BIG-IP. The data format used by the API though is unlikely to be the same as the data format expected by the user.

Remember that, in the user’s world view, they are unaware of:

  • The F5 product API data format
  • The internal Ansible module representation

The user is only familiar with the format of the parameters sent to the module. This classes adaptation, therefore, needs to go towards making sure that what was sent to the API is translated back to what the user is familiar with.

Therefore, this adapter for the actions property is tasked with converting the API representation of the data back into a format that is capable of being recognized by the Ansible user.

In the implementation here, you can see that key names are being changed to the ones that are known to the user. Additionally, data is being deleted from the existing dictionaries so that it is not accidentally sent to the user.

Received values

The values that are received by the ReportableChanges are those that were contained in the UsableChanges class. You can see this at work in the exec_module method of the ModuleManager class.

For example:

reportable = ReportableChanges(params=self.changes.to_return())
changes = reportable.to_return()

Where self.changes is the UsableChanges object, and to_return is a method that takes the returnables class variable into account.

Note that the returnables class variable is defined in the ReportableChanges class. It is not always this way. Indeed, you will often find this variable defined in the base Parameters class. Because the ReportableChanges ultimately inherits from the base Parameters class, it is a matter of taste where you put it.

Conclusion

You’re now aware of its place in the pipeline, and prior to that knowledge, you already had a firm understanding of the purpose of the various adapters in the module.

It’s not always necessary to implement this class. Indeed, in a good API, you will never need to be concerned with this class. Situations that warrant it usually involve complex data types that needed to be converted to representations that the user is familiar with.

In the next section, we’ll look at the main function.