diff --git a/README.md b/README.md index 45ace67..b8a203f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # Nautobot to Arista CloudVision Sync -A plugin for [Nautobot](https://github.com/nautobot/nautobot) that allows synchronization of data directly between CloudVision and Nautobot. It synchronizes user device tags from Nautobot into CloudVision while using devices and system tags from CloudVision to ensure device syncronization and populate device metadata. Here is a table showing the data mappings when syncing from CloudVision. +A plugin for [Nautobot](https://github.com/nautobot/nautobot) that allows synchronization of data directly between CloudVision and Nautobot. It synchronizes user device tags from Nautobot into CloudVision while using devices and system tags from CloudVision to ensure device synchronization and populate device metadata. Here is a table showing the data mappings when syncing from CloudVision. | CloudVision System Tags | Nautobot Device Custom Field | |-------------------------|------------------------------| -| topology_network_type | Topology Network Typ | -| mlag | MLAG | +| topology_network_type | Topology Network Type | +| mlag | mlag | | mpls | mpls | | model | Device Platform* | -| systype | Systype | +| systype | systype | | serialnumber | Device Serial Number | | pimbidir | pimbidir | | sflow | sFlow | -| eostrain | eostrain | -| tapagg | tapagg | +| eostrain | EOS Train | +| tapagg | TAP Aggregation | | pim | pim | | bgp | bgp | | terminattr | TerminAttr Version | @@ -56,7 +56,7 @@ To ensure Nautobot to Arista CloudVision Sync is automatically re-installed duri # echo nautobot_ssot_aristacv >> local_requirements.txt ``` -Once installed, the plugin needs to be enabled in your `nautobot_configuration.py` +Once installed, the plugin needs to be enabled in your `nautobot_configuration.py` and plugin settings need to be defined. ```python # In your configuration.py @@ -67,15 +67,27 @@ PLUGINS = ["nautobot_ssot", "nautobot_ssot_aristacv"] # ADD YOUR SETTINGS HERE # } # "nautobot_ssot_aristacv": { -# ADD YOUR SETTINGS HERE +# "cvaas_token": "", +# "cvp_host": "", +# "cvp_user": "", +# "cvp_password": "", +# "insecure": "", +# "from_cloudvision_default_site": "", +# "from_cloudvision_default_device_role": "", +# "from_cloudvision_default_device_role_color": "", +# "from_cloudvision_default_device_status": "", +# "from_cloudvision_default_device_status_color": "", +# "delete_devices_on_sync_cv_source": "" # } # } ``` +> All plugin settings are defined in the picture above as an example. Only some will be needed as described below. + Upon installation, this plugin creates the following custom fields in Nautobot: - `arista_bgp` -- `arists_eos` +- `arista_eos` - `arista_eostrain` - `arista_mlag` - `arista_mpls` @@ -91,27 +103,40 @@ Upon installation, this plugin creates the following custom fields in Nautobot: > While these contain the prefix "arista" in the custom field admin portal, when looking at them on a device the prefix is removed. -The plugin can connect to either on-premise or a cloud instance of CloudVision. To connect to an on-premise instance you must set the following variables in the nautobot configuration file. +The plugin can connect to either on-premise or a cloud instance of CloudVision. To connect to an on-premise instance, you must set the following variables in the Nautobot configuration file. -- `cvp_host` string: The hostname or address of the onprem instance of CloudVision -- `cvp_user` string: The username used to connect to the onprem instance CloudVision. -- `cvp_password` string: The password used to connect to the onprem instance CloudVision. -- `insecure` boolean: If true, the plugin will download the certificate from CloudVision and trusted for gRPC. +| Configuration Variable | Type | Usage | +|------------------------|---------|--------------------------------------------------------------------------------------------------| +| cvp_host | string | Hostname or ip address of the onprem instance of CloudVision. | +| cvp_user | string | The username used to connect to the onprem instance of CloudVision. | +| cvp_password | string | The password used by the user specified above. | +| insecure | boolean | If true, the plugin will download the certificate from CloudVision and trust it for gRPC calls. | To connect to a cloud instance of CloudVision you must set the following variable: -- `cvaas_token` string: Token to be used when connected to CloudVision as a Service. +| Configuration Variable | Type | Usage | +|------------------------|--------|---------------------------------------------------------------| +| cvaas_token | string | Token to be used when connecting to CloudVision as a Service. | + +When syncing from CloudVision, this plugin will create new Arista devices that do not exist in Nautobot. In order for this to work properly, you must provide the following default values in the nautobot config file. + +| Configuration Variable | Type | Usage | Default | +|----------------------------------------------|--------|------------------------------------------------------------|----------------------| +| from_cloudvision_default_site | string | Default site created when syncing new devices to Nautobot. | cloudvision_imported | +| from_cloudvision_default_device_role | string | Default role created when syncing new devices to Nautobot. | network | +| from_cloudvision_default_device_role_color | string | Default role color used for default role. | ff0000 | +| from_cloudvision_default_device_status | string | Default status used when syncing new devices to Nautobot. | cloudvision_imported | +| from_cloudvision_default_device_status_color | string | Default status color used for default status. | ff0000 | -When syncing from CloudVision, this plugin will create new devices that do not exist in Nautobot. In order for this to work properly, you must provide the following default value sin the nautobot config file. +> When these variables are not defined in the plugin settings, the plugin will use the default values mentioned. -- `from_cloudvision_default_site` string: The default site used when syncing creates new devices in Nautobot. -- `from_cloudvision_default_device_role` string: The default device role used when the syncing creates new devices in Nautobot. -- `from_cloudvision_default_device_role_color` string: The default color to assign to the default role. -- `from_cloudvision_default_device_status`: string: The default status used when the syncing creates new devices in Nautobot. +Lastly, when an Arista device exists in Nautobot but not in CloudVision, this plugin can either delete or leave the device in Nautobot. That behavior can be set with the following variable in the nautobot config file. -Lastly, when a device exists in Nautobot but not in CloudVision, this plugin can either delete or leave the device in Nautobot. That behavior can be set with the following variable in the nautobot config file. +| Configuration Variable | Type | Usage | Default | +|---------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| de;ete_deices_on_sync_cv_source | boolean | If true, devices in Nautobot with device type manufacturer name set to Arista that do not exist in CloudVision but do exist in Nautobot upon sync will be deleted. | False | -- `delete_devices_on_sync_cv_source` boolean (default False): If true, this will delete devices in Nautbot that do not exist in CloudVision when syncing from CloudVision. +> When this variable is not defined in the plugin settings, the plugin will default to using `False`. ## Usage diff --git a/development/docker-compose.requirements.yml b/development/docker-compose.requirements.yml index 175cd29..dfc2353 100644 --- a/development/docker-compose.requirements.yml +++ b/development/docker-compose.requirements.yml @@ -19,7 +19,5 @@ services: env_file: - "dev.env" - "creds.env" - ports: - - "6379:6379" volumes: postgres_data: {} diff --git a/nautobot_ssot_aristacv/__init__.py b/nautobot_ssot_aristacv/__init__.py index 41f7b68..4febf83 100644 --- a/nautobot_ssot_aristacv/__init__.py +++ b/nautobot_ssot_aristacv/__init__.py @@ -1,6 +1,6 @@ """Plugin declaration for aristacv_sync.""" -__version__ = "0.1.0" +__version__ = "1.0.1" import os diff --git a/nautobot_ssot_aristacv/diffsync/fromcv/models.py b/nautobot_ssot_aristacv/diffsync/fromcv/models.py index 69566b0..48ff19f 100644 --- a/nautobot_ssot_aristacv/diffsync/fromcv/models.py +++ b/nautobot_ssot_aristacv/diffsync/fromcv/models.py @@ -4,13 +4,20 @@ from django.conf import settings from nautobot.dcim.models import Device as NautobotDevice from nautobot.dcim.models import Platform as NautobotPlatform -from nautobot.extras.models.statuses import Status from typing import List, Optional import nautobot_ssot_aristacv.diffsync.nbutils as nbutils import nautobot_ssot_aristacv.diffsync.cvutils as cvutils import distutils +DEFAULT_SITE = "cloudvision_imported" +DEFAULT_DEVICE_ROLE = "network" +DEFAULT_DEVICE_ROLE_COLOR = "ff0000" +DEFAULT_DEVICE_STATUS = "cloudvision_imported" +DEFAULT_DEVICE_STATUS_COLOR = "ff0000" +DEFAULT_DELETE_DEVICES_ON_SYNC = False + + class Device(DiffSyncModel): """Device Model.""" @@ -28,7 +35,7 @@ class Device(DiffSyncModel): def create(cls, diffsync, ids, attrs): """Create device object in Nautobot.""" configs = settings.PLUGINS_CONFIG.get("nautobot_ssot_aristacv", {}) - default_site_object = nbutils.verify_site(configs.get("from_cloudvision_default_site")) + default_site_object = nbutils.verify_site(configs.get("from_cloudvision_default_site", DEFAULT_SITE)) cvutils.connect() device_dict = next((device for device in cvutils.get_devices() if device["hostname"] == ids["name"]), {}) @@ -36,11 +43,17 @@ def create(cls, diffsync, ids, attrs): device_type_object = nbutils.verify_device_type_object(device_type_cv) device_role_object = nbutils.verify_device_role_object( - configs.get("from_cloudvision_default_device_role"), - configs.get("from_cloudvision_default_device_role_color"), + configs.get("from_cloudvision_default_device_role", DEFAULT_DEVICE_ROLE), + configs.get("from_cloudvision_default_device_role_color", DEFAULT_DEVICE_ROLE_COLOR), ) + + device_status = nbutils.verify_device_status( + configs.get("from_cloudvision_default_device_status", DEFAULT_DEVICE_STATUS), + configs.get("from_cloudvision_default_device_status_color", DEFAULT_DEVICE_STATUS_COLOR), + ) + new_device = NautobotDevice( - status=Status.objects.get(name=configs.get("from_cloudvision_default_device_status")), + status=device_status, device_type=device_type_object, device_role=device_role_object, site=default_site_object, @@ -59,7 +72,7 @@ def update(self, diffsync, attrs): def delete(self): """Delete device object in Nautobot.""" configs = settings.PLUGINS_CONFIG.get("nautobot_ssot_aristacv", {}) - if configs.get("delete_devices_on_sync"): + if configs.get("delete_devices_on_sync", DEFAULT_DELETE_DEVICES_ON_SYNC): self.diffsync.job.log_warning(message=f"Device {self.name} will be deleted per plugin settings.") device = NautobotDevice.objects.get(name=self.name) device.delete() diff --git a/nautobot_ssot_aristacv/diffsync/nbutils.py b/nautobot_ssot_aristacv/diffsync/nbutils.py index aaf9ffa..e7a2e30 100644 --- a/nautobot_ssot_aristacv/diffsync/nbutils.py +++ b/nautobot_ssot_aristacv/diffsync/nbutils.py @@ -2,6 +2,7 @@ from nautobot.dcim.models import DeviceType, DeviceRole, Site, Manufacturer from nautobot.extras.models.statuses import Status from nautobot.extras.models.customfields import CustomField +from django.contrib.contenttypes.models import ContentType def verify_site(site_name): @@ -58,6 +59,28 @@ def verify_device_role_object(role_name, role_color): return role_obj +def verify_device_status(device_status, device_status_color): + """Verifies device status object exists in Nautobot. If not, creates specified device status. + + Args: + device_status (str): Status name. + device_status_color (str): Status color. + """ + try: + status_obj = Status.objects.get(name=device_status) + except Status.DoesNotExist: + dcim_device = ContentType.objects.get(app_label="dcim", model="device") + status_obj = Status( + name=device_status, + slug=device_status.lower(), + color=device_status_color, + description="Devices imported from CloudVision.", + ) + status_obj.validated_save() + status_obj.content_types.set([dcim_device]) + return status_obj + + def assign_arista_cf(device): """Assigns arista custom fields to device.""" for cf in CustomField.objects.filter(name__contains="arista"): diff --git a/nautobot_ssot_aristacv/jobs.py b/nautobot_ssot_aristacv/jobs.py index 93c7ea2..95da5db 100644 --- a/nautobot_ssot_aristacv/jobs.py +++ b/nautobot_ssot_aristacv/jobs.py @@ -18,6 +18,14 @@ from nautobot_ssot_aristacv.diffsync.fromcv.cloudvision import CloudVision as C from nautobot_ssot_aristacv.diffsync.fromcv.nautobot import Nautobot as N +from nautobot_ssot_aristacv.diffsync.fromcv.models import ( + DEFAULT_SITE, + DEFAULT_DEVICE_ROLE, + DEFAULT_DEVICE_ROLE_COLOR, + DEFAULT_DEVICE_STATUS, + DEFAULT_DEVICE_STATUS_COLOR, + DEFAULT_DELETE_DEVICES_ON_SYNC, +) import nautobot_ssot_aristacv.diffsync.cvutils as cvutils @@ -44,21 +52,33 @@ def config_information(cls): "CloudVision host": configs.get("cvp_host"), "Username": configs.get("cvp_user"), "Insecure": configs.get("insecure"), - "Delete devices on sync": configs.get("delete_devices_on_sync"), - "New device default site": configs.get("from_cloudvision_default_site"), - "New device default role": configs.get("from_cloudvision_default_device_role"), - "New device default role color": configs.get("from_cloudvision_default_device_role_color"), - "New device default status": configs.get("from_cloudvision_default_device_status") + "Delete devices on sync": configs.get("delete_devices_on_sync", str(DEFAULT_DELETE_DEVICES_ON_SYNC)), + "New device default site": configs.get("from_cloudvision_default_site", DEFAULT_SITE), + "New device default role": configs.get("from_cloudvision_default_device_role", DEFAULT_DEVICE_ROLE), + "New device default role color": configs.get( + "from_cloudvision_default_device_role_color", DEFAULT_DEVICE_ROLE_COLOR + ), + "New device default status": configs.get( + "from_cloudvision_default_device_status", DEFAULT_DEVICE_STATUS + ), + "New device default status color": configs.get( + "from_cloudvision_default_device_status", DEFAULT_DEVICE_STATUS_COLOR + ) # Password is intentionally omitted! } return { "Server type": "CVaaS", "CloudVision host": "www.arista.io", - "Delete_devices_on_sync": configs.get("delete_devices_on_sync"), - "New device default site": configs.get("from_cloudvision_default_site"), - "New device default role": configs.get("from_cloudvision_default_device_role"), - "New device default role color": configs.get("from_cloudvision_default_device_role_color"), - "New device default status": configs.get("from_cloudvision_default_device_status") + "Delete_devices_on_sync": configs.get("delete_devices_on_sync", str(DEFAULT_DELETE_DEVICES_ON_SYNC)), + "New device default site": configs.get("from_cloudvision_default_site", DEFAULT_SITE), + "New device default role": configs.get("from_cloudvision_default_device_role", DEFAULT_DEVICE_ROLE), + "New device default role color": configs.get( + "from_cloudvision_default_device_role_color", DEFAULT_DEVICE_ROLE_COLOR + ), + "New device default status": configs.get("from_cloudvision_default_device_status", DEFAULT_DEVICE_STATUS), + "New device default status color": configs.get( + "from_cloudvision_default_device_status", DEFAULT_DEVICE_STATUS_COLOR + ) # Token is intentionally omitted! } diff --git a/nautobot_ssot_aristacv/signals.py b/nautobot_ssot_aristacv/signals.py index 4d50394..5bc231f 100644 --- a/nautobot_ssot_aristacv/signals.py +++ b/nautobot_ssot_aristacv/signals.py @@ -54,7 +54,7 @@ def post_migrate_create_custom_fields(apps, **kwargs): { "name": "arista_mlag", "type": CustomFieldTypeChoices.TYPE_TEXT, - "label": "MLAG", + "label": "mlag", }, { "name": "arista_tapagg", diff --git a/pyproject.toml b/pyproject.toml index ec20e83..3971220 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nautobot-ssot-aristacv" -version = "0.1.0" +version = "1.0.1" description = "Nautobot SSoT Arista CloudVision" authors = ["Network to Code, LLC "]