netbox-docker-plugin 5.1.0__tar.gz → 5.2.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.
- {netbox_docker_plugin-5.1.0/netbox_docker_plugin.egg-info → netbox_docker_plugin-5.2.0}/PKG-INFO +5 -1
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/__init__.py +1 -1
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/api/serializers.py +3 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/api/views.py +8 -2
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/filtersets.py +1 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/host.py +37 -1
- netbox_docker_plugin-5.2.0/netbox_docker_plugin/migrations/1044_host_virtual_machine.py +29 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/container.py +23 -3
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/host.py +7 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tables.py +2 -0
- netbox_docker_plugin-5.2.0/netbox_docker_plugin/template_content.py +23 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/host.html +11 -0
- netbox_docker_plugin-5.2.0/netbox_docker_plugin/templates/netbox_docker_plugin/virtual_machine_host_table.html +4 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/container/test_container_api.py +25 -4
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/container/test_container_api_exec.py +8 -3
- netbox_docker_plugin-5.2.0/netbox_docker_plugin/tests/container/test_container_delete.py +137 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/container/test_container_operation_view.py +6 -2
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/container/test_container_validation.py +36 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/container/test_container_views.py +6 -2
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/host/test_host_views.py +2 -2
- netbox_docker_plugin-5.2.0/netbox_docker_plugin/tests/host/test_host_virtual_machine.py +74 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/volume/test_volume_api.py +3 -1
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/urls.py +1 -1
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/container.py +60 -1
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/host.py +15 -6
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0/netbox_docker_plugin.egg-info}/PKG-INFO +5 -1
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin.egg-info/SOURCES.txt +6 -0
- netbox_docker_plugin-5.2.0/netbox_docker_plugin.egg-info/requires.txt +5 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/pyproject.toml +7 -1
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/LICENSE +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/MANIFEST.in +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/README.md +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/api/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/api/renderers.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/api/urls.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/bind.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/container.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/device.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/env.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/image.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/label.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/log_driver_option.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/mount.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/network.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/network_setting.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/port.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/registry.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/sysctl.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/volume.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0001_initial.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0002_image.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0003_image_size.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0004_volume.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0005_network.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0006_container_port_mount_label_env_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0007_remove_container_netbox_docker_plugin_container_unique_name_host__and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0008_alter_network_unique_together_network_networkid_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0009_remove_container_network_networksettings_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0010_container_containerid_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0011_host_token.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0012_host_state.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0013_host_netbox_url.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0014_container_operation.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0015_remove_mount_netbox_docker_plugin_mount_unique_volume_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0016_alter_env_value.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0017_network_state.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0018_alter_mount_volume_alter_networksetting_network.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0019_host_agent_version_host_docker_api_version.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0020_container_hostname.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0021_registry_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0022_bind_bind_netbox_docker_plugin_bind_unique_bind.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0023_delete_hosts.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0024_registry_host_alter_registry_name_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0025_alter_image_version.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0026_image_digest.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0027_container_restart_policy.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0028_mount_and_bind_read_only.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0029_alter_container_state.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0030_alter_container_containerid_alter_container_hostname_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0031_container_cap_add.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0032_device_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0033_host_operation.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0034_volume_max_size.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0035_container_log_driver_logdriveroption.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0036_alter_container_log_driver.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0037_alter_env_value.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/0038_container_cmd.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/1039_alter_port_public_port.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/1040_alter_container_log_driver.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/1041_sysctl.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/1042_registry_username_max_length.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/1043_container_cap_drop_container_extra_hosts_and_more.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/migrations/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/image.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/network.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/registry.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/volume.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/navigation.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/signals.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/container-exec.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/container-layout.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/container-logs.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/container.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/host-graph.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/image.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/network.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/registry.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templates/netbox_docker_plugin/volume.html +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templatetags/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/templatetags/host.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/base.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/container/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/container/test_container_actions.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/host/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/host/test_host_api.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/host/test_replace_password.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/image/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/image/test_force_pull_image_with_special_cf.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/image/test_image_api.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/image/test_image_views.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/network/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/network/test_network_api.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/network/test_network_views.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/registry/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/registry/test_registry_api.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/registry/test_registry_views.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/test_init.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/volume/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/tests/volume/test_volume_views.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/utilities/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/__init__.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/bind.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/device.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/env.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/image.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/label.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/log_driver_option.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/mount.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/network.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/network_setting.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/port.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/registry.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/sysctl.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/views/volume.py +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin.egg-info/dependency_links.txt +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin.egg-info/top_level.txt +0 -0
- {netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/setup.cfg +0 -0
{netbox_docker_plugin-5.1.0/netbox_docker_plugin.egg-info → netbox_docker_plugin-5.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-docker-plugin
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2.0
|
|
4
4
|
Summary: Manage Docker with Netbox & style.
|
|
5
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
|
|
@@ -10,6 +10,10 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Requires-Python: >=3.12
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: requests-mock; extra == "dev"
|
|
15
|
+
Requires-Dist: coverage; extra == "dev"
|
|
16
|
+
Requires-Dist: pylint; extra == "dev"
|
|
13
17
|
Dynamic: license-file
|
|
14
18
|
|
|
15
19
|
# Netbox Docker Plugin
|
|
@@ -11,7 +11,7 @@ class NetBoxDockerConfig(PluginConfig):
|
|
|
11
11
|
name = "netbox_docker_plugin"
|
|
12
12
|
verbose_name = " NetBox Docker Plugin"
|
|
13
13
|
description = "Manage Docker"
|
|
14
|
-
version = "5.
|
|
14
|
+
version = "5.2.0"
|
|
15
15
|
base_url = "docker"
|
|
16
16
|
min_version = "4.5.0"
|
|
17
17
|
author = "Vincent Simonin <vincent@saashup.com>, David Delassus <david.jose.delassus@gmail.com>"
|
{netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/api/serializers.py
RENAMED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
from rest_framework import serializers
|
|
6
6
|
from utilities.query import dict_to_filter_params
|
|
7
7
|
from users.models import Token
|
|
8
|
+
from virtualization.api.serializers import VirtualMachineSerializer
|
|
8
9
|
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
|
9
10
|
from ..models.host import Host
|
|
10
11
|
from ..models.image import Image
|
|
@@ -693,6 +694,7 @@ class HostSerializer(NetBoxModelSerializer):
|
|
|
693
694
|
containers = NestedContainerSerializer(many=True, read_only=True)
|
|
694
695
|
registries = NestedRegistrySerializer(many=True, read_only=True)
|
|
695
696
|
token = NestedTokenSerializer(read_only=True)
|
|
697
|
+
virtual_machine = VirtualMachineSerializer(required=False)
|
|
696
698
|
|
|
697
699
|
class Meta:
|
|
698
700
|
"""Host Serializer Meta class"""
|
|
@@ -706,6 +708,7 @@ class HostSerializer(NetBoxModelSerializer):
|
|
|
706
708
|
"name",
|
|
707
709
|
"state",
|
|
708
710
|
"token",
|
|
711
|
+
"virtual_machine",
|
|
709
712
|
"netbox_base_url",
|
|
710
713
|
"agent_version",
|
|
711
714
|
"docker_api_version",
|
|
@@ -39,7 +39,13 @@ class HostViewSet(NetBoxModelViewSet):
|
|
|
39
39
|
"""Host view set class"""
|
|
40
40
|
|
|
41
41
|
queryset = Host.objects.prefetch_related(
|
|
42
|
-
"images",
|
|
42
|
+
"images",
|
|
43
|
+
"volumes",
|
|
44
|
+
"networks",
|
|
45
|
+
"containers",
|
|
46
|
+
"registries",
|
|
47
|
+
"virtual_machine",
|
|
48
|
+
"tags",
|
|
43
49
|
)
|
|
44
50
|
filterset_class = filtersets.HostFilterSet
|
|
45
51
|
serializer_class = HostSerializer
|
|
@@ -108,7 +114,7 @@ class ImageViewSet(NetBoxModelViewSet):
|
|
|
108
114
|
url,
|
|
109
115
|
timeout=10,
|
|
110
116
|
data=json.dumps({"data": data}, cls=DjangoJSONEncoder),
|
|
111
|
-
headers={"Content-Type": "application/json"}
|
|
117
|
+
headers={"Content-Type": "application/json"},
|
|
112
118
|
)
|
|
113
119
|
resp.raise_for_status()
|
|
114
120
|
|
{netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/forms/host.py
RENAMED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from django import forms
|
|
4
4
|
from utilities.forms.rendering import FieldSet
|
|
5
|
-
from utilities.forms.fields import TagFilterField
|
|
5
|
+
from utilities.forms.fields import TagFilterField, DynamicModelChoiceField
|
|
6
|
+
from virtualization.models import VirtualMachine
|
|
6
7
|
from netbox.forms import (
|
|
7
8
|
NetBoxModelForm,
|
|
8
9
|
NetBoxModelImportForm,
|
|
@@ -12,9 +13,43 @@ from netbox.forms import (
|
|
|
12
13
|
from ..models.host import Host, HostStateChoices
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
class HostAddForm(NetBoxModelForm):
|
|
17
|
+
"""Host form definition class"""
|
|
18
|
+
|
|
19
|
+
virtual_machine = DynamicModelChoiceField(
|
|
20
|
+
queryset=VirtualMachine.objects.all(),
|
|
21
|
+
selector=True,
|
|
22
|
+
required=False,
|
|
23
|
+
label="Virtual Machine",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
class Meta:
|
|
27
|
+
"""Host form definition Meta class"""
|
|
28
|
+
|
|
29
|
+
model = Host
|
|
30
|
+
fields = (
|
|
31
|
+
"name",
|
|
32
|
+
"endpoint",
|
|
33
|
+
"virtual_machine",
|
|
34
|
+
)
|
|
35
|
+
help_texts = {"name": "Unique Name", "endpoint": "Docker instance endpoint"}
|
|
36
|
+
labels = {"name": "Name", "endpoint": "Endpoint"}
|
|
37
|
+
|
|
38
|
+
def __init__(self, *args, **kwargs):
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
self.fields.pop("tags", None)
|
|
41
|
+
|
|
42
|
+
|
|
15
43
|
class HostForm(NetBoxModelForm):
|
|
16
44
|
"""Host form definition class"""
|
|
17
45
|
|
|
46
|
+
virtual_machine = DynamicModelChoiceField(
|
|
47
|
+
queryset=VirtualMachine.objects.all(),
|
|
48
|
+
selector=True,
|
|
49
|
+
required=False,
|
|
50
|
+
label="Virtual Machine",
|
|
51
|
+
)
|
|
52
|
+
|
|
18
53
|
class Meta:
|
|
19
54
|
"""Host form definition Meta class"""
|
|
20
55
|
|
|
@@ -22,6 +57,7 @@ class HostForm(NetBoxModelForm):
|
|
|
22
57
|
fields = (
|
|
23
58
|
"name",
|
|
24
59
|
"endpoint",
|
|
60
|
+
"virtual_machine",
|
|
25
61
|
"tags",
|
|
26
62
|
)
|
|
27
63
|
help_texts = {"name": "Unique Name", "endpoint": "Docker instance endpoint"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# pylint: disable=C0103
|
|
2
|
+
"""Migration file"""
|
|
3
|
+
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
"""Migration file"""
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
(
|
|
13
|
+
"netbox_docker_plugin",
|
|
14
|
+
"1043_container_cap_drop_container_extra_hosts_and_more",
|
|
15
|
+
),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
operations = [
|
|
19
|
+
migrations.AddField(
|
|
20
|
+
model_name="host",
|
|
21
|
+
name="virtual_machine",
|
|
22
|
+
field=models.OneToOneField(
|
|
23
|
+
blank=True,
|
|
24
|
+
null=True,
|
|
25
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
26
|
+
to="virtualization.virtualmachine",
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
]
|
{netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/container.py
RENAMED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
# pylint: disable=E1101
|
|
4
4
|
|
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
|
5
6
|
from django.contrib.postgres.fields import ArrayField
|
|
6
7
|
from django.core.exceptions import ValidationError
|
|
7
|
-
from django.db import models
|
|
8
|
-
from django.db.models.functions import Lower
|
|
9
|
-
from django.urls import reverse
|
|
10
8
|
from django.core.validators import (
|
|
11
9
|
MinLengthValidator,
|
|
12
10
|
MaxLengthValidator,
|
|
13
11
|
MinValueValidator,
|
|
14
12
|
MaxValueValidator,
|
|
15
13
|
)
|
|
14
|
+
from django.db import models
|
|
15
|
+
from django.db.models.functions import Lower
|
|
16
|
+
from django.urls import reverse
|
|
17
|
+
from extras.models import JournalEntry
|
|
16
18
|
from utilities.choices import ChoiceSet
|
|
17
19
|
from utilities.querysets import RestrictedQuerySet
|
|
18
20
|
from netbox.models import NetBoxModel
|
|
@@ -306,6 +308,18 @@ class Container(NetBoxModel):
|
|
|
306
308
|
),
|
|
307
309
|
)
|
|
308
310
|
|
|
311
|
+
def delete(self, using=None, keep_parents=False):
|
|
312
|
+
ct = ContentType.objects.get_for_model(self)
|
|
313
|
+
qs = JournalEntry.objects.filter(
|
|
314
|
+
assigned_object_type=ct,
|
|
315
|
+
assigned_object_id=self.pk,
|
|
316
|
+
)
|
|
317
|
+
# _raw_delete issues a single SQL DELETE bypassing Django's Collector,
|
|
318
|
+
# avoiding per-row post_delete signals (ObjectChange writes) that make
|
|
319
|
+
# bulk journal cleanup O(n) slow.
|
|
320
|
+
qs._raw_delete(qs.db) # pylint: disable=protected-access
|
|
321
|
+
return super().delete(using, keep_parents)
|
|
322
|
+
|
|
309
323
|
def __str__(self):
|
|
310
324
|
return f"{self.name}"
|
|
311
325
|
|
|
@@ -314,6 +328,7 @@ class Container(NetBoxModel):
|
|
|
314
328
|
return reverse("plugins:netbox_docker_plugin:container", args=[self.pk])
|
|
315
329
|
|
|
316
330
|
def clean(self):
|
|
331
|
+
"""Validate that the image belongs to the same host and has been pulled."""
|
|
317
332
|
super().clean()
|
|
318
333
|
|
|
319
334
|
if self.host != self.image.host:
|
|
@@ -321,6 +336,11 @@ class Container(NetBoxModel):
|
|
|
321
336
|
{"image": f"Image {self.image} does not belong to host {self.host}."}
|
|
322
337
|
)
|
|
323
338
|
|
|
339
|
+
if not self.image.ImageID:
|
|
340
|
+
raise ValidationError(
|
|
341
|
+
{"image": f"Image {self.image} has no ID yet. Pull or refresh it first."}
|
|
342
|
+
)
|
|
343
|
+
|
|
324
344
|
|
|
325
345
|
class Port(models.Model):
|
|
326
346
|
"""Container definition class"""
|
{netbox_docker_plugin-5.1.0 → netbox_docker_plugin-5.2.0}/netbox_docker_plugin/models/host.py
RENAMED
|
@@ -9,6 +9,7 @@ from django.core.validators import (
|
|
|
9
9
|
)
|
|
10
10
|
from utilities.choices import ChoiceSet
|
|
11
11
|
from users.models import Token
|
|
12
|
+
from virtualization.models import VirtualMachine
|
|
12
13
|
from netbox.models import NetBoxModel
|
|
13
14
|
|
|
14
15
|
|
|
@@ -64,6 +65,12 @@ class Host(NetBoxModel):
|
|
|
64
65
|
default=HostStateChoices.STATE_CREATED,
|
|
65
66
|
)
|
|
66
67
|
token = models.ForeignKey(Token, on_delete=models.SET_NULL, null=True, blank=True)
|
|
68
|
+
virtual_machine = models.OneToOneField(
|
|
69
|
+
VirtualMachine,
|
|
70
|
+
on_delete=models.SET_NULL,
|
|
71
|
+
null=True,
|
|
72
|
+
blank=True,
|
|
73
|
+
)
|
|
67
74
|
netbox_base_url = models.CharField(
|
|
68
75
|
max_length=1024,
|
|
69
76
|
validators=[URLValidator()],
|
|
@@ -26,6 +26,7 @@ class HostTable(NetBoxTable):
|
|
|
26
26
|
"""Host Table definition class"""
|
|
27
27
|
|
|
28
28
|
name = tables.Column(linkify=True)
|
|
29
|
+
virtual_machine = tables.Column(linkify=True, verbose_name="Virtual Machine")
|
|
29
30
|
image_count = columns.LinkedCountColumn(
|
|
30
31
|
viewname="plugins:netbox_docker_plugin:image_list",
|
|
31
32
|
url_params={"host_id": "pk"},
|
|
@@ -62,6 +63,7 @@ class HostTable(NetBoxTable):
|
|
|
62
63
|
"name",
|
|
63
64
|
"endpoint",
|
|
64
65
|
"state",
|
|
66
|
+
"virtual_machine",
|
|
65
67
|
"agent_version",
|
|
66
68
|
"docker_api_version",
|
|
67
69
|
"image_count",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Template modifications definitions"""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=W0223
|
|
4
|
+
|
|
5
|
+
from netbox.plugins import PluginTemplateExtension
|
|
6
|
+
from .models import Host
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VirtualMachineHostTable(PluginTemplateExtension):
|
|
10
|
+
"""Virtual machine object template"""
|
|
11
|
+
|
|
12
|
+
models = ["virtualization.virtualmachine"]
|
|
13
|
+
|
|
14
|
+
def right_page(self):
|
|
15
|
+
return self.render(
|
|
16
|
+
"netbox_docker_plugin/virtual_machine_host_table.html",
|
|
17
|
+
extra_context={
|
|
18
|
+
"Host": Host.objects.filter(virtual_machine=self.context["object"])
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
template_extensions = [VirtualMachineHostTable]
|
|
@@ -27,6 +27,17 @@
|
|
|
27
27
|
<th scope="row">Endpoint</th>
|
|
28
28
|
<td>{{ object.endpoint|remove_password }}</td>
|
|
29
29
|
</tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<th scope="row">Virtual Machine</th>
|
|
32
|
+
<td>
|
|
33
|
+
{% if object.virtual_machine %}
|
|
34
|
+
<a href="{% url 'virtualization:virtualmachine' pk=object.virtual_machine.pk %}">
|
|
35
|
+
{{ object.virtual_machine|placeholder }}</a>
|
|
36
|
+
{% else %}
|
|
37
|
+
{{ object.virtual_machine|placeholder }}</a>
|
|
38
|
+
{% endif %}
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
30
41
|
<tr>
|
|
31
42
|
<th scope="row">State</th>
|
|
32
43
|
<td>{{ object.get_state_display }}</td>
|
|
@@ -70,8 +70,18 @@ class ContainerApiTestCase(
|
|
|
70
70
|
host=host2, name="registry2", serveraddress="http://localhost:8082"
|
|
71
71
|
)
|
|
72
72
|
|
|
73
|
-
image1 = Image.objects.create(
|
|
74
|
-
|
|
73
|
+
image1 = Image.objects.create(
|
|
74
|
+
host=host1,
|
|
75
|
+
name="image1",
|
|
76
|
+
registry=registry1,
|
|
77
|
+
ImageID="sha256:abc123",
|
|
78
|
+
)
|
|
79
|
+
image2 = Image.objects.create(
|
|
80
|
+
host=host2,
|
|
81
|
+
name="image2",
|
|
82
|
+
registry=registry2,
|
|
83
|
+
ImageID="sha256:abc456",
|
|
84
|
+
)
|
|
75
85
|
|
|
76
86
|
network1 = Network.objects.create(host=host1, name="network1")
|
|
77
87
|
network2 = Network.objects.create(host=host2, name="network2")
|
|
@@ -244,6 +254,7 @@ class ContainerApiTestCase(
|
|
|
244
254
|
host=host3,
|
|
245
255
|
name="image3",
|
|
246
256
|
registry=registry3,
|
|
257
|
+
ImageID="sha256:abc789",
|
|
247
258
|
)
|
|
248
259
|
container11 = Container.objects.create(
|
|
249
260
|
host=host3,
|
|
@@ -330,8 +341,18 @@ class ContainerApiTestCase(
|
|
|
330
341
|
host=host2, name="registry5", serveraddress="http://localhost:8082"
|
|
331
342
|
)
|
|
332
343
|
|
|
333
|
-
image1 = Image.objects.create(
|
|
334
|
-
|
|
344
|
+
image1 = Image.objects.create(
|
|
345
|
+
host=host1,
|
|
346
|
+
name="image",
|
|
347
|
+
registry=registry1,
|
|
348
|
+
ImageID="sha256:abc101112",
|
|
349
|
+
)
|
|
350
|
+
image2 = Image.objects.create(
|
|
351
|
+
host=host2,
|
|
352
|
+
name="image",
|
|
353
|
+
registry=registry2,
|
|
354
|
+
ImageID="sha256:abc131415",
|
|
355
|
+
)
|
|
335
356
|
|
|
336
357
|
container = Container.objects.create(host=host1, image=image1, name="container")
|
|
337
358
|
|
|
@@ -28,7 +28,12 @@ class ContainerApiExecTestCase(BaseAPITestCase):
|
|
|
28
28
|
host=host1, name="registry1", serveraddress="http://localhost:8080"
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
image1 = Image.objects.create(
|
|
31
|
+
image1 = Image.objects.create(
|
|
32
|
+
host=host1,
|
|
33
|
+
name="image1",
|
|
34
|
+
registry=registry1,
|
|
35
|
+
ImageID="sha256:abc123",
|
|
36
|
+
)
|
|
32
37
|
|
|
33
38
|
container = Container.objects.create(
|
|
34
39
|
host=host1,
|
|
@@ -86,11 +91,11 @@ class ContainerApiExecTestCase(BaseAPITestCase):
|
|
|
86
91
|
m.put(
|
|
87
92
|
"http://localhost:8080/api/engine/containers/1234/exec",
|
|
88
93
|
text="Error",
|
|
89
|
-
status_code=
|
|
94
|
+
status_code=500,
|
|
90
95
|
)
|
|
91
96
|
|
|
92
97
|
response = self.client.post(
|
|
93
98
|
self.endpoint, **self.header, data={"cmd": ["ls"]}, format="json"
|
|
94
99
|
)
|
|
95
100
|
self.assertHttpStatus(response, status.HTTP_502_BAD_GATEWAY)
|
|
96
|
-
self.assertEqual(response.data,
|
|
101
|
+
self.assertEqual(response.data, "Error")
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Container deletion tests — journal entry cleanup"""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
from django.test import TestCase
|
|
7
|
+
|
|
8
|
+
from extras.models import JournalEntry
|
|
9
|
+
from netbox_docker_plugin.models.container import Container
|
|
10
|
+
from netbox_docker_plugin.models.host import Host
|
|
11
|
+
from netbox_docker_plugin.models.image import Image
|
|
12
|
+
from netbox_docker_plugin.models.registry import Registry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _make_journal_entries(container, count):
|
|
16
|
+
ct = ContentType.objects.get_for_model(Container)
|
|
17
|
+
batch_size = 10000
|
|
18
|
+
for start in range(0, count, batch_size):
|
|
19
|
+
JournalEntry.objects.bulk_create(
|
|
20
|
+
[
|
|
21
|
+
JournalEntry(
|
|
22
|
+
assigned_object_type=ct,
|
|
23
|
+
assigned_object_id=container.pk,
|
|
24
|
+
created_by=None,
|
|
25
|
+
kind="info",
|
|
26
|
+
comments=f"entry {start + i}",
|
|
27
|
+
)
|
|
28
|
+
for i in range(min(batch_size, count - start))
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ContainerDeleteJournalTestCase(TestCase):
|
|
34
|
+
"""Verify that container deletion cleans up journal entries in batches."""
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def setUpTestData(cls):
|
|
38
|
+
cls.host = Host.objects.create(endpoint="http://localhost:8080", name="host1")
|
|
39
|
+
cls.registry = Registry.objects.create(
|
|
40
|
+
host=cls.host,
|
|
41
|
+
name="registry1",
|
|
42
|
+
serveraddress="http://localhost:8080",
|
|
43
|
+
)
|
|
44
|
+
cls.image = Image.objects.create(
|
|
45
|
+
host=cls.host, name="image1", registry=cls.registry
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def _make_container(self, name="container1", state="exited"):
|
|
49
|
+
"""Create and return a Container in the given state."""
|
|
50
|
+
return Container.objects.create(
|
|
51
|
+
host=self.host,
|
|
52
|
+
image=self.image,
|
|
53
|
+
name=name,
|
|
54
|
+
operation="none",
|
|
55
|
+
state=state,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def test_delete_container_with_no_journal_entries(self):
|
|
59
|
+
"""Container with no journal entries can be deleted without errors."""
|
|
60
|
+
container = self._make_container()
|
|
61
|
+
pk = container.pk
|
|
62
|
+
t0 = time.monotonic()
|
|
63
|
+
container.delete()
|
|
64
|
+
print(
|
|
65
|
+
f"Deleted container (0 journal entries) in {(time.monotonic() - t0) * 1000:.1f}ms"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
self.assertFalse(Container.objects.filter(pk=pk).exists())
|
|
69
|
+
|
|
70
|
+
def test_delete_container_removes_journal_entries(self):
|
|
71
|
+
"""Journal entries linked to the deleted container are removed."""
|
|
72
|
+
container = self._make_container(name="container2")
|
|
73
|
+
_make_journal_entries(container, 5)
|
|
74
|
+
ct = ContentType.objects.get_for_model(Container)
|
|
75
|
+
|
|
76
|
+
self.assertEqual(
|
|
77
|
+
JournalEntry.objects.filter(
|
|
78
|
+
assigned_object_type=ct, assigned_object_id=container.pk
|
|
79
|
+
).count(),
|
|
80
|
+
5,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
pk = container.pk
|
|
84
|
+
t0 = time.monotonic()
|
|
85
|
+
container.delete()
|
|
86
|
+
print(
|
|
87
|
+
f"Deleted container (5 journal entries) in {(time.monotonic() - t0) * 1000:.1f}ms"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self.assertFalse(Container.objects.filter(pk=pk).exists())
|
|
91
|
+
self.assertEqual(
|
|
92
|
+
JournalEntry.objects.filter(
|
|
93
|
+
assigned_object_type=ct, assigned_object_id=pk
|
|
94
|
+
).count(),
|
|
95
|
+
0,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def test_delete_container_removes_many_journal_entries(self):
|
|
99
|
+
"""Deletion removes all journal entries regardless of count."""
|
|
100
|
+
container = self._make_container(name="container3")
|
|
101
|
+
_make_journal_entries(container, 500000)
|
|
102
|
+
ct = ContentType.objects.get_for_model(Container)
|
|
103
|
+
|
|
104
|
+
pk = container.pk
|
|
105
|
+
t0 = time.monotonic()
|
|
106
|
+
container.delete()
|
|
107
|
+
elapsed = (time.monotonic() - t0) * 1000
|
|
108
|
+
print(f"Deleted container (500000 journal entries) in {elapsed:.1f}ms")
|
|
109
|
+
|
|
110
|
+
self.assertFalse(Container.objects.filter(pk=pk).exists())
|
|
111
|
+
self.assertEqual(
|
|
112
|
+
JournalEntry.objects.filter(
|
|
113
|
+
assigned_object_type=ct, assigned_object_id=pk
|
|
114
|
+
).count(),
|
|
115
|
+
0,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def test_delete_does_not_remove_journal_entries_of_other_containers(self):
|
|
119
|
+
"""Deleting one container must not remove journal entries of another."""
|
|
120
|
+
container_a = self._make_container(name="container4a")
|
|
121
|
+
container_b = self._make_container(name="container4b")
|
|
122
|
+
_make_journal_entries(container_a, 3)
|
|
123
|
+
_make_journal_entries(container_b, 3)
|
|
124
|
+
ct = ContentType.objects.get_for_model(Container)
|
|
125
|
+
|
|
126
|
+
t0 = time.monotonic()
|
|
127
|
+
container_a.delete()
|
|
128
|
+
print(
|
|
129
|
+
f"Deleted container (3 journal entries) in {(time.monotonic() - t0) * 1000:.1f}ms"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self.assertEqual(
|
|
133
|
+
JournalEntry.objects.filter(
|
|
134
|
+
assigned_object_type=ct, assigned_object_id=container_b.pk
|
|
135
|
+
).count(),
|
|
136
|
+
3,
|
|
137
|
+
)
|
|
@@ -257,8 +257,12 @@ class ContainerViewsTestCase(BaseModelViewTestCase, ModelViewTestCase):
|
|
|
257
257
|
host=host2, name="registry2", serveraddress="http://localhost:8082"
|
|
258
258
|
)
|
|
259
259
|
|
|
260
|
-
image1 = Image.objects.create(
|
|
261
|
-
|
|
260
|
+
image1 = Image.objects.create(
|
|
261
|
+
host=host1, name="image1", registry=registry1, ImageID="sha256:abc123"
|
|
262
|
+
)
|
|
263
|
+
image2 = Image.objects.create(
|
|
264
|
+
host=host2, name="image2", registry=registry2, ImageID="sha256:abc456"
|
|
265
|
+
)
|
|
262
266
|
|
|
263
267
|
Container.objects.create(
|
|
264
268
|
host=host1,
|
|
@@ -39,6 +39,36 @@ class ContainerValidationTestCase(TestCase):
|
|
|
39
39
|
|
|
40
40
|
self.assertTrue(isinstance(container.id, int))
|
|
41
41
|
|
|
42
|
+
def test_that_container_image_must_have_an_id(self):
|
|
43
|
+
"""Test that the image must have been pulled (non-empty ImageID)"""
|
|
44
|
+
|
|
45
|
+
with self.assertRaises(ValidationError) as cm:
|
|
46
|
+
container = Container(
|
|
47
|
+
host=self.objects["host1"],
|
|
48
|
+
image=self.objects["image1"],
|
|
49
|
+
name="container_no_image_id",
|
|
50
|
+
operation="none",
|
|
51
|
+
state="created",
|
|
52
|
+
)
|
|
53
|
+
container.clean()
|
|
54
|
+
|
|
55
|
+
self.assertEqual(
|
|
56
|
+
cm.exception.message_dict["image"],
|
|
57
|
+
["Image image1:latest has no ID yet. Pull or refresh it first."],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def test_that_container_image_with_id_passes_validation(self):
|
|
61
|
+
"""Test that an image with a non-empty ImageID passes validation"""
|
|
62
|
+
|
|
63
|
+
container = Container(
|
|
64
|
+
host=self.objects["host1"],
|
|
65
|
+
image=self.objects["image1_pulled"],
|
|
66
|
+
name="container_with_image_id",
|
|
67
|
+
operation="none",
|
|
68
|
+
state="created",
|
|
69
|
+
)
|
|
70
|
+
container.clean() # must not raise
|
|
71
|
+
|
|
42
72
|
def test_that_container_image_must_be_on_the_same_host(self):
|
|
43
73
|
"""Test that Container image must be on the same host"""
|
|
44
74
|
|
|
@@ -138,6 +168,12 @@ class ContainerValidationTestCase(TestCase):
|
|
|
138
168
|
cls.objects["image1"] = Image.objects.create(
|
|
139
169
|
host=cls.objects["host1"], name="image1", registry=cls.objects["registry1"]
|
|
140
170
|
)
|
|
171
|
+
cls.objects["image1_pulled"] = Image.objects.create(
|
|
172
|
+
host=cls.objects["host1"],
|
|
173
|
+
name="image1_pulled",
|
|
174
|
+
registry=cls.objects["registry1"],
|
|
175
|
+
ImageID="sha256:abc123",
|
|
176
|
+
)
|
|
141
177
|
cls.objects["image2"] = Image.objects.create(
|
|
142
178
|
host=cls.objects["host2"], name="image2", registry=cls.objects["registry2"]
|
|
143
179
|
)
|
|
@@ -28,8 +28,12 @@ class ContainerViewsTestCase(
|
|
|
28
28
|
host=host2, name="registry2", serveraddress="http://localhost:8082"
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
image1 = Image.objects.create(
|
|
32
|
-
|
|
31
|
+
image1 = Image.objects.create(
|
|
32
|
+
host=host1, name="image1", registry=registry1, ImageID="sha256:abc123"
|
|
33
|
+
)
|
|
34
|
+
image2 = Image.objects.create(
|
|
35
|
+
host=host2, name="image2", registry=registry2, ImageID="sha256:abc456"
|
|
36
|
+
)
|
|
33
37
|
|
|
34
38
|
container1 = Container.objects.create(
|
|
35
39
|
host=host1,
|
|
@@ -26,6 +26,7 @@ class HostViewsTestCase(BaseModelViewTestCase, ViewTestCases.PrimaryObjectViewTe
|
|
|
26
26
|
"host7,http://localhost:8084",
|
|
27
27
|
)
|
|
28
28
|
bulk_edit_data = {"endpoint": "http://localhost:8083"}
|
|
29
|
+
validation_excluded_fields = ["tags"]
|
|
29
30
|
|
|
30
31
|
def setUp(self):
|
|
31
32
|
super().setUp()
|
|
@@ -91,8 +92,7 @@ class HostViewsTestCase(BaseModelViewTestCase, ViewTestCases.PrimaryObjectViewTe
|
|
|
91
92
|
"""Test the host error state"""
|
|
92
93
|
|
|
93
94
|
self.assertEqual(
|
|
94
|
-
|
|
95
|
-
HostStateChoices.STATE_ERROR
|
|
95
|
+
self.objects["host_error_state"].state, HostStateChoices.STATE_ERROR
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
@classmethod
|