netbox-docker-plugin 1.7.0__tar.gz → 1.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {netbox_docker_plugin-1.7.0/netbox_docker_plugin.egg-info → netbox_docker_plugin-1.8.0}/PKG-INFO +2 -2
  2. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/__init__.py +5 -3
  3. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/container.py +2 -24
  4. netbox_docker_plugin-1.8.0/netbox_docker_plugin/migrations/0029_alter_container_state.py +20 -0
  5. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/container.py +35 -2
  6. netbox_docker_plugin-1.8.0/netbox_docker_plugin/signals.py +33 -0
  7. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/container.html +28 -4
  8. netbox_docker_plugin-1.8.0/netbox_docker_plugin/tests/container/test_container_actions.py +171 -0
  9. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_api.py +22 -3
  10. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_operation_view.py +35 -10
  11. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_validation.py +10 -0
  12. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_views.py +30 -9
  13. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0/netbox_docker_plugin.egg-info}/PKG-INFO +2 -2
  14. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin.egg-info/SOURCES.txt +3 -0
  15. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/pyproject.toml +2 -1
  16. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/LICENSE +0 -0
  17. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/MANIFEST.in +0 -0
  18. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/README.md +0 -0
  19. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/__init__.py +0 -0
  20. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/serializers.py +0 -0
  21. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/urls.py +0 -0
  22. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/views.py +0 -0
  23. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/filtersets.py +0 -0
  24. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/__init__.py +0 -0
  25. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/bind.py +0 -0
  26. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/env.py +0 -0
  27. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/host.py +0 -0
  28. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/image.py +0 -0
  29. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/label.py +0 -0
  30. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/mount.py +0 -0
  31. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/network.py +0 -0
  32. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/network_setting.py +0 -0
  33. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/port.py +0 -0
  34. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/registry.py +0 -0
  35. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/volume.py +0 -0
  36. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0001_initial.py +0 -0
  37. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0002_image.py +0 -0
  38. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0003_image_size.py +0 -0
  39. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0004_volume.py +0 -0
  40. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0005_network.py +0 -0
  41. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0006_container_port_mount_label_env_and_more.py +0 -0
  42. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0007_remove_container_netbox_docker_plugin_container_unique_name_host__and_more.py +0 -0
  43. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0008_alter_network_unique_together_network_networkid_and_more.py +0 -0
  44. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0009_remove_container_network_networksettings_and_more.py +0 -0
  45. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0010_container_containerid_and_more.py +0 -0
  46. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0011_host_token.py +0 -0
  47. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0012_host_state.py +0 -0
  48. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0013_host_netbox_url.py +0 -0
  49. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0014_container_operation.py +0 -0
  50. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0015_remove_mount_netbox_docker_plugin_mount_unique_volume_and_more.py +0 -0
  51. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0016_alter_env_value.py +0 -0
  52. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0017_network_state.py +0 -0
  53. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0018_alter_mount_volume_alter_networksetting_network.py +0 -0
  54. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0019_host_agent_version_host_docker_api_version.py +0 -0
  55. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0020_container_hostname.py +0 -0
  56. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0021_registry_and_more.py +0 -0
  57. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0022_bind_bind_netbox_docker_plugin_bind_unique_bind.py +0 -0
  58. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0023_delete_hosts.py +0 -0
  59. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0024_registry_host_alter_registry_name_and_more.py +0 -0
  60. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0025_alter_image_version.py +0 -0
  61. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0026_image_digest.py +0 -0
  62. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0027_container_restart_policy.py +0 -0
  63. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0028_mount_and_bind_read_only.py +0 -0
  64. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/__init__.py +0 -0
  65. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/__init__.py +0 -0
  66. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/host.py +0 -0
  67. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/image.py +0 -0
  68. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/network.py +0 -0
  69. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/registry.py +0 -0
  70. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/volume.py +0 -0
  71. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/navigation.py +0 -0
  72. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tables.py +0 -0
  73. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/host.html +0 -0
  74. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/image.html +0 -0
  75. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/network.html +0 -0
  76. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/registry.html +0 -0
  77. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/volume.html +0 -0
  78. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templatetags/__init__.py +0 -0
  79. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templatetags/host.py +0 -0
  80. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/__init__.py +0 -0
  81. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/base.py +0 -0
  82. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/__init__.py +0 -0
  83. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/__init__.py +0 -0
  84. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/test_host_api.py +0 -0
  85. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/test_host_views.py +0 -0
  86. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/test_replace_password.py +0 -0
  87. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/image/__init__.py +0 -0
  88. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/image/test_image_api.py +0 -0
  89. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/image/test_image_views.py +0 -0
  90. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/network/__init__.py +0 -0
  91. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/network/test_network_api.py +0 -0
  92. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/network/test_network_views.py +0 -0
  93. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/registry/__init__.py +0 -0
  94. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/registry/test_registry_api.py +0 -0
  95. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/registry/test_registry_views.py +0 -0
  96. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/test_init.py +0 -0
  97. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/volume/__init__.py +0 -0
  98. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/volume/test_volume_api.py +0 -0
  99. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/volume/test_volume_views.py +0 -0
  100. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/urls.py +0 -0
  101. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/utilities/__init__.py +0 -0
  102. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/__init__.py +0 -0
  103. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/bind.py +0 -0
  104. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/container.py +0 -0
  105. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/env.py +0 -0
  106. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/host.py +0 -0
  107. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/image.py +0 -0
  108. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/label.py +0 -0
  109. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/mount.py +0 -0
  110. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/network.py +0 -0
  111. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/network_setting.py +0 -0
  112. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/port.py +0 -0
  113. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/registry.py +0 -0
  114. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/volume.py +0 -0
  115. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin.egg-info/dependency_links.txt +0 -0
  116. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin.egg-info/top_level.txt +0 -0
  117. {netbox_docker_plugin-1.7.0 → netbox_docker_plugin-1.8.0}/setup.cfg +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: netbox-docker-plugin
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: Manage Docker with Netbox & style.
5
- Author-email: Vincent Simonin <vincent@saashup.com>
5
+ Author-email: Vincent Simonin <vincent@saashup.com>, David Delassus <david.jose.delassus@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/SaaShup/netbox-docker-plugin
7
7
  Project-URL: Bug Tracker, https://github.com/SaaShup/netbox-docker-plugin/issues
