netbox-docker-plugin 1.6.2__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.6.2/netbox_docker_plugin.egg-info → netbox_docker_plugin-1.8.0}/PKG-INFO +2 -2
  2. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/__init__.py +5 -3
  3. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/serializers.py +3 -0
  4. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/bind.py +2 -0
  5. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/container.py +2 -24
  6. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/mount.py +2 -0
  7. netbox_docker_plugin-1.8.0/netbox_docker_plugin/migrations/0028_mount_and_bind_read_only.py +25 -0
  8. netbox_docker_plugin-1.8.0/netbox_docker_plugin/migrations/0029_alter_container_state.py +20 -0
  9. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/container.py +41 -2
  10. netbox_docker_plugin-1.8.0/netbox_docker_plugin/signals.py +33 -0
  11. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tables.py +24 -4
  12. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/container.html +28 -4
  13. netbox_docker_plugin-1.8.0/netbox_docker_plugin/tests/container/test_container_actions.py +171 -0
  14. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_api.py +22 -3
  15. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_operation_view.py +35 -10
  16. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_validation.py +10 -0
  17. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/test_container_views.py +30 -9
  18. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0/netbox_docker_plugin.egg-info}/PKG-INFO +2 -2
  19. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin.egg-info/SOURCES.txt +4 -0
  20. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/pyproject.toml +2 -1
  21. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/LICENSE +0 -0
  22. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/MANIFEST.in +0 -0
  23. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/README.md +0 -0
  24. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/__init__.py +0 -0
  25. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/urls.py +0 -0
  26. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/api/views.py +0 -0
  27. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/filtersets.py +0 -0
  28. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/__init__.py +0 -0
  29. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/env.py +0 -0
  30. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/host.py +0 -0
  31. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/image.py +0 -0
  32. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/label.py +0 -0
  33. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/network.py +0 -0
  34. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/network_setting.py +0 -0
  35. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/port.py +0 -0
  36. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/registry.py +0 -0
  37. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/forms/volume.py +0 -0
  38. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0001_initial.py +0 -0
  39. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0002_image.py +0 -0
  40. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0003_image_size.py +0 -0
  41. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0004_volume.py +0 -0
  42. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0005_network.py +0 -0
  43. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0006_container_port_mount_label_env_and_more.py +0 -0
  44. {netbox_docker_plugin-1.6.2 → 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
  45. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0008_alter_network_unique_together_network_networkid_and_more.py +0 -0
  46. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0009_remove_container_network_networksettings_and_more.py +0 -0
  47. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0010_container_containerid_and_more.py +0 -0
  48. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0011_host_token.py +0 -0
  49. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0012_host_state.py +0 -0
  50. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0013_host_netbox_url.py +0 -0
  51. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0014_container_operation.py +0 -0
  52. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0015_remove_mount_netbox_docker_plugin_mount_unique_volume_and_more.py +0 -0
  53. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0016_alter_env_value.py +0 -0
  54. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0017_network_state.py +0 -0
  55. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0018_alter_mount_volume_alter_networksetting_network.py +0 -0
  56. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0019_host_agent_version_host_docker_api_version.py +0 -0
  57. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0020_container_hostname.py +0 -0
  58. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0021_registry_and_more.py +0 -0
  59. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0022_bind_bind_netbox_docker_plugin_bind_unique_bind.py +0 -0
  60. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0023_delete_hosts.py +0 -0
  61. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0024_registry_host_alter_registry_name_and_more.py +0 -0
  62. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0025_alter_image_version.py +0 -0
  63. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0026_image_digest.py +0 -0
  64. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/0027_container_restart_policy.py +0 -0
  65. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/migrations/__init__.py +0 -0
  66. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/__init__.py +0 -0
  67. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/host.py +0 -0
  68. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/image.py +0 -0
  69. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/network.py +0 -0
  70. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/registry.py +0 -0
  71. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/models/volume.py +0 -0
  72. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/navigation.py +0 -0
  73. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/host.html +0 -0
  74. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/image.html +0 -0
  75. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/network.html +0 -0
  76. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/registry.html +0 -0
  77. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templates/netbox_docker_plugin/volume.html +0 -0
  78. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templatetags/__init__.py +0 -0
  79. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/templatetags/host.py +0 -0
  80. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/__init__.py +0 -0
  81. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/base.py +0 -0
  82. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/container/__init__.py +0 -0
  83. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/__init__.py +0 -0
  84. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/test_host_api.py +0 -0
  85. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/test_host_views.py +0 -0
  86. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/host/test_replace_password.py +0 -0
  87. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/image/__init__.py +0 -0
  88. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/image/test_image_api.py +0 -0
  89. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/image/test_image_views.py +0 -0
  90. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/network/__init__.py +0 -0
  91. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/network/test_network_api.py +0 -0
  92. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/network/test_network_views.py +0 -0
  93. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/registry/__init__.py +0 -0
  94. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/registry/test_registry_api.py +0 -0
  95. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/registry/test_registry_views.py +0 -0
  96. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/test_init.py +0 -0
  97. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/volume/__init__.py +0 -0
  98. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/volume/test_volume_api.py +0 -0
  99. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/tests/volume/test_volume_views.py +0 -0
  100. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/urls.py +0 -0
  101. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/utilities/__init__.py +0 -0
  102. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/__init__.py +0 -0
  103. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/bind.py +0 -0
  104. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/container.py +0 -0
  105. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/env.py +0 -0
  106. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/host.py +0 -0
  107. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/image.py +0 -0
  108. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/label.py +0 -0
  109. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/mount.py +0 -0
  110. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/network.py +0 -0
  111. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/network_setting.py +0 -0
  112. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/port.py +0 -0
  113. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/registry.py +0 -0
  114. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin/views/volume.py +0 -0
  115. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin.egg-info/dependency_links.txt +0 -0
  116. {netbox_docker_plugin-1.6.2 → netbox_docker_plugin-1.8.0}/netbox_docker_plugin.egg-info/top_level.txt +0 -0
  117. {netbox_docker_plugin-1.6.2 → 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.6.2
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.6.2"
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()
@@ -159,6 +159,7 @@ class NestedMountSerializer(WritableNestedSerializer):
159
159
  fields = (
160
160
  "id",
161
161
  "source",
162
+ "read_only",
162
163
  )
163
164
 
164
165
 
@@ -340,6 +341,7 @@ class MountSerializer(serializers.ModelSerializer):
340
341
  fields = (
341
342
  "source",
342
343
  "volume",
344
+ "read_only",
343
345
  )
344
346
 
345
347
 
@@ -353,6 +355,7 @@ class BindSerializer(serializers.ModelSerializer):
353
355
  fields = (
354
356
  "host_path",
355
357
  "container_path",
358
+ "read_only",
356
359
  )
357
360
 
358
361
 
@@ -27,11 +27,13 @@ class BindForm(BootstrapMixin, forms.ModelForm):
27
27
  "container",
28
28
  "host_path",
29
29
  "container_path",
30
+ "read_only",
30
31
  )
31
32
  labels = {
32
33
  "container": "Container",
33
34
  "host_path": "Path to mount on host",
34
35
  "container_path": "Mountpoint in container",
36
+ "read_only": "Mount as read-only within the container",
35
37
  }
36
38
 
37
39
 
@@ -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):
@@ -31,11 +31,13 @@ class MountForm(BootstrapMixin, forms.ModelForm):
31
31
  "container",
