Netbox Module: netbox_ip_address (#48424)
* Tested netbox_ip_address with several conditions and working as intended
This commit is contained in:
parent
59dcf3f277
commit
69988cfca0
2 changed files with 343 additions and 42 deletions
|
@ -7,49 +7,49 @@ __metaclass__ = type
|
|||
|
||||
API_APPS_ENDPOINTS = dict(
|
||||
circuits=[],
|
||||
dcim=['device_roles', 'device_types', 'devices', 'interfaces', 'platforms', 'racks', 'sites'],
|
||||
dcim=["device_roles", "device_types", "devices", "interfaces", "platforms", "racks", "sites"],
|
||||
extras=[],
|
||||
ipam=['ip_addresses', 'prefixes', 'vrfs'],
|
||||
ipam=["ip_addresses", "prefixes", "vrfs"],
|
||||
secrets=[],
|
||||
tenancy=['tenants', 'tenant_groups'],
|
||||
virtualization=['clusters']
|
||||
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'
|
||||
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'
|
||||
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(
|
||||
|
@ -58,9 +58,12 @@ FACE_ID = dict(
|
|||
)
|
||||
|
||||
NO_DEFAULT_ID = set([
|
||||
'primary_ip',
|
||||
'primary_ip4',
|
||||
'primary_ip6'
|
||||
"primary_ip",
|
||||
"primary_ip4",
|
||||
"primary_ip6",
|
||||
"vrf",
|
||||
"nat_inside",
|
||||
"nat_outside"
|
||||
])
|
||||
|
||||
DEVICE_STATUS = dict(
|
||||
|
@ -120,14 +123,27 @@ def find_ids(nb, data):
|
|||
nb_app = getattr(nb, app)
|
||||
nb_endpoint = getattr(nb_app, endpoint)
|
||||
|
||||
query_id = nb_endpoint.get(**{QUERY_TYPES.get(k, "q"): search})
|
||||
if k == "interface":
|
||||
query_id = nb_endpoint.get(**{"name": v["name"], "device": v["device"]})
|
||||
elif k == "nat_inside":
|
||||
if v.get("vrf"):
|
||||
vrf_id = nb.ipam.vrfs.get(**{"name": v["vrf"]})
|
||||
query_id = nb_endpoint.get(**{"address": v["address"], "vrf_id": vrf_id.id})
|
||||
else:
|
||||
try:
|
||||
query_id = nb_endpoint.get(**{"address": v["address"]})
|
||||
except ValueError:
|
||||
return {"failed": "Multiple results found while searching for %s: %s - Specify a VRF within %s" % (k, v["address"], k)}
|
||||
else:
|
||||
query_id = nb_endpoint.get(**{QUERY_TYPES.get(k, "q"): search})
|
||||
|
||||
if k in NO_DEFAULT_ID:
|
||||
pass
|
||||
elif query_id:
|
||||
if query_id:
|
||||
data[k] = query_id.id
|
||||
elif k in NO_DEFAULT_ID:
|
||||
pass
|
||||
else:
|
||||
data[k] = 1
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
|
285
lib/ansible/modules/net_tools/netbox/netbox_ip_address.py
Normal file
285
lib/ansible/modules/net_tools/netbox/netbox_ip_address.py
Normal file
|
@ -0,0 +1,285 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Mikhail Yohman (@fragmentedpacket) <mikhail.yohman@gmail.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_ip_address
|
||||
short_description: Creates or removes IP addresses from Netbox
|
||||
description:
|
||||
- Creates or removes IP addresses 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)
|
||||
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 IP address configuration
|
||||
suboptions:
|
||||
family:
|
||||
description:
|
||||
- Specifies with address family the IP address belongs to
|
||||
choices:
|
||||
- 4
|
||||
- 6
|
||||
address:
|
||||
description:
|
||||
- Required if state is C(present)
|
||||
vrf:
|
||||
description:
|
||||
- VRF that IP address is associated with
|
||||
tenant:
|
||||
description:
|
||||
- The tenant that the device will be assigned to
|
||||
status:
|
||||
description:
|
||||
- The status of the IP address
|
||||
choices:
|
||||
- Active
|
||||
- Reserved
|
||||
- Deprecated
|
||||
- DHCP
|
||||
role:
|
||||
description:
|
||||
- The role of the IP address
|
||||
choices:
|
||||
- Loopback
|
||||
- Secondary
|
||||
- Anycast
|
||||
- VIP
|
||||
- VRRP
|
||||
- HSRP
|
||||
- GLBP
|
||||
- CARP
|
||||
interface:
|
||||
description:
|
||||
- The name and device of the interface that the IP address should be assigned to
|
||||
description:
|
||||
description:
|
||||
- The description of the interface
|
||||
nat_inside:
|
||||
description:
|
||||
- The inside IP address this IP is assigned to
|
||||
tags:
|
||||
description:
|
||||
- Any tags that the IP address 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 IP address module"
|
||||
connection: local
|
||||
hosts: localhost
|
||||
gather_facts: False
|
||||
|
||||
tasks:
|
||||
- name: Create IP address within Netbox with only required information
|
||||
netbox_ip_address:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
address: 192.168.1.10
|
||||
state: present
|
||||
|
||||
- name: Delete IP address within netbox
|
||||
netbox_ip_address:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
address: 192.168.1.10
|
||||
state: absent
|
||||
|
||||
- name: Create IP address with several specified options
|
||||
netbox_ip_address:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
family: 4
|
||||
address: 192.168.1.20
|
||||
vrf: Test
|
||||
tenant: Test Tenant
|
||||
status: Reserved
|
||||
role: Loopback
|
||||
description: Test description
|
||||
tags:
|
||||
- Schnozzberry
|
||||
state: present
|
||||
|
||||
- name: Create IP address and assign a nat_inside IP
|
||||
netbox_ip_address:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
family: 4
|
||||
address: 192.168.1.30
|
||||
vrf: Test
|
||||
nat_inside:
|
||||
address: 192.168.1.20
|
||||
vrf: Test
|
||||
interface:
|
||||
name: GigabitEthernet1
|
||||
device: test100
|
||||
'''
|
||||
|
||||
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, IP_ADDRESS_ROLE, IP_ADDRESS_STATUS
|
||||
import json
|
||||
|
||||
try:
|
||||
import pynetbox
|
||||
HAS_PYNETBOX = True
|
||||
except ImportError:
|
||||
HAS_PYNETBOX = False
|
||||
|
||||
|
||||
def netbox_create_ip_address(nb, nb_endpoint, data):
|
||||
result = []
|
||||
if data.get('vrf'):
|
||||
norm_data = normalize_data(data)
|
||||
if norm_data.get("status"):
|
||||
norm_data["status"] = IP_ADDRESS_STATUS.get(norm_data["status"].lower())
|
||||
if norm_data.get("role"):
|
||||
norm_data["role"] = IP_ADDRESS_ROLE.get(norm_data["role"].lower())
|
||||
data = find_ids(nb, norm_data)
|
||||
if data.get('failed'):
|
||||
result.append(data)
|
||||
return result
|
||||
|
||||
if not nb_endpoint.get(address=data["address"], vrf_id=data['vrf']):
|
||||
try:
|
||||
return nb_endpoint.create([data])
|
||||
except pynetbox.RequestError as e:
|
||||
return json.loads(e.error)
|
||||
else:
|
||||
result.append({'failed': '%s already exists in Netbox' % (data["address"])})
|
||||
else:
|
||||
if not nb_endpoint.get(address=data["address"]):
|
||||
norm_data = normalize_data(data)
|
||||
if norm_data.get("status"):
|
||||
norm_data["status"] = IP_ADDRESS_STATUS.get(norm_data["status"].lower())
|
||||
if norm_data.get("role"):
|
||||
norm_data["role"] = IP_ADDRESS_ROLE.get(norm_data["role"].lower())
|
||||
data = find_ids(nb, norm_data)
|
||||
|
||||
try:
|
||||
return nb_endpoint.create([data])
|
||||
except pynetbox.RequestError as e:
|
||||
return json.loads(e.error)
|
||||
else:
|
||||
result.append({'failed': '%s already exists in Netbox' % (data["address"])})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def netbox_delete_ip_address(nb, nb_endpoint, data):
|
||||
norm_data = normalize_data(data)
|
||||
result = []
|
||||
if data.get('vrf'):
|
||||
data = find_ids(nb, norm_data)
|
||||
endpoint = nb_endpoint.get(address=norm_data["address"], vrf_id=data['vrf'])
|
||||
else:
|
||||
endpoint = nb_endpoint.get(address=norm_data["address"])
|
||||
|
||||
try:
|
||||
if endpoint.delete():
|
||||
result.append({'success': '%s deleted from Netbox' % (norm_data["address"])})
|
||||
except AttributeError:
|
||||
result.append({'failed': '%s not found' % (norm_data["address"])})
|
||||
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 = 'ipam'
|
||||
endpoint = 'ip_addresses'
|
||||
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_ip_address(nb, nb_endpoint, data)
|
||||
if response[0].get('created'):
|
||||
changed = True
|
||||
else:
|
||||
response = netbox_delete_ip_address(nb, nb_endpoint, data)
|
||||
if 'success' in response[0]:
|
||||
changed = True
|
||||
module.exit_json(changed=changed, meta=response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue