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
2 changes: 1 addition & 1 deletion netbox_docker_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class NetBoxDockerConfig(PluginConfig):
name = "netbox_docker_plugin"
verbose_name = " NetBox Docker Plugin"
description = "Manage Docker"
version = "5.1.1"
version = "5.2.0"
base_url = "docker"
min_version = "4.5.0"
author = "Vincent Simonin <vincent@saashup.com>, David Delassus <david.jose.delassus@gmail.com>"
Expand Down
3 changes: 3 additions & 0 deletions netbox_docker_plugin/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from rest_framework import serializers
from utilities.query import dict_to_filter_params
from users.models import Token
from virtualization.api.serializers import VirtualMachineSerializer
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
from ..models.host import Host
from ..models.image import Image
Expand Down Expand Up @@ -693,6 +694,7 @@ class HostSerializer(NetBoxModelSerializer):
containers = NestedContainerSerializer(many=True, read_only=True)
registries = NestedRegistrySerializer(many=True, read_only=True)
token = NestedTokenSerializer(read_only=True)
virtual_machine = VirtualMachineSerializer(required=False)

class Meta:
"""Host Serializer Meta class"""
Expand All @@ -706,6 +708,7 @@ class Meta:
"name",
"state",
"token",
"virtual_machine",
"netbox_base_url",
"agent_version",
"docker_api_version",
Expand Down
10 changes: 8 additions & 2 deletions netbox_docker_plugin/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ class HostViewSet(NetBoxModelViewSet):
"""Host view set class"""

queryset = Host.objects.prefetch_related(
"images", "volumes", "networks", "containers", "registries", "tags"
"images",
"volumes",
"networks",
"containers",
"registries",
"virtual_machine",
"tags",
)
filterset_class = filtersets.HostFilterSet
serializer_class = HostSerializer
Expand Down Expand Up @@ -108,7 +114,7 @@ def force_pull(self, _request, **_kwargs):
url,
timeout=10,
data=json.dumps({"data": data}, cls=DjangoJSONEncoder),
headers={"Content-Type": "application/json"}
headers={"Content-Type": "application/json"},
)
resp.raise_for_status()

Expand Down
1 change: 1 addition & 0 deletions netbox_docker_plugin/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Meta:
"state",
"agent_version",
"docker_api_version",
"virtual_machine",
)

# pylint: disable=W0613
Expand Down
38 changes: 37 additions & 1 deletion netbox_docker_plugin/forms/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from django import forms
from utilities.forms.rendering import FieldSet
from utilities.forms.fields import TagFilterField
from utilities.forms.fields import TagFilterField, DynamicModelChoiceField
from virtualization.models import VirtualMachine
from netbox.forms import (
NetBoxModelForm,
NetBoxModelImportForm,
Expand All @@ -12,16 +13,51 @@
from ..models.host import Host, HostStateChoices


class HostAddForm(NetBoxModelForm):
"""Host form definition class"""

virtual_machine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all(),
selector=True,
required=False,
label="Virtual Machine",
)

class Meta:
"""Host form definition Meta class"""

model = Host
fields = (
"name",
"endpoint",
"virtual_machine",
)
help_texts = {"name": "Unique Name", "endpoint": "Docker instance endpoint"}
labels = {"name": "Name", "endpoint": "Endpoint"}

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields.pop("tags", None)


class HostForm(NetBoxModelForm):
"""Host form definition class"""

virtual_machine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all(),
selector=True,
required=False,
label="Virtual Machine",
)

class Meta:
"""Host form definition Meta class"""

model = Host
fields = (
"name",
"endpoint",
"virtual_machine",
"tags",
)
help_texts = {"name": "Unique Name", "endpoint": "Docker instance endpoint"}
Expand Down
29 changes: 29 additions & 0 deletions netbox_docker_plugin/migrations/1044_host_virtual_machine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# pylint: disable=C0103
"""Migration file"""

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
"""Migration file"""

dependencies = [
(
"netbox_docker_plugin",
"1043_container_cap_drop_container_extra_hosts_and_more",
),
]

operations = [
migrations.AddField(
model_name="host",
name="virtual_machine",
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="virtualization.virtualmachine",
),
),
]
6 changes: 6 additions & 0 deletions netbox_docker_plugin/models/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,13 +328,19 @@ def get_absolute_url(self):
return reverse("plugins:netbox_docker_plugin:container", args=[self.pk])

def clean(self):
"""Validate that the image belongs to the same host and has been pulled."""
super().clean()

if self.host != self.image.host:
raise ValidationError(
{"image": f"Image {self.image} does not belong to host {self.host}."}
)

if not self.image.ImageID:
raise ValidationError(
{"image": f"Image {self.image} has no ID yet. Pull or refresh it first."}
)


class Port(models.Model):
"""Container definition class"""
Expand Down
7 changes: 7 additions & 0 deletions netbox_docker_plugin/models/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
)
from utilities.choices import ChoiceSet
from users.models import Token
from virtualization.models import VirtualMachine
from netbox.models import NetBoxModel


Expand Down Expand Up @@ -64,6 +65,12 @@ class Host(NetBoxModel):
default=HostStateChoices.STATE_CREATED,
)
token = models.ForeignKey(Token, on_delete=models.SET_NULL, null=True, blank=True)
virtual_machine = models.OneToOneField(
VirtualMachine,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
netbox_base_url = models.CharField(
max_length=1024,
validators=[URLValidator()],
Expand Down
2 changes: 2 additions & 0 deletions netbox_docker_plugin/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class HostTable(NetBoxTable):
"""Host Table definition class"""

name = tables.Column(linkify=True)
virtual_machine = tables.Column(linkify=True, verbose_name="Virtual Machine")
image_count = columns.LinkedCountColumn(
viewname="plugins:netbox_docker_plugin:image_list",
url_params={"host_id": "pk"},
Expand Down Expand Up @@ -62,6 +63,7 @@ class Meta(NetBoxTable.Meta):
"name",
"endpoint",
"state",
"virtual_machine",
"agent_version",
"docker_api_version",
"image_count",
Expand Down
23 changes: 23 additions & 0 deletions netbox_docker_plugin/template_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Template modifications definitions"""

# pylint: disable=W0223

from netbox.plugins import PluginTemplateExtension
from .models import Host


class VirtualMachineHostTable(PluginTemplateExtension):
"""Virtual machine object template"""

models = ["virtualization.virtualmachine"]

def right_page(self):
return self.render(
"netbox_docker_plugin/virtual_machine_host_table.html",
extra_context={
"Host": Host.objects.filter(virtual_machine=self.context["object"])
},
)


template_extensions = [VirtualMachineHostTable]
11 changes: 11 additions & 0 deletions netbox_docker_plugin/templates/netbox_docker_plugin/host.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ <h2 class="card-header">Host</h2>
<th scope="row">Endpoint</th>
<td>{{ object.endpoint|remove_password }}</td>
</tr>
<tr>
<th scope="row">Virtual Machine</th>
<td>
{% if object.virtual_machine %}
<a href="{% url 'virtualization:virtualmachine' pk=object.virtual_machine.pk %}">
{{ object.virtual_machine|placeholder }}</a>
{% else %}
{{ object.virtual_machine|placeholder }}</a>
{% endif %}
</td>
</tr>
<tr>
<th scope="row">State</th>
<td>{{ object.get_state_display }}</td>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="card">
<h2 class="card-header">Docker Host</h2>
{% htmx_table 'plugins:netbox_docker_plugin:host_list' virtual_machine=object.pk %}
</div>
29 changes: 25 additions & 4 deletions netbox_docker_plugin/tests/container/test_container_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,18 @@ def setUpTestData(cls) -> None:
host=host2, name="registry2", serveraddress="http://localhost:8082"
)

image1 = Image.objects.create(host=host1, name="image1", registry=registry1)
image2 = Image.objects.create(host=host2, name="image2", registry=registry2)
image1 = Image.objects.create(
host=host1,
name="image1",
registry=registry1,
ImageID="sha256:abc123",
)
image2 = Image.objects.create(
host=host2,
name="image2",
registry=registry2,
ImageID="sha256:abc456",
)

network1 = Network.objects.create(host=host1, name="network1")
network2 = Network.objects.create(host=host2, name="network2")
Expand Down Expand Up @@ -244,6 +254,7 @@ def test_that_patch_overwrites_data_only_when_explicitly_set(self):
host=host3,
name="image3",
registry=registry3,
ImageID="sha256:abc789",
)
container11 = Container.objects.create(
host=host3,
Expand Down Expand Up @@ -330,8 +341,18 @@ def test_that_container_host_cannot_be_changed(self):
host=host2, name="registry5", serveraddress="http://localhost:8082"
)

image1 = Image.objects.create(host=host1, name="image", registry=registry1)
image2 = Image.objects.create(host=host2, name="image", registry=registry2)
image1 = Image.objects.create(
host=host1,
name="image",
registry=registry1,
ImageID="sha256:abc101112",
)
image2 = Image.objects.create(
host=host2,
name="image",
registry=registry2,
ImageID="sha256:abc131415",
)

container = Container.objects.create(host=host1, image=image1, name="container")

Expand Down
11 changes: 8 additions & 3 deletions netbox_docker_plugin/tests/container/test_container_api_exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ def setUp(self):
host=host1, name="registry1", serveraddress="http://localhost:8080"
)

image1 = Image.objects.create(host=host1, name="image1", registry=registry1)
image1 = Image.objects.create(
host=host1,
name="image1",
registry=registry1,
ImageID="sha256:abc123",
)

container = Container.objects.create(
host=host1,
Expand Down Expand Up @@ -86,11 +91,11 @@ def test_that_exec_endpoint_fail_with_backend_error(self):
m.put(
"http://localhost:8080/api/engine/containers/1234/exec",
text="Error",
status_code= 500
status_code=500,
)

response = self.client.post(
self.endpoint, **self.header, data={"cmd": ["ls"]}, format="json"
)
self.assertHttpStatus(response, status.HTTP_502_BAD_GATEWAY)
self.assertEqual(response.data, 'Error')
self.assertEqual(response.data, "Error")
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,12 @@ def setUpTestData(cls):
host=host2, name="registry2", serveraddress="http://localhost:8082"
)

image1 = Image.objects.create(host=host1, name="image1", registry=registry1)
image2 = Image.objects.create(host=host2, name="image2", registry=registry2)
image1 = Image.objects.create(
host=host1, name="image1", registry=registry1, ImageID="sha256:abc123"
)
image2 = Image.objects.create(
host=host2, name="image2", registry=registry2, ImageID="sha256:abc456"
)

Container.objects.create(
host=host1,
Expand Down
Loading
Loading