8
8
  Keywords: netbox,netbox-plugin,docker
@@ -10,12 +10,14 @@ class NetBoxDockerConfig(PluginConfig):
10
10
  name = "netbox_docker_plugin"
11
11
  verbose_name = " NetBox Docker Plugin"
12
12
  description = "Manage Docker"
13
- version = "1.7.0"
13
+ version = "1.8.0"
14
14
  base_url = "docker"
15
- author= "Vincent Simonin"
16
- author_email= "vincent@saashup.com"
15
+ author= "Vincent Simonin <vincent@saashup.com>, David Delassus <david.jose.delassus@gmail.com>"
16
+ author_email= "vincent@saashup.com, david.jose.delassus@gmail.com"
17
17
 
18
18
  def ready(self):
19
+ from . import signals # pylint: disable=unused-import, import-outside-toplevel
20
+
19
21
  post_migrate.connect(create_webhook)
20
22
 
21
23
  super().ready()
@@ -14,11 +14,7 @@ from netbox.forms import (
14
14
  )
15
15
  from ..models.volume import Volume
16
16
  from ..models.host import Host
17
- from ..models.container import (
18
- Container,
19
- ContainerStateChoices,
20
- ContainerRestartPolicyChoices,
21
- )
17
+ from ..models.container import Container, ContainerRestartPolicyChoices
22
18
  from ..models.image import Image
23
19
 
24
20
 
@@ -44,7 +40,6 @@ class ContainerForm(NetBoxModelForm):
44
40
  "image",
45
41
  "name",
46
42
  "hostname",
47
- "state",
48
43
  "restart_policy",
49
44
  "tags",
50
45
  )
@@ -53,7 +48,6 @@ class ContainerForm(NetBoxModelForm):
53
48
  "host": "Host",
54
49
  "image": "Image",
55
50
  "hostname": "Hostname",
56
- "state": "State",
57
51
  "restart_policy": "Restart Policy",
58
52
  }
59
53
 
@@ -79,9 +73,6 @@ class ContainerFilterForm(NetBoxModelFilterSetForm):
79
73
  required=False,
80
74
  label="Image",
81
75
  )
82
- state = forms.ChoiceField(
83
- label="State", choices=ContainerStateChoices, required=False
84
- )
85
76
  restart_policy = forms.ChoiceField(
86
77
  label="Restart Policy", choices=ContainerRestartPolicyChoices, required=False
87
78
  )
