Skip to content

Commit

Permalink
Merge pull request #263 from collin-wicker/ssot_snow_delete_function
Browse files Browse the repository at this point in the history
Ssot snow delete function
  • Loading branch information
jdrew82 authored Apr 16, 2024
2 parents 58716bc + c3a8bf4 commit 09e36c3
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import json
import os

from collections import defaultdict
from diffsync import DiffSync
from diffsync.enum import DiffSyncFlags
from diffsync.exceptions import ObjectAlreadyExists, ObjectNotFound
Expand All @@ -16,6 +17,10 @@
class ServiceNowDiffSync(DiffSync):
"""DiffSync adapter using pysnow to communicate with a ServiceNow server."""

# create defaultdict object to store objects that should be deleted from ServiceNow if they do not
# exist in Nautobot
objects_to_delete = defaultdict(list)

company = models.Company
device = models.Device # child of location
interface = models.Interface # child of device
Expand Down Expand Up @@ -293,3 +298,23 @@ def sync_complete(self, source, diff, flags=DiffSyncFlags.NONE, logger=None):
self.bulk_create_interfaces()

source.tag_involved_objects(target=self)

# If there are objects inside any of the lists in objects_to_delete then iterate over those objects
# and remove them from ServiceNow
if (
self.objects_to_delete["interface"]
or self.objects_to_delete["device"]
or self.objects_to_delete["product_model"]
or self.objects_to_delete["location"]
or self.objects_to_delete["company"]
):
for grouping in (
"interface",
"device",
"product_model",
"location",
"company",
):
for sn_object in self.objects_to_delete[grouping]:
sn_object.delete()
self.objects_to_delete[grouping] = []
36 changes: 27 additions & 9 deletions nautobot_ssot/integrations/servicenow/diffsync/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ServiceNowCRUDMixin:
_sys_id_cache = {}
"""Dict of table -> column_name -> value -> sys_id."""

def map_data_to_sn_record(self, data, mapping_entry, existing_record=None):
def map_data_to_sn_record(self, data, mapping_entry, existing_record=None, clear_cache=False):
"""Map create/update data from DiffSync to a corresponding ServiceNow data record."""
record = existing_record or {}
for mapping in mapping_entry.get("mappings", []):
Expand All @@ -31,6 +31,9 @@ def map_data_to_sn_record(self, data, mapping_entry, existing_record=None):
raise NotImplementedError
column_name = mapping["reference"]["column"]
if value is not None:
# if clear_cache is set to True then clear the cache for the object
if clear_cache:
self._sys_id_cache.setdefault(tablename, {}).setdefault(column_name, {})[value] = {}
# Look in the cache first
sys_id = self._sys_id_cache.get(tablename, {}).get(column_name, {}).get(value, None)
if not sys_id:
Expand All @@ -40,7 +43,6 @@ def map_data_to_sn_record(self, data, mapping_entry, existing_record=None):
else:
sys_id = target["sys_id"]
self._sys_id_cache.setdefault(tablename, {}).setdefault(column_name, {})[value] = sys_id

record[mapping["reference"]["key"]] = sys_id
else:
raise NotImplementedError
Expand Down Expand Up @@ -82,7 +84,27 @@ def update(self, attrs):
super().update(attrs)
return self

# TODO delete() method
def delete(self):
"""Delete an existing instance in ServiceNow if it does not exist in Nautobot. This code adds the ServiceNow object to the objects_to_delete dict of lists. The actual delete occurs in the post-run method of adapter_servicenow.py."""
entry = self.diffsync.mapping_data[self.get_type()]
sn_resource = self.diffsync.client.resource(api_path=f"/table/{entry['table']}")
query = self.map_data_to_sn_record(data=self.get_identifiers(), mapping_entry=entry)
try:
sn_resource.get(query=query).one()
except pysnow.exceptions.MultipleResults:
self.diffsync.job.logger.error(
f"Unsure which record to update, as query {query} matched more than one item "
f"in table {entry['table']}"
)
return None
self.diffsync.job.logger.warning(f"{self._modelname} {self.get_identifiers()} will be deleted.")
_object = sn_resource.get(query=query)
self.diffsync.objects_to_delete[self._modelname].append(_object)
self.map_data_to_sn_record(
data=self.get_identifiers(), mapping_entry=entry, clear_cache=True
) # remove device cache
super().delete()
return self


class Company(ServiceNowCRUDMixin, DiffSyncModel):
Expand All @@ -96,7 +118,7 @@ class Company(ServiceNowCRUDMixin, DiffSyncModel):
}

name: str
manufacturer: bool = False
manufacturer: bool = True

product_models: List["ProductModel"] = []

Expand All @@ -110,7 +132,7 @@ class ProductModel(ServiceNowCRUDMixin, DiffSyncModel):
_modelname = "product_model"
_identifiers = ("manufacturer_name", "model_name", "model_number")

manufacturer_name: Optional[str] # some ServiceNow products have no associated manufacturer?
manufacturer_name: str
# Nautobot has only one combined "model" field, but ServiceNow has both name and number
model_name: str
model_number: str
Expand Down Expand Up @@ -196,8 +218,6 @@ def create(cls, diffsync, ids, attrs):

return model

# TODO delete() method


class Interface(ServiceNowCRUDMixin, DiffSyncModel):
"""ServiceNow Interface model."""
Expand Down Expand Up @@ -253,8 +273,6 @@ def create(cls, diffsync, ids, attrs):
model = super().create(diffsync, ids=ids, attrs=attrs)
return model

# TODO delete() method


class IPAddress(ServiceNowCRUDMixin, DiffSyncModel):
"""An IPv4 or IPv6 address."""
Expand Down
6 changes: 1 addition & 5 deletions nautobot_ssot/integrations/servicenow/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ class ServiceNowDataTarget(DataTarget, Job): # pylint: disable=abstract-method

debug = BooleanVar(description="Enable for more verbose logging.")

# TODO: not yet implemented
# delete_records = BooleanVar(
# description="Delete records from ServiceNow if not present in Nautobot",
# default=False,
# )
delete_records = BooleanVar(description="Delete synced records from ServiceNow if not present in Nautobot")

site_filter = ObjectVar(
description="Only sync records belonging to a single Site.",
Expand Down

0 comments on commit 09e36c3

Please sign in to comment.