Skip to content

Commit 4cfd2fc

Browse files
Documentation: Add getting started docs for modules, inventory, and some advance usages (#406)
1 parent 45bbf6a commit 4cfd2fc

File tree

11 files changed

+635
-90
lines changed

11 files changed

+635
-90
lines changed

docs/advanced/index.rst

Lines changed: 0 additions & 8 deletions
This file was deleted.

docs/conf.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@
3535
# Add any Sphinx extension module names here, as strings. They can be
3636
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
3737
# ones.
38-
extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon"]
38+
extensions = [
39+
"sphinx.ext.autodoc",
40+
"sphinx.ext.napoleon",
41+
"sphinx.ext.autosectionlabel",
42+
]
3943

4044
# Add any paths that contain templates here, relative to this directory.
4145
templates_path = ["_templates"]

docs/getting_started/how-to-use.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=========================================
2+
Using Ansible Collections
3+
=========================================
4+
5+
.. toctree::
6+
:maxdepth: 4
7+
8+
Modules <how-to-use/modules>
9+
Inventory <how-to-use/inventory>
10+
Advanced Usage - Modules <how-to-use/advanced>

docs/getting_started/how-to-use/advanced.rst

Lines changed: 301 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
============
2+
Inventory
3+
============
4+
5+
This page will just have quick examples that people may have had questions about, but the normal plugin documentation should be referenced for normal usage.
6+
7+
The inventory plugin documentation can be found :ref:`here<ansible_collections.netbox.netbox.nb_inventory_inventory>`.
8+
9+
Using Compose to Set ansible_network_os to Platform Slug
10+
------------------------------------------------------------------
11+
12+
.. code-block:: yaml
13+
14+
---
15+
plugin: netbox.netbox.nb_inventory
16+
compose:
17+
ansible_network_os: platform.slug
18+
19+
20+
Using Keyed Groups to set ansible_network_os to Platform Slug
21+
----------------------------------------------------------------
22+
23+
.. code-block:: yaml
24+
25+
---
26+
plugin: netbox.netbox.nb_inventory
27+
keyed_groups:
28+
- key: platform
29+
prefix: "network_os"
30+
separator: "_"
31+
32+
.. _post: http://blog.networktocode.com/post/ansible-constructed-inventory/
33+
.. note:: The above examples are excerpts from the following blog post_.
34+
35+
36+
Using Inventory Plugin Within AWX/Tower
37+
----------------------------------------
38+
39+
This will cover the basic usage of the NetBox inventory plugin within this collection.
40+
41+
1. Define ``collections/requirements.yml`` within a Git project.
42+
2. AWX/Tower will download the collection on each run. This can be handled differently or excluded if storing Ansible Collections on the AWX/Tower box.
43+
3. Define ``inventory.yml`` in Git project that adheres to inventory plugin structure.
44+
4. Add Git project to AWX/Tower as a project.
45+
5. Create inventory and select ``source from project``.
46+
6. Select the AWX/Tower project from Step 2
47+
7. Select the ``inventory.yml`` file in the project from Step 3
48+
8. Make sure your Tower installation uses Python 3 or select the proper ``ANSIBLE ENVIRONMENT``
49+
9. Click ``Save`` and sync source.
Loading
Loading
Loading
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
Modules
2+
===============
3+
4+
Specifying Modules in a Playbook
5+
----------------------------------
6+
7+
There are two methods when using a collection in a playbook (in preferred order):
8+
9+
1. Using the Fully Qualified Collection Name (FQCN) of the module, e.g. ``netbox.netbox.netbox_device`` at the task level.
10+
2. Using the ``collections`` directive at the play level.
11+
12+
13+
.. code-block:: yaml
14+
15+
---
16+
- hosts: "localhost"
17+
18+
tasks:
19+
- name: "Configure a device in NetBox"
20+
netbox.netbox.netbox_device:
21+
<.. omitted>
22+
23+
24+
.. code-block:: yaml
25+
26+
---
27+
- hosts: "localhost"
28+
collections:
29+
- netbox.netbox
30+
31+
tasks:
32+
- name: "Configure a device in NetBox"
33+
netbox_device:
34+
<.. omitted>
35+
36+
37+
To validate that the playbook is using the collection modules and not the Ansible builtin modules, add ``-vvv`` and look for the following line within the output for each task executed.
38+
39+
.. code-block:: bash
40+
41+
Using module file /Users/netbox/.ansible/collections/ansible_collections/netbox/netbox/plugins/modules/netbox_device.py
42+
43+
44+
You can find more information at the official Ansible docs_.
45+
46+
.. _docs: https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#installing-collections
47+
48+
.. note:: If you are on MacOS and are running into ``ERROR! A worker was found in a dead state errors``, try running the playbook with ``env no_proxy='*'`` tag in front of the playbook. This is a known issue with MacOS as per this reference: https://github.com/ansible/ansible/issues/32554#issuecomment-642896861
49+
50+
Module Arguments & States
51+
----------------------------
52+
53+
This section will provide details on why some module arguments are required for certain states or even change if an object already exists.
54+
55+
Before we go any further, let's provide some preliminary knowledge of how Ansible works when accepting arguments into a module.
56+
57+
Ansible provides several builtin methods when initializing the ``AnsibleModule`` to help build flexible module argument requirements. We aren't going to explore all the options in depth, but there are three that we currently use within this collection.
58+
59+
- **argument_spec**: Defines a schema for acceptable arguments for a module along with their type, and specifying whether an argument is required or not.
60+
- **required_if**: Allows logic to define that an argument is required if another argument and it's value meets the specified condition.
61+
- **required_one_of**: Specifies logic that at least one of the arguments specified is required.
62+
63+
Most modules will require the minimal amount of arguments to find a unique object in NetBox via the **argument_spec** that gets passed into ``AnsibleModule``.
64+
65+
.. _Module Development: https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#ansiblemodule
66+
.. note:: For more information, please view Ansible's documentation for Module Development_.
67+
68+
This does mean that the modules may make the call to NetBox to create an object and the API will reply back advising the user that required fields were missing for the specific operation and causes the module to fail.
69+
70+
.. code-block:: bash
71+
72+
failed: [localhost] (item={'unit': 2, 'type': 'nexus-child'}) => {"ansible_loop_var": "item", "changed": false, "item": {"type": "nexus-child", "unit": 2}, "msg": "{\"device_role\":[\"This field is required.\"]}"}
73+
74+
75+
To expand further, our ``present`` state can either **create** or **update** an object. If the object does not exist within NetBox it will send a ``POST`` and create the object.
76+
If the object already exists, the fetched object from NetBox is captured with ``pynetbox`` and will already have the required fields, which means the user only needs to provide the updated fields.
77+
The next step is to then compare the object obtained from NetBox to the data passed in by the user into the module and only update the fields that are different. This is all handled behind the scenes within the modules.
78+
79+
Hopefully this helps paint a picture as to why certain design decisions were made and how you can better consume this collection. Let's move onto some examples.
80+
81+
State: Present - Create
82+
+++++++++++++++++
83+
84+
When creating an object, you will need to provide the same arguments as you would if you were creating an object via the API.
85+
86+
Let's take a look at creating a device via the API.
87+
88+
.. image:: ./media/api_device_post.png
89+
:scale: 35 %
90+
91+
The required fields are marked by ``*`` and we can see the following are fields are required:
92+
93+
- **device_type**
94+
- **device_role**
95+
- **site**
96+
97+
These same fields are required when creating a device via the :ref:`netbox_device <ansible_collections.netbox.netbox.netbox_device_module>` module, but with the important addition of **name**.
98+
99+
.. code-block:: yaml
100+
101+
---
102+
...
103+
tasks:
104+
- name: "Example for state: present"
105+
netbox.netbox.netbox_device:
106+
netbox_url: "http://netbox.local"
107+
netbox_token: "thisIsMyToken"
108+
data:
109+
name: "Test Device"
110+
device_type: "C9410R"
111+
device_role: "Core Switch"
112+
site: "Main"
113+
state: present
114+
115+
The reasoning behind requiring **name** within the Ansible modules is to provide the module with the ability to distinguish between devices or objects within NetBox. The name helps make the device unique rather than attempting to only
116+
search on ``device_type``, ``device_role``, and ``site`` as these do not make a device unique and makes it difficult to assume which device the user cares about.
117+
These modules are abstracting away the API interaction and some of the logic which means we require the users to provide a bit more information as to what they're intending to do. We're trying to keep the abstractions to a minimum,
118+
but that isn't always possible.
119+
120+
Along with forcing a user to provide some uniqueness to their objects in NetBox, we also try and mirror the module interaction with the GUI interaction where we can to prevent burdening the user.
121+
For instance, the ``slug`` field is required when interacting with the API for the majority of models in NetBox, but constructing the ``slug`` is handled for the user within the GUI. To stay aligned with the GUI,
122+
we abstract that away from the user by constructing the ``slug`` from the ``name`` using the same rules as the NetBox GUI.
123+
124+
For reference, here is the code that **slugifies** the ``name`` argument when a user does not provide a ``slug``.
125+
126+
.. code-block:: python
127+
128+
def _to_slug(self, value):
129+
"""
130+
:returns slug (str): Slugified value
131+
:params value (str): Value that needs to be changed to slug format
132+
"""
133+
if value is None:
134+
return value
135+
elif isinstance(value, int):
136+
return value
137+
else:
138+
removed_chars = re.sub(r"[^\-\.\w\s]", "", value)
139+
convert_chars = re.sub(r"[\-\.\s]+", "-", removed_chars)
140+
return convert_chars.strip().lower()
141+
142+
143+
Now that we have a better understanding of why certain arguments are required or not, let's look into updating an existing object.
144+
145+
State: Present - Update
146+
+++++++++++++++++
147+
148+
Now that we have created our device (**Test Device**), let's update it by adding a serial number.
149+
150+
.. code-block:: yaml
151+
152+
---
153+
...
154+
tasks:
155+
- name: "Example state: present - Update"
156+
netbox.netbox.netbox_device:
157+
netbox_url: "http://netbox.local"
158+
netbox_token: "thisIsMyToken"
159+
data:
160+
name: "Test Device"
161+
serial_number: "FXS110011"
162+
state: "present"
163+
164+
We're only providing the **name** which makes the device unique and then the argument we want updated. As stated above, it will see that the device exist, and then update the ``serial_number`` field and then send a
165+
``PATCH`` to the NetBox API.
166+
167+
.. note::
168+
You can add the ``query_params`` argument to specify specific fields that make the object unique in your environment.
169+
170+
Defining ``query_params`` overrides the default fields the modules use when attempting to resolve the object.
171+
172+
More information can be found in the :ref:`Advanced Usage` section.
173+
174+
175+
State: Absent - Delete
176+
+++++++++++++++++
177+
178+
The uniqueness of the device (**name** or user specified **query_params**) and the ``state`` set to ``absent`` are the only requirements for deleting an object.
179+
180+
.. code-block:: yaml
181+
182+
---
183+
...
184+
tasks:
185+
- name: "Example state: absent"
186+
netbox.netbox.netbox_device:
187+
netbox_url: "http://netbox.local"
188+
netbox_token: "thisIsMyToken"
189+
data:
190+
name: "Test Device"
191+
state: "absent"
192+
193+
Tags
194+
+++++++++++++++
195+
196+
Not all models support tags, but several of them do so I wanted to talk a little bit more about them outside of a module context since they're
197+
applied the same irregardless of which module you're using.
198+
199+
.. note::
200+
Tags changed significantly starting NetBox 2.10.
201+
202+
We will not be covering pre-2.10 tags within these docs.
203+
204+
Tags are now a model within NetBox and require being resolved like any other model such as ``device_type`` shown above. This requires the user to provide a list of dictionaries
205+
that specify fields that are unique to each tag. Name can be used, but we always suggest that you use ``slug`` when available.
206+
207+
.. code-block:: yaml
208+
209+
---
210+
...
211+
tasks:
212+
- name: "Example using tags"
213+
netbox.netbox.netbox_device:
214+
netbox_url: "http://netbox.local"
215+
netbox_token: "thisIsMyToken"
216+
data:
217+
name: "Test Device"
218+
tags:
219+
- slug: "my-new-tag1"
220+
- slug: "my-new-tag2"
221+
state: "present"
222+
223+
.. warning:: Everything discussed above can be applied to each module, but may need to swap out any arguments for module specific arguments.

0 commit comments

Comments
 (0)