The ArgumentSpec¶
The ArgumentSpec
defines which arguments your module will accept.
Earlier, when you were writing the DOCUMENTATION
variable, you identified the arguments to
the module and the values those arguments took.
Now is the time you would concern yourself with implementing the code that reflects this documentation.
The ArgumentSpec
is your opportunity to turn documentation into code that you will provide to Ansible.
Ansible has the ability to parse these arguments and provide a small set of enforcement
checks to them. It determines what needs to be checked by virtue of the ArgumentSpec
class
you provide to it.
First, here is a look at the ArgumentSpec
class for this module.
class ArgumentSpec(object):
def __init__(self):
self.supports_check_mode = True
argument_spec = dict(
description=dict(),
actions=dict(
type='list',
elements='dict',
options=dict(
type=dict(
choices=[
'forward',
'enable',
'ignore'
],
required=True
),
pool=dict(),
asm_policy=dict()
),
mutually_exclusive=[
['pool', 'asm_policy']
]
),
conditions=dict(
type='list',
options=dict(
type=dict(
choices=[
'http_uri',
'all_traffic'
],
required=True
)
),
path_begins_with_any=dict()
),
name=dict(required=True),
policy=dict(required=True),
state=dict(
default='present',
choices=['absent', 'present']
),
partition=dict(
default='Common',
fallback=(env_fallback, ['F5_PARTITION'])
)
)
self.argument_spec = {}
self.argument_spec.update(f5_argument_spec)
self.argument_spec.update(argument_spec)
The ArgumentSpec class¶
The class is located near the bottom of the module. It is by convention that the F5 module developers put it there. This location is not a technical requirement, but you are required to follow it per the coding conventions that F5 has established.
Looking at the body of this class, you’ll note that it only consists of an __init__()
method. This class has no purpose outside of encapsulating the requirements that it will
deliver to Ansible. Therefore, it typically has no real functionality.
The order in which the code is written, however, is of deep importance. Let’s take a look at that.
The check_mode declaration¶
Typically, the first thing you find in an ArgumentSpec
class is the creation of an
instance variable named supports_check_mode
. This is almost always True
.
Check mode lets the Ansible user ask a module to run without doing anything to a device. It’s a way for the user to know (before they run Ansible in non-check mode) that the module is going to change something on their system.
A deficiency of this feature though, is that it is not implemented in Ansible core. It is instead left to the will of the module developer whether or not to support this functionality.
The end result is that most modules do not use it, and therefore, it is not a feature you can rely on.
This doesn’t mean that F5 needs to perpetuate the problem though. The F5 module developers, by default, expect that a module should support check mode. There are very few cases where it is impossible, or impractical, to support it.
This instance variable is how the module declares that it will support it. Later on in the module, the F5 developers will add the implementation of the support.
The argument_spec¶
The argument_spec
is the body of what defines the arguments your module can accept. You’ll
notice that is is nearly a complete reflection of what was specified in the DOCUMENTATION
variable earlier.
Note
This variable is not an instance variable; it has no self.
attached to
it. This is important for unit testing. When unit tests are written and run, they
usually begin with an import
of the ArgumentSpec
class from the appropriate
module being tested.
If the module were only ever declaring and updating an instance variable, then the unit tests would begin failing.
For example, when running many module unit tests, the developer might see the first module’s tests pass, but then the second module’s tests fail with errors that mention that a mutual exclusivity is being violated. This may sound weird, but is actually very common.
The cause is the global instance of the ArgumentSpec
class being re-used. And this
problem manifests itself in particular when you are maintaining an instance variable.
One test may use one of the mutually-exclusive properties; it sets it in the
ArgumentSpec
. The next test tries to use the other, but since the ArgumentSpec
is re-used, the first property was never cleared. Now you have both properties (which
are mutually exclusive) being set to a value. This is an error, and your tests will
fail.
Putting the arguments in a local variable prevents this, because that variable is
destroyed between runs of the tests and usage of the ArgumentSpec
.
After the argument spec is locally defined, another variable is created and set to an empty dictionary value.
This variable is named identically to the first, except this time it is an instance
variable. The module always sets this to an empty dict
to ensure that no collisions
happen between unit tests.
Next, this instance variable is updated with all of the parameters in the base argument
spec that was imported at the top of the module. This gives the ArgumentSpec
all of
the common parameters such as user
, password
, and server
.
Finally, the instance variable is updated with this module’s arguments. The order to this updating is important, because it gives the module authors the ability to override any of the parameters that are defined in the base parameter configuration.
Conclusion¶
This is one of the easier classes to write because you have largely done all the work when
you wrote the DOCUMENTATION
variable earlier.
With this class out of the way, the next class to explore is the ModuleManager
class. This class is the traffic cop of the module. The stubbing tool provides a boilerplate
version of this class to you. You, as the developer, are expected to replace certain key
instances of API calls in it.