Development Guide for Ansible OpenStack Collection
Ansible OpenStack collection is a set of Ansible modules for interacting with the OpenStack API as either an admin or an end user.
We, and the OpenStack community in general, use OpenDev for its development. Patches are submitted to OpenDev Gerrit. Pull requests submitted through GitHub will be ignored. Please read OpenStack's Developer Workflow for details.
For hacking on the Ansible OpenStack collection it helps to prepare a DevStack environment first.
- Bug tracker
- Mailing list
email@example.com. Prefix subjects with
[aco]for faster responses.
- Code Hosting
- Code Review
- For an example on how to write a
*_infomodule, have a look at modules
- For an example on how to write a regular non-
*_infomodule, have a look at module
openstack.cloud.federation_idpor any other module which uses
- Do NOT use modules which define a
_system_state_changefunction as examples, because they often do not properly define Ansible's check mode, idempotency and/or updates. Refer to modules which use
class StateMachine. In cases where using
class StateMachinewould cause code bloat, it might help to look at modules which define a
- This collection is named
openstack.cloud. There is no need for further namespace prefixing.
- Name any module that a cloud consumer would expect from openstackclient (OSC), for example
nova. This naming convention acknowledges that the end user does not care which service manages the resource - that is a deployment detail. For example, cloud consumers may not know whether their floating ip address are managed by Nova or Neutron.
- If the resource being managed has an
id, it should be returned.
- If the resource being managed has an associated object more complex than an
id, that should be returned instead of the
- Modules should return a value of type
listor other primitive data types. For example,
self.exit_json(changed=False, floating_ips=floating_ips)should to be a list of
to_dict()on openstacksdk objects to convert resources to dictionaries. Setting its parameters such as
Falsewill drop computed attributes from the resulting dict. Read
to_dict's docstring for more parameters.
- Module results have to be documented in
- We should document which attribute cannot be updated in
DOCUMENTATIONvariable. For example, insert
'This attribute cannot be updated.'to
DOCUMENTATIONlike we did for the
servermodule and others.
- Sorting module options in
DOCUMENTATION, attributes in
RETURN, entries in
argument_specand expected fields in integration tests will make reviewing easier and faster.
- It should be assumed that the cloud consumer does not know details about the deployment choices their cloud provider made. A best effort should be made to present one sane interface to the Ansible user regardless of deployer choices.
- It should be assumed that a user may have more than one cloud account that they wish to combine as part of a single Ansible-managed infrastructure.
- All modules should work appropriately against all existing versions of OpenStack regardless of upstream EOL status. The reason for this is that the Ansible modules are for consumers of cloud APIs who are not in a position to impact what version of OpenStack their cloud provider is running. It is known that there are OpenStack Public Clouds running rather old versions of OpenStack, but from a user point of view the Ansible modules can still support these users without impacting use of more modern versions.
- Modules should
- be idempotent (not being idempotent requires a solid reason),
- return whether something has
- be based on (be subclasses of)
- should include
extends_documentation_fragment: openstackin their
- be registered in
meta/action_groups.ymlfor enabling the variables to be set in group level.
- Complex functionality, cloud interaction or interoperability code should be moved to openstacksdk.
- OpenStack API interactions should happen via openstacksdk and not via OpenStack component libraries. The OpenStack component libraries do no have end users as a primary audience, they are for intra-server communication.
- When a resource exist and should be deleted (absent), then pass the resource to the
delete_*function, not its name. Passing a name requires openstacksdk to find that resource again, doing a unnecessary api call, because we queried the resource before.
*_infomodules never raise exceptions when resources cannot be found. When resources cannot be found, then a
*_infomodule returns an empty list instead. For example, module
openstack.cloud.neutron_rbac_policies_infowill return an empty list when no project with name given in module parameter
projectcan be found.
- When a id is given in
*_infomodules, then we do not need nor want extra code to handle that. Instead most openstacksdk resources allow to pass ids as query arguments to OpenStack API. For example,
identity.identity_providers()can be used for both cases: Where an id is given and where no id is given. No need to call
EXAMPLESdocstring in modules (and Ansible's own modules) consist of a list of tasks. They do not contain YAML directives end marker line (---) and do not define playbooks (e.g. hosts keyword). They shall be simple, e.g. do not do fancy loops, heavy use of variables or use Ansible directives for no apparent reason such as ignore_errors or register.
self.params.get('...')can be replaced with
self.params['...']because parameters from
argument_specwill always be in
self.params. If not defined differently, they have a default value of
- Writing code to check that some options cannot be updated and to fail if user still tries to update that value is most
often not worth it. It would require much more code to catch all cases where updates are impossible and we would have
to implement it consistently across modules. Atm we are fine with documenting which attribute cannot be updated in
DOCUMENTATIONvariable. We could simply drop these checks and insert
'This attribute cannot be updated.'to
DOCUMENTATIONlike we did for the server module and others.
- openstacksdk functions often accept IDs but no names, e.g.
project_idparameter. Most modules in our collection use names for finding resources, so we want to support the same for resources attributes such as
- Constraints for module parameters and error handling can often be implemented in
module_kwargsallows to define dependencies between module options such as
- When using openstacksdk's
self.conn.*.find_*), then pass
ignore_missing=Falseinstead of checking its return value and failing with
self.fail_json()if it is
- Use module option names which match attribute names used in openstacksdk, e.g. use
shared. When refactoring modules, keep old option names as aliases to keep backward compatibility. Using openstacksdk names provides two benefits:
- The module inputs and outputs do match, are consistent and thus the module is easier to use.
- Most code for filters and query arguments can be replaced with loops. This patch for floating_ip_info has some ideas for how to write loops.
- Use functions from openstacksdk's proxy layer instead of its cloud layer, if possible. For example,
self.conn.get_network(). As a guideline use this decision tree:
- If a functionality requires a single api call (to the OpenStack API), then use functions from openstacksdk's proxy layer.
- If a functionality requires several api calls (to the OpenStack API), e.g. when creating and attaching a floating ip to a server, then use functions from openstacksdk's cloud layer.
- When unsure which of openstacksdk's layers to use, then first go to proxy layer, then to its cloud layer and if this is not sufficient, then use its resource layer. Mainly, this applies to functions retrieving information, i.e. all calls where we get info about cloud resources should be changed to openstacksdk functions which return proxy resources.
- It is perfectly fine to use openstacksdk's cloud layer for functionality which is not provided by openstacksdk's
proxy layer. SDK's cloud layer is not going away.
list_*functions from openstacksdk's cloud layer such as
search_users()allow to filter retrieved results with function parameter
filters. openstacksdk's proxy layer does not provide an equivalent and thus the use of
search_users()is perfectly fine.
- Modules have to be tested with CI integration tests (if possible).
- Each module has a corresponding Ansible role containing integration tests in
- Ensure role names of integration tests in
ci/rolesmatch the module names. Only exception are
*_infomodules: Their integration tests are located in the same Ansible roles as their non-
*_infoequivalents (to reduce redundant code). For example, tests for both modules
federation_mapping_infocan be found in role
- Zuul CI jobs are defined in
- Add assertions on return values from Ansible modules in integration tests. For an example, refer to
ci/roles/floating_ip/tasks/main.yml. We need those checks to validate return values from openstacksdk, which might change across releases. Adding those assertions will be done in minutes, while checking the output manually during code reviews takes much more time.
- Our Zuul CI jobs will run
ansible-testfor sanity checking.
tox -elinters_latestto run various linters against your code.
- Study our Review Guidelines before submitting a patch.
- Use Gerrit's work-in-progress feature to mark the status of the patch. A minus workflow (-w) will be reset when a new patchset is uploaded and hence easy to miss.
- When you edit a patch, first rebase your patch on top of the current branch. Sometimes we replace code in all modules
which might cause merge conflicts for you otherwise. For example, we dropped all options with default values from
Read Release Guide on how to publish new releases.
- Only members of group
ansible-collections-openstack-coreare allowed to merge patches.
- Only members of group
ansible-collections-openstack-releaseare allowed to push tags and trigger our release job
- Only members of
openstacknamespace in Ansible Galaxy are allowed to apply changes to meta properties of Ansible collection
openstack.cloudon Ansible Galaxy.