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_idsin their playbook when they can writeorganizationsIn addition to Ansible’s validation options, we provide
required_pluginswhich 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
ForemanEntityAnsibleModulefor your module calledForemanMyEntityModuleto work withMyEntityforeman 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 types 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>_idwhere 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>)_idswhere 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 indomainfor an example. The sub entities must be described byforeman_spec=<sub_entity>_spec.invisible=TrueThe 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_KEYSor ‘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']),
]