Newer
Older
from dcim.models import Device, DeviceRole, DeviceType, Site
from ipam.models import Prefix
from dcim.choices import DeviceStatusChoices
from virtualization.choices import VirtualMachineStatusChoices
from virtualization.models import VirtualMachine
Fahrudin Halilovic
committed
from extras.models import Tag
from rest_framework import serializers
import json
import logging
from core.models import Job
from .models import AWXInventory
logger = logging.getLogger(__name__)
Fahrudin Halilovic
committed
group_prefixes = {Site: "site_", DeviceRole: "devicerole_", DeviceType: "devicetype_", Prefix: "prefix_", Tag:"tag_"}
Fahrudin Halilovic
committed
class TagSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
"name": "{}{}".format(group_prefixes[Tag], instance.slug.replace("-", "_")),
"variables": json.dumps({"netbox_tag_name": instance.name}),
}
class SiteSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
"name": "{}{}".format(group_prefixes[Site], instance.slug.replace("-", "_")),
"variables": json.dumps(
{
"netbox_site_status": instance.status,
}
),
}
class DeviceRoleSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
"name": "{}{}".format(group_prefixes[DeviceRole], instance.slug.replace("-", "_")),
"variables": json.dumps({})
}
class DeviceTypeSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
"name": "{}{}".format(group_prefixes[DeviceType], instance.slug.replace("-", "_")),
"description": instance.description,
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
"variables": json.dumps(
{
"netbox_devicetype_model": instance.model,
}
),
}
class PrefixSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
"name": "{}{}".format(group_prefixes[Prefix], str(instance.prefix).replace(".", "_").replace("/", "_")),
"variables": json.dumps({"netbox_prefix": "{}".format(str(instance.prefix))}),
}
class InterfaceSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
ip_addresses = []
for ip in instance.ip_addresses.all():
serializer = IPAddressSerializer(ip)
ip_addresses.append(serializer.data)
return {
"name": instance.name,
"mac": instance.mac_address,
"ip_addresses": ip_addresses
}
class IPAddressSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
"address": str(instance.address),
"dns_name": instance.dns_name
}
class DeviceSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
variables = {}
variables["netbox_interfaces"] = []
for interface in instance.interfaces.all():
serializer = InterfaceSerializer(interface)
variables["netbox_interfaces"].append(serializer.data)
return {
"name": getattr(instance.primary_ip4, 'dns_name', instance.name),
"description": instance.description,
"enabled": instance.status == DeviceStatusChoices.STATUS_ACTIVE,
"variables": json.dumps(variables),
}
class VMInterfaceSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
ip_addresses = []
for ip in instance.ip_addresses.all():
serializer = IPAddressSerializer(ip)
ip_addresses.append(serializer.data)
return {
"name": instance.name,
"mac": instance.mac_address,
"ip_addresses": ip_addresses
}
class VMSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
variables = {
"netbox_virtualmachine_name": instance.name,
"netbox_virtualmachine_vcpus": float(instance.vcpus) if instance.vcpus is not None else 0.0,
"netbox_virtualmachine_memory": instance.memory or 0,
"netbox_virtualmachine_disk": instance.disk or 0,
}
variables["netbox_interfaces"] = []
for interface in instance.interfaces.all():
serializer = VMInterfaceSerializer(interface)
variables["netbox_interfaces"].append(serializer.data)
return {
"name": getattr(instance.primary_ip4, 'dns_name', instance.name),
"description": instance.description,
"enabled": instance.status == VirtualMachineStatusChoices.STATUS_ACTIVE,
"variables": json.dumps(variables),
}
class AWXHostSerializer(serializers.BaseSerializer):
def to_internal_value(self, data):
return {
"name": data.get('name'),
"description": data.get('description'),
"enabled": data.get('enabled'),
"variables": data.get('variables')
}
class AWXGroupSerializer(serializers.BaseSerializer):
def to_internal_value(self, data):
return {
"name": data.get('name'),
"variables": data.get('variables')
}
serializers = {
Site: SiteSerializer,
DeviceRole: DeviceRoleSerializer,
DeviceType: DeviceTypeSerializer,
Prefix: PrefixSerializer,
Device: DeviceSerializer,
VirtualMachine: VMSerializer,
Fahrudin Halilovic
committed
Tag: TagSerializer
}
def sync_host(inventory, sender, instance):
serializer = serializers[sender](instance)
Fahrudin Halilovic
committed
# If the host doesn't exist, create it.
inventory.create_host(serializer.data)
host = inventory.get_host(serializer.data["name"])
else:
Fahrudin Halilovic
committed
# If the host exists, update it if necessary.
host_serializer = AWXHostSerializer(data=host)
if not host_serializer.is_valid() or host_serializer.validated_data != serializer.data:
inventory.update_host(host["id"], serializer.data)
Fahrudin Halilovic
committed
# Sync group associations
current_groups = host["summary_fields"]["groups"]["results"]
# Handle associations for each type (Site, DeviceRole, DeviceType, and Tags)
Fahrudin Halilovic
committed
sync_host_group_association(inventory, host, Site, instance.site, current_groups)
Fahrudin Halilovic
committed
sync_host_group_association(inventory, host, DeviceRole, instance.role, current_groups)
if isinstance(instance, Device) and instance.device_type:
sync_host_group_association(inventory, host, DeviceType, instance.device_type, current_groups)
if tags := instance.tags.all():
for tag in tags:
sync_host_group_association(inventory, host, Tag, tag, current_groups)
Fahrudin Halilovic
committed
# Disassociate groups that are no longer relevant
disassociate_removed_groups(inventory, host, instance, current_groups)
Fahrudin Halilovic
committed
def sync_host_group_association(inventory, host, object_type, instance, current_groups):
"""
Associates the host with the appropriate AWX group.
"""
serializer = serializers[object_type](instance)
group_name = serializer.data["name"]
Fahrudin Halilovic
committed
# Filter out the groups that match the current object_type (e.g., Site, Tag)
relevant_groups = [
group for group in current_groups if group["name"].startswith(group_prefixes[object_type])
Fahrudin Halilovic
committed
# Check if the host is already in the desired group
if group_name not in [group["name"] for group in relevant_groups]:
Fahrudin Halilovic
committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
if group:
inventory.associate_host_group(host["id"], group["id"])
logger.info(f"Host {host['name']} associated with group {group_name}.")
def disassociate_removed_groups(inventory, host, instance, current_groups):
"""
Disassociates the host from AWX groups that are no longer relevant.
"""
# Collect the current groups that should remain (based on the instance's attributes)
valid_group_names = set()
if instance.site:
valid_group_names.add(f"{group_prefixes[Site]}{instance.site.slug.replace('-', '_')}")
if instance.role:
valid_group_names.add(f"{group_prefixes[DeviceRole]}{instance.role.slug.replace('-', '_')}")
if isinstance(instance, Device) and instance.device_type:
valid_group_names.add(f"{group_prefixes[DeviceType]}{instance.device_type.slug.replace('-', '_')}")
if tags := instance.tags.all():
valid_group_names.update(
f"{group_prefixes[Tag]}{tag.slug.replace('-', '_')}" for tag in tags
)
# Disassociate from groups that are no longer valid
for group in current_groups:
if group["name"] not in valid_group_names:
inventory.disassociate_host_group(host["id"], group["id"])
logger.info(f"Host {host['name']} disassociated from group {group['name']}.")
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def delete_host(inventory, sender, instance):
serializer = serializers[sender](instance)
inventory.delete_host(serializer.data["name"])
def sync_group(inventory, sender, instance):
serializer = serializers[sender](instance)
group = inventory.get_group(serializer.data["name"])
if group is None:
inventory.create_group(serializer.data)
group = inventory.get_group(serializer.data["name"])
else:
group_serializer = AWXGroupSerializer(data=group)
if not group_serializer.is_valid() or group_serializer.validated_data != serializer.data:
print("Group will be updated due to:")
print(group_serializer.validated_data)
print(serializer.data)
inventory.update_group(serializer.data["name"], serializer.data)
def delete_group(inventory, sender, instance):
serializer = serializers[sender](instance)
inventory.delete_group(serializer.data["name"])
def sync_all(job):
inventory = job.object
logger.info("Performing full inventory sync for inventory {}".format(inventory.inventory_id))
job.start()
for site in Site.objects.all():
sync_group(inventory, Site, site)
for device_role in DeviceRole.objects.all():
sync_group(inventory, DeviceRole, device_role)
for device_type in DeviceType.objects.all():
sync_group(inventory, DeviceType, device_type)
Fahrudin Halilovic
committed
for tag in Tag.objects.all():
sync_group(inventory, Tag, tag)
for device in Device.objects.all():
if not device.primary_ip4 is None and device.primary_ip4.dns_name:
sync_host(inventory, Device, device)
for vm in VirtualMachine.objects.all():
if not vm.primary_ip4 is None and vm.primary_ip4.dns_name:
sync_host(inventory, VirtualMachine, vm)