Skip to content
Snippets Groups Projects

Add support for Tags as AWX Groups with m2m-changed signal handling, including...

Merged Fahrudin Halilovic requested to merge feature/INFRA-10378-awx-tag-sync into main
2 files
+ 176
76
Compare changes
  • Side-by-side
  • Inline
Files
2
+ 108
57
from django.db.models.signals import post_save, pre_save, post_delete
import logging
from django.db.models.signals import post_save, pre_save, post_delete, m2m_changed
from django.dispatch import receiver
from django_rq import get_queue
from dcim.models import Interface, Device, DeviceRole, DeviceType, Site
from ipam.models import Prefix
from virtualization.models import VirtualMachine
from extras.models import Tag
from netbox.config import get_config
from netbox.constants import RQ_QUEUE_DEFAULT
from .models import AWXInventory
import logging
logger = logging.getLogger(__name__)
__all__ = [
"update_host",
]
# Configure queue name from settings
queue_name = get_config().QUEUE_MAPPINGS.get("awx", RQ_QUEUE_DEFAULT)
@receiver(post_save, sender=Site)
@receiver(post_save, sender=DeviceRole)
@receiver(post_save, sender=DeviceType)
@receiver(post_save, sender=Prefix)
def save_group_instance(sender, instance, created, **kwargs):
rq_queue = get_queue(queue_name)
for inventory in AWXInventory.objects.all():
if inventory.enabled == True:
params = {"inventory": inventory, "sender": sender, "instance": instance}
rq_queue.enqueue("netbox_awx_plugin.synchronization.sync_group", **params)
@receiver(pre_save, sender=Site)
@receiver(pre_save, sender=DeviceRole)
@receiver(pre_save, sender=DeviceType)
@receiver(pre_save, sender=Prefix)
def pre_save_group_instance(sender, instance, **kwargs):
if sender.objects.filter(id=instance.id).exists():
instance.__original_object = sender.objects.get(id=instance.id)
@receiver(post_delete, sender=Site)
@receiver(post_delete, sender=DeviceRole)
@receiver(post_delete, sender=DeviceType)
@receiver(post_delete, sender=Prefix)
def save_group_instance(sender, instance, **kwargs):
def enqueue_task(task_name, inventory, sender, instance):
"""
Helper function to enqueue AWX tasks in the RQ queue.
"""
rq_queue = get_queue(queue_name)
for inventory in AWXInventory.objects.all():
if inventory.enabled == True:
params = {"inventory": inventory, "sender": sender, "instance": instance}
rq_queue.enqueue("netbox_awx_plugin.synchronization.delete_group", **params)
@receiver(post_save, sender=Device)
@receiver(post_save, sender=VirtualMachine)
def save_device(sender, instance, created, **kwargs):
device = sender.objects.get(id=instance.id)
if not device.primary_ip4 is None and device.primary_ip4.dns_name:
rq_queue = get_queue(queue_name)
for inventory in AWXInventory.objects.all():
if inventory.enabled == True:
params = {"inventory": inventory, "sender": sender, "instance": device}
rq_queue.enqueue("netbox_awx_plugin.synchronization.sync_host", **params)
@receiver(post_save, sender=Interface)
def save_interface(sender, instance, **kwargs):
save_device(Device, instance.device, **kwargs)
task_params = {"inventory": inventory, "sender": sender, "instance": instance}
logger.debug(f"Enqueuing task '{task_name}' for {sender.__name__} instance {instance.pk}")
rq_queue.enqueue(f"netbox_awx_plugin.synchronization.{task_name}", **task_params)
def process_inventory_task(task_name, sender, instance):
"""
Processes a task for all enabled AWX inventories.
"""
inventories = AWXInventory.objects.filter(enabled=True)
if not inventories.exists():
logger.info(f"No enabled AWX inventories found. Skipping {task_name}.")
return
for inventory in inventories:
enqueue_task(task_name, inventory, sender, instance)
### Signal Handlers for Group-Related Models ###
@receiver([post_save], sender=Site)
@receiver([post_save], sender=DeviceRole)
@receiver([post_save], sender=DeviceType)
@receiver([post_save], sender=Prefix)
def handle_group_post_save(sender, instance, created, **kwargs):
"""
Handles post-save events for group-related models like Site, DeviceRole, etc.
This synchronizes AWX groups when the object is saved.
"""
process_inventory_task("sync_group", sender, instance)
@receiver([pre_save], sender=Site)
@receiver([pre_save], sender=DeviceRole)
@receiver([pre_save], sender=DeviceType)
@receiver([pre_save], sender=Prefix)
def handle_group_pre_save(sender, instance, **kwargs):
"""
Handles pre-save events for group-related models to store the original instance for comparison.
"""
if instance.pk and sender.objects.filter(pk=instance.pk).exists():
instance._original = sender.objects.get(pk=instance.pk)
logger.debug(f"Original object stored for {sender.__name__} instance {instance.pk}")
@receiver([post_delete], sender=Site)
@receiver([post_delete], sender=DeviceRole)
@receiver([post_delete], sender=DeviceType)
@receiver([post_delete], sender=Prefix)
def handle_group_post_delete(sender, instance, **kwargs):
"""
Handles post-delete events for group-related models to remove AWX groups when objects are deleted.
"""
process_inventory_task("delete_group", sender, instance)
### Signal Handlers for Device and VirtualMachine ###
@receiver([post_save], sender=Device)
@receiver([post_save], sender=VirtualMachine)
def handle_device_post_save(sender, instance, **kwargs):
"""
Handles post-save events for Devices and Virtual Machines.
Synchronizes AWX hosts if the instance meets the IP and DNS requirements.
"""
instance = sender.objects.select_related("primary_ip4").get(pk=instance.pk)
if instance.primary_ip4 and instance.primary_ip4.dns_name:
logger.debug(f"Device/VM {instance.pk} meets IP/DNS criteria. Triggering host sync.")
process_inventory_task("sync_host", sender, instance)
else:
logger.debug(f"Device/VM {instance.pk} does not meet IP/DNS criteria for AWX sync.")
@receiver([post_save], sender=Interface)
def handle_interface_post_save(sender, instance, **kwargs):
"""
Handles post-save events for Interfaces. Delegates synchronization to the associated Device.
"""
if instance.device:
logger.debug(f"Interface {instance.pk} saved, delegating sync to associated Device.")
handle_device_post_save(Device, instance.device, **kwargs)
### Tag Synchronization ###
@receiver(post_save, sender=Tag)
def handle_tag_post_save(sender, instance, created, **kwargs):
"""
Handles post-save events for Tags. Synchronizes AWX groups when a Tag is saved.
"""
process_inventory_task("sync_group", sender, instance)
@receiver(m2m_changed, sender=Device.tags.through)
@receiver(m2m_changed, sender=VirtualMachine.tags.through)
def handle_tag_assignment(sender, instance, action, reverse, model, pk_set, **kwargs):
"""
Handles many-to-many change events (m2m_changed) for Device and VirtualMachine tags.
Associates hosts with AWX groups when tags are assigned.
"""
if action == "post_add":
logger.debug(f"Tags added to {instance}. Syncing host with AWX group.")
process_inventory_task("sync_host", Device if Device.tags.through else VirtualMachine, instance)
if action == "post_remove":
logger.debug(f"Tags removed to {instance}. Syncing host with AWX group.")
process_inventory_task("sync_host", Device if Device.tags.through else VirtualMachine, instance)
\ No newline at end of file
Loading