Sometimes you cannot write the API parameters as methods. For example, the bigip_device_dns APIs parameters include:

dns.proxy.__iter__

This attribute is mapped to forwarders in the Ansible module.

The pattern is to use methods decorated as properties in Python and then to call those methods when setting values and getting values.

For example, you would map the dns.proxy.__iter__ API attribute to the _values key “forwarders”. Normally you would set the API attributes directly in the dictionary. You would get those API-specific keys when you return the values to compare.

This makes the getters for the Module options look messy though.

You could make the API attributes have their own @property decorators, but this won’t work in the “dns” case mentioned above.

NEED
a pattern for a single Ansible Option Parameter that returns 2 API attributes.
For example in the bigip_virtual_server module there is an option called
enabled vlans. This, however, actually sets two (possibly 3) values in the API:

vlans (list)

vlansDisabled (boolean True)

vlansEnabled (boolean True)

what is a pattern that, that supports that?

The pattern is that the api_attributes is an arbitrary list of attributes that
you want to send to the API.

The api_params() method uses this list to iterate over the

param_api_map does not work for situations where the Ansible->API relationship
is 1->n (bigip_virtual_server with enabled_vlans) param_api_map only works
for 1->1

The @property decorators represent an adapter pattern. Inside the ModuleManager, when you need to compare the data, these properties return that data in a known format.

The API’s resource attributes differ in structure and name from the options that a user can provide to a module.

For example, an API resource may have an attribute called minSupportedBIGIPVersion. However, the user-facing portion of the module may refer to this attribute as min_bigip_version.

You should do this because:

It provides an abstraction of the API so the name of the thing you’re modifying is not closely tied to the implementation of the API.

Many times the API attribute names are vague, and this abstraction makes them more clear.

The Resource Attributes use camelCase variable naming, while some of Python and nearly all of Ansible use snake_case variable naming.

For clarity’s sake, all of the attributes are typically compared by the option name in Ansible and not the Resource attribute name.

This allows you to look at the names of variables and match them to the names of the options in the Ansible module.

While the names of properties usually mirror the names of the module options available to the user, the values of those properties do not.

Values of the properties reflect the values that the API resource accepts. This is because, ultimately, the values you need to deal with are the values that will update the API.

Therefore, when you receive options from the module, you transform them into the values that would appropriate for the API. When you receive values from the API, you might order them or cast some of their values to specific types so that comparisons can occur, but otherwise you don’t really touch them.

Sometimes you do not know ahead of time what the value of that property should be. Often you must set two or more options before you can know the value of another option.

Consider a module that accepts an IP address option and a gateway mask option, but needs to return a CIDR representation of those two values. Without getting both values, you cannot produce the one value.

That is why you calculate the necessary value at time of getattr, and not at the time of setattr.

Use the module_utils test suite to verify AnsibleF5Parameters classes¶

This is important in case there is a pattern you miss for adapting API attributes and module params.

In many cases, the values that you process from the user will match the values that you send to BIG-IP.

For example, consider the following parameters:

-name:This is an examplebigip_device_sshd:banner:"enabled"banner_text:"bannertextgoeshere"port:"1234"password:"secret"server:"lb.mydomain.com"user:"admin"

The module code that implements this is a collection of different adapters. Collectively, they allow the module to convert the information the user provides into a format that can the BIG-IP can receive and send.

By using this class, you can complete the cycle:

User (params) -> Module -> REST -> Module -> User (changed params)

Most of the adapters adapt data to meet the format expect by the REST API. Use the Changes class to adapt the data to meet the format expected by the end user.

If there is a need to change the value to something that is more “human” so that the user can understand it, that job is undertaken by the Changes module.

An example is the bigip_device_connectivity module, where it acts as a way to translate BIG-IP’s representation of “none” (any6) to the human word “none”.

As you can see, it is quite simple and does not take into consideration anything more complicated than simply comparing the values.

This difference is not conducive to more complicated data structures or types of data.

int(5)=='5'

The above fails to satisfy this simple (albeit erroneous due to established patterns) difference.

Note

This is logically incorrect because the Adapter pattern you should use for the Parameters class mandates that @property values return a specific data type (in the above case int) and should never be non-deterministic.

To check for differences in more complicated data structures, use of the Difference class.

By default, it uses the simple comparison to diff the parameters provided, and discovered, by the module.

To make use of it, you must do the following.

First, define this class in your module.

Second, add @property methods for each of the values you want to compare.

Remember, the properties of the Parameter classes are the names exposed to the module user and not the names of REST API parameters themselves (unless it perfectly matches), because the REST API camel-cases all parameter names.

To provide custom diffing for the members module parameter, you can add this as a @property to the Difference class:

@propertydefmembers(self):ifself.want.membersisNone:returnNoneifset(self.want.members)==set(self.have.members):returnNoneifself.want.appendisFalse:returnself.want.members# Checking to see if the supplied list is a subset of the current# list is only relevant if user provides the `append` parameternew_members=set(self.want.members)current_members=set(self.have.members)ifnew_members.issubset(current_members):returnNoneresult=list(set(self.have.members+self.want.members))returnresult

These @property methods must be named after the Parameter you want to compare.

Additionally, the return value of these @property definitions is one of two values.

Python None if there is no difference.

The value of the difference if there is one. Later, the module reports this value as what changed when the module ran.

Finally, to make use of this new difference class, you must change the following method in the ModuleManager code:

_update_changed_options

The new value of this method must include the usage of the Difference class as a new object. For example: