Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions nbxsync/api/views/zabbixsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
from rest_framework.viewsets import ViewSet
from django.apps import apps
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema

from nbxsync.api.serializers import ZabbixHostInterfaceSerializer
from nbxsync.models import ZabbixHostInterface
from nbxsync.constants import OBJECT_TYPE_MODEL_MAP


class ZabbixSyncViewSet(ViewSet):
permission_classes = [IsAuthenticated]
serializer_class = ZabbixHostInterfaceSerializer

@extend_schema(exclude=True)
def create(self, request, **kwargs):
obj_type = (request.data.get('obj_type') or '').strip().lower()
obj_id = request.data.get('obj_id')
Expand Down
24 changes: 20 additions & 4 deletions nbxsync/forms/zabbixtagassignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from nbxsync.constants import ASSIGNMENT_TYPE_TO_FIELD
from nbxsync.models import ZabbixTag, ZabbixTagAssignment, ZabbixConfigurationGroup
from nbxsync.utils import get_assigned_zabbixobjects

__all__ = ('ZabbixTagAssignmentForm', 'ZabbixTagAssignmentFilterForm', 'ZabbixTagAssignmentBulkEditForm')
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -71,21 +72,23 @@ def assignable_fields(self):
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
initial = kwargs.get('initial', {}).copy()
target = None

if instance and instance.assigned_object:
target = instance.assigned_object
for model_class, field in ASSIGNMENT_TYPE_TO_FIELD.items():
if isinstance(instance.assigned_object, model_class):
initial[field] = instance.assigned_object
initial[field] = target
break

elif 'assigned_object_type' in initial and 'assigned_object_id' in initial:
try:
content_type = ContentType.objects.get(pk=initial['assigned_object_type'])
obj = content_type.get_object_for_this_type(pk=initial['assigned_object_id'])
target = content_type.get_object_for_this_type(pk=initial['assigned_object_id'])

for model_class, field in ASSIGNMENT_TYPE_TO_FIELD.items():
if isinstance(obj, model_class):
initial[field] = obj.pk
if isinstance(target, model_class):
initial[field] = target.pk
break

except Exception as e:
Expand All @@ -95,6 +98,19 @@ def __init__(self, *args, **kwargs):
kwargs['initial'] = initial
super().__init__(*args, **kwargs)

if target is not None:
assigned = get_assigned_zabbixobjects(target)
excluded_ids = set()
for assigned_tag in assigned['tags']:
excluded_ids.add(assigned_tag.zabbixtag_id)

if instance is not None and instance.pk and instance.zabbixtag_id:
excluded_ids.discard(instance.zabbixtag_id)

if excluded_ids:
self.fields['zabbixtag'].queryset = ZabbixTag.objects.exclude(pk__in=excluded_ids)
self.fields['zabbixtag'].widget.add_query_params({'id__n': list(excluded_ids)})

def clean(self):
super().clean()

Expand Down
22 changes: 19 additions & 3 deletions nbxsync/forms/zabbixtemplateassignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from nbxsync.constants import ASSIGNMENT_TYPE_TO_FIELD, ASSIGNMENT_TYPE_TO_FIELD_NBOBJS
from nbxsync.models import ZabbixTemplate, ZabbixTemplateAssignment, ZabbixConfigurationGroup
from nbxsync.utils import get_assigned_zabbixobjects

__all__ = ('ZabbixTemplateAssignmentForm', 'ZabbixTemplateAssignmentFilterForm')
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -71,8 +72,10 @@ def assignable_fields(self):
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
initial = kwargs.get('initial', {}).copy()
target = None

if instance and instance.assigned_object:
target = instance.assigned_object
for model_class, field in ASSIGNMENT_TYPE_TO_FIELD.items():
if isinstance(instance.assigned_object, model_class):
initial[field] = instance.assigned_object
Expand All @@ -81,11 +84,11 @@ def __init__(self, *args, **kwargs):
elif 'assigned_object_type' in initial and 'assigned_object_id' in initial:
try:
content_type = ContentType.objects.get(pk=initial['assigned_object_type'])
obj = content_type.get_object_for_this_type(pk=initial['assigned_object_id'])
target = content_type.get_object_for_this_type(pk=initial['assigned_object_id'])

for model_class, field in ASSIGNMENT_TYPE_TO_FIELD.items():
if isinstance(obj, model_class):
initial[field] = obj.pk
if isinstance(target, model_class):
initial[field] = target.pk
break

except Exception as e:
Expand All @@ -95,6 +98,19 @@ def __init__(self, *args, **kwargs):
kwargs['initial'] = initial
super().__init__(*args, **kwargs)

if target is not None:
assigned = get_assigned_zabbixobjects(target)
excluded_ids = set()
for assigned_template in assigned['templates']:
excluded_ids.add(assigned_template.zabbixtemplate_id)

