summaryrefslogtreecommitdiffstats
path: root/ansible_collections/openstack/cloud/docs/contributing.md
blob: d1026d818c834af8108be1b9d27b899803f29580 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# 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][
opendev-gerrit]. Pull requests submitted through GitHub will be ignored. Please read OpenStack's [Developer Workflow][
openstack-developer-workflow] for details.

For hacking on the Ansible OpenStack collection it helps to [prepare a DevStack environment](devstack.md) first.

## Hosting

* [Bug tracker][storyboard]
* [Mailing list `openstack-discuss@lists.openstack.org`][openstack-discuss].
  Prefix subjects with `[aoc]` or `[aco]` for faster responses.
* [Code Hosting][opendev-a-c-o]
* [Code Review][gerrit-a-c-o]

## Branches

For rationale behind our `master` and `stable/1.0.0` branches and details on our relation to [openstacksdk][
openstacksdk], please read our [branching docs](branching.md).

## Examples

* For an example on how to write a `*_info` module, have a look at modules [`openstack.cloud.identity_role_info`](
  ../plugins/modules/identity_role_info.py) or [`openstack.cloud.neutron_rbac_policies_info`](
  ../plugins/modules/neutron_rbac_policies_info.py).
* For an example on how to write a regular non-`*_info` module, have a look at module
  [`openstack.cloud.federation_idp`](../plugins/modules/federation_idp.py) or any other module which uses
  [`class StateMachine`](../plugins/module_utils/resource.py).
* Do NOT use modules which define a `_system_state_change` function as examples, because they often do not properly
  define Ansible's check mode, idempotency and/or updates. Refer to modules which use [`class StateMachine`](
  ../plugins/module_utils/resource.py). In cases where using `class StateMachine` would cause code bloat, it might help
  to look at modules which define a `_will_change` function instead.

## Naming

* 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)][openstackclient], for example `server`
  instead of `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.

## Interface

* 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 `id`.
* Modules should return a value of type `dict`, `list` or other primitive data types. For example, `floating_ips` in
  `self.exit_json(changed=False, floating_ips=floating_ips)` should to be a list of `dict`s. Use `to_dict()` on
  [openstacksdk][openstacksdk] objects to convert resources to dictionaries. Setting its parameters such as `computed`
  to `False` will drop computed attributes from the resulting dict. Read [`to_dict`'s docstring][openstacksdk-to-dict]
  for more parameters.
* Module results have to be documented in `RETURN` docstring.
* We should document which attribute cannot be updated in `DOCUMENTATION` variable. For example, insert
  `'This attribute cannot be updated.'` to `DOCUMENTATION` like we did for the `server` module and others.
* Sorting module options in `DOCUMENTATION`, attributes in `RETURN`, entries in `argument_spec` and expected fields in
  integration tests will make reviewing easier and faster.

## Interoperability

* 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.

## Coding Guidelines

* Modules should
  + be idempotent (not being idempotent requires a solid reason),
  + return whether something has `changed`,
  + support `check mode`,
  + be based on (be subclasses of) `OpenStackModule` in
    `ansible_collections.openstack.cloud.plugins.module_utils.openstack`,
  + should include `extends_documentation_fragment: openstack` in their  `DOCUMENTATION` docstring,
  + be registered in `meta/action_groups.yml` for enabling the variables to be set in
    [group level][ansible-module-defaults].
* Complex functionality, cloud interaction or interoperability code should be moved to [openstacksdk][openstacksdk].
* OpenStack API interactions should happen via [openstacksdk][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.
* `*_info` modules never raise exceptions when resources cannot be found. When resources cannot be found, then a
  `*_info` module returns an empty list instead. For example, module `openstack.cloud.neutron_rbac_policies_info` will
  return an empty list when no project with name given in module parameter `project` can be found.
* When a id is given in `*_info` modules, then we do not need nor want extra code to handle that. Instead most
  [openstacksdk][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 `get_identity_provider()`.
* `EXAMPLES` docstring 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_spec` will always
  be in `self.params`. If not defined differently, they have a default value of `None`.
* 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
  `DOCUMENTATION` variable. We could simply drop these checks and insert `'This attribute cannot be updated.'` to
  `DOCUMENTATION` like we did for the server module and others.
