How to write modules
First of all, please have a look at the Ansible module development guide and get familiar with the general Ansible module layout.
When looking at actual modules in this repository (domain
is a nice short example), you will notice a few differences to a “regular” Ansible module:
Instead of
AnsibleModule
, we useForemanEntityAnsibleModule
(and a few others, seeplugins/module_utils/foreman_helper.py
) which provides an abstraction layer for talking with the Foreman APIInstead of Ansible’s
argument_spec
, we provide an enhanced version calledforeman_spec
. It handles the translation from Ansible module arguments to Foreman API parameters, as nobody wants to writeorganization_ids
in their playbook when they can writeorganizations
In addition to Ansible’s validation options, we provide
required_plugins
which will check for installed Foreman plugins should the module require any.
The rest of the module is usually very minimalistic:
Create a Sub class of
ForemanEntityAnsibleModule
for your module calledForemanMyEntityModule
to work withMyEntity
foreman resource and use this one for your module definition. Eg: If the foreman entity is namedArchitecture
:[...] class ForemanArchitectureModule(ForemanEntityAnsibleModule): pass [...]
Connect to the API and run the module Eg: Like previous example, if the foreman entity is named
Architecture
:[...] def main(): module = ForemanArchitectureModule( argument_spec=dict( [...] ), foreman_spec=dict( [...] ), ) with module.api_connection(): module.run() if __name__ == '__main__': main()
You can see a complete example of simple module in architecture
In some cases, you will have to handle some custom workflows/validations, you can see some examples in bookmark
, compute_attribute
, hostgroup
, provisioning_template
…
Specification of the foreman_spec
The foreman_spec
can be seen as an extended version of Ansible’s argument_spec
. It understands more parameters (e.g. flat_name
) and supports more type
s than the original version. An argument_spec
will be generated from an foreman_spec
. Any parameters not directly known or consumed by foreman_spec
will be passed directly to the argument_spec
.
In addition to Ansible’s argument_spec
, foreman_spec
understands the following types:
type='entity'
The referenced value is another Foreman entity. This is usually combined withflat_name=<entity>_id
. If no flat_name is provided, fallback to<entity>_id
where entity is the foreman_spec key. egdefault_organization=dict(type='entity')
=>flat_name=default_organization_id
.type='entity_list'
The referenced value is a list of Foreman entities. This is usually combined withflat_name=<entity>_ids
. If no flat_name is provided, fallback tosingularize(<entity>)_ids
where entity is the foreman_spec key. egorganizations=dict(type='entity_list')
=>flat_name=organization_ids
.type='nested_list'
The referenced value is a list of Foreman entities that are not included in the main API call. The module must handle the entities separately. See domain parameters indomain
for an example. The sub entities must be described byforeman_spec=<sub_entity>_spec
.invisible=True
The parameter is available to the API call, but it will be excluded from Ansible’sargument_spec
.search_by='login'
: Used withtype='entity'
ortype='entity_list'
. Field used to search the sub entity. Defaults to value provided byENTITY_KEYS
or ‘name’ if no value found.search_operator='~'
: Used withtype='entity'
ortype='entity_list'
. Operator used to search the sub entity. Defaults to ‘=’. For fuzzy search use ‘~’.resource_type='organizations'
: Used withtype='entity'
ortype='entity_list'
. Resource type used to build API resource PATH. Defaults to pluralized entity key.resolve=False
: Defaults to ‘True’. If set to false, the sub entity will not be resolved automatically.ensure=False
: Defaults to ‘True’. If set to false, it will be removed before sending data to the foreman server.scope=['organization']
: Defaults to ‘[]’. A list of entities that are used to build the lookup scope for this one.
flat_name
provides a way to translate the name of a module argument as known to Ansible to the name understood by the Foreman API.
You can add new or override generated Ansible module arguments, by specifying them in the argument_spec
as usual.
Entity lookup
Sometimes you need to access entities before module.run()
can take over.
You can trigger the automatic lookup of entities via <entity_variable> = module.lookup_entity('<entity_name>')
.
If you only need the entity to be used as a scope parameter, it is enough to call scope = module.scope_for('organization')
.
In case, the automatic lookup process is unable perform the proper find for a specific entity type, it must be looked up manually and then set via module.set_entity('<entity_name>', search_result)
to prevent the automatism from trying.
In instances of ForemanEntityAnsibleModule
the main entity is references as ‘entity’ in the above context.
required_plugins
A module can pass an optional required_plugins
list to ForemanAnsibleModule
, which will indicate whether the module needs any Foreman plugins to be installed to work.
You can either specify that the whole module needs a specific plugin, like Katello modules:
required_plugins=[
('katello', ['*']),
]
Or specific parameters, like the discovery_proxy
parameter of subnet
which needs the Discovery plugin:
required_plugins=[
('discovery', ['discovery_proxy']),
]