if instance is not None and instance.pk and instance.zabbixtemplate_id:
excluded_ids.discard(instance.zabbixtemplate_id)

if excluded_ids:
self.fields['zabbixtemplate'].queryset = ZabbixTemplate.objects.exclude(pk__in=excluded_ids)
self.fields['zabbixtemplate'].widget.add_query_params({'id__n': list(excluded_ids)})

def clean(self):
super().clean()

Expand Down
2 changes: 1 addition & 1 deletion nbxsync/jobs/synctemplates.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def run(self):

self.instance.last_sync_state = True
self.instance.last_sync = make_aware(datetime.datetime.now())
self.instance.last_sync_message = 'Succes'
self.instance.last_sync_message = 'Success'
self.instance.save()

except ConnectionError as e:
Expand Down
46 changes: 44 additions & 2 deletions nbxsync/models/zabbixmacroassignment.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import re

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models

from netbox.models import NetBoxModel
from jinja2 import TemplateError, TemplateSyntaxError, UndefinedError
from utilities.jinja2 import render_jinja2

from netbox.models import NetBoxModel
from nbxsync.constants import ASSIGNMENT_MODELS
from nbxsync.models import SyncInfoModel
from nbxsync.models import SyncInfoModel, ZabbixConfigurationGroup

__all__ = ('ZabbixMacroAssignment',)

TEMPLATE_PATTERN = re.compile(r'({{.*?}}|{%-?\s*.*?\s*-?%}|{#.*?#})')


class ZabbixMacroAssignment(SyncInfoModel, NetBoxModel):
zabbixmacro = models.ForeignKey('nbxsync.ZabbixMacro', on_delete=models.CASCADE, related_name='zabbixmacroassignment')
Expand Down Expand Up @@ -59,6 +65,42 @@ def __str__(self):
return f'{self.zabbixmacro.macro[:-1]}:{self.context}}}'
return self.zabbixmacro.macro

def is_template(self):
return bool(TEMPLATE_PATTERN.search(self.value))

def render(self, **context):
if isinstance(self.assigned_object, ZabbixConfigurationGroup):
return self.value, True

context = self.get_context(**context)

try:
output = render_jinja2(self.value, context)
output = output.replace('\r\n', '\n')
return output, True

except TemplateSyntaxError as err:
return self.value, False

except UndefinedError as err:
return self.value, False

except TemplateError as err:
return self.value, False

except Exception as err:
return self.value, False

def get_context(self, **extra_context):
context = {
'object': self.assigned_object,
'macro': self.zabbixmacro.macro,
'value': self.value,
'description': self.zabbixmacro.description,
}
context.update(extra_context)
return context

@property
def full_name(self):
return self
10 changes: 10 additions & 0 deletions nbxsync/tables/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,13 @@ def render(self, value):
if model is None:
return capfirst(value.model.replace('_', ' '))
return capfirst(model._meta.verbose_name)


class JinjaValueColumn(tables.Column):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def render(self, value, record, table):
instance = getattr(table, 'instance', None)
output, success = record.render(object=instance)
return output
23 changes: 16 additions & 7 deletions nbxsync/tables/zabbixmacroassignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from nbxsync.models import ZabbixMacroAssignment
from nbxsync.tables import ZabbixInheritedAssignmentTable
from nbxsync.tables.columns import ContentTypeModelNameColumn, InheritanceAwareActionsColumn
from nbxsync.tables.columns import ContentTypeModelNameColumn, InheritanceAwareActionsColumn, JinjaValueColumn
from nbxsync.choices import ZabbixMacroTypeChoices

__all__ = ('ZabbixMacroAssignmentTable', 'ZabbixMacroAssignmentObjectViewTable')
Expand All @@ -18,6 +18,11 @@ class ZabbixMacroAssignmentTable(ZabbixInheritedAssignmentTable, NetBoxTable):
zabbixmacro = tables.Column(accessor='zabbixmacro.macro', verbose_name=_('Zabbix Macro'), linkify={'viewname': 'plugins:nbxsync:zabbixmacro', 'args': [A('zabbixmacro.pk')]})
macro_full_name = tables.Column(accessor='full_name', verbose_name=_('Macro'), order_by='zabbixmacro__macro')
actions = InheritanceAwareActionsColumn()
rendered_output = JinjaValueColumn(
verbose_name='Value',
orderable=False,
accessor='value',
)

class Meta(NetBoxTable.Meta):
model = ZabbixMacroAssignment
Expand All @@ -30,10 +35,10 @@ class Meta(NetBoxTable.Meta):
'inherited_from',
'is_context',
'regex',
'value',
'rendered_output',
'created',
'last_updated',
'actions,',
'actions',
)
default_columns = (
'pk',
Expand All @@ -43,7 +48,7 @@ class Meta(NetBoxTable.Meta):
'macro_full_name',
'is_context',
'regex',
'value',
'rendered_output',
'inherited_from',
)

