Skip to content

Commit

Permalink
✨ Add host refresh operation
Browse files Browse the repository at this point in the history
  • Loading branch information
fanshan committed Sep 9, 2024
1 parent ce077ce commit 670a5a9
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 47 deletions.
9 changes: 5 additions & 4 deletions netbox_docker_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
from django.db.models.signals import post_migrate
from .utilities import create_webhook


class NetBoxDockerConfig(PluginConfig):
"""Plugin Config Class"""

name = "netbox_docker_plugin"
verbose_name = " NetBox Docker Plugin"
description = "Manage Docker"
version = "1.18.0"
version = "1.19.0"
max_version = "3.7.8"
base_url = "docker"
author= "Vincent Simonin <[email protected]>, David Delassus <[email protected]>"
author_email= "[email protected], [email protected]"
author = "Vincent Simonin <[email protected]>, David Delassus <[email protected]>"
author_email = "[email protected], [email protected]"

def ready(self):
from . import signals # pylint: disable=unused-import, import-outside-toplevel
from . import signals # pylint: disable=unused-import, import-outside-toplevel

post_migrate.connect(create_webhook)

Expand Down
2 changes: 2 additions & 0 deletions netbox_docker_plugin/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Meta:
"state",
"agent_version",
"docker_api_version",
"operation",
)


Expand Down Expand Up @@ -610,6 +611,7 @@ class Meta:
"netbox_base_url",
"agent_version",
"docker_api_version",
"operation",
"custom_fields",
"created",
"last_updated",
Expand Down
10 changes: 10 additions & 0 deletions netbox_docker_plugin/forms/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,13 @@ class HostBulkEditForm(NetBoxModelBulkEditForm):

model = Host
fieldsets = (("General", ("endpoint",)),)


class HostOperationForm(NetBoxModelForm):
"""Host Operation form definition class"""

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

model = Host
fields = ("operation",)
20 changes: 20 additions & 0 deletions netbox_docker_plugin/migrations/0032_host_operation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# pylint: disable=C0103
"""Migration file"""

from django.db import migrations, models


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

dependencies = [
("netbox_docker_plugin", "0031_device_and_more"),
]

