Skip to content

Commit 1f43926

Browse files
authored
Merge pull request #1459 from dchiquito/datasources
Add `netbox_data_sources` module
2 parents 8ca870e + 3faf1b3 commit 1f43926

File tree

6 files changed

+365
-0
lines changed

6 files changed

+365
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
minor_changes:
2+
- netbox_data_source - New module `#1459 <https://github.com/netbox-community/ansible_modules/pull/1459>`
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright: (c) 2025, Daniel Chiquito (@dchiquito) <daniel.chiquito@gmail.com>
3+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4+
from __future__ import absolute_import, division, print_function
5+
6+
__metaclass__ = type
7+
8+
from ansible_collections.netbox.netbox.plugins.module_utils.netbox_utils import (
9+
NetboxModule,
10+
ENDPOINT_NAME_MAPPING,
11+
SLUG_REQUIRED,
12+
)
13+
14+
NB_DATA_SOURCES = "data_sources"
15+
16+
17+
class NetboxCoreModule(NetboxModule):
18+
def __init__(self, module, endpoint):
19+
super().__init__(module, endpoint)
20+
21+
def _handle_state_new(self, nb_app, nb_endpoint, endpoint_name, data):
22+
if self.state == "new":
23+
self.nb_object, diff = self._create_netbox_object(nb_endpoint, data)
24+
self.result["msg"] = "%s created" % (endpoint_name)
25+
self.result["changed"] = True
26+
self.result["diff"] = diff
27+
28+
def run(self):
29+
"""
30+
This function should have all necessary code for endpoints within the application
31+
to create/update/delete the endpoint objects
32+
Supported endpoints:
33+
- data_sources
34+
"""
35+
# Used to dynamically set key when returning results
36+
endpoint_name = ENDPOINT_NAME_MAPPING[self.endpoint]
37+
38+
self.result = {"changed": False}
39+
40+
application = self._find_app(self.endpoint)
41+
nb_app = getattr(self.nb, application)
42+
nb_endpoint = getattr(nb_app, self.endpoint)
43+
user_query_params = self.module.params.get("query_params")
44+
45+
data = self.data
46+
47+
# Used for msg output
48+
if data.get("name"):
49+
name = data["name"]
50+
elif data.get("slug"):
51+
name = data["slug"]
52+
53+
if self.endpoint in SLUG_REQUIRED:
54+
if not data.get("slug"):
55+
data["slug"] = self._to_slug(name)
56+
57+
# Make color params lowercase
58+
if data.get("color"):
59+
data["color"] = data["color"].lower()
60+
61+
# Handle journal entry
62+
if self.state == "new" and endpoint_name == "journal_entry":
63+
self._handle_state_new(nb_app, nb_endpoint, endpoint_name, data)
64+
else:
65+
object_query_params = self._build_query_params(
66+
endpoint_name, data, user_query_params
67+
)
68+
self.nb_object = self._nb_endpoint_get(
69+
nb_endpoint, object_query_params, name
70+
)
71+
72+
if self.state == "present":
73+
self._ensure_object_exists(nb_endpoint, endpoint_name, name, data)
74+
elif self.state == "absent":
75+
self._ensure_object_absent(endpoint_name, name)
76+
77+
try:
78+
serialized_object = self.nb_object.serialize()
79+
except AttributeError:
80+
serialized_object = self.nb_object
81+
82+
self.result.update({endpoint_name: serialized_object})
83+
84+
self.module.exit_json(**self.result)

