summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFragmentedPacket <mikhail.yohman@gmail.com>2018-11-09 08:24:13 +0100
committerJohn R Barker <john@johnrbarker.com>2018-11-09 08:24:13 +0100
commit3147dc2a15ea50e1d329ac540fd3cc994f40e8ff (patch)
tree075a1409c380a49a626f08b156022c84451650f3
parentDo not use str() on exceptions (#46950) (diff)
downloadansible-3147dc2a15ea50e1d329ac540fd3cc994f40e8ff.tar.xz
ansible-3147dc2a15ea50e1d329ac540fd3cc994f40e8ff.zip
Netbox_device.py module (#46936)
* netbox_device module * Add init.py to each directory * Fixed a few of the shippable failed tests * No need for import pynetbox in netbox_utils-removed, changed syntax for set * A bit more cleanup * Fixed the 'data' to have suboptions * Fixed formatting for device_role * Attempting to fix shippable errors * Final testing and updated documentation * Fixed return type and removed testing result files * Updated some returns to be a list to keep 'meta' formatting consistent * Updated module to standardize the meta return type * Updated short_description and added David Gomez as author * Updated short_description, added David Gomez as author, added module direcotry to BOTMETA.yml * Updated data type to dict and removed JSON from netbox_utils
-rw-r--r--.github/BOTMETA.yml1
-rw-r--r--lib/ansible/module_utils/net_tools/netbox/__init__.py0
-rw-r--r--lib/ansible/module_utils/net_tools/netbox/netbox_utils.py144
-rw-r--r--lib/ansible/modules/net_tools/netbox/__init__.py0
-rw-r--r--lib/ansible/modules/net_tools/netbox/netbox_device.py257
5 files changed, 402 insertions, 0 deletions
diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml
index a5dbfeeffb..aa4a623fa4 100644
--- a/.github/BOTMETA.yml
+++ b/.github/BOTMETA.yml
@@ -199,6 +199,7 @@ files:
labels:
- networking
- infoblox
+ $modules/net_tools/netbox/: fragmentedpacket
$modules/network/a10/: ericchou1 mischapeters
$modules/network/aci/: $team_aci
$modules/network/aireos/: jmighion
diff --git a/lib/ansible/module_utils/net_tools/netbox/__init__.py b/lib/ansible/module_utils/net_tools/netbox/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/net_tools/netbox/__init__.py
diff --git a/lib/ansible/module_utils/net_tools/netbox/netbox_utils.py b/lib/ansible/module_utils/net_tools/netbox/netbox_utils.py
new file mode 100644
index 0000000000..5cef19bbf7
--- /dev/null
+++ b/lib/ansible/module_utils/net_tools/netbox/netbox_utils.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2018, Mikhail Yohman (@fragmentedpacket) <mikhail.yohman@gmail.com>
+# Copyright: (c) 2018, David Gomez (@amb1s1) <david.gomez@networktocode.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+__metaclass__ = type
+
+API_APPS_ENDPOINTS = dict(
+ circuits=[],
+ dcim=['device_roles', 'device_types', 'devices', 'interfaces', 'platforms', 'racks', 'sites'],
+ extras=[],
+ ipam=['ip_addresses', 'prefixes', 'vrfs'],
+ secrets=[],
+ tenancy=['tenants', 'tenant_groups'],
+ virtualization=['clusters']
+)
+
+QUERY_TYPES = dict(
+ cluster='name',
+ device_role='slug',
+ device_type='slug',
+ manufacturer='slug',
+ nat_inside='address',
+ nat_outside='address',
+ platform='slug',
+ primary_ip='address',
+ primary_ip4='address',
+ primary_ip6='address',
+ rack='slug',
+ region='slug',
+ site='slug',
+ tenant='slug',
+ tenant_group='slug',
+ vrf='name'
+)
+
+CONVERT_TO_ID = dict(
+ cluster='clusters',
+ device_role='device_roles',
+ device_type='device_types',
+ interface='interfaces',
+ nat_inside='ip_addresses',
+ nat_outside='ip_addresses',
+ platform='platforms',
+ primary_ip='ip_addresses',
+ primary_ip4='ip_addresses',
+ primary_ip6='ip_addresses',
+ rack='racks',
+ site='sites',
+ tenant='tenants',
+ tenant_group='tenant_groups',
+ vrf='vrfs'
+)
+
+FACE_ID = dict(
+ front=0,
+ rear=1
+)
+
+NO_DEFAULT_ID = set([
+ 'primary_ip',
+ 'primary_ip4',
+ 'primary_ip6'
+])
+
+DEVICE_STATUS = dict(
+ offline=0,
+ active=1,
+ planned=2,
+ staged=3,
+ failed=4,
+ inventory=5
+)
+
+IP_ADDRESS_STATUS = dict(
+ active=1,
+ reserved=2,
+ deprecated=3,
+ dhcp=5
+)
+
+IP_ADDRESS_ROLE = dict(
+ loopback=10,
+ secondary=20,
+ anycast=30,
+ vip=40,
+ vrrp=41,
+ hsrp=42,
+ glbp=43,
+ carp=44
+)
+
+PREFIX_STATUS = dict(
+ container=0,
+ active=1,
+ reserved=2,
+ deprecated=3
+)
+
+VLAN_STATUS = dict(
+ active=1,
+ reserved=2,
+ deprecated=3
+)
+
+
+def find_app(endpoint):
+ for k, v in API_APPS_ENDPOINTS.items():
+ if endpoint in v:
+ nb_app = k
+ return nb_app
+
+
+def find_ids(nb, data):
+ for k, v in data.items():
+ if k in CONVERT_TO_ID:
+ endpoint = CONVERT_TO_ID[k]
+ search = v
+ app = find_app(endpoint)
+ nb_app = getattr(nb, app)
+ nb_endpoint = getattr(nb_app, endpoint)
+
+ query_id = nb_endpoint.get(**{QUERY_TYPES.get(k, "q"): search})
+
+ if k in NO_DEFAULT_ID:
+ pass
+ elif query_id:
+ data[k] = query_id.id
+ else:
+ data[k] = 1
+ return data
+
+
+def normalize_data(data):
+ for k, v in data.items():
+ data_type = QUERY_TYPES.get(k, "q")
+ if data_type == "slug":
+ if "-" in v:
+ data[k] = v.replace(" ", "").lower()
+ elif " " in v:
+ data[k] = v.replace(" ", "-").lower()
+ else:
+ data[k] = v.lower()
+ return data
diff --git a/lib/ansible/modules/net_tools/netbox/__init__.py b/lib/ansible/modules/net_tools/netbox/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/modules/net_tools/netbox/__init__.py
diff --git a/lib/ansible/modules/net_tools/netbox/netbox_device.py b/lib/ansible/modules/net_tools/netbox/netbox_device.py
new file mode 100644
index 0000000000..0581564cc6
--- /dev/null
+++ b/lib/ansible/modules/net_tools/netbox/netbox_device.py
@@ -0,0 +1,257 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2018, Mikhail Yohman (@FragmentedPacket) <mikhail.yohman@gmail.com>
+# Copyright: (c) 2018, David Gomez (@amb1s1) <david.gomez@networktocode.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+---
+module: netbox_device
+short_description: Create or delete devices within Netbox
+description:
+ - Creates or removes devices from Netbox
+notes:
+ - Tags should be defined as a YAML list
+ - This should be ran with connection C(local) and hosts C(localhost)
+author:
+ - Mikhail Yohman (@FragmentedPacket)
+ - David Gomez (@amb1s1)
+requirements:
+ - pynetbox
+version_added: '2.8'
+options:
+ netbox_url:
+ description:
+ - URL of the Netbox instance resolvable by Ansible control host
+ required: true
+ netbox_token:
+ description:
+ - The token created within Netbox to authorize API access
+ required: true
+ data:
+ description:
+ - Defines the device configuration
+ suboptions:
+ name:
+ description:
+ - The name of the device
+ device_type:
+ description:
+ - Required if I(state=present)
+ device_role:
+ description:
+ - Required if I(state=present)
+ tenant:
+ description:
+ - The tenant that the device will be assigned to
+ platform:
+ description:
+ - The platform of the device
+ serial:
+ description:
+ - Serial number of the device
+ asset_tag:
+ description:
+ - Asset tag that is associated to the device
+ site:
+ description:
+ - Required if I(state=present)
+ rack:
+ description:
+ - The name of the rack to assign the device to
+ position:
+ description:
+ - The position of the device in the rack defined above
+ face:
+ description:
+ - Required if I(rack) is defined
+ status:
+ description:
+ - The status of the device
+ choices:
+ - Active
+ - Offline
+ - Planned
+ - Staged
+ - Failed
+ - Inventory
+ cluster:
+ description:
+ - Cluster that the device will be assigned to
+ comments:
+ description:
+ - Comments that may include additional information in regards to the device
+ tags:
+ description:
+ - Any tags that the device may need to be associated with
+ custom_fields:
+ description:
+ - must exist in Netbox
+ required: true
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ choices: [ absent, present ]
+ default: present
+ validate_certs:
+ description:
+ - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates.
+ default: 'yes'
+ type: bool
+'''
+
+EXAMPLES = r'''
+- name: "Test Netbox modules"
+ connection: local
+ hosts: localhost
+ gather_facts: False
+
+ tasks:
+ - name: Create device within Netbox with only required information
+ netbox_device:
+ netbox_url: http://netbox.local
+ netbox_token: thisIsMyToken
+ data:
+ name: Test (not really required, but helpful)
+ device_type: C9410R
+ device_role: Core Switch
+ site: Main
+ state: present
+
+ - name: Delete device within netbox
+ netbox_device:
+ netbox_url: http://netbox.local
+ netbox_token: thisIsMyToken
+ data:
+ name: Test
+ state: absent
+
+ - name: Create device with tags
+ netbox_device:
+ netbox_url: http://netbox.local
+ netbox_token: thisIsMyToken
+ data:
+ name: Test
+ device_type: C9410R
+ device_role: Core Switch
+ site: Main
+ tags:
+ - Schnozzberry
+ state: present
+
+ - name: Create device and assign to rack and position
+ netbox_device:
+ netbox_url: http://netbox.local
+ netbox_token: thisIsMyToken
+ data:
+ name: Test
+ device_type: C9410R
+ device_role: Core Switch
+ site: Main
+ rack: Test Rack
+ position: 10
+ face: Front
+'''
+
+RETURN = r'''
+meta:
+ description: Message indicating failure or returns results with the object created within Netbox
+ returned: always
+ type: list
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.net_tools.netbox.netbox_utils import find_ids, normalize_data, DEVICE_STATUS, FACE_ID
+import json
+try:
+ import pynetbox
+ HAS_PYNETBOX = True
+except:
+ HAS_PYNETBOX = False
+
+
+def netbox_create_device(nb, nb_endpoint, data):
+ norm_data = normalize_data(data)
+ if norm_data.get("status"):
+ norm_data["status"] = DEVICE_STATUS.get(norm_data["status"].lower(), 0)
+ if norm_data.get("face"):
+ norm_data["face"] = FACE_ID.get(norm_data["face"].lower(), 0)
+ data = find_ids(nb, norm_data)
+ try:
+ return nb_endpoint.create([norm_data])
+ except pynetbox.RequestError as e:
+ return json.loads(e.error)
+
+
+def netbox_delete_device(nb_endpoint, data):
+ norm_data = normalize_data(data)
+ endpoint = nb_endpoint.get(name=norm_data["name"])
+ result = []
+ try:
+ if endpoint.delete():
+ result.append({'success': '%s deleted from Netbox' % (norm_data["name"])})
+ except AttributeError:
+ result.append({'failed': '%s not found' % (norm_data["name"])})
+ return result
+
+
+def main():
+ '''
+ Main entry point for module execution
+ '''
+ argument_spec = dict(
+ netbox_url=dict(type="str", required=True),
+ netbox_token=dict(type="str", required=True, no_log=True),
+ data=dict(type="dict", required=True),
+ state=dict(required=False, default='present', choices=['present', 'absent']),
+ validate_certs=dict(type="bool", default=True)
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec,
+ supports_check_mode=False)
+
+ # Fail module if pynetbox is not installed
+ if not HAS_PYNETBOX:
+ module.fail_json(msg='pynetbox is required for this module')
+
+ # Assign variables to be used with module
+ changed = False
+ app = 'dcim'
+ endpoint = 'devices'
+ url = module.params["netbox_url"]
+ token = module.params["netbox_token"]
+ data = module.params["data"]
+ state = module.params["state"]
+ validate_certs = module.params["validate_certs"]
+
+ # Attempt to create Netbox API object
+ try:
+ nb = pynetbox.api(url, token=token, ssl_verify=validate_certs)
+ except Exception:
+ module.fail_json(msg="Failed to establish connection to Netbox API")
+ try:
+ nb_app = getattr(nb, app)
+ except AttributeError:
+ module.fail_json(msg="Incorrect application specified: %s" % (app))
+
+ nb_endpoint = getattr(nb_app, endpoint)
+ if 'present' in state:
+ response = netbox_create_device(nb, nb_endpoint, data)
+ if response[0].get('created'):
+ changed = True
+ else:
+ response = netbox_delete_device(nb_endpoint, data)
+ if 'success' in response[0]:
+ changed = True
+ module.exit_json(changed=changed, meta=response)
+
+
+if __name__ == "__main__":
+ main()