operations = [
migrations.AddField(
model_name="host",
name="operation",
field=models.CharField(default="none", max_length=32),
),
]
5 changes: 4 additions & 1 deletion netbox_docker_plugin/models/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ def can_recreate(self) -> bool:
@property
def can_delete(self) -> bool:
"""Check if the container can be deleted"""
return self.host.state == HostStateChoices.STATE_DELETED or self.state in [
return self.host.state in [
HostStateChoices.STATE_DELETED,
HostStateChoices.STATE_REFRESHING,
] or self.state in [
"created",
"paused",
"exited",
Expand Down
29 changes: 29 additions & 0 deletions netbox_docker_plugin/models/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,27 @@ class HostStateChoices(ChoiceSet):
STATE_CREATED = "created"
STATE_DELETED = "deleted"
STATE_RUNNING = "running"
STATE_REFRESHING = "refreshing"

CHOICES = [
(STATE_CREATED, "Created", "dark"),
(STATE_RUNNING, "Running", "blue"),
(STATE_DELETED, "Deleted", "blue"),
(STATE_REFRESHING, "Refreshing", "blue"),
]


class HostOperationChoices(ChoiceSet):
"""Host operation choices definition class"""

key = "Host.operation"

OPERATION_REFRESH = "refresh"
OPERATION_NONE = "none"

CHOICES = [
(OPERATION_REFRESH, "Refresh", "blue"),
(OPERATION_NONE, "None", "white"),
]


Expand Down Expand Up @@ -62,6 +78,11 @@ class Host(NetBoxModel):
null=True,
blank=True,
)
operation = models.CharField(
max_length=32,
choices=HostOperationChoices,
default=HostOperationChoices.OPERATION_NONE,
)

class Meta:
"""Host Model Meta Class"""
Expand All @@ -80,3 +101,11 @@ def delete(self, using=None, keep_parents=False):
self.save()

return super().delete(using, keep_parents)

def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
if self.operation == HostOperationChoices.OPERATION_REFRESH:
self.state = HostStateChoices.STATE_REFRESHING

return super().save(force_insert, force_update, using, update_fields)
86 changes: 46 additions & 40 deletions netbox_docker_plugin/templates/netbox_docker_plugin/host.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
{% load plugins %}
{% load host %}

{% block extra_controls %}
<form action="{% url 'plugins:netbox_docker_plugin:host_operation' pk=object.id operation='refresh' %}" method="post">
{% csrf_token %}
<input type="hidden" name="operation" value="refresh">
<button type="submit" class="btn btn-sm btn-warning">
<i class="mdi mdi-cog-refresh"></i> Refresh
</button>
</form>
{% endblock extra_controls %}

{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
Expand Down Expand Up @@ -47,15 +57,14 @@ <h5 class="card-header">HOST</h5>
<div class="card">
<h5 class="card-header">Registry</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'plugins:netbox_docker_plugin:registry_list' %}?host_id={{ object.pk }}"
hx-trigger="load"
></div>
hx-get="{% url 'plugins:netbox_docker_plugin:registry_list' %}?host_id={{ object.pk }}" hx-trigger="load"></div>
{% if perms.netbox_docker_plugin.add_container %}
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:registry_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Registry
</a>
</div>
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:registry_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}"
class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Registry
</a>
</div>
{% endif %}
</div>
{% plugin_full_width_page object %}
Expand All @@ -64,15 +73,15 @@ <h5 class="card-header">Registry</h5>
<div class="card">
<h5 class="card-header">Container</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'plugins:netbox_docker_plugin:container_list' %}?host_id={{ object.pk }}"
hx-trigger="load"
></div>
hx-get="{% url 'plugins:netbox_docker_plugin:container_list' %}?host_id={{ object.pk }}" hx-trigger="load">
</div>
{% if perms.netbox_docker_plugin.add_container %}
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:container_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Container
</a>
</div>
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:container_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}"
class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Container
</a>
</div>
{% endif %}
</div>
{% plugin_full_width_page object %}
Expand All @@ -81,15 +90,14 @@ <h5 class="card-header">Container</h5>
<div class="card">
<h5 class="card-header">Images</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'plugins:netbox_docker_plugin:image_list' %}?host_id={{ object.pk }}"
hx-trigger="load"
></div>
hx-get="{% url 'plugins:netbox_docker_plugin:image_list' %}?host_id={{ object.pk }}" hx-trigger="load"></div>
{% if perms.netbox_docker_plugin.add_image %}
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:image_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add an Image
</a>
</div>
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:image_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}"
class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add an Image
</a>
</div>
{% endif %}
</div>
{% plugin_full_width_page object %}
Expand All @@ -98,15 +106,14 @@ <h5 class="card-header">Images</h5>
<div class="card">
<h5 class="card-header">Volumes</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'plugins:netbox_docker_plugin:volume_list' %}?host_id={{ object.pk }}"
hx-trigger="load"
></div>
hx-get="{% url 'plugins:netbox_docker_plugin:volume_list' %}?host_id={{ object.pk }}" hx-trigger="load"></div>
{% if perms.netbox_docker_plugin.add_volume %}
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:volume_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Volume
</a>
</div>
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:volume_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}"
class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Volume
</a>
</div>
{% endif %}
</div>
{% plugin_full_width_page object %}
Expand All @@ -115,15 +122,14 @@ <h5 class="card-header">Volumes</h5>
<div class="card">
<h5 class="card-header">Networks</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'plugins:netbox_docker_plugin:network_list' %}?host_id={{ object.pk }}"
hx-trigger="load"
></div>
hx-get="{% url 'plugins:netbox_docker_plugin:network_list' %}?host_id={{ object.pk }}" hx-trigger="load"></div>
{% if perms.netbox_docker_plugin.add_network %}
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:network_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Network
</a>
</div>
<div class="card-footer text-end noprint">
<a href="{% url 'plugins:netbox_docker_plugin:network_add' %}?host={{ object.pk }}&return_url={{ object.get_absolute_url }}"
class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Network
</a>
</div>
{% endif %}
</div>
{% plugin_full_width_page object %}
Expand Down
32 changes: 32 additions & 0 deletions netbox_docker_plugin/tests/container/test_container_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,33 @@ def test_delete_container_on_deleted_host(self):