plugins/module_utils/netbox_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
"providers": {},
4040
"provider_networks": {},
4141
},
42+
core={
43+
"data_sources": {},
44+
},
4245
dcim={
4346
"cables": {},
4447
"console_ports": {},
@@ -371,6 +374,7 @@
371374
"custom_fields": "custom_field",
372375
"custom_field_choice_sets": "choice_set",
373376
"custom_links": "custom_link",
377+
"data_sources": "data_source",
374378
"device_bays": "device_bay",
375379
"device_bay_templates": "device_bay_template",
376380
"devices": "device",
@@ -482,6 +486,7 @@
482486
"custom_field_choice_set": set(["name"]),
483487
"choice_set": set(["name"]),
484488
"custom_link": set(["name"]),
489+
"data_source": set(["name"]),
485490
"dcim.consoleport": set(["name", "device"]),
486491
"dcim.consoleserverport": set(["name", "device"]),
487492
"dcim.frontport": set(["name", "device", "rear_port"]),
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
# Copyright: (c) 2025, Daniel Chiquito (@dchiquito) <daniel.chiquito@gmail.com>
4+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
__metaclass__ = type
7+
8+
DOCUMENTATION = r"""
9+
---
10+
module: netbox_data_source
11+
short_description: Creates or removes data sources from NetBox
12+
description:
13+
- Creates or removes data sources from NetBox
14+
author:
15+
- Daniel Chiquito (@dchiquito)
16+
requirements:
17+
- pynetbox
18+
version_added: "3.22.0"
19+
extends_documentation_fragment:
20+
- netbox.netbox.common
21+
options:
22+
data:
23+
type: dict
24+
description:
25+
- Defines the data source configuration
26+
suboptions:
27+
name:
28+
description:
29+
- Name of the data source
30+
required: true
31+
type: str
32+
type:
33+
description:
34+
- The origin of the data source
35+
choices:
36+
- local
37+
- git
38+
- amazon-s3
39+
required: false
40+
type: str
41+
source_url:
42+
description:
43+
- URL of the data source to be created
44+
required: false
45+
type: str
46+
enabled:
47+
description:
48+
- Whether or not this data source can be synced
49+
required: false
50+
type: bool
51+
description:
52+
description:
53+
- Description of the data source
54+
required: false
55+
type: str
56+
ignore_rules:
57+
description:
58+
- Patterns (one per line) matching files to ignore when syncing
59+
required: false
60+
type: str
61+
sync_interval:
62+
description:
63+
- The interval in seconds between syncs
64+
required: false
65+
choices:
66+
- 1
67+
- 60
68+
- 720
69+
- 1440
70+
- 10080
71+
- 43200
72+
type: int
73+
comments:
74+
description:
75+
- Comments about the data source
76+
required: false
77+
type: str
78+
required: true
79+
"""
80+
81+
EXAMPLES = r"""
82+
- name: "Test NetBox modules"
83+
connection: local
84+
hosts: localhost
85+
gather_facts: false
86+
87+
tasks:
88+
- name: "Create a new data source with only required information"
89+
netbox.netbox.netbox_data_source:
90+
netbox_url: http://netbox.local
91+
netbox_token: thisIsMyToken
92+
data:
93+
name: "Data Source 1"
94+
type: "local"
95+
source_url: "/tmp/data-source.txt"
96+
enabled: true
97+
state: present
98+
- name: "Update that data source with other fields"
99+
netbox.netbox.netbox_data_source:
100+
netbox_url: http://netbox.local
101+
netbox_token: thisIsMyToken
102+
data:
103+
name: "Data Source 1"
104+
type: "amazon-s3"
105+
source_url: "path/to/bucket"
106+
enabled: false
107+
description: "My first data source"
108+
ignore_rules: ".*\nfoo.txt\n*.yml"
109+
sync_interval: 1440
110+
comments: "Some commentary on this data source"
111+
state: present
112+
- name: "Delete the data source"
113+
netbox.netbox.netbox_data_source:
114+
netbox_url: http://netbox.local
115+
netbox_token: thisIsMyToken
116+
data:
117+
name: "Data Source 1"
118+
state: absent
119+
"""
120+
121+
RETURN = r"""
122+
data_source:
123+
description: Serialized object as created or already existent within NetBox
124+
returned: on creation
125+
type: dict
126+
msg:
127+
description: Message indicating failure or info about what has been achieved
128+
returned: always
129+
type: str
130+
"""
131+
132+
133+
from ansible_collections.netbox.netbox.plugins.module_utils.netbox_utils import (
134+
NetboxAnsibleModule,
135+
NETBOX_ARG_SPEC,
136+
)
137+
from ansible_collections.netbox.netbox.plugins.module_utils.netbox_core import (
138+
NetboxCoreModule,
139+
NB_DATA_SOURCES,
140+
)
141+
from copy import deepcopy
142+
143+
144+
def main():
145+
"""
146+
Main entry point for module execution
147+
"""
148+
argument_spec = deepcopy(NETBOX_ARG_SPEC)
149+
argument_spec.update(
150+
dict(
151+
data=dict(
152+
type="dict",
153+
required=True,
154+
options=dict(
155+
name=dict(required=True, type="str"),
156+
type=dict(
157+
required=False,
158+
choices=["local", "git", "amazon-s3"],
159+
type="str",
160+
),
161+
source_url=dict(required=False, type="str"),
162+
enabled=dict(required=False, type="bool"),
163+
description=dict(required=False, type="str"),
164+
ignore_rules=dict(required=False, type="str"),
165+
sync_interval=dict(
166+
required=False,
167+
choices=[1, 60, 60 * 12, 60 * 24, 60 * 24 * 7, 60 * 24 * 30],
168+
type="int",
169+
),
170+
comments=dict(required=False, type="str"),
171+
),
172+
),
173+
)
174+
)
175+
176+
required_if = [
177+
("state", "present", ["name", "type", "source_url", "enabled"]),
178+
("state", "absent", ["name"]),
179+
]
180+
181+
module = NetboxAnsibleModule(
182+
argument_spec=argument_spec, supports_check_mode=True, required_if=required_if
183+
)
184+
185+
netbox_data_source = NetboxCoreModule(module, NB_DATA_SOURCES)
186+
netbox_data_source.run()
187+
188+
189+
if __name__ == "__main__": # pragma: no cover
190+
main()

tests/integration/targets/v4.3/tasks/main.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,3 +741,12 @@
741741
- netbox_circuit_termination
742742
tags:
743743
- netbox_circuit_termination
744+
745+
- name: NETBOX_DATA_SOURCE TESTS
746+
ansible.builtin.include_tasks:
747+
file: netbox_data_source.yml
748+
apply:
749+
tags:
750+
- netbox_data_source
751+
tags:
752+
- netbox_data_source
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
##
3+
##
4+
### NETBOX_DATA_SOURCE
5+
##
6+
##
7+
- name: "DATA SOURCE 1: Create"
8+
netbox.netbox.netbox_data_source:
9+
netbox_url: http://localhost:32768
10+
netbox_token: "0123456789abcdef0123456789abcdef01234567"
11+
data:
12+
name: "Data Source 1"
13+
type: "local"
14+
source_url: "/tmp/data-source.txt"
15+
enabled: true
16+
state: present
17+
register: test_one
18+
19+
- name: "DATA SOURCE 1: Assert - Create"
20+
ansible.builtin.assert:
21+
that:
22+
- test_one is changed
23+
- test_one['diff']['before']['state'] == "absent"
24+
- test_one['diff']['after']['state'] == "present"
25+
- test_one['data_source']['name'] == "Data Source 1"
26+
- test_one['data_source']['type'] == "local"
27+
- test_one['data_source']['source_url'] == "/tmp/data-source.txt"
28+
- test_one['data_source']['enabled'] == true
29+
- test_one['msg'] == "data_source Data Source 1 created"
30+
31+
- name: "DATA SOURCE 2: Update"
32+
netbox.netbox.netbox_data_source:
33+
netbox_url: http://localhost:32768
34+
netbox_token: "0123456789abcdef0123456789abcdef01234567"
35+
data:
36+
name: "Data Source 1"
37+
type: "amazon-s3"
38+
source_url: "path/to/bucket"
39+
enabled: false
40+
description: "My first data source"
41+
ignore_rules: ".*\nfoo.txt\n*.yml"
42+
sync_interval: 1440
43+
comments: "Some commentary on this data source"
44+
state: present
45+
register: test_two
46+
47+
- name: "DATA SOURCE 2: Assert - Update"
48+
ansible.builtin.assert:
49+
that:
50+
- test_two is changed
51+
- test_two['data_source']['name'] == "Data Source 1"
52+
- test_two['data_source']['type'] == "amazon-s3"
53+
- test_two['data_source']['source_url'] == "path/to/bucket"
54+
- test_two['data_source']['enabled'] == false
55+
- test_two['data_source']['description'] == "My first data source"
56+
- test_two['data_source']['ignore_rules'] == ".*\nfoo.txt\n*.yml"
57+
- test_two['data_source']['sync_interval'] == 1440
58+
- test_two['data_source']['comments'] == "Some commentary on this data source"
59+
- test_two['msg'] == "data_source Data Source 1 updated"
60+
61+
- name: "DATA SOURCE 3: Delete"
62+
netbox.netbox.netbox_data_source:
63+
netbox_url: http://localhost:32768
64+
netbox_token: "0123456789abcdef0123456789abcdef01234567"
65+
data:
66+
name: "Data Source 1"
67+
state: absent
68+
register: test_three
69+
70+
- name: "DATA SOURCE 3: Assert - Delete"
71+
ansible.builtin.assert:
72+
that:
73+
- test_three is changed
74+
- test_three['diff']['before']['state'] == "present"
75+
- test_three['diff']['after']['state'] == "absent"

0 commit comments

Comments
 (0)