Expand All @@ -58,7 +63,11 @@ class ZabbixMacroAssignmentObjectViewTable(ZabbixInheritedAssignmentTable, NetBo
assigned_object = tables.Column(verbose_name=_('Assigned To'), linkify=True, orderable=False)
macro_full_name = tables.Column(accessor='full_name', verbose_name=_('Macro'), order_by='zabbixmacro__macro', linkify={'viewname': 'plugins:nbxsync:zabbixmacro', 'args': [A('zabbixmacro.pk')]})
actions = InheritanceAwareActionsColumn()
value = tables.Column(verbose_name='Value')
rendered_output = JinjaValueColumn(
verbose_name='Value',
orderable=False,
accessor='value',
)

class Meta(NetBoxTable.Meta):
model = ZabbixMacroAssignment
Expand All @@ -71,7 +80,7 @@ class Meta(NetBoxTable.Meta):
'inherited_from',
'is_regex',
'context',
'value',
'rendered_output',
'created',
'last_updated',
'actions,',
Expand All @@ -81,7 +90,7 @@ class Meta(NetBoxTable.Meta):
'macro_full_name',
'is_regex',
'context',
'value',
'rendered_output',
'inherited_from',
)

Expand Down
2 changes: 1 addition & 1 deletion nbxsync/templates/nbxsync/forms/zabbixhostinterface.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ <h2 class="col-9 offset-3">SNMPv3 Configuration</h2>
<div class="field-group my-5">
<div class="row mb-2">
{% for fieldset in form.fieldsets %}
{% if fieldset.name == "Assignment" %}
{% if fieldset.name == _('Assignment') %}
{% render_fieldset form fieldset %}
{% endif %}
{% endfor %}
Expand Down
2 changes: 1 addition & 1 deletion nbxsync/templates/nbxsync/forms/zabbixmacroassignment.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ <h2 class="col-9 offset-3">Generic</h2>
</div>
</div>
{% for fieldset in form.fieldsets %}
{% if fieldset.name == "Assignment" %}
{% if fieldset.name == _('Assignment') %}
{% render_fieldset form fieldset %}
{% endif %}
{% endfor %}
Expand Down
8 changes: 7 additions & 1 deletion nbxsync/templates/nbxsync/zabbixmacroassignment.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{% load static %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load zabbix_macros %}
{% block content %}
<div class="row">
<div class="col col-md-6">
Expand Down Expand Up @@ -43,8 +44,13 @@ <h5 class="card-header">Zabbix Macro Assignment</h5>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% render_zabbix_macro_assignment object as rendered_output %}
<div class="card">
<div class="card-header">Rendered Value</div>
<div class="card-body">{{ rendered_output|safe }}</div>
</div>
{% plugin_right_page object %}
{% include 'inc/panels/tags.html' %}
</div>
</div
</div>
{% endblock %}
1 change: 1 addition & 0 deletions nbxsync/templatetags/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .zabbix_tags import *
from .zabbix_hostgroups import *
from .zabbix_hostinterfaces import *
from .zabbix_macros import *
from .render_field import *
9 changes: 9 additions & 0 deletions nbxsync/templatetags/zabbix_macros.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django import template

register = template.Library()


@register.simple_tag
def render_zabbix_macro_assignment(assignment, **context):
output, success = assignment.render(**context)
return output
12 changes: 12 additions & 0 deletions nbxsync/tests/utils/test_hostgroupsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,15 @@ def test_set_id_dynamic_updates_existing_hostgroup(self):
self.assertEqual(updated_hg.groupid, 999)
self.assertEqual(updated_hg.name, 'ExistingGroup')
self.assertEqual(updated_hg.zabbixserver, self.zabbixserver)

def test_set_id_dynamic_creates_rendered_hostgroup_when_missing(self):
rendered_name, ok = self.assignment_dynamic.render()
self.assertTrue(ok)

sync = HostGroupSync(api=None, netbox_obj=self.assignment_dynamic)
sync.set_id(321)

created_hg = ZabbixHostgroup.objects.get(zabbixserver=self.zabbixserver, name=rendered_name)
self.assertEqual(created_hg.value, rendered_name)
self.assertEqual(created_hg.groupid, 321)
self.assertEqual(created_hg.description, 'Automatically generated from template')
6 changes: 6 additions & 0 deletions nbxsync/tests/utils/test_hostsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ def __init__(self):
self.zabbixmacro = DummyZabbixMacro(type_=1, description='Test Macro')
self.value = 'secret-value'

def render(self, object):
return 'secret-value', True

def __str__(self):
return '{$TEST_MACRO}'

Expand All @@ -265,6 +268,9 @@ def __init__(self):
self.zabbixmacro = DummyZabbixMacro(type_=1, description='From NetBox')
self.value = 'nb-value'

def render(self, object):
return 'nb-value', True

def __str__(self):
return '{$FROM_NETBOX}'

Expand Down
Loading