@@ -91,13 +82,6 @@ class ContainerFilterForm(NetBoxModelFilterSetForm):
91
82
  class ContainerImportForm(NetBoxModelImportForm):
92
83
  """Container importation form definition class"""
93
84
 
94
- state = forms.ChoiceField(
95
- label="State",
96
- choices=ContainerStateChoices,
97
- required=False,
98
- help_text="Container State. Can be `created`, `restarting`, "
99
- + "`running`, `paused`, `exited` or `dead`.",
100
- )
101
85
  restart_policy = forms.ChoiceField(
102
86
  label="Restart Policy",
103
87
  choices=ContainerRestartPolicyChoices,
@@ -114,7 +98,6 @@ class ContainerImportForm(NetBoxModelImportForm):
114
98
  "name",
115
99
  "host",
116
100
  "image",
117
- "state",
118
101
  "hostname",
119
102
  )
120
103
  labels = {
@@ -127,11 +110,6 @@ class ContainerImportForm(NetBoxModelImportForm):
127
110
  class ContainerBulkEditForm(NetBoxModelBulkEditForm):
128
111
  """Container bulk edit form definition class"""
129
112
 
130
- state = forms.ChoiceField(
131
- label="State",
132
- choices=ContainerStateChoices,
133
- required=True,
134
- )
135
113
  restart_policy = forms.ChoiceField(
136
114
  label="Restart Policy",
137
115
  choices=ContainerRestartPolicyChoices,
@@ -142,7 +120,7 @@ class ContainerBulkEditForm(NetBoxModelBulkEditForm):
142
120
  )
143
121
 
144
122
  model = Container
145
- fieldsets = (("General", ("state", "restart_policy", "hostname")),)
123
+ fieldsets = (("General", ("restart_policy", "hostname")),)
146
124
 
147
125
 
148
126
  class ContainerOperationForm(NetBoxModelForm):
@@ -0,0 +1,20 @@
1
+ # pylint: disable=C0103
2
+ """Migration file"""
3
+
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+ """Migration file"""
9
+
10
+ dependencies = [
11
+ ("netbox_docker_plugin", "0028_mount_and_bind_read_only"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AlterField(
16
+ model_name="container",
17
+ name="state",
18
+ field=models.CharField(default="none", max_length=32),
19
+ ),
20
+ ]
@@ -16,7 +16,7 @@ from utilities.choices import ChoiceSet
16
16
  from utilities.querysets import RestrictedQuerySet
17
17
  from netbox.models import NetBoxModel
18
18
  from .image import Image
19
- from .host import Host
19
+ from .host import Host, HostStateChoices
20
20
  from .network import Network
21
21
  from .volume import Volume
22
22
 
@@ -26,7 +26,7 @@ class ContainerStateChoices(ChoiceSet):
26
26
 
27
27
  key = "Container.state"
28
28
 
29
- DEFAULT_VALUE = "created"
29
+ DEFAULT_VALUE = "none"
30
30
 
31
31
  CHOICES = [
32
32
  ("created", "Created", "dark"),
@@ -133,6 +133,39 @@ class Container(NetBoxModel):
133
133
  default=ContainerRestartPolicyChoices.DEFAULT_VALUE,
134
134
  )
135
135
 
136
+ @property
137
+ def can_create(self) -> bool:
138
+ """ Check if the container can be created """
139
+ return self.state == "none"
140
+
141
+ @property
142
+ def can_start(self) -> bool:
143
+ """ Check if the container can be started """
144
+ return self.state in ["created", "exited", "dead"]
145
+
146
+ @property
147
+ def can_stop(self) -> bool:
148
+ """ Check if the container can be stopped """
149
+ return self.state in ["running"]
150
+
151
+ @property
152
+ def can_restart(self) -> bool:
153
+ """ Check if the container can be restarted """
154
+ return self.state in ["running"]
155
+
156
+ @property
157
+ def can_recreate(self) -> bool:
158
+ """ Check if the container can be recreated """
159
+ return self.state in ["created", "running", "exited", "dead"]
160
+
161
+ @property
162
+ def can_delete(self) -> bool:
163
+ """ Check if the container can be deleted """
164
+ return (
165
+ self.host.state == HostStateChoices.STATE_DELETED
166
+ or self.state in ["created", "paused", "exited", "dead"]
167
+ )
168
+
136
169
  class Meta:
137
170
  """Image Model Meta Class"""
138
171
 
@@ -0,0 +1,33 @@
1
+ """ Hooks on Django model signals. """
2
+
3
+ from django.db.models.signals import pre_delete, pre_save
4
+ from django.dispatch import receiver
5
+ from utilities.exceptions import AbortRequest
6
+
7
+ from .models import Container
8
+
9
+
10
+ @receiver(pre_delete, sender=Container)
11
+ def prevent_deleting_running_containers(instance, **_kwargs):
12
+ """ Abort deletion if container is running. """
13
+
14
+ if not instance.can_delete:
15
+ raise AbortRequest(
16
+ f"The container {instance.name} is running, please stop it before deletion"
17
+ )
18
+
19
+
20
+ @receiver(pre_save, sender=Container)
21
+ def restrict_container_actions(instance, **_kwargs):
22
+ """ Check if the container's state is correct for the given action """
23
+
24
+ if any([
25
+ instance.operation == "create" and not instance.can_create,
26
+ instance.operation == "start" and not instance.can_start,
27
+ instance.operation == "restart" and not instance.can_restart,
28
+ instance.operation == "stop" and not instance.can_stop,
29
+ instance.operation == "recreate" and not instance.can_recreate,
30
+ ]):
31
+ raise AbortRequest(
32
+ f"Cannot {instance.operation} container {instance.name} in state {instance.state}"
33
+ )
@@ -6,7 +6,13 @@
6
6
  <form action="{% url 'plugins:netbox_docker_plugin:container_operation' pk=object.id operation='start' %}" method="post">
7
7
  {% csrf_token %}
8
8
  <input type="hidden" name="operation" value="start">
9
- <button type="submit" class="btn btn-sm btn-success">
9
+ <button
10
+ type="submit"
11
+ class="btn btn-sm btn-success"
12
+ {% if not object.can_start %}
13
+ disabled
14
+ {% endif %}
15
+ >
10
16
  <i class="mdi mdi-play"></i> Start
11
17
  </button>
12
18
  </form>
@@ -14,7 +20,13 @@
14
20
  <form action="{% url 'plugins:netbox_docker_plugin:container_operation' pk=object.id operation='stop' %}" method="post">
15
21
  {% csrf_token %}
16
22
  <input type="hidden" name="operation" value="stop">
17
- <button type="submit" class="btn btn-sm btn-danger">
23
+ <button
24
+ type="submit"
25
+ class="btn btn-sm btn-danger"
26
+ {% if not object.can_stop %}
27
+ disabled
28
+ {% endif %}
29
+ >
18
30
  <i class="mdi mdi-stop"></i> Stop
19
31
  </button>
20
32
  </form>
@@ -22,7 +34,13 @@
22
34
  <form action="{% url 'plugins:netbox_docker_plugin:container_operation' pk=object.id operation='restart' %}" method="post">
23
35
  {% csrf_token %}
24
36
  <input type="hidden" name="operation" value="restart">
25
- <button type="submit" class="btn btn-sm btn-light">
37
+ <button
38
+ type="submit"
39
+ class="btn btn-sm btn-light"
40
+ {% if not object.can_restart %}
41
+ disabled
42
+ {% endif %}
43
+ >
26
44
  <i class="mdi mdi-restart"></i> Restart
27
45
  </button>
28
46
  </form>
@@ -30,7 +48,13 @@
30
48
  <form action="{% url 'plugins:netbox_docker_plugin:container_operation' pk=object.id operation='recreate' %}" method="post">
31
49
  {% csrf_token %}
32
50
  <input type="hidden" name="operation" value="recreate">
33
- <button type="submit" class="btn btn-sm btn-dark">
51
+ <button
52
+ type="submit"
53
+ class="btn btn-sm btn-dark"
54
+ {% if not object.can_recreate %}
55
+ disabled
56
+ {% endif %}
57
+ >
34
58
  <i class="mdi mdi-autorenew"></i> Recreate
35
59
  </button>
36
60
  </form>
@@ -0,0 +1,171 @@
1
+ """ Container action restrictions test suite """
2
+
3
+ from django.db import transaction
4
+ from django.test import TestCase
5
+ from utilities.exceptions import AbortRequest
6
+ from netbox_docker_plugin.models.container import Container
7
+ from netbox_docker_plugin.models.host import Host, HostStateChoices
8
+ from netbox_docker_plugin.models.image import Image
9
+ from netbox_docker_plugin.models.registry import Registry
10
+
11
+
12
+ class ContainerActionTestCase(TestCase):
13
+ """ Test the various scenarii of container actions """
14
+
15
+ objects = {}
16
+
17
+ def test_allowed_operations(self):
18
+ """ Test that only specific operations are allowed according to a state """
19
+
20
+ cases = [
21
+ ("create", ["none"]),
22
+ ("start", ["created", "exited", "dead"]),
23
+ ("stop", ["running"]),
24
+ ("restart", ["running"]),
25
+ ("recreate", ["created", "exited", "dead", "running"]),
26
+ ("noop", ["created", "restarting", "running", "paused", "exited", "dead", "none"]),
27
+ ]
28
+
29
+ for operation, states in cases:
30
+ for state in states:
31
+ with self.subTest(operation=operation, state=state):
32
+ with transaction.atomic():
33
+ name = f"container-{operation}-{state}"
34
+ obj = Container.objects.create(
35
+ host=self.objects["host1"],
36
+ image=self.objects["image2"],
37
+ name=name,
38
+ operation="none",
39
+ state=state,
40
+ )
41
+
42
+ obj.operation = operation
43
+ obj.save()
44
+
45
+ self.assertEqual(
46
+ Container.objects.get(name=name).operation,
47
+ operation,
48
+ )
49
+
50
+ def test_forbidden_operations(self):
51
+ """ Test that forbidden operations raise an AbortRequest """
52
+
53
+ cases = [
54
+ ("create", ["created", "restarting", "running", "paused", "exited", "dead"]),
55
+ ("start", ["running", "restarting", "paused", "none"]),
56
+ ("stop", ["created", "restarting", "paused", "exited", "dead", "none"]),
57
+ ("restart", ["created", "restarting", "paused", "exited", "dead", "none"]),
58
+ ("recreate", ["restarting", "paused", "none"]),
59
+ ]
60
+
61
+ for operation, states in cases:
62
+ for state in states:
63
+ with self.subTest(operation=operation, state=state):
64
+ with transaction.atomic():
65
+ with self.assertRaises(
66
+ AbortRequest,
67
+ msg=f"AbortRequest not raised for {state}",
68
+ ):
69
+ name = f"container-{operation}-{state}"
70
+ obj = Container.objects.create(
71
+ host=self.objects["host1"],
72
+ image=self.objects["image2"],
73
+ name=name,
74
+ operation="none",
75
+ state=state,
76
+ )
77
+
78
+ obj.operation = operation
79
+ obj.save()
80
+
81
+ def test_delete_non_running_container(self):
82
+ """ Test that a non running container can be deleted """
83
+
84
+ for state in ["created", "paused", "exited", "dead"]:
85
+ with self.subTest(operation="delete", state=state):
86
+ with transaction.atomic():
87
+ name = f"container-delete-{state}"
88
+ obj = Container.objects.create(
89
+ host=self.objects["host1"],
90
+ image=self.objects["image2"],
91
+ name=name,
92
+ operation="none",
93
+ state=state,
94
+ )
95
+
96
+ obj.delete()
97
+
98
+ self.assertFalse(Container.objects.filter(name=name).exists())
99
+
100
+ def test_delete_running_container(self):
101
+ """ Test that a running container cannot be deleted """
102
+
103
+ for state in ["running", "restarting", "none"]:
104
+ with self.subTest(operation="delete", state=state):
105
+ with transaction.atomic():
106
+ with self.assertRaises(
107
+ AbortRequest,
108
+ msg=f"AbortRequest not raised for {state}",
109
+ ):
110
+ name = f"container-delete-{state}"
111
+ obj = Container.objects.create(
112
+ host=self.objects["host1"],
113
+ image=self.objects["image2"],
114
+ name=name,
115
+ operation="none",
116
+ state=state,
117
+ )
118
+
119
+ obj.delete()
120
+
121
+ def test_delete_container_on_deleted_host(self):
122
+ """ Test that a container on a deleted host can be deleted """
123
+
124
+ for state in ["created", "running", "restarted", "paused", "exited", "dead", "none"]:
125
+ with self.subTest(operation="delete", state=state):
126
+ with transaction.atomic():
127
+ name = f"container-delete-{state}"
128
+ obj = Container.objects.create(
129
+ host=self.objects["host2"],
130
+ image=self.objects["image2"],
131
+ name=name,
132
+ operation="none",
133
+ state=state,
134
+ )
135
+
136
+ obj.delete()
137
+
138
+ self.assertFalse(Container.objects.filter(name=name).exists())
139
+
140
+ @classmethod
141
+ def setUpTestData(cls):
142
+ cls.objects["host1"] = Host.objects.create(
143
+ endpoint="http://localhost:8080",
144
+ name="host1",
145
+ )
146
+ cls.objects["registry1"] = Registry.objects.create(
147
+ host=cls.objects["host1"],
148
+ name="registry1",
149
+ serveraddress="http://localhost:8080",
150
+ )
151
+ cls.objects["image2"] = Image.objects.create(
152
+ host=cls.objects["host1"],
153
+ registry=cls.objects["registry1"],
154
+ name="image2",
155
+ )
156
+
157
+ cls.objects["host2"] = Host.objects.create(
158
+ endpoint="http://localhost:8080",
159
+ name="host2",
160
+ state=HostStateChoices.STATE_DELETED,
161
+ )
162
+ cls.objects["registry2"] = Registry.objects.create(
163
+ host=cls.objects["host2"],
164
+ name="registry2",
165
+ serveraddress="http://localhost:8080",
166
+ )
167
+ cls.objects["image2"] = Image.objects.create(
168
+ host=cls.objects["host2"],
169
+ registry=cls.objects["registry2"],
170
+ name="image2",
171
+ )
@@ -1,3 +1,4 @@
1
+ # pylint: disable=R0801
1
2
  """Container Test Case"""
2
3
 
3
4
  from utilities.testing import APIViewTestCases
@@ -64,9 +65,27 @@ class ContainerApiTestCase(
64
65
  volume2 = Volume.objects.create(host=host1, name="volume2")
65
66
  volume3 = Volume.objects.create(host=host2, name="volume3")
66
67
 
67
- Container.objects.create(host=host1, image=image1, name="container1")
68
- Container.objects.create(host=host1, image=image1, name="container2")
69
- Container.objects.create(host=host2, image=image2, name="container3")
68
+ Container.objects.create(
69
+ host=host1,
70
+ image=image1,
71
+ name="container1",
72
+ operation="none",
73
+ state="created",
74
+ )
75
+ Container.objects.create(
76
+ host=host1,
77
+ image=image1,
78
+ name="container2",
79
+ operation="none",
80
+ state="created",
81
+ )
82
+ Container.objects.create(
83
+ host=host2,
84
+ image=image2,
85
+ name="container3",
86
+ operation="none",
87
+ state="created",
88
+ )
70
89
 
71
90
  cls.create_data = [
72
91
  {
@@ -1,3 +1,4 @@
1
+ # pylint: disable=R0801
1
2
  """Container operation Views Test Case"""
2
3
 
3
4
  from django.urls import reverse
@@ -22,7 +23,7 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
22
23
 
23
24
  def test_operation_start_object_without_permission(self):
24
25
  """Test operation start object without permission"""
25
- instance = self._get_queryset().first()
26
+ instance = self._get_queryset().exclude(state="running").first()
26
27
 
27
28
  # Try GET without permission
28
29
  with disable_warnings("django.request"):
@@ -42,7 +43,7 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
42
43
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
43
44
  def test_operation_start_object_with_permission(self):
44
45
  """Test operation start object with permission""" """ """
45
- instance = self._get_queryset().first()
46
+ instance = self._get_queryset().exclude(state="running").first()
46
47
 
47
48
  # Assign model-level permission
48
49
  obj_perm = ObjectPermission(name="Test permission", actions=["change"])
@@ -78,7 +79,7 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
78
79
 
79
80
  def test_operation_stop_object_without_permission(self):
80
81
  """Test operation stop object without permission"""
81
- instance = self._get_queryset().first()
82
+ instance = self._get_queryset().filter(state="running").first()
82
83
 
83
84
  # Try GET without permission
84
85
  with disable_warnings("django.request"):
@@ -98,7 +99,7 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
98
99
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
99
100
  def test_operation_stop_object_with_permission(self):
100
101
  """Test operation stop object with permission""" """ """
101
- instance = self._get_queryset().first()
102
+ instance = self._get_queryset().filter(state="running").first()
102
103
 
103
104
  # Assign model-level permission
104
105
  obj_perm = ObjectPermission(name="Test permission", actions=["change"])
@@ -134,7 +135,7 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
134
135
 
135
136
  def test_operation_restart_object_without_permission(self):
136
137
  """Test operation restart object without permission"""
137
- instance = self._get_queryset().first()
138
+ instance = self._get_queryset().filter(state="running").first()
138
139
 
139
140
  # Try GET without permission
140
141
  with disable_warnings("django.request"):
@@ -154,7 +155,7 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
154
155
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
155
156
  def test_operation_restart_object_with_permission(self):
156
157
  """Test operation restart object with permission""" """ """
157
- instance = self._get_queryset().first()
158
+ instance = self._get_queryset().filter(state="running").first()
158
159
 
159
160
  # Assign model-level permission
160
161
  obj_perm = ObjectPermission(name="Test permission", actions=["change"])
@@ -259,7 +260,31 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
259
260
  image1 = Image.objects.create(host=host1, name="image1", registry=registry1)
260
261
  image2 = Image.objects.create(host=host2, name="image2", registry=registry2)
261
262
 
262
- Container.objects.create(host=host1, image=image1, name="container1")
263
- Container.objects.create(host=host1, image=image1, name="container2")
264
- Container.objects.create(host=host2, image=image2, name="container3")
265
- Container.objects.create(host=host2, image=image2, name="container4")
263
+ Container.objects.create(
264
+ host=host1,
265
+ image=image1,
266
+ name="container1",
267
+ operation="none",
268
+ state="created",
269
+ )
270
+ Container.objects.create(
271
+ host=host1,
272
+ image=image1,
273
+ name="container2",
274
+ operation="none",
275
+ state="created",
276
+ )
277
+ Container.objects.create(
278
+ host=host2,
279
+ image=image2,
280
+ name="container3",
281
+ operation="none",
282
+ state="created",
283
+ )
284
+ Container.objects.create(
285
+ host=host2,
286
+ image=image2,
287
+ name="container4",
288
+ operation="none",
289
+ state="running",
290
+ )
@@ -23,6 +23,8 @@ class ContainerValidationTestCase(TestCase):
23
23
  host=self.objects["host1"],
24
24
  image=self.objects["image1"],
25
25
  name="container1",
26
+ operation="none",
27
+ state="created",
26
28
  )
27
29
 
28
30
  self.assertTrue(isinstance(container.id, int))
@@ -31,6 +33,8 @@ class ContainerValidationTestCase(TestCase):
31
33
  host=self.objects["host2"],
32
34
  image=self.objects["image2"],
33
35
  name="container2",
36
+ operation="none",
37
+ state="created",
34
38
  )
35
39
 
36
40
  self.assertTrue(isinstance(container.id, int))
@@ -43,6 +47,8 @@ class ContainerValidationTestCase(TestCase):
43
47
  host=self.objects["host1"],
44
48
  image=self.objects["image2"],
45
49
  name="container2",
50
+ operation="none",
51
+ state="created",
46
52
  )
47
53
  container.clean()
48
54
 
@@ -63,6 +69,8 @@ class ContainerValidationTestCase(TestCase):
63
69
  host=self.objects["host1"],
64
70
  image=self.objects["image1"],
65
71
  name="container3",
72
+ operation="none",
73
+ state="created",
66
74
  )
67
75
 
68
76
  mount = Mount(
@@ -88,6 +96,8 @@ class ContainerValidationTestCase(TestCase):
88
96
  host=self.objects["host1"],
89
97
  image=self.objects["image1"],
90
98
  name="container3",
99
+ operation="none",
100
+ state="created",
91
101
  )
92
102
 
93
103
  network_setting = NetworkSetting(
@@ -1,3 +1,4 @@
1
+ # pylint: disable=R0801
1
2
  """Container Views Test Case"""
2
3
 
3
4
  from utilities.testing import ViewTestCases
@@ -31,19 +32,39 @@ class ContainerViewsTestCase(
31
32
  image2 = Image.objects.create(host=host2, name="image2", registry=registry2)
32
33
 
33
34
  container1 = Container.objects.create(
34
- host=host1, image=image1, name="container1", restart_policy="always"
35
+ host=host1,
36
+ image=image1,
37
+ name="container1",
38
+ restart_policy="always",
39
+ operation="none",
40
+ state="created",
35
41
  )
36
42
  container2 = Container.objects.create(
37
- host=host1, image=image1, name="container2"
43
+ host=host1,
44
+ image=image1,
45
+ name="container2",
46
+ operation="none",
47
+ state="created",
48
+ )
49
+ Container.objects.create(
50
+ host=host2,
51
+ image=image2,
52
+ name="container3",
53
+ operation="none",
54
+ state="created",
55
+ )
56
+ Container.objects.create(
57
+ host=host2,
58
+ image=image2,
59
+ name="container4",
60
+ operation="none",
61
+ state="created",
38
62
  )
39
- Container.objects.create(host=host2, image=image2, name="container3")
40
- Container.objects.create(host=host2, image=image2, name="container4")
41
63
 
42
64
  cls.form_data = {
43
65
  "name": "container5",
44
66
  "host": host1.pk,
45
67
  "image": image1.pk,
46
- "state": "created",
47
68
  "restart_policy": "unless-stopped",
48
69
  }
49
70
 
@@ -54,12 +75,12 @@ class ContainerViewsTestCase(
54
75
  )
55
76
 
56
77
  cls.bulk_edit_data = {
57
- "state": "running",
78
+ "hostname": "h",
58
79
  "restart_policy": "always",
59
80
  }
60
81
 
61
82
  cls.csv_update_data = (
62
- "id,name,host,image,state,hostname,restart_policy",
63
- f"{container1.pk},container1,{host1.pk},{image1.pk},paused,,on-failure",
64
- f"{container2.pk},container2,{host1.pk},{image1.pk},running,container2,unless-stopped",
83
+ "id,name,host,image,hostname,restart_policy",
84
+ f"{container1.pk},container1,{host1.pk},{image1.pk},,on-failure",
85
+ f"{container2.pk},container2,{host1.pk},{image1.pk},container2,unless-stopped",
65
86
  )
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: netbox-docker-plugin
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: Manage Docker with Netbox & style.
5
- Author-email: Vincent Simonin <vincent@saashup.com>
5
+ Author-email: Vincent Simonin <vincent@saashup.com>, David Delassus <david.jose.delassus@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/SaaShup/netbox-docker-plugin
7
7
  Project-URL: Bug Tracker, https://github.com/SaaShup/netbox-docker-plugin/issues
8
8
  Keywords: netbox,netbox-plugin,docker
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  netbox_docker_plugin/__init__.py
6
6
  netbox_docker_plugin/filtersets.py
7
7
  netbox_docker_plugin/navigation.py
8
+ netbox_docker_plugin/signals.py
8
9
  netbox_docker_plugin/tables.py
9
10
  netbox_docker_plugin/urls.py
10
11
  netbox_docker_plugin.egg-info/PKG-INFO
@@ -56,6 +57,7 @@ netbox_docker_plugin/migrations/0025_alter_image_version.py
56
57
  netbox_docker_plugin/migrations/0026_image_digest.py
57
58
  netbox_docker_plugin/migrations/0027_container_restart_policy.py
58
59
  netbox_docker_plugin/migrations/0028_mount_and_bind_read_only.py
60
+ netbox_docker_plugin/migrations/0029_alter_container_state.py
59
61
  netbox_docker_plugin/migrations/__init__.py
60
62
  netbox_docker_plugin/models/__init__.py
61
63
  netbox_docker_plugin/models/container.py
@@ -76,6 +78,7 @@ netbox_docker_plugin/tests/__init__.py
76
78
  netbox_docker_plugin/tests/base.py
77
79
  netbox_docker_plugin/tests/test_init.py
78
80
  netbox_docker_plugin/tests/container/__init__.py
81
+ netbox_docker_plugin/tests/container/test_container_actions.py
79
82
  netbox_docker_plugin/tests/container/test_container_api.py
80
83
  netbox_docker_plugin/tests/container/test_container_operation_view.py
81
84
  netbox_docker_plugin/tests/container/test_container_validation.py
@@ -4,9 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "netbox-docker-plugin"
7
- version = "1.7.0"
7
+ version = "1.8.0"
8
8
  authors = [
9
9
  { name="Vincent Simonin", email="vincent@saashup.com" },
10
+ { name="David Delassus", email="david.jose.delassus@gmail.com" }
10
11
  ]
11
12
  description = "Manage Docker with Netbox & style."
12
13
  readme = "README.md"