mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 13:56:09 -07:00 
			
		
		
		
	* Fix some module author names During some data analysis we noticed some typos in author lines.
		
			
				
	
	
		
			348 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/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_site
 | |
| short_description: Creates or removes sites from Netbox
 | |
| description:
 | |
|   - Creates or removes sites 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 site configuration
 | |
|     suboptions:
 | |
|       name:
 | |
|         description:
 | |
|           - Name of the site to be created
 | |
|         required: true
 | |
|         type: str
 | |
|       status:
 | |
|         description:
 | |
|           - Status of the site
 | |
|         choices:
 | |
|           - Active
 | |
|           - Planned
 | |
|           - Retired
 | |
|         type: str
 | |
|       region:
 | |
|         description:
 | |
|           - The region that the site should be associated with
 | |
|         type: str
 | |
|       tenant:
 | |
|         description:
 | |
|           - The tenant the site will be assigned to
 | |
|         type: str
 | |
|       facility:
 | |
|         description:
 | |
|           - Data center provider or facility, ex. Equinix NY7
 | |
|         type: str
 | |
|       asn:
 | |
|         description:
 | |
|           - The ASN associated with the site
 | |
|         type: int
 | |
|       time_zone:
 | |
|         description:
 | |
|           - Timezone associated with the site, ex. America/Denver
 | |
|         type: str
 | |
|       description:
 | |
|         description:
 | |
|           - The description of the prefix
 | |
|         type: str
 | |
|       physical_address:
 | |
|         description:
 | |
|           - Physical address of site
 | |
|         type: str
 | |
|       shipping_address:
 | |
|         description:
 | |
|           - Shipping address of site
 | |
|         type: str
 | |
|       latitude:
 | |
|         description:
 | |
|           - Latitude in decimal format
 | |
|         type: int
 | |
|       longitude:
 | |
|         description:
 | |
|           - Longitude in decimal format
 | |
|         type: int
 | |
|       contact_name:
 | |
|         description:
 | |
|           - Name of contact for site
 | |
|         type: str
 | |
|       contact_phone:
 | |
|         description:
 | |
|           - Contact phone number for site
 | |
|         type: str
 | |
|       contact_email:
 | |
|         description:
 | |
|           - Contact email for site
 | |
|         type: str
 | |
|       comments:
 | |
|         description:
 | |
|           - Comments for the site. This can be markdown syntax
 | |
|         type: str
 | |
|       tags:
 | |
|         description:
 | |
|           - Any tags that the prefix may need to be associated with
 | |
|         type: list
 | |
|       custom_fields:
 | |
|         description:
 | |
|           - must exist in Netbox
 | |
|         type: dict
 | |
|     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 site module"
 | |
|   connection: local
 | |
|   hosts: localhost
 | |
|   gather_facts: False
 | |
|   tasks:
 | |
|     - name: Create site within Netbox with only required information
 | |
|       netbox_site:
 | |
|         netbox_url: http://netbox.local
 | |
|         netbox_token: thisIsMyToken
 | |
|         data:
 | |
|           name: Test - Colorado
 | |
|         state: present
 | |
| 
 | |
|     - name: Delete site within netbox
 | |
|       netbox_site:
 | |
|         netbox_url: http://netbox.local
 | |
|         netbox_token: thisIsMyToken
 | |
|         data:
 | |
|           name: Test - Colorado
 | |
|         state: absent
 | |
| 
 | |
|     - name: Create site with all parameters
 | |
|       netbox_site:
 | |
|         netbox_url: http://netbox.local
 | |
|         netbox_token: thisIsMyToken
 | |
|         data:
 | |
|           name: Test - California
 | |
|           status: Planned
 | |
|           region: Test Region
 | |
|           tenant: Test Tenant
 | |
|           facility: EquinoxCA7
 | |
|           asn: 65001
 | |
|           time_zone: America/Los Angeles
 | |
|           description: This is a test description
 | |
|           physical_address: Hollywood, CA, 90210
 | |
|           shipping_address: Hollywood, CA, 90210
 | |
|           latitude: 10.100000
 | |
|           longitude: 12.200000
 | |
|           contact_name: Jenny
 | |
|           contact_phone: 867-5309
 | |
|           contact_email: jenny@changednumber.com
 | |
|           comments: ### Placeholder
 | |
|         state: present
 | |
| """
 | |
| 
 | |
| RETURN = r"""
 | |
| site:
 | |
|   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,
 | |
|     SITE_STATUS,
 | |
| )
 | |
| 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 = "sites"
 | |
|     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_site_present(nb, nb_endpoint, norm_data)
 | |
|             )
 | |
|         else:
 | |
|             return module.exit_json(
 | |
|                 **ensure_site_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("status"):
 | |
|         data["status"] = SITE_STATUS.get(data["status"].lower())
 | |
| 
 | |
|     if "-" in data["name"]:
 | |
|         site_slug = data["name"].replace(" ", "").lower()
 | |
|     elif " " in data["name"]:
 | |
|         site_slug = data["name"].replace(" ", "-").lower()
 | |
|     else:
 | |
|         site_slug = data["name"].lower()
 | |
| 
 | |
|     data["slug"] = site_slug
 | |
| 
 | |
|     return data
 | |
| 
 | |
| 
 | |
| def ensure_site_present(nb, nb_endpoint, data):
 | |
|     """
 | |
|     :returns dict(interface, msg, changed): dictionary resulting of the request,
 | |
|     where 'site' is the serialized interface fetched or newly created in Netbox
 | |
|     """
 | |
| 
 | |
|     if not isinstance(data, dict):
 | |
|         changed = False
 | |
|         return {"msg": data, "changed": changed}
 | |
| 
 | |
|     nb_site = nb_endpoint.get(slug=data["slug"])
 | |
|     result = dict()
 | |
|     if not nb_site:
 | |
|         site, diff = create_netbox_object(nb_endpoint, data, module.check_mode)
 | |
|         changed = True
 | |
|         msg = "Site %s created" % (data["name"])
 | |
|         result["diff"] = diff
 | |
|     else:
 | |
|         site, diff = update_netbox_object(nb_site, data, module.check_mode)
 | |
|         if site is False:
 | |
|             module.fail_json(
 | |
|                 msg="Request failed, couldn't update device: %s" % (data["name"])
 | |
|             )
 | |
|         if diff:
 | |
|             msg = "Site %s updated" % (data["name"])
 | |
|             changed = True
 | |
|             result["diff"] = diff
 | |
|         else:
 | |
|             msg = "Site %s already exists" % (data["name"])
 | |
|             changed = False
 | |
| 
 | |
|     result.update({"site": site, "msg": msg, "changed": changed})
 | |
|     return result
 | |
| 
 | |
| 
 | |
| def ensure_site_absent(nb, nb_endpoint, data):
 | |
|     """
 | |
|     :returns dict(msg, changed)
 | |
|     """
 | |
|     nb_site = nb_endpoint.get(slug=data["slug"])
 | |
|     result = dict()
 | |
|     if nb_site:
 | |
|         dummy, diff = delete_netbox_object(nb_site, module.check_mode)
 | |
|         changed = True
 | |
|         msg = "Site %s deleted" % (data["name"])
 | |
|         result["diff"] = diff
 | |
|     else:
 | |
|         msg = "Site %s already absent" % (data["name"])
 | |
|         changed = False
 | |
| 
 | |
|     result.update({"msg": msg, "changed": changed})
 | |
|     return result
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     main()
 |