* [openstacksdk][openstacksdk] functions often accept IDs but no names, e.g. `find_address_scope()` and
  `create_address_scope()` accept a `project_id` parameter. Most modules in our collection use names for finding
  resources, so we want to support the same for resources attributes such as `project_id` in `AddressScope`.
* Constraints for module parameters and error handling can often be implemented in `argument_spec` or `module_kwargs`
  `module_kwargs` allows to define dependencies between module options such as [`mutually_exclusive`,
  `required_together`, `required_if` etc.][ansible-argument-spec-dependencies].
* When using [openstacksdk][openstacksdk]'s `find_*` functions (`self.conn.*.find_*`), then pass `ignore_missing=False`
  instead of checking its return value and failing with `self.fail_json()` if it is `None`.
* Use module option names which match attribute names used in [openstacksdk][openstacksdk], e.g. use `is_shared` instead
  of `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](https://review.opendev.org/c/openstack/ansible-collections-openstack/+/828613).
* Use functions from [openstacksdk][openstacksdk]'s proxy layer instead of its cloud layer, if possible. For example,
  use `self.conn.network.find_network()`, not `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][openstacksdk-cloud-layer-stays].
    For example, `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.

## Testing

* Modules have to be tested with CI integration tests (if possible).
* Each module has a corresponding Ansible role containing integration tests in [`ci/roles`](../ci/roles) directory.
* Ensure role names of integration tests in [`ci/roles`](../ci/roles) match the module names.
  Only exception are `*_info` modules: Their integration tests are located in the same Ansible roles as their
  non-`*_info` equivalents (to reduce redundant code). For example, tests for both modules `federation_mapping` and
  `federation_mapping_info` can be found in role `federation_mapping`.
* Zuul CI jobs are defined in [`.zuul.yaml`](../.zuul.yaml).
* Add assertions on return values from Ansible modules in integration tests. For an example, refer to
  [`ci/roles/floating_ip/tasks/main.yml`](../ci/roles/floating_ip/tasks/main.yml).
  We need those checks to validate return values from [openstacksdk][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-test` for sanity checking.
* Use `tox -elinters_latest` to run various linters against your code.

## Upload

* Study our [Review Guidelines](reviewing.md) 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
  `argument_spec` such as `required=False`.

## Release

Read [Release Guide](releasing.md) on how to publish new releases.

## Permissions

* Only [members of group `ansible-collections-openstack-core`][group-a-c-o-core] are allowed to merge patches.
* Only [members of group `ansible-collections-openstack-release`][group-a-c-o-release] are allowed to push tags and
  trigger our release job `ansible-collections-openstack-release` in [galaxy.yml](../galaxy.yml).
* Only members of `openstack` namespace in Ansible Galaxy are allowed to apply changes to meta properties of Ansible
  collection [`openstack.cloud`][ansible-galaxy-openstack-cloud] on Ansible Galaxy.

[ansible-argument-spec-dependencies]: https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec-dependencies
[ansible-galaxy-openstack-cloud]: https://galaxy.ansible.com/openstack/cloud
[ansible-module-defaults]: https://docs.ansible.com/ansible/latest/user_guide/playbooks_module_defaults.html
[gerrit-a-c-o]: https://review.opendev.org/q/status:open+project:openstack/ansible-collections-openstack
[group-a-c-o-core]: https://review.opendev.org/admin/groups/0e01228e912733e8b9a8d957631e41665aa0ffbd,members
[group-a-c-o-release]: https://review.opendev.org/admin/groups/8bca2018f3710f94374aee4b3c9771b9ff0a2254,members
[opendev-a-c-o]: https://opendev.org/openstack/ansible-collections-openstack
[opendev-gerrit]: https://review.opendev.org/
[openstack-developer-workflow]: https://docs.openstack.org/infra/manual/developers.html#development-workflow
[openstack-discuss]: http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
[openstackclient]: https://docs.openstack.org/python-openstackclient/latest/
[openstacksdk-cloud-layer-stays]: https://meetings.opendev.org/irclogs/%23openstack-sdks/%23openstack-sdks.2022-04-27.log.html
[openstacksdk-to-dict]: https://opendev.org/openstack/openstacksdk/src/branch/master/openstack/resource.py
[openstacksdk]: https://opendev.org/openstack/openstacksdk
[storyboard]: https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack