Netbox interface.py (#53212)
* Created netbox_interface module and updated netbox_utils * Updated documentation * Updated descriptions to include type and argument spec to include required subtions of data * refactored to use new shared functions create()/delete()/update() * Fixed conflicts * Added region to API_APPS_ENDPOINTS
This commit is contained in:
parent
48e83c39ba
commit
b11f48e645
1 changed files with 351 additions and 0 deletions
351
lib/ansible/modules/net_tools/netbox/netbox_interface.py
Normal file
351
lib/ansible/modules/net_tools/netbox/netbox_interface.py
Normal file
|
@ -0,0 +1,351 @@
|
|||
#!/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_interface
|
||||
short_description: Creates or removes interfaces from Netbox
|
||||
description:
|
||||
- Creates or removes interfaces 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
|
||||
type: str
|
||||
netbox_token:
|
||||
description:
|
||||
- The token created within Netbox to authorize API access
|
||||
required: true
|
||||
type: str
|
||||
data:
|
||||
description:
|
||||
- Defines the prefix configuration
|
||||
suboptions:
|
||||
device:
|
||||
description:
|
||||
- Name of the device the interface will be associated with (case-sensitive)
|
||||
required: true
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name of the interface to be created
|
||||
required: true
|
||||
type: str
|
||||
form_factor:
|
||||
description:
|
||||
- |
|
||||
Form factor of the interface:
|
||||
ex. 1000Base-T (1GE), Virtual, 10GBASE-T (10GE)
|
||||
This has to be specified exactly as what is found within UI
|
||||
type: str
|
||||
enabled:
|
||||
description:
|
||||
- Sets whether interface shows enabled or disabled
|
||||
type: bool
|
||||
lag:
|
||||
description:
|
||||
- Parent LAG interface will be a member of
|
||||
type: dict
|
||||
mtu:
|
||||
description:
|
||||
- The MTU of the interface
|
||||
type: str
|
||||
mac_address:
|
||||
description:
|
||||
- The MAC address of the interface
|
||||
type: str
|
||||
mgmt_only:
|
||||
description:
|
||||
- This interface is used only for out-of-band management
|
||||
type: bool
|
||||
description:
|
||||
description:
|
||||
- The description of the prefix
|
||||
type: str
|
||||
mode:
|
||||
description:
|
||||
- The mode of the interface
|
||||
choices:
|
||||
- Access
|
||||
- Tagged
|
||||
- Tagged All
|
||||
type: str
|
||||
untagged_vlan:
|
||||
description:
|
||||
- The untagged VLAN to be assigned to interface
|
||||
type: dict
|
||||
tagged_vlans:
|
||||
description:
|
||||
- A list of tagged VLANS to be assigned to interface. Mode must be set to either C(Tagged) or C(Tagged All)
|
||||
type: list
|
||||
tags:
|
||||
description:
|
||||
- Any tags that the prefix may need to be associated with
|
||||
type: list
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
type: str
|
||||
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 interface module"
|
||||
connection: local
|
||||
hosts: localhost
|
||||
gather_facts: False
|
||||
tasks:
|
||||
- name: Create interface within Netbox with only required information
|
||||
netbox_interface:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
device: test100
|
||||
name: GigabitEthernet1
|
||||
state: present
|
||||
- name: Delete interface within netbox
|
||||
netbox_interface:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
device: test100
|
||||
name: GigabitEthernet1
|
||||
state: absent
|
||||
- name: Create LAG with several specified options
|
||||
netbox_interface:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
device: test100
|
||||
name: port-channel1
|
||||
form_factor: Link Aggregation Group (LAG)
|
||||
mtu: 1600
|
||||
mgmt_only: false
|
||||
mode: Access
|
||||
state: present
|
||||
- name: Create interface and assign it to parent LAG
|
||||
netbox_interface:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
device: test100
|
||||
name: GigabitEthernet1
|
||||
enabled: false
|
||||
form_factor: 1000Base-t (1GE)
|
||||
lag:
|
||||
name: port-channel1
|
||||
mtu: 1600
|
||||
mgmt_only: false
|
||||
mode: Access
|
||||
state: present
|
||||
- name: Create interface as a trunk port
|
||||
netbox_interface:
|
||||
netbox_url: http://netbox.local
|
||||
netbox_token: thisIsMyToken
|
||||
data:
|
||||
device: test100
|
||||
name: GigabitEthernet25
|
||||
enabled: false
|
||||
form_factor: 1000Base-t (1GE)
|
||||
untagged_vlan:
|
||||
name: Wireless
|
||||
site: Test Site
|
||||
tagged_vlans:
|
||||
- name: Data
|
||||
site: Test Site
|
||||
- name: VoIP
|
||||
site: Test Site
|
||||
mtu: 1600
|
||||
mgmt_only: true
|
||||
mode: Tagged
|
||||
state: present
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
interface:
|
||||
description: Serialized object as created or already existent within Netbox
|
||||
returned: on creation
|
||||
type: dict
|
||||
msg:
|
||||
description: Message indicating failure or info about what has been achieved
|
||||
returned: always
|
||||
type: str
|
||||
"""
|
||||
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.net_tools.netbox.netbox_utils import (
|
||||
find_ids,
|
||||
normalize_data,
|
||||
create_netbox_object,
|
||||
delete_netbox_object,
|
||||
update_netbox_object,
|
||||
INTF_FORM_FACTOR,
|
||||
INTF_MODE,
|
||||
)
|
||||
from ansible.module_utils.compat import ipaddress
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
PYNETBOX_IMP_ERR = None
|
||||
try:
|
||||
import pynetbox
|
||||
HAS_PYNETBOX = True
|
||||
except ImportError:
|
||||
PYNETBOX_IMP_ERR = traceback.format_exc()
|
||||
HAS_PYNETBOX = False
|
||||
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
global module
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
# Fail module if pynetbox is not installed
|
||||
if not HAS_PYNETBOX:
|
||||
module.fail_json(msg=missing_required_lib('pynetbox'), exception=PYNETBOX_IMP_ERR)
|
||||
# Assign variables to be used with module
|
||||
app = "dcim"
|
||||
endpoint = "interfaces"
|
||||
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)
|
||||
norm_data = normalize_data(data)
|
||||
try:
|
||||
norm_data = _check_and_adapt_data(nb, norm_data)
|
||||
|
||||
if "present" in state:
|
||||
return module.exit_json(
|
||||
**ensure_interface_present(nb, nb_endpoint, norm_data)
|
||||
)
|
||||
else:
|
||||
return module.exit_json(
|
||||
**ensure_interface_absent(nb, nb_endpoint, norm_data)
|
||||
)
|
||||
except pynetbox.RequestError as e:
|
||||
return module.fail_json(msg=json.loads(e.error))
|
||||
except ValueError as e:
|
||||
return module.fail_json(msg=str(e))
|
||||
except AttributeError as e:
|
||||
return module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
def _check_and_adapt_data(nb, data):
|
||||
data = find_ids(nb, data)
|
||||
|
||||
if data.get("form_factor"):
|
||||
data["form_factor"] = INTF_FORM_FACTOR.get(data["form_factor"].lower())
|
||||
if data.get("mode"):
|
||||
data["mode"] = INTF_MODE.get(data["mode"].lower())
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_interface_present(nb, nb_endpoint, data):
|
||||
"""
|
||||
:returns dict(interface, msg, changed): dictionary resulting of the request,
|
||||
where 'interface' is the serialized interface fetched or newly created in Netbox
|
||||
"""
|
||||
|
||||
if not isinstance(data, dict):
|
||||
changed = False
|
||||
return {"msg": data, "changed": changed}
|
||||
|
||||
nb_intf = nb_endpoint.get(name=data["name"], device_id=data["device"])
|
||||
result = dict()
|
||||
|
||||
if not nb_intf:
|
||||
intf, diff = create_netbox_object(nb_endpoint, data, module.check_mode)
|
||||
changed = True
|
||||
msg = "Interface %s created" % (data["name"])
|
||||
else:
|
||||
intf, diff = update_netbox_object(nb_intf, data, module.check_mode)
|
||||
if intf is False:
|
||||
module.fail_json(
|
||||
msg="Request failed, couldn't update device: %s" % (data["name"])
|
||||
)
|
||||
if diff:
|
||||
msg = "Interface %s updated" % (data["name"])
|
||||
changed = True
|
||||
result["diff"] = diff
|
||||
else:
|
||||
msg = "Interface %s already exists" % (data["name"])
|
||||
changed = False
|
||||
result.update({"interface": intf, "msg": msg, "changed": changed})
|
||||
return result
|
||||
|
||||
|
||||
def ensure_interface_absent(nb, nb_endpoint, data):
|
||||
"""
|
||||
:returns dict(msg, changed, diff)
|
||||
"""
|
||||
nb_intf = nb_endpoint.get(name=data["name"], device_id=data["device"])
|
||||
result = dict()
|
||||
if nb_intf:
|
||||
dummy, diff = delete_netbox_object(nb_intf, module.check_mode)
|
||||
changed = True
|
||||
msg = "Interface %s deleted" % (data["name"])
|
||||
result["diff"] = diff
|
||||
else:
|
||||
msg = "Interface %s already absent" % (data["name"])
|
||||
changed = False
|
||||
|
||||
result.update({"msg": msg, "changed": changed})
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Reference in a new issue