55from uuid import UUID
66
77from diffsync import Diff , DiffSync , DiffSyncFlags , DiffSyncModel
8- from diffsync .exceptions import ObjectAlreadyExists
8+ from diffsync .exceptions import ObjectAlreadyExists , ObjectNotFound
99from pydantic .error_wrappers import ValidationError
1010import structlog
1111
1212import nautobot_netbox_importer .diffsync .models as n2nmodels
13+ from nautobot_netbox_importer .diffsync .models .validation import netbox_pk_to_nautobot_pk
1314
1415
1516class N2NDiffSync (DiffSync ):
@@ -31,7 +32,6 @@ class N2NDiffSync(DiffSync):
3132 permission = n2nmodels .Permission
3233 token = n2nmodels .Token
3334 user = n2nmodels .User
34- userconfig = n2nmodels .UserConfig
3535
3636 # Circuits
3737 circuit = n2nmodels .Circuit
@@ -76,6 +76,7 @@ class N2NDiffSync(DiffSync):
7676 # Extras
7777 configcontext = n2nmodels .ConfigContext
7878 customfield = n2nmodels .CustomField
79+ customfieldchoice = n2nmodels .CustomFieldChoice
7980 customlink = n2nmodels .CustomLink
8081 exporttemplate = n2nmodels .ExportTemplate
8182 jobresult = n2nmodels .JobResult
@@ -121,17 +122,18 @@ class N2NDiffSync(DiffSync):
121122 # The specific order of models below is constructed empirically, but basically attempts to place all models
122123 # in sequence so that if model A has a hard dependency on a reference to model B, model B gets processed first.
123124 #
125+ # Note: with the latest changes in design for this plugin (using deterministic UUIDs in Nautobot to allow
126+ # direct mapping of NetBox PKs to Nautobot PKs), this order is now far less critical than it was previously.
127+ #
124128
125129 top_level = (
126130 # "contenttype", Not synced, as these are hard-coded in NetBox/Nautobot
127- "customfield" ,
128- "permission" ,
131+ # "permission", Not synced, as these are superseded by "objectpermission"
129132 "group" ,
130- "user" ,
133+ "user" , # Includes NetBox "userconfig" model as well
131134 "objectpermission" ,
132135 "token" ,
133- "userconfig" ,
134- # "status", Not synced, as these are hard-coded in NetBox/Nautobot
136+ # "status", Not synced, as these are hard-coded in NetBox and autogenerated in Nautobot
135137 # Need Tenant and TenantGroup before we can populate Sites
136138 "tenantgroup" ,
137139 "tenant" , # Not all Tenants belong to a TenantGroup
@@ -194,7 +196,6 @@ class N2NDiffSync(DiffSync):
194196 # Interface/VMInterface -> Device/VirtualMachine (device)
195197 # Interface comes after Device because it MUST have a Device to be created;
196198 # IPAddress comes after Interface because we use the assigned_object as part of the IP's unique ID.
197- # We will fixup the Device->primary_ip reference in fixup_data_relations()
198199 "ipaddress" ,
199200 "cable" ,
200201 "service" ,
@@ -207,6 +208,10 @@ class N2NDiffSync(DiffSync):
207208 "webhook" ,
208209 "taggeditem" ,
209210 "jobresult" ,
211+ # Imported last so that any "required=True" CustomFields do not cause Nautobot to reject
212+ # NetBox records that predate the creation of those CustomFields
213+ "customfield" ,
214+ "customfieldchoice" ,
210215 )
211216
212217 def __init__ (self , * args , ** kwargs ):
@@ -229,41 +234,10 @@ def add(self, obj: DiffSyncModel):
229234 self ._data_by_pk [modelname ][obj .pk ] = obj
230235 super ().add (obj )
231236
232- def fixup_data_relations (self ):
233- """Iterate once more over all models and fix up any leftover FK relations."""
234- for name in self .top_level :
235- instances = self .get_all (name )
236- if not instances :
237- self .logger .info ("No instances to review" , model = name )
238- else :
239- self .logger .info (f"Reviewing all { len (instances )} instances" , model = name )
240- for diffsync_instance in instances :
241- for fk_field , target_name in diffsync_instance .fk_associations ().items ():
242- value = getattr (diffsync_instance , fk_field )
243- if not value :
244- continue
245- if "*" in target_name :
246- target_content_type_field = target_name [1 :]
247- target_content_type = getattr (diffsync_instance , target_content_type_field )
248- target_name = target_content_type ["model" ]
249- target_class = getattr (self , target_name )
250- if "pk" in value :
251- new_value = self .get_fk_identifiers (diffsync_instance , target_class , value ["pk" ])
252- if isinstance (new_value , (UUID , int )):
253- self .logger .error (
254- "Still unable to resolve reference?" ,
255- source = diffsync_instance ,
256- target = target_name ,
257- pk = new_value ,
258- )
259- else :
260- self .logger .debug (
261- "Replacing forward reference with identifiers" , pk = value ["pk" ], identifiers = new_value
262- )
263- setattr (diffsync_instance , fk_field , new_value )
264-
265237 def get_fk_identifiers (self , source_object , target_class , pk ):
266238 """Helper to load_record: given a class and a PK, get the identifiers of the given instance."""
239+ if isinstance (pk , int ):
240+ pk = netbox_pk_to_nautobot_pk (target_class .get_type (), pk )
267241 target_record = self .get_by_pk (target_class , pk )
268242 if not target_record :
269243 self .logger .debug (
@@ -281,6 +255,8 @@ def get_by_pk(self, obj, pk):
281255 modelname = obj
282256 else :
283257 modelname = obj .get_type ()
258+ if pk not in self ._data_by_pk [modelname ]:
259+ raise ObjectNotFound (f"PK { pk } not found in stored { modelname } instances" )
284260 return self ._data_by_pk [modelname ].get (pk )
285261
286262 def make_model (self , diffsync_model , data ):
@@ -293,18 +269,21 @@ def make_model(self, diffsync_model, data):
293269 "This may be an issue with your source data or may reflect a bug in this plugin." ,
294270 action = "load" ,
295271 exception = str (exc ),
296- model = diffsync_model ,
272+ model = diffsync_model . get_type () ,
297273 model_data = data ,
298274 )
299275 return None
300276 try :
301277 self .add (instance )
302278 except ObjectAlreadyExists :
279+ existing_instance = self .get (diffsync_model , instance .get_unique_id ())
303280 self .logger .warning (
304- "Apparent duplicate object encountered. "
281+ "Apparent duplicate object encountered? "
305282 "This may be an issue with your source data or may reflect a bug in this plugin." ,
306- model = instance ,
307- model_id = instance .get_unique_id (),
283+ duplicate_id = instance .get_identifiers (),
284+ model = diffsync_model .get_type (),
285+ pk_1 = existing_instance .pk ,
286+ pk_2 = instance .pk ,
308287 )
309288 return instance
310289
0 commit comments