self.assertFalse(Container.objects.filter(name=name).exists())

def test_delete_container_on_refreshing_host(self):
"""Test that a container on a refreshing host can be deleted"""

for state in [
"created",
"running",
"restarted",
"paused",
"exited",
"dead",
"none",
]:
with self.subTest(operation="delete", state=state):
with transaction.atomic():
name = f"container-delete-{state}"
obj = Container.objects.create(
host=self.objects["host3"],
image=self.objects["image2"],
name=name,
operation="none",
state=state,
)

obj.delete()

self.assertFalse(Container.objects.filter(name=name).exists())

@classmethod
def setUpTestData(cls):
cls.objects["host1"] = Host.objects.create(
Expand Down Expand Up @@ -193,3 +220,8 @@ def setUpTestData(cls):
registry=cls.objects["registry2"],
name="image2",
)
cls.objects["host3"] = Host.objects.create(
endpoint="http://localhost:8080",
name="host3",
state=HostStateChoices.STATE_REFRESHING,
)
1 change: 1 addition & 0 deletions netbox_docker_plugin/tests/host/test_host_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class HostApiTestCase(
"endpoint",
"id",
"name",
"operation",
"state",
"url",
]
Expand Down
5 changes: 5 additions & 0 deletions netbox_docker_plugin/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
host_views.HostDeleteView.as_view(),
name="host_delete",
),
path(
"hosts/<int:pk>/operation/<str:operation>/",
host_views.HostOperationView.as_view(),
name="host_operation",
),
path(
"hosts/<int:pk>/graph/",
host_views.HostGraphView.as_view(),
Expand Down
15 changes: 14 additions & 1 deletion netbox_docker_plugin/utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,19 @@
"payload_url": "{{ data.host.endpoint }}/api/engine/volumes",
"ssl_verification": False,
},
{
"name": "[Docker] Modify host",
"content_types": "host",
"enabled": True,
"type_create": False,
"type_update": True,
"type_delete": False,
"type_job_start": False,
"type_job_end": False,
"http_method": "POST",
"payload_url": "{{ data.endpoint }}/api/engine/endpoint",
"ssl_verification": False,
},
{
"name": "[Docker] Modify image",
"content_types": "image",
Expand Down Expand Up @@ -198,7 +211,7 @@ def create_webhook(app_config, **kwargs):
type_job_start=webhook["type_job_start"],
type_job_end=webhook["type_job_end"],
action_object_id=obj.pk,
action_object_type=wh_content_type
action_object_type=wh_content_type,
)
eventrule.save()

Expand Down
11 changes: 11 additions & 0 deletions netbox_docker_plugin/views/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,14 @@ class HostBulkDeleteView(generic.BulkDeleteView):
queryset = Host.objects.all()
filterset = filtersets.HostFilterSet
table = tables.HostTable


class HostOperationView(generic.ObjectEditView):
"""Host operation view definition"""

def get_object(self, **kwargs):
new_kwargs = {"pk": kwargs["pk"]}
return super().get_object(**new_kwargs)

queryset = Host.objects.all()
form = host.HostOperationForm
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "netbox-docker-plugin"
version = "1.18.0"
version = "1.19.0"
authors = [
{ name="Vincent Simonin", email="[email protected]" },
{ name="David Delassus", email="[email protected]" }
Expand Down

0 comments on commit 670a5a9

Please sign in to comment.