32
32
  "source",
33
33
  "volume",
34
+ "read_only",
34
35
  )
35
36
  labels = {
36
37
  "container": "Container",
37
38
  "source": "Source directory",
38
39
  "volume": "Volume",
40
+ "read_only": "Mount as read-only within the container",
39
41
  }
40
42
 
41
43
 
@@ -0,0 +1,25 @@
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", "0027_container_restart_policy"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="mount",
17
+ name="read_only",
18
+ field=models.BooleanField(default=False),
19
+ ),
20
+ migrations.AddField(
21
+ model_name="bind",
22
+ name="read_only",
23
+ field=models.BooleanField(default=False),
24
+ ),
25
+ ]
@@ -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
 
@@ -301,6 +334,9 @@ class Mount(models.Model):
301
334
  MaxLengthValidator(limit_value=1024),
302
335
  ],
303
336
  )
337
+ read_only = models.BooleanField(
338
+ default=False,
339
+ )
304
340
 
305
341
  class Meta:
306
342
  """Mount Model Meta Class"""
@@ -349,6 +385,9 @@ class Bind(models.Model):
349
385
  MaxLengthValidator(limit_value=1024),
350
386
  ],
351
387
  )
388
+ read_only = models.BooleanField(
389
+ default=False,
390
+ )
352
391
 
353
392
  class Meta:
354
393
  """Mount Model Meta Class"""
@@ -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
+ )
@@ -335,8 +335,18 @@ class MountTable(NetBoxTable):
335
335
  """Mount Table definition Meta class"""
336
336
 
337
337
  model = Mount
338
- fields = ("container", "source", "volume", "container")
339
- default_columns = ("container", "source", "volume")
338
+ fields = (
339
+ "container",
340
+ "source",
341
+ "volume",
342
+ "read_only",
343
+ )
344
+ default_columns = (
345
+ "container",
346
+ "source",
347
+ "volume",
348
+ "read_only",
349
+ )
340
350
 
341
351
 
342
352
  class BindTable(NetBoxTable):
@@ -350,8 +360,18 @@ class BindTable(NetBoxTable):
350
360
  """Bind Table definition Meta class"""
351
361
 
352
362
  model = Bind
353
- fields = ("container", "host_path", "container_path")
354
- default_columns = ("container", "host_path", "container_path")
363
+ fields = (
364
+ "container",
365
+ "host_path",
366
+ "container_path",
367
+ "read_only",
368
+ )
369
+ default_columns = (
370
+ "container",
371
+ "host_path",
372
+ "container_path",
373
+ "read_only",
374
+ )
355
375
 
356
376
 
357
377
  class NetworkSettingTable(NetBoxTable):
@@ -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
  {