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:
Mikhail Yohman 2019-04-03 12:20:28 -06:00 committed by Nilashish Chakraborty
parent 48e83c39ba
commit b11f48e645

View 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()