mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-19 05:21:55 -06:00
Merge pull request #2445 from digitalocean/local-config-context
Local config context
This commit is contained in:
commit
7616bcad3d
@ -1,3 +1,5 @@
|
|||||||
# Contextual Configuration Data
|
# Contextual Configuration Data
|
||||||
|
|
||||||
Sometimes it is desirable to associate arbitrary data with a group of devices to aid in their configuration. For example, you might want to associate a set of syslog servers for all devices at a particular site. Context data enables the association of arbitrary data to devices and virtual machines grouped by region, site, role, platform, and/or tenant. Context data is arranged hierarchically, so that data with a higher weight can be entered to override more general lower-weight data. Multiple instances of data are automatically merged by NetBox to present a single dictionary for each object.
|
Sometimes it is desirable to associate arbitrary data with a group of devices to aid in their configuration. For example, you might want to associate a set of syslog servers for all devices at a particular site. Context data enables the association of arbitrary data to devices and virtual machines grouped by region, site, role, platform, and/or tenant. Context data is arranged hierarchically, so that data with a higher weight can be entered to override more general lower-weight data. Multiple instances of data are automatically merged by NetBox to present a single dictionary for each object.
|
||||||
|
|
||||||
|
Devices and Virtual Machines may also have a local config context defined. This local context will always overwrite the rendered config context objects for the Device/VM. This is useful in situations were the device requires a one-off value different from the rest of the environment.
|
||||||
|
@ -412,7 +412,7 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
||||||
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', 'created',
|
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', 'created',
|
||||||
'last_updated',
|
'last_updated', 'local_context_data',
|
||||||
]
|
]
|
||||||
validators = []
|
validators = []
|
||||||
|
|
||||||
@ -448,7 +448,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
|||||||
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
||||||
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields',
|
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields',
|
||||||
'config_context', 'created', 'last_updated',
|
'config_context', 'created', 'last_updated', 'local_context_data',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_config_context(self, obj):
|
def get_config_context(self, obj):
|
||||||
|
@ -18,7 +18,7 @@ from utilities.forms import (
|
|||||||
AnnotatedMultipleChoiceField, APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
AnnotatedMultipleChoiceField, APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
||||||
BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ComponentForm,
|
BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ComponentForm,
|
||||||
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField,
|
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField,
|
||||||
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField,
|
FlexibleModelChoiceField, JSONField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .constants import (
|
from .constants import (
|
||||||
@ -822,16 +822,19 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
tags = TagField(required=False)
|
tags = TagField(required=False)
|
||||||
|
local_context_data = JSONField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face',
|
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face',
|
||||||
'status', 'platform', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'tags',
|
'status', 'platform', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'tags',
|
||||||
|
'local_context_data'
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'device_role': "The function this device serves",
|
'device_role': "The function this device serves",
|
||||||
'serial': "Chassis serial number",
|
'serial': "Chassis serial number",
|
||||||
|
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered config context"
|
||||||
}
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
'face': forms.Select(attrs={'filter-for': 'position'}),
|
'face': forms.Select(attrs={'filter-for': 'position'}),
|
||||||
|
19
netbox/dcim/migrations/0063_device_local_context_data.py
Normal file
19
netbox/dcim/migrations/0063_device_local_context_data.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 2.0.8 on 2018-09-16 02:01
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0062_interface_mtu'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='device',
|
||||||
|
name='local_context_data',
|
||||||
|
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -711,6 +711,11 @@ class ConfigContext(models.Model):
|
|||||||
|
|
||||||
class ConfigContextModel(models.Model):
|
class ConfigContextModel(models.Model):
|
||||||
|
|
||||||
|
local_context_data = JSONField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@ -724,6 +729,10 @@ class ConfigContextModel(models.Model):
|
|||||||
for context in ConfigContext.objects.get_for_object(self):
|
for context in ConfigContext.objects.get_for_object(self):
|
||||||
data.update(context.data)
|
data.update(context.data)
|
||||||
|
|
||||||
|
# If the object has local config context data defined, that data overwrites all rendered data
|
||||||
|
if self.local_context_data is not None:
|
||||||
|
data.update(self.local_context_data)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,9 +106,11 @@ class ObjectConfigContextView(View):
|
|||||||
|
|
||||||
obj = get_object_or_404(self.object_class, pk=pk)
|
obj = get_object_or_404(self.object_class, pk=pk)
|
||||||
source_contexts = ConfigContext.objects.get_for_object(obj)
|
source_contexts = ConfigContext.objects.get_for_object(obj)
|
||||||
|
model_name = self.object_class._meta.model_name
|
||||||
|
|
||||||
return render(request, 'extras/object_configcontext.html', {
|
return render(request, 'extras/object_configcontext.html', {
|
||||||
self.object_class._meta.model_name: obj,
|
model_name: obj,
|
||||||
|
'obj': obj,
|
||||||
'rendered_context': obj.get_config_context(),
|
'rendered_context': obj.get_config_context(),
|
||||||
'source_contexts': source_contexts,
|
'source_contexts': source_contexts,
|
||||||
'base_template': self.base_template,
|
'base_template': self.base_template,
|
||||||
|
@ -77,6 +77,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><strong>Local Config Context Data</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_field form.local_context_data %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Tags</strong></div>
|
<div class="panel-heading"><strong>Tags</strong></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
@ -16,6 +16,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Local Context</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% if obj.local_context_data %}
|
||||||
|
<pre>{{ obj.local_context_data|render_json }}</pre>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
<span class="help-block">
|
||||||
|
<i class="fa fa-info-circle"></i>
|
||||||
|
The local config context overwrites all source contexts.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Source Contexts</strong>
|
<strong>Source Contexts</strong>
|
||||||
|
@ -48,6 +48,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><strong>Local Config Context Data</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_field form.local_context_data %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Tags</strong></div>
|
<div class="panel-heading"><strong>Tags</strong></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
@ -107,6 +107,7 @@ class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4',
|
'id', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4',
|
||||||
'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
|
'local_context_data',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -117,6 +118,7 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
||||||
'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
|
'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
|
||||||
|
'local_context_data',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_config_context(self, obj):
|
def get_config_context(self, obj):
|
||||||
|
@ -16,7 +16,8 @@ from tenancy.models import Tenant
|
|||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||||
ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
|
ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
|
||||||
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea, add_blank_choice
|
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, SmallTextarea,
|
||||||
|
add_blank_choice
|
||||||
)
|
)
|
||||||
from .constants import VM_STATUS_CHOICES
|
from .constants import VM_STATUS_CHOICES
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
@ -246,6 +247,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
tags = TagField(required=False)
|
tags = TagField(required=False)
|
||||||
|
local_context_data = JSONField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
@ -253,6 +255,9 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
||||||
'vcpus', 'memory', 'disk', 'comments', 'tags',
|
'vcpus', 'memory', 'disk', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
help_texts = {
|
||||||
|
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered config context",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 2.0.8 on 2018-09-16 02:01
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('virtualization', '0007_change_logging'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='virtualmachine',
|
||||||
|
name='local_context_data',
|
||||||
|
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
Loading…
Reference in New Issue
Block a user