From 0dae877dac14ee908192aa597beade7a68849485 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:36:53 +0200 Subject: [PATCH 1/9] filebrowser: remove strict run_as (#753) --- ix-dev/community/filebrowser/app.yaml | 2 +- ix-dev/community/filebrowser/questions.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ix-dev/community/filebrowser/app.yaml b/ix-dev/community/filebrowser/app.yaml index ee97400e1e..5a2d3dcb63 100644 --- a/ix-dev/community/filebrowser/app.yaml +++ b/ix-dev/community/filebrowser/app.yaml @@ -33,4 +33,4 @@ sources: - https://hub.docker.com/r/filebrowser/filebrowser title: File Browser train: community -version: 1.1.11 +version: 1.1.12 diff --git a/ix-dev/community/filebrowser/questions.yaml b/ix-dev/community/filebrowser/questions.yaml index 1337e731e7..73000e3e9f 100644 --- a/ix-dev/community/filebrowser/questions.yaml +++ b/ix-dev/community/filebrowser/questions.yaml @@ -50,7 +50,7 @@ questions: description: The user id that File Browser files will be owned by. schema: type: int - min: 568 + min: 0 default: 568 required: true - variable: group @@ -58,7 +58,7 @@ questions: description: The group id that File Browser files will be owned by. schema: type: int - min: 568 + min: 0 default: 568 required: true From faf0b84a19db5b72d74849d88c9961018b44f8ca Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:37:08 +0200 Subject: [PATCH 2/9] v2-lib: auto add labels from a top level structure (#746) * auto add labels from a top level structure * improve message --- library/2.0.3/tests/test_labels.py | 45 ---------- library/{2.0.3 => 2.0.4}/__init__.py | 0 library/{2.0.3 => 2.0.4}/configs.py | 0 library/{2.0.3 => 2.0.4}/container.py | 14 +++ library/{2.0.3 => 2.0.4}/depends.py | 0 library/{2.0.3 => 2.0.4}/deploy.py | 0 library/{2.0.3 => 2.0.4}/deps.py | 0 library/{2.0.3 => 2.0.4}/device.py | 0 library/{2.0.3 => 2.0.4}/devices.py | 0 library/{2.0.3 => 2.0.4}/dns.py | 0 library/{2.0.3 => 2.0.4}/environment.py | 0 library/{2.0.3 => 2.0.4}/error.py | 0 library/{2.0.3 => 2.0.4}/formatter.py | 0 library/{2.0.3 => 2.0.4}/functions.py | 0 library/{2.0.3 => 2.0.4}/healthcheck.py | 0 library/{2.0.3 => 2.0.4}/labels.py | 3 + library/{2.0.3 => 2.0.4}/notes.py | 0 library/{2.0.3 => 2.0.4}/portal.py | 0 library/{2.0.3 => 2.0.4}/portals.py | 0 library/{2.0.3 => 2.0.4}/ports.py | 0 library/{2.0.3 => 2.0.4}/render.py | 8 ++ library/{2.0.3 => 2.0.4}/resources.py | 0 library/{2.0.3 => 2.0.4}/restart.py | 0 library/{2.0.3 => 2.0.4}/storage.py | 0 library/{2.0.3 => 2.0.4}/tests/__init__.py | 0 .../tests/test_build_image.py | 0 .../{2.0.3 => 2.0.4}/tests/test_configs.py | 0 .../{2.0.3 => 2.0.4}/tests/test_container.py | 0 .../{2.0.3 => 2.0.4}/tests/test_depends.py | 0 library/{2.0.3 => 2.0.4}/tests/test_deps.py | 0 library/{2.0.3 => 2.0.4}/tests/test_device.py | 0 library/{2.0.3 => 2.0.4}/tests/test_dns.py | 0 .../tests/test_environment.py | 0 .../{2.0.3 => 2.0.4}/tests/test_functions.py | 0 .../tests/test_healthcheck.py | 0 library/2.0.4/tests/test_labels.py | 88 +++++++++++++++++++ library/{2.0.3 => 2.0.4}/tests/test_notes.py | 0 library/{2.0.3 => 2.0.4}/tests/test_portal.py | 0 library/{2.0.3 => 2.0.4}/tests/test_ports.py | 0 library/{2.0.3 => 2.0.4}/tests/test_render.py | 0 .../{2.0.3 => 2.0.4}/tests/test_resources.py | 0 .../{2.0.3 => 2.0.4}/tests/test_restart.py | 0 .../{2.0.3 => 2.0.4}/tests/test_volumes.py | 0 library/{2.0.3 => 2.0.4}/validations.py | 0 library/{2.0.3 => 2.0.4}/volume_mount.py | 0 .../{2.0.3 => 2.0.4}/volume_mount_types.py | 0 library/{2.0.3 => 2.0.4}/volume_sources.py | 0 library/{2.0.3 => 2.0.4}/volume_types.py | 0 library/{2.0.3 => 2.0.4}/volumes.py | 0 library/hashes.yaml | 2 +- 50 files changed, 114 insertions(+), 46 deletions(-) delete mode 100644 library/2.0.3/tests/test_labels.py rename library/{2.0.3 => 2.0.4}/__init__.py (100%) rename library/{2.0.3 => 2.0.4}/configs.py (100%) rename library/{2.0.3 => 2.0.4}/container.py (94%) rename library/{2.0.3 => 2.0.4}/depends.py (100%) rename library/{2.0.3 => 2.0.4}/deploy.py (100%) rename library/{2.0.3 => 2.0.4}/deps.py (100%) rename library/{2.0.3 => 2.0.4}/device.py (100%) rename library/{2.0.3 => 2.0.4}/devices.py (100%) rename library/{2.0.3 => 2.0.4}/dns.py (100%) rename library/{2.0.3 => 2.0.4}/environment.py (100%) rename library/{2.0.3 => 2.0.4}/error.py (100%) rename library/{2.0.3 => 2.0.4}/formatter.py (100%) rename library/{2.0.3 => 2.0.4}/functions.py (100%) rename library/{2.0.3 => 2.0.4}/healthcheck.py (100%) rename library/{2.0.3 => 2.0.4}/labels.py (92%) rename library/{2.0.3 => 2.0.4}/notes.py (100%) rename library/{2.0.3 => 2.0.4}/portal.py (100%) rename library/{2.0.3 => 2.0.4}/portals.py (100%) rename library/{2.0.3 => 2.0.4}/ports.py (100%) rename library/{2.0.3 => 2.0.4}/render.py (85%) rename library/{2.0.3 => 2.0.4}/resources.py (100%) rename library/{2.0.3 => 2.0.4}/restart.py (100%) rename library/{2.0.3 => 2.0.4}/storage.py (100%) rename library/{2.0.3 => 2.0.4}/tests/__init__.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_build_image.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_configs.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_container.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_depends.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_deps.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_device.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_dns.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_environment.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_functions.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_healthcheck.py (100%) create mode 100644 library/2.0.4/tests/test_labels.py rename library/{2.0.3 => 2.0.4}/tests/test_notes.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_portal.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_ports.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_render.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_resources.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_restart.py (100%) rename library/{2.0.3 => 2.0.4}/tests/test_volumes.py (100%) rename library/{2.0.3 => 2.0.4}/validations.py (100%) rename library/{2.0.3 => 2.0.4}/volume_mount.py (100%) rename library/{2.0.3 => 2.0.4}/volume_mount_types.py (100%) rename library/{2.0.3 => 2.0.4}/volume_sources.py (100%) rename library/{2.0.3 => 2.0.4}/volume_types.py (100%) rename library/{2.0.3 => 2.0.4}/volumes.py (100%) diff --git a/library/2.0.3/tests/test_labels.py b/library/2.0.3/tests/test_labels.py deleted file mode 100644 index 0ce94fde40..0000000000 --- a/library/2.0.3/tests/test_labels.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_disallowed_label(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.labels.add_label("com.docker.compose.service", "test_service") - - -def test_add_duplicate_label(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.labels.add_label("my.custom.label", "test_value") - with pytest.raises(Exception): - c1.labels.add_label("my.custom.label", "test_value1") - - -def test_add_label(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.labels.add_label("my.custom.label1", "test_value1") - c1.labels.add_label("my.custom.label2", "test_value2") - output = render.render() - assert output["services"]["test_container"]["labels"] == { - "my.custom.label1": "test_value1", - "my.custom.label2": "test_value2", - } diff --git a/library/2.0.3/__init__.py b/library/2.0.4/__init__.py similarity index 100% rename from library/2.0.3/__init__.py rename to library/2.0.4/__init__.py diff --git a/library/2.0.3/configs.py b/library/2.0.4/configs.py similarity index 100% rename from library/2.0.3/configs.py rename to library/2.0.4/configs.py diff --git a/library/2.0.3/container.py b/library/2.0.4/container.py similarity index 94% rename from library/2.0.3/container.py rename to library/2.0.4/container.py index 5f2e275d4b..8089be4f75 100644 --- a/library/2.0.3/container.py +++ b/library/2.0.4/container.py @@ -70,11 +70,25 @@ def __init__(self, render_instance: "Render", name: str, image: str): self.ports: Ports = Ports(self._render_instance) self._auto_set_network_mode() + self._auto_add_labels() def _auto_set_network_mode(self): if self._render_instance.values.get("network", {}).get("host_network", False): self.set_network_mode("host") + def _auto_add_labels(self): + labels = self._render_instance.values.get("labels", []) + if not labels: + return + + for label in labels: + containers = label.get("containers", []) + if not containers: + raise RenderError(f'Label [{label.get("key", "")}] must have at least one container') + + if self._name in containers: + self.labels.add_label(label["key"], label["value"]) + def _resolve_image(self, image: str): images = self._render_instance.values["images"] if image not in images: diff --git a/library/2.0.3/depends.py b/library/2.0.4/depends.py similarity index 100% rename from library/2.0.3/depends.py rename to library/2.0.4/depends.py diff --git a/library/2.0.3/deploy.py b/library/2.0.4/deploy.py similarity index 100% rename from library/2.0.3/deploy.py rename to library/2.0.4/deploy.py diff --git a/library/2.0.3/deps.py b/library/2.0.4/deps.py similarity index 100% rename from library/2.0.3/deps.py rename to library/2.0.4/deps.py diff --git a/library/2.0.3/device.py b/library/2.0.4/device.py similarity index 100% rename from library/2.0.3/device.py rename to library/2.0.4/device.py diff --git a/library/2.0.3/devices.py b/library/2.0.4/devices.py similarity index 100% rename from library/2.0.3/devices.py rename to library/2.0.4/devices.py diff --git a/library/2.0.3/dns.py b/library/2.0.4/dns.py similarity index 100% rename from library/2.0.3/dns.py rename to library/2.0.4/dns.py diff --git a/library/2.0.3/environment.py b/library/2.0.4/environment.py similarity index 100% rename from library/2.0.3/environment.py rename to library/2.0.4/environment.py diff --git a/library/2.0.3/error.py b/library/2.0.4/error.py similarity index 100% rename from library/2.0.3/error.py rename to library/2.0.4/error.py diff --git a/library/2.0.3/formatter.py b/library/2.0.4/formatter.py similarity index 100% rename from library/2.0.3/formatter.py rename to library/2.0.4/formatter.py diff --git a/library/2.0.3/functions.py b/library/2.0.4/functions.py similarity index 100% rename from library/2.0.3/functions.py rename to library/2.0.4/functions.py diff --git a/library/2.0.3/healthcheck.py b/library/2.0.4/healthcheck.py similarity index 100% rename from library/2.0.3/healthcheck.py rename to library/2.0.4/healthcheck.py diff --git a/library/2.0.3/labels.py b/library/2.0.4/labels.py similarity index 92% rename from library/2.0.3/labels.py rename to library/2.0.4/labels.py index 438226650d..f1e667ba00 100644 --- a/library/2.0.3/labels.py +++ b/library/2.0.4/labels.py @@ -17,6 +17,9 @@ def __init__(self, render_instance: "Render"): self._labels: dict[str, str] = {} def add_label(self, key: str, value: str): + if not key: + raise RenderError("Labels must have a key") + if key.startswith("com.docker.compose"): raise RenderError(f"Label [{key}] cannot start with [com.docker.compose] as it is reserved") diff --git a/library/2.0.3/notes.py b/library/2.0.4/notes.py similarity index 100% rename from library/2.0.3/notes.py rename to library/2.0.4/notes.py diff --git a/library/2.0.3/portal.py b/library/2.0.4/portal.py similarity index 100% rename from library/2.0.3/portal.py rename to library/2.0.4/portal.py diff --git a/library/2.0.3/portals.py b/library/2.0.4/portals.py similarity index 100% rename from library/2.0.3/portals.py rename to library/2.0.4/portals.py diff --git a/library/2.0.3/ports.py b/library/2.0.4/ports.py similarity index 100% rename from library/2.0.3/ports.py rename to library/2.0.4/ports.py diff --git a/library/2.0.3/render.py b/library/2.0.4/render.py similarity index 85% rename from library/2.0.3/render.py rename to library/2.0.4/render.py index a2d70cfe89..8421a833c6 100644 --- a/library/2.0.3/render.py +++ b/library/2.0.4/render.py @@ -66,6 +66,14 @@ def render(self): "services": {c._name: c.render() for c in self._containers.values()}, } + # Make sure that after services are rendered + # there are no labels that target a non-existent container + # This is to prevent typos + for label in self.values.get("labels", []): + for c in label.get("containers", []): + if c not in self.container_names(): + raise RenderError(f"Label [{label['key']}] references container [{c}] which does not exist") + if self.volumes.has_volumes(): result["volumes"] = self.volumes.render() diff --git a/library/2.0.3/resources.py b/library/2.0.4/resources.py similarity index 100% rename from library/2.0.3/resources.py rename to library/2.0.4/resources.py diff --git a/library/2.0.3/restart.py b/library/2.0.4/restart.py similarity index 100% rename from library/2.0.3/restart.py rename to library/2.0.4/restart.py diff --git a/library/2.0.3/storage.py b/library/2.0.4/storage.py similarity index 100% rename from library/2.0.3/storage.py rename to library/2.0.4/storage.py diff --git a/library/2.0.3/tests/__init__.py b/library/2.0.4/tests/__init__.py similarity index 100% rename from library/2.0.3/tests/__init__.py rename to library/2.0.4/tests/__init__.py diff --git a/library/2.0.3/tests/test_build_image.py b/library/2.0.4/tests/test_build_image.py similarity index 100% rename from library/2.0.3/tests/test_build_image.py rename to library/2.0.4/tests/test_build_image.py diff --git a/library/2.0.3/tests/test_configs.py b/library/2.0.4/tests/test_configs.py similarity index 100% rename from library/2.0.3/tests/test_configs.py rename to library/2.0.4/tests/test_configs.py diff --git a/library/2.0.3/tests/test_container.py b/library/2.0.4/tests/test_container.py similarity index 100% rename from library/2.0.3/tests/test_container.py rename to library/2.0.4/tests/test_container.py diff --git a/library/2.0.3/tests/test_depends.py b/library/2.0.4/tests/test_depends.py similarity index 100% rename from library/2.0.3/tests/test_depends.py rename to library/2.0.4/tests/test_depends.py diff --git a/library/2.0.3/tests/test_deps.py b/library/2.0.4/tests/test_deps.py similarity index 100% rename from library/2.0.3/tests/test_deps.py rename to library/2.0.4/tests/test_deps.py diff --git a/library/2.0.3/tests/test_device.py b/library/2.0.4/tests/test_device.py similarity index 100% rename from library/2.0.3/tests/test_device.py rename to library/2.0.4/tests/test_device.py diff --git a/library/2.0.3/tests/test_dns.py b/library/2.0.4/tests/test_dns.py similarity index 100% rename from library/2.0.3/tests/test_dns.py rename to library/2.0.4/tests/test_dns.py diff --git a/library/2.0.3/tests/test_environment.py b/library/2.0.4/tests/test_environment.py similarity index 100% rename from library/2.0.3/tests/test_environment.py rename to library/2.0.4/tests/test_environment.py diff --git a/library/2.0.3/tests/test_functions.py b/library/2.0.4/tests/test_functions.py similarity index 100% rename from library/2.0.3/tests/test_functions.py rename to library/2.0.4/tests/test_functions.py diff --git a/library/2.0.3/tests/test_healthcheck.py b/library/2.0.4/tests/test_healthcheck.py similarity index 100% rename from library/2.0.3/tests/test_healthcheck.py rename to library/2.0.4/tests/test_healthcheck.py diff --git a/library/2.0.4/tests/test_labels.py b/library/2.0.4/tests/test_labels.py new file mode 100644 index 0000000000..ffa21eceac --- /dev/null +++ b/library/2.0.4/tests/test_labels.py @@ -0,0 +1,88 @@ +import pytest + +from render import Render + + +@pytest.fixture +def mock_values(): + return { + "images": { + "test_image": { + "repository": "nginx", + "tag": "latest", + } + }, + } + + +def test_add_disallowed_label(mock_values): + render = Render(mock_values) + c1 = render.add_container("test_container", "test_image") + c1.healthcheck.disable() + with pytest.raises(Exception): + c1.labels.add_label("com.docker.compose.service", "test_service") + + +def test_add_duplicate_label(mock_values): + render = Render(mock_values) + c1 = render.add_container("test_container", "test_image") + c1.healthcheck.disable() + c1.labels.add_label("my.custom.label", "test_value") + with pytest.raises(Exception): + c1.labels.add_label("my.custom.label", "test_value1") + + +def test_add_label_on_non_existing_container(mock_values): + mock_values["labels"] = [ + { + "key": "my.custom.label1", + "value": "test_value1", + "containers": ["test_container", "test_container2"], + }, + ] + render = Render(mock_values) + c1 = render.add_container("test_container", "test_image") + c1.healthcheck.disable() + with pytest.raises(Exception): + render.render() + + +def test_add_label(mock_values): + render = Render(mock_values) + c1 = render.add_container("test_container", "test_image") + c1.healthcheck.disable() + c1.labels.add_label("my.custom.label1", "test_value1") + c1.labels.add_label("my.custom.label2", "test_value2") + output = render.render() + assert output["services"]["test_container"]["labels"] == { + "my.custom.label1": "test_value1", + "my.custom.label2": "test_value2", + } + + +def test_auto_add_labels(mock_values): + mock_values["labels"] = [ + { + "key": "my.custom.label1", + "value": "test_value1", + "containers": ["test_container", "test_container2"], + }, + { + "key": "my.custom.label2", + "value": "test_value2", + "containers": ["test_container"], + }, + ] + render = Render(mock_values) + c1 = render.add_container("test_container", "test_image") + c2 = render.add_container("test_container2", "test_image") + c1.healthcheck.disable() + c2.healthcheck.disable() + output = render.render() + assert output["services"]["test_container"]["labels"] == { + "my.custom.label1": "test_value1", + "my.custom.label2": "test_value2", + } + assert output["services"]["test_container2"]["labels"] == { + "my.custom.label1": "test_value1", + } diff --git a/library/2.0.3/tests/test_notes.py b/library/2.0.4/tests/test_notes.py similarity index 100% rename from library/2.0.3/tests/test_notes.py rename to library/2.0.4/tests/test_notes.py diff --git a/library/2.0.3/tests/test_portal.py b/library/2.0.4/tests/test_portal.py similarity index 100% rename from library/2.0.3/tests/test_portal.py rename to library/2.0.4/tests/test_portal.py diff --git a/library/2.0.3/tests/test_ports.py b/library/2.0.4/tests/test_ports.py similarity index 100% rename from library/2.0.3/tests/test_ports.py rename to library/2.0.4/tests/test_ports.py diff --git a/library/2.0.3/tests/test_render.py b/library/2.0.4/tests/test_render.py similarity index 100% rename from library/2.0.3/tests/test_render.py rename to library/2.0.4/tests/test_render.py diff --git a/library/2.0.3/tests/test_resources.py b/library/2.0.4/tests/test_resources.py similarity index 100% rename from library/2.0.3/tests/test_resources.py rename to library/2.0.4/tests/test_resources.py diff --git a/library/2.0.3/tests/test_restart.py b/library/2.0.4/tests/test_restart.py similarity index 100% rename from library/2.0.3/tests/test_restart.py rename to library/2.0.4/tests/test_restart.py diff --git a/library/2.0.3/tests/test_volumes.py b/library/2.0.4/tests/test_volumes.py similarity index 100% rename from library/2.0.3/tests/test_volumes.py rename to library/2.0.4/tests/test_volumes.py diff --git a/library/2.0.3/validations.py b/library/2.0.4/validations.py similarity index 100% rename from library/2.0.3/validations.py rename to library/2.0.4/validations.py diff --git a/library/2.0.3/volume_mount.py b/library/2.0.4/volume_mount.py similarity index 100% rename from library/2.0.3/volume_mount.py rename to library/2.0.4/volume_mount.py diff --git a/library/2.0.3/volume_mount_types.py b/library/2.0.4/volume_mount_types.py similarity index 100% rename from library/2.0.3/volume_mount_types.py rename to library/2.0.4/volume_mount_types.py diff --git a/library/2.0.3/volume_sources.py b/library/2.0.4/volume_sources.py similarity index 100% rename from library/2.0.3/volume_sources.py rename to library/2.0.4/volume_sources.py diff --git a/library/2.0.3/volume_types.py b/library/2.0.4/volume_types.py similarity index 100% rename from library/2.0.3/volume_types.py rename to library/2.0.4/volume_types.py diff --git a/library/2.0.3/volumes.py b/library/2.0.4/volumes.py similarity index 100% rename from library/2.0.3/volumes.py rename to library/2.0.4/volumes.py diff --git a/library/hashes.yaml b/library/hashes.yaml index f48970ad26..006d1d119f 100644 --- a/library/hashes.yaml +++ b/library/hashes.yaml @@ -1,3 +1,3 @@ 0.0.1: f074617a82a86d2a6cc78a4c8a4296fc9d168e456f12713e50c696557b302133 1.1.4: 6e32ff5969906d9c3a10fea2b17fdd3197afb052d3432344da03188d8a907113 -2.0.3: 8ebbf2ffeaccfe0c0f608d93eb536667570974c415a4a75b0f0e8400f42fe427 +2.0.4: 0e79e3390d3ea73649ee2ac05a4af9ed944a02e95289b5c7e2eb047d475a4651 From bf3360982c0c6addbab97ec7e507acf7df9945d7 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:37:19 +0200 Subject: [PATCH 3/9] listmonk: bump to v4 (#754) * chore(deps): update listmonk/listmonk docker tag to v4 * listmonk: it needs to run as root and then drop privileges --------- Co-authored-by: bugclerk --- ix-dev/community/listmonk/app.yaml | 22 ++++++++++++++----- ix-dev/community/listmonk/ix_values.yaml | 4 +--- .../listmonk/templates/docker-compose.yaml | 13 ++++++----- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/ix-dev/community/listmonk/app.yaml b/ix-dev/community/listmonk/app.yaml index eae0446203..79340e5c2d 100644 --- a/ix-dev/community/listmonk/app.yaml +++ b/ix-dev/community/listmonk/app.yaml @@ -1,5 +1,15 @@ -app_version: v3.0.0 -capabilities: [] +app_version: v4.0.1 +capabilities: +- description: Listmonk is able to chown files. + name: CHOWN +- description: Listmonk is able to bypass permission checks. + name: DAC_OVERRIDE +- description: Listmonk is able bypass permission checks for it's sub-processes. + name: FOWNER +- description: Listmonk is able to set group ID for it's sub-processes. + name: SETGID +- description: Listmonk is able to set user ID for it's sub-processes. + name: SETUID categories: - productivity description: Listmonk is a self-hosted newsletter and mailing list manager. @@ -17,10 +27,10 @@ maintainers: url: https://www.truenas.com/ name: listmonk run_as_context: -- description: Listmonk runs as any non-root user. - gid: 568 +- description: Listmonk runs as root user. + gid: 0 group_name: listmonk - uid: 568 + uid: 0 user_name: listmonk - description: Postgres runs as non-root user. gid: 999 @@ -36,4 +46,4 @@ sources: - https://github.com/knadh/listmonk title: Listmonk train: community -version: 1.0.9 +version: 1.0.10 diff --git a/ix-dev/community/listmonk/ix_values.yaml b/ix-dev/community/listmonk/ix_values.yaml index 9debcaae36..176f1b1be2 100644 --- a/ix-dev/community/listmonk/ix_values.yaml +++ b/ix-dev/community/listmonk/ix_values.yaml @@ -1,7 +1,7 @@ images: image: repository: listmonk/listmonk - tag: v3.0.0 + tag: v4.0.1 postgres_image: repository: postgres tag: "15.8" @@ -11,8 +11,6 @@ consts: listmonk_container_name: listmonk postgres_container_name: postgres perms_container_name: permissions - run_as_user: 1000 - run_as_group: 1000 db_name: listmonk db_user: listmonk pg_run_as_user: 999 diff --git a/ix-dev/community/listmonk/templates/docker-compose.yaml b/ix-dev/community/listmonk/templates/docker-compose.yaml index f3680d93be..e78e39c5ee 100644 --- a/ix-dev/community/listmonk/templates/docker-compose.yaml +++ b/ix-dev/community/listmonk/templates/docker-compose.yaml @@ -24,16 +24,16 @@ {% endfor %} {% do storage_items.items.append(ix_lib.base.storage.storage_item(data=dict(values.storage.uploads, **{"mount_path": "/listmonk/uploads"}), - values=values, perm_opts={"mount_path": "/mnt/listmonk/uploads", "mode": "check", "uid": values.consts.run_as_user, "gid": values.consts.run_as_group} + values=values, perm_opts={"mount_path": "/mnt/listmonk/uploads", "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group} )) %} {% do storage_items.items.append(ix_lib.base.storage.storage_item(data={"type": "temporary", "mount_path": "/tmp"}, - perm_opts={"mount_path": "/mnt/listmonk/tmp", "mode": "check", "uid": values.consts.run_as_user, "gid": values.consts.run_as_group} + perm_opts={"mount_path": "/mnt/listmonk/tmp", "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group} )) %} {% for store in values.storage.additional_storage %} {% do storage_items.items.append(ix_lib.base.storage.storage_item(data=store, values=values, - perm_opts={"mount_path": "/mnt/listmonk/dir_%s"|format(loop.index0), "mode": "check", "uid": values.consts.run_as_user, "gid": values.consts.run_as_group} + perm_opts={"mount_path": "/mnt/listmonk/dir_%s"|format(loop.index0), "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group} )) %} {% endfor %} @@ -60,6 +60,7 @@ services: {{ values.consts.init_container_name }}: image: {{ ix_lib.base.utils.get_image(images=values.images, name="image") }} + user: "0:0" restart: on-failure deploy: resources: {{ ix_lib.base.resources.resources(values.resources) | tojson }} @@ -85,7 +86,7 @@ services: volumes: {{ volume_mounts.items | tojson }} {{ values.consts.listmonk_container_name }}: - user: {{ "%d:%d" | format(values.consts.run_as_user, values.consts.run_as_group) }} + user: "0:0" image: {{ ix_lib.base.utils.get_image(images=values.images, name="image") }} restart: unless-stopped deploy: @@ -100,7 +101,9 @@ services: {{ values.consts.perms_container_name }}: condition: service_completed_successfully {% endif %} - cap_drop: {{ ix_lib.base.security.get_caps().drop | tojson }} + {% set caps = ix_lib.base.security.get_caps(add=["CHOWN", "FOWNER", "DAC_OVERRIDE", "SETUID", "SETGID"]) %} + cap_add: {{ caps.add | tojson }} + cap_drop: {{ caps.drop | tojson }} security_opt: {{ ix_lib.base.security.get_sec_opts() | tojson }} {% if values.network.dns_opts %} dns_opt: {{ ix_lib.base.network.dns_opts(values.network.dns_opts) | tojson }} From 4fcc147a71e80f4d05322f2bcfb66c0019d6c938 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:39:37 +0200 Subject: [PATCH 4/9] nextcloud: fix typo (#760) --- ix-dev/stable/nextcloud/app.yaml | 2 +- ix-dev/stable/nextcloud/templates/docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ix-dev/stable/nextcloud/app.yaml b/ix-dev/stable/nextcloud/app.yaml index 10b246f172..8a70a2f165 100644 --- a/ix-dev/stable/nextcloud/app.yaml +++ b/ix-dev/stable/nextcloud/app.yaml @@ -66,4 +66,4 @@ sources: - https://github.com/truenas/charts/tree/master/charts/nextcloud title: Nextcloud train: stable -version: 1.4.0 +version: 1.4.1 diff --git a/ix-dev/stable/nextcloud/templates/docker-compose.yaml b/ix-dev/stable/nextcloud/templates/docker-compose.yaml index 1bc1fd4634..ae46ca3c3a 100644 --- a/ix-dev/stable/nextcloud/templates/docker-compose.yaml +++ b/ix-dev/stable/nextcloud/templates/docker-compose.yaml @@ -100,7 +100,7 @@ {% if values.nextcloud.host %} {% do trusted_domains.x.append( values.nextcloud.host if ":" in values.nextcloud.host - else "%s:%d"|format(values.nextcloud.host, values.nextcloud.port) + else "%s:%d"|format(values.nextcloud.host, values.network.web_port) ) %} {% endif %} {% do nc_env.x.append(("NEXTCLOUD_TRUSTED_DOMAINS", trusted_domains.x|unique|list|join(" "))) %} From 2e34ef0a7a46b2ef07f1b88b011cbe58c493bd19 Mon Sep 17 00:00:00 2001 From: sonicaj Date: Thu, 31 Oct 2024 16:42:28 +0000 Subject: [PATCH 5/9] Publish new changes in catalog [skip ci] --- .../filebrowser/{1.1.11 => 1.1.12}/README.md | 0 .../filebrowser/{1.1.11 => 1.1.12}/app.yaml | 2 +- .../{1.1.11 => 1.1.12}/ix_values.yaml | 0 .../migrations/migrate_from_kubernetes | 0 .../migrations/migration_helpers/__init__.py | 0 .../migrations/migration_helpers/cpu.py | 0 .../migration_helpers/dns_config.py | 0 .../migration_helpers/kubernetes_secrets.py | 0 .../migrations/migration_helpers/memory.py | 0 .../migrations/migration_helpers/resources.py | 0 .../migrations/migration_helpers/storage.py | 0 .../{1.1.11 => 1.1.12}/questions.yaml | 4 ++-- .../templates/docker-compose.yaml | 0 .../templates/library/base_v1_1_4/__init__.py | 0 .../library/base_v1_1_4/environment.py | 0 .../library/base_v1_1_4/healthchecks.py | 0 .../templates/library/base_v1_1_4/mariadb.py | 0 .../templates/library/base_v1_1_4/metadata.py | 0 .../templates/library/base_v1_1_4/network.py | 0 .../library/base_v1_1_4/permissions.py | 0 .../templates/library/base_v1_1_4/ports.py | 0 .../templates/library/base_v1_1_4/postgres.py | 0 .../templates/library/base_v1_1_4/redis.py | 0 .../library/base_v1_1_4/resources.py | 0 .../templates/library/base_v1_1_4/security.py | 0 .../templates/library/base_v1_1_4/storage.py | 0 .../templates/library/base_v1_1_4/utils.py | 0 .../templates/test_values/basic-values.yaml | 0 .../templates/test_values/https-values.yaml | 0 .../listmonk/{1.0.9 => 1.0.10}/README.md | 0 .../listmonk/{1.0.9 => 1.0.10}/app.yaml | 22 ++++++++++++++----- .../listmonk/{1.0.9 => 1.0.10}/ix_values.yaml | 4 +--- .../migrations/migrate_from_kubernetes | 0 .../migrations/migration_helpers/__init__.py | 0 .../migrations/migration_helpers/cpu.py | 0 .../migration_helpers/dns_config.py | 0 .../migration_helpers/kubernetes_secrets.py | 0 .../migrations/migration_helpers/memory.py | 0 .../migrations/migration_helpers/resources.py | 0 .../migrations/migration_helpers/storage.py | 0 .../listmonk/{1.0.9 => 1.0.10}/questions.yaml | 0 .../templates/docker-compose.yaml | 13 ++++++----- .../templates/library/base_v1_1_4/__init__.py | 0 .../library/base_v1_1_4/environment.py | 0 .../library/base_v1_1_4/healthchecks.py | 0 .../templates/library/base_v1_1_4/mariadb.py | 0 .../templates/library/base_v1_1_4/metadata.py | 0 .../templates/library/base_v1_1_4/network.py | 0 .../library/base_v1_1_4/permissions.py | 0 .../templates/library/base_v1_1_4/ports.py | 0 .../templates/library/base_v1_1_4/postgres.py | 0 .../templates/library/base_v1_1_4/redis.py | 0 .../library/base_v1_1_4/resources.py | 0 .../templates/library/base_v1_1_4/security.py | 0 .../templates/library/base_v1_1_4/storage.py | 0 .../templates/library/base_v1_1_4/utils.py | 0 .../templates/test_values/basic-values.yaml | 0 .../nextcloud/{1.4.0 => 1.4.1}/README.md | 0 .../nextcloud/{1.4.0 => 1.4.1}/app.yaml | 2 +- .../nextcloud/{1.4.0 => 1.4.1}/ix_values.yaml | 0 .../migrations/migrate_from_kubernetes | 0 .../migrations/migration_helpers/__init__.py | 0 .../migrations/migration_helpers/cpu.py | 0 .../migration_helpers/dns_config.py | 0 .../migration_helpers/kubernetes_secrets.py | 0 .../migrations/migration_helpers/memory.py | 0 .../migrations/migration_helpers/resources.py | 0 .../migrations/migration_helpers/storage.py | 0 .../nextcloud/{1.4.0 => 1.4.1}/questions.yaml | 0 .../templates/docker-compose.yaml | 2 +- .../templates/library/base_v2_0_3/__init__.py | 0 .../templates/library/base_v2_0_3/configs.py | 0 .../library/base_v2_0_3/container.py | 0 .../templates/library/base_v2_0_3/depends.py | 0 .../templates/library/base_v2_0_3/deploy.py | 0 .../templates/library/base_v2_0_3/deps.py | 0 .../templates/library/base_v2_0_3/device.py | 0 .../templates/library/base_v2_0_3/devices.py | 0 .../templates/library/base_v2_0_3/dns.py | 0 .../library/base_v2_0_3/environment.py | 0 .../templates/library/base_v2_0_3/error.py | 0 .../library/base_v2_0_3/formatter.py | 0 .../library/base_v2_0_3/functions.py | 0 .../library/base_v2_0_3/healthcheck.py | 0 .../templates/library/base_v2_0_3/labels.py | 0 .../templates/library/base_v2_0_3/notes.py | 0 .../templates/library/base_v2_0_3/portal.py | 0 .../templates/library/base_v2_0_3/portals.py | 0 .../templates/library/base_v2_0_3/ports.py | 0 .../templates/library/base_v2_0_3/render.py | 0 .../library/base_v2_0_3/resources.py | 0 .../templates/library/base_v2_0_3/restart.py | 0 .../templates/library/base_v2_0_3/storage.py | 0 .../library/base_v2_0_3/tests/__init__.py | 0 .../base_v2_0_3/tests/test_build_image.py | 0 .../library/base_v2_0_3/tests/test_configs.py | 0 .../base_v2_0_3/tests/test_container.py | 0 .../library/base_v2_0_3/tests/test_depends.py | 0 .../library/base_v2_0_3/tests/test_deps.py | 0 .../library/base_v2_0_3/tests/test_device.py | 0 .../library/base_v2_0_3/tests/test_dns.py | 0 .../base_v2_0_3/tests/test_environment.py | 0 .../base_v2_0_3/tests/test_functions.py | 0 .../base_v2_0_3/tests/test_healthcheck.py | 0 .../library/base_v2_0_3/tests/test_labels.py | 0 .../library/base_v2_0_3/tests/test_notes.py | 0 .../library/base_v2_0_3/tests/test_portal.py | 0 .../library/base_v2_0_3/tests/test_ports.py | 0 .../library/base_v2_0_3/tests/test_render.py | 0 .../base_v2_0_3/tests/test_resources.py | 0 .../library/base_v2_0_3/tests/test_restart.py | 0 .../library/base_v2_0_3/tests/test_volumes.py | 0 .../library/base_v2_0_3/validations.py | 0 .../library/base_v2_0_3/volume_mount.py | 0 .../library/base_v2_0_3/volume_mount_types.py | 0 .../library/base_v2_0_3/volume_sources.py | 0 .../library/base_v2_0_3/volume_types.py | 0 .../templates/library/base_v2_0_3/volumes.py | 0 .../templates/macros/nc.jinja.conf | 0 .../templates/macros/nc.jinja.sh | 0 .../templates/test_values/basic-values.yaml | 0 .../templates/test_values/https-values.yaml | 0 .../templates/test_values/no-cron-values.yaml | 0 .../test_values/same-vol-values.yaml | 0 124 files changed, 30 insertions(+), 19 deletions(-) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/README.md (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/app.yaml (98%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/ix_values.yaml (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migrate_from_kubernetes (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migration_helpers/__init__.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migration_helpers/cpu.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migration_helpers/dns_config.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migration_helpers/kubernetes_secrets.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migration_helpers/memory.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migration_helpers/resources.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/migrations/migration_helpers/storage.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/questions.yaml (99%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/docker-compose.yaml (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/__init__.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/environment.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/healthchecks.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/mariadb.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/metadata.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/network.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/permissions.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/ports.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/postgres.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/redis.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/resources.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/security.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/storage.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/library/base_v1_1_4/utils.py (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/test_values/basic-values.yaml (100%) rename trains/community/filebrowser/{1.1.11 => 1.1.12}/templates/test_values/https-values.yaml (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/README.md (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/app.yaml (65%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/ix_values.yaml (85%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migrate_from_kubernetes (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migration_helpers/__init__.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migration_helpers/cpu.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migration_helpers/dns_config.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migration_helpers/kubernetes_secrets.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migration_helpers/memory.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migration_helpers/resources.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/migrations/migration_helpers/storage.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/questions.yaml (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/docker-compose.yaml (94%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/__init__.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/environment.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/healthchecks.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/mariadb.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/metadata.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/network.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/permissions.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/ports.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/postgres.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/redis.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/resources.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/security.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/storage.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/library/base_v1_1_4/utils.py (100%) rename trains/community/listmonk/{1.0.9 => 1.0.10}/templates/test_values/basic-values.yaml (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/README.md (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/app.yaml (99%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/ix_values.yaml (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migrate_from_kubernetes (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migration_helpers/__init__.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migration_helpers/cpu.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migration_helpers/dns_config.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migration_helpers/kubernetes_secrets.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migration_helpers/memory.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migration_helpers/resources.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/migrations/migration_helpers/storage.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/questions.yaml (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/docker-compose.yaml (99%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/__init__.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/configs.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/container.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/depends.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/deploy.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/deps.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/device.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/devices.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/dns.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/environment.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/error.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/formatter.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/functions.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/healthcheck.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/labels.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/notes.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/portal.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/portals.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/ports.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/render.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/resources.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/restart.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/storage.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/__init__.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_build_image.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_configs.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_container.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_depends.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_deps.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_device.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_dns.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_environment.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_functions.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_healthcheck.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_labels.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_notes.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_portal.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_ports.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_render.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_resources.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_restart.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/tests/test_volumes.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/validations.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/volume_mount.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/volume_mount_types.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/volume_sources.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/volume_types.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/library/base_v2_0_3/volumes.py (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/macros/nc.jinja.conf (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/macros/nc.jinja.sh (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/test_values/basic-values.yaml (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/test_values/https-values.yaml (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/test_values/no-cron-values.yaml (100%) rename trains/stable/nextcloud/{1.4.0 => 1.4.1}/templates/test_values/same-vol-values.yaml (100%) diff --git a/trains/community/filebrowser/1.1.11/README.md b/trains/community/filebrowser/1.1.12/README.md similarity index 100% rename from trains/community/filebrowser/1.1.11/README.md rename to trains/community/filebrowser/1.1.12/README.md diff --git a/trains/community/filebrowser/1.1.11/app.yaml b/trains/community/filebrowser/1.1.12/app.yaml similarity index 98% rename from trains/community/filebrowser/1.1.11/app.yaml rename to trains/community/filebrowser/1.1.12/app.yaml index ee97400e1e..5a2d3dcb63 100644 --- a/trains/community/filebrowser/1.1.11/app.yaml +++ b/trains/community/filebrowser/1.1.12/app.yaml @@ -33,4 +33,4 @@ sources: - https://hub.docker.com/r/filebrowser/filebrowser title: File Browser train: community -version: 1.1.11 +version: 1.1.12 diff --git a/trains/community/filebrowser/1.1.11/ix_values.yaml b/trains/community/filebrowser/1.1.12/ix_values.yaml similarity index 100% rename from trains/community/filebrowser/1.1.11/ix_values.yaml rename to trains/community/filebrowser/1.1.12/ix_values.yaml diff --git a/trains/community/filebrowser/1.1.11/migrations/migrate_from_kubernetes b/trains/community/filebrowser/1.1.12/migrations/migrate_from_kubernetes similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migrate_from_kubernetes rename to trains/community/filebrowser/1.1.12/migrations/migrate_from_kubernetes diff --git a/trains/community/filebrowser/1.1.11/migrations/migration_helpers/__init__.py b/trains/community/filebrowser/1.1.12/migrations/migration_helpers/__init__.py similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migration_helpers/__init__.py rename to trains/community/filebrowser/1.1.12/migrations/migration_helpers/__init__.py diff --git a/trains/community/filebrowser/1.1.11/migrations/migration_helpers/cpu.py b/trains/community/filebrowser/1.1.12/migrations/migration_helpers/cpu.py similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migration_helpers/cpu.py rename to trains/community/filebrowser/1.1.12/migrations/migration_helpers/cpu.py diff --git a/trains/community/filebrowser/1.1.11/migrations/migration_helpers/dns_config.py b/trains/community/filebrowser/1.1.12/migrations/migration_helpers/dns_config.py similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migration_helpers/dns_config.py rename to trains/community/filebrowser/1.1.12/migrations/migration_helpers/dns_config.py diff --git a/trains/community/filebrowser/1.1.11/migrations/migration_helpers/kubernetes_secrets.py b/trains/community/filebrowser/1.1.12/migrations/migration_helpers/kubernetes_secrets.py similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migration_helpers/kubernetes_secrets.py rename to trains/community/filebrowser/1.1.12/migrations/migration_helpers/kubernetes_secrets.py diff --git a/trains/community/filebrowser/1.1.11/migrations/migration_helpers/memory.py b/trains/community/filebrowser/1.1.12/migrations/migration_helpers/memory.py similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migration_helpers/memory.py rename to trains/community/filebrowser/1.1.12/migrations/migration_helpers/memory.py diff --git a/trains/community/filebrowser/1.1.11/migrations/migration_helpers/resources.py b/trains/community/filebrowser/1.1.12/migrations/migration_helpers/resources.py similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migration_helpers/resources.py rename to trains/community/filebrowser/1.1.12/migrations/migration_helpers/resources.py diff --git a/trains/community/filebrowser/1.1.11/migrations/migration_helpers/storage.py b/trains/community/filebrowser/1.1.12/migrations/migration_helpers/storage.py similarity index 100% rename from trains/community/filebrowser/1.1.11/migrations/migration_helpers/storage.py rename to trains/community/filebrowser/1.1.12/migrations/migration_helpers/storage.py diff --git a/trains/community/filebrowser/1.1.11/questions.yaml b/trains/community/filebrowser/1.1.12/questions.yaml similarity index 99% rename from trains/community/filebrowser/1.1.11/questions.yaml rename to trains/community/filebrowser/1.1.12/questions.yaml index 1337e731e7..73000e3e9f 100644 --- a/trains/community/filebrowser/1.1.11/questions.yaml +++ b/trains/community/filebrowser/1.1.12/questions.yaml @@ -50,7 +50,7 @@ questions: description: The user id that File Browser files will be owned by. schema: type: int - min: 568 + min: 0 default: 568 required: true - variable: group @@ -58,7 +58,7 @@ questions: description: The group id that File Browser files will be owned by. schema: type: int - min: 568 + min: 0 default: 568 required: true diff --git a/trains/community/filebrowser/1.1.11/templates/docker-compose.yaml b/trains/community/filebrowser/1.1.12/templates/docker-compose.yaml similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/docker-compose.yaml rename to trains/community/filebrowser/1.1.12/templates/docker-compose.yaml diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/__init__.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/__init__.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/__init__.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/__init__.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/environment.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/environment.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/environment.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/environment.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/healthchecks.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/healthchecks.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/healthchecks.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/healthchecks.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/mariadb.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/mariadb.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/mariadb.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/mariadb.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/metadata.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/metadata.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/metadata.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/metadata.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/network.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/network.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/network.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/network.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/permissions.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/permissions.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/permissions.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/permissions.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/ports.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/ports.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/ports.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/ports.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/postgres.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/postgres.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/postgres.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/postgres.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/redis.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/redis.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/redis.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/redis.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/resources.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/resources.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/resources.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/resources.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/security.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/security.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/security.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/security.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/storage.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/storage.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/storage.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/storage.py diff --git a/trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/utils.py b/trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/utils.py similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/library/base_v1_1_4/utils.py rename to trains/community/filebrowser/1.1.12/templates/library/base_v1_1_4/utils.py diff --git a/trains/community/filebrowser/1.1.11/templates/test_values/basic-values.yaml b/trains/community/filebrowser/1.1.12/templates/test_values/basic-values.yaml similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/test_values/basic-values.yaml rename to trains/community/filebrowser/1.1.12/templates/test_values/basic-values.yaml diff --git a/trains/community/filebrowser/1.1.11/templates/test_values/https-values.yaml b/trains/community/filebrowser/1.1.12/templates/test_values/https-values.yaml similarity index 100% rename from trains/community/filebrowser/1.1.11/templates/test_values/https-values.yaml rename to trains/community/filebrowser/1.1.12/templates/test_values/https-values.yaml diff --git a/trains/community/listmonk/1.0.9/README.md b/trains/community/listmonk/1.0.10/README.md similarity index 100% rename from trains/community/listmonk/1.0.9/README.md rename to trains/community/listmonk/1.0.10/README.md diff --git a/trains/community/listmonk/1.0.9/app.yaml b/trains/community/listmonk/1.0.10/app.yaml similarity index 65% rename from trains/community/listmonk/1.0.9/app.yaml rename to trains/community/listmonk/1.0.10/app.yaml index eae0446203..79340e5c2d 100644 --- a/trains/community/listmonk/1.0.9/app.yaml +++ b/trains/community/listmonk/1.0.10/app.yaml @@ -1,5 +1,15 @@ -app_version: v3.0.0 -capabilities: [] +app_version: v4.0.1 +capabilities: +- description: Listmonk is able to chown files. + name: CHOWN +- description: Listmonk is able to bypass permission checks. + name: DAC_OVERRIDE +- description: Listmonk is able bypass permission checks for it's sub-processes. + name: FOWNER +- description: Listmonk is able to set group ID for it's sub-processes. + name: SETGID +- description: Listmonk is able to set user ID for it's sub-processes. + name: SETUID categories: - productivity description: Listmonk is a self-hosted newsletter and mailing list manager. @@ -17,10 +27,10 @@ maintainers: url: https://www.truenas.com/ name: listmonk run_as_context: -- description: Listmonk runs as any non-root user. - gid: 568 +- description: Listmonk runs as root user. + gid: 0 group_name: listmonk - uid: 568 + uid: 0 user_name: listmonk - description: Postgres runs as non-root user. gid: 999 @@ -36,4 +46,4 @@ sources: - https://github.com/knadh/listmonk title: Listmonk train: community -version: 1.0.9 +version: 1.0.10 diff --git a/trains/community/listmonk/1.0.9/ix_values.yaml b/trains/community/listmonk/1.0.10/ix_values.yaml similarity index 85% rename from trains/community/listmonk/1.0.9/ix_values.yaml rename to trains/community/listmonk/1.0.10/ix_values.yaml index 9debcaae36..176f1b1be2 100644 --- a/trains/community/listmonk/1.0.9/ix_values.yaml +++ b/trains/community/listmonk/1.0.10/ix_values.yaml @@ -1,7 +1,7 @@ images: image: repository: listmonk/listmonk - tag: v3.0.0 + tag: v4.0.1 postgres_image: repository: postgres tag: "15.8" @@ -11,8 +11,6 @@ consts: listmonk_container_name: listmonk postgres_container_name: postgres perms_container_name: permissions - run_as_user: 1000 - run_as_group: 1000 db_name: listmonk db_user: listmonk pg_run_as_user: 999 diff --git a/trains/community/listmonk/1.0.9/migrations/migrate_from_kubernetes b/trains/community/listmonk/1.0.10/migrations/migrate_from_kubernetes similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migrate_from_kubernetes rename to trains/community/listmonk/1.0.10/migrations/migrate_from_kubernetes diff --git a/trains/community/listmonk/1.0.9/migrations/migration_helpers/__init__.py b/trains/community/listmonk/1.0.10/migrations/migration_helpers/__init__.py similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migration_helpers/__init__.py rename to trains/community/listmonk/1.0.10/migrations/migration_helpers/__init__.py diff --git a/trains/community/listmonk/1.0.9/migrations/migration_helpers/cpu.py b/trains/community/listmonk/1.0.10/migrations/migration_helpers/cpu.py similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migration_helpers/cpu.py rename to trains/community/listmonk/1.0.10/migrations/migration_helpers/cpu.py diff --git a/trains/community/listmonk/1.0.9/migrations/migration_helpers/dns_config.py b/trains/community/listmonk/1.0.10/migrations/migration_helpers/dns_config.py similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migration_helpers/dns_config.py rename to trains/community/listmonk/1.0.10/migrations/migration_helpers/dns_config.py diff --git a/trains/community/listmonk/1.0.9/migrations/migration_helpers/kubernetes_secrets.py b/trains/community/listmonk/1.0.10/migrations/migration_helpers/kubernetes_secrets.py similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migration_helpers/kubernetes_secrets.py rename to trains/community/listmonk/1.0.10/migrations/migration_helpers/kubernetes_secrets.py diff --git a/trains/community/listmonk/1.0.9/migrations/migration_helpers/memory.py b/trains/community/listmonk/1.0.10/migrations/migration_helpers/memory.py similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migration_helpers/memory.py rename to trains/community/listmonk/1.0.10/migrations/migration_helpers/memory.py diff --git a/trains/community/listmonk/1.0.9/migrations/migration_helpers/resources.py b/trains/community/listmonk/1.0.10/migrations/migration_helpers/resources.py similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migration_helpers/resources.py rename to trains/community/listmonk/1.0.10/migrations/migration_helpers/resources.py diff --git a/trains/community/listmonk/1.0.9/migrations/migration_helpers/storage.py b/trains/community/listmonk/1.0.10/migrations/migration_helpers/storage.py similarity index 100% rename from trains/community/listmonk/1.0.9/migrations/migration_helpers/storage.py rename to trains/community/listmonk/1.0.10/migrations/migration_helpers/storage.py diff --git a/trains/community/listmonk/1.0.9/questions.yaml b/trains/community/listmonk/1.0.10/questions.yaml similarity index 100% rename from trains/community/listmonk/1.0.9/questions.yaml rename to trains/community/listmonk/1.0.10/questions.yaml diff --git a/trains/community/listmonk/1.0.9/templates/docker-compose.yaml b/trains/community/listmonk/1.0.10/templates/docker-compose.yaml similarity index 94% rename from trains/community/listmonk/1.0.9/templates/docker-compose.yaml rename to trains/community/listmonk/1.0.10/templates/docker-compose.yaml index f3680d93be..e78e39c5ee 100644 --- a/trains/community/listmonk/1.0.9/templates/docker-compose.yaml +++ b/trains/community/listmonk/1.0.10/templates/docker-compose.yaml @@ -24,16 +24,16 @@ {% endfor %} {% do storage_items.items.append(ix_lib.base.storage.storage_item(data=dict(values.storage.uploads, **{"mount_path": "/listmonk/uploads"}), - values=values, perm_opts={"mount_path": "/mnt/listmonk/uploads", "mode": "check", "uid": values.consts.run_as_user, "gid": values.consts.run_as_group} + values=values, perm_opts={"mount_path": "/mnt/listmonk/uploads", "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group} )) %} {% do storage_items.items.append(ix_lib.base.storage.storage_item(data={"type": "temporary", "mount_path": "/tmp"}, - perm_opts={"mount_path": "/mnt/listmonk/tmp", "mode": "check", "uid": values.consts.run_as_user, "gid": values.consts.run_as_group} + perm_opts={"mount_path": "/mnt/listmonk/tmp", "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group} )) %} {% for store in values.storage.additional_storage %} {% do storage_items.items.append(ix_lib.base.storage.storage_item(data=store, values=values, - perm_opts={"mount_path": "/mnt/listmonk/dir_%s"|format(loop.index0), "mode": "check", "uid": values.consts.run_as_user, "gid": values.consts.run_as_group} + perm_opts={"mount_path": "/mnt/listmonk/dir_%s"|format(loop.index0), "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group} )) %} {% endfor %} @@ -60,6 +60,7 @@ services: {{ values.consts.init_container_name }}: image: {{ ix_lib.base.utils.get_image(images=values.images, name="image") }} + user: "0:0" restart: on-failure deploy: resources: {{ ix_lib.base.resources.resources(values.resources) | tojson }} @@ -85,7 +86,7 @@ services: volumes: {{ volume_mounts.items | tojson }} {{ values.consts.listmonk_container_name }}: - user: {{ "%d:%d" | format(values.consts.run_as_user, values.consts.run_as_group) }} + user: "0:0" image: {{ ix_lib.base.utils.get_image(images=values.images, name="image") }} restart: unless-stopped deploy: @@ -100,7 +101,9 @@ services: {{ values.consts.perms_container_name }}: condition: service_completed_successfully {% endif %} - cap_drop: {{ ix_lib.base.security.get_caps().drop | tojson }} + {% set caps = ix_lib.base.security.get_caps(add=["CHOWN", "FOWNER", "DAC_OVERRIDE", "SETUID", "SETGID"]) %} + cap_add: {{ caps.add | tojson }} + cap_drop: {{ caps.drop | tojson }} security_opt: {{ ix_lib.base.security.get_sec_opts() | tojson }} {% if values.network.dns_opts %} dns_opt: {{ ix_lib.base.network.dns_opts(values.network.dns_opts) | tojson }} diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/__init__.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/__init__.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/__init__.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/__init__.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/environment.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/environment.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/environment.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/environment.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/healthchecks.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/healthchecks.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/healthchecks.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/healthchecks.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/mariadb.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/mariadb.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/mariadb.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/mariadb.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/metadata.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/metadata.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/metadata.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/metadata.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/network.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/network.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/network.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/network.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/permissions.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/permissions.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/permissions.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/permissions.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/ports.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/ports.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/ports.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/ports.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/postgres.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/postgres.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/postgres.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/postgres.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/redis.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/redis.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/redis.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/redis.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/resources.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/resources.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/resources.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/resources.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/security.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/security.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/security.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/security.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/storage.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/storage.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/storage.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/storage.py diff --git a/trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/utils.py b/trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/utils.py similarity index 100% rename from trains/community/listmonk/1.0.9/templates/library/base_v1_1_4/utils.py rename to trains/community/listmonk/1.0.10/templates/library/base_v1_1_4/utils.py diff --git a/trains/community/listmonk/1.0.9/templates/test_values/basic-values.yaml b/trains/community/listmonk/1.0.10/templates/test_values/basic-values.yaml similarity index 100% rename from trains/community/listmonk/1.0.9/templates/test_values/basic-values.yaml rename to trains/community/listmonk/1.0.10/templates/test_values/basic-values.yaml diff --git a/trains/stable/nextcloud/1.4.0/README.md b/trains/stable/nextcloud/1.4.1/README.md similarity index 100% rename from trains/stable/nextcloud/1.4.0/README.md rename to trains/stable/nextcloud/1.4.1/README.md diff --git a/trains/stable/nextcloud/1.4.0/app.yaml b/trains/stable/nextcloud/1.4.1/app.yaml similarity index 99% rename from trains/stable/nextcloud/1.4.0/app.yaml rename to trains/stable/nextcloud/1.4.1/app.yaml index 10b246f172..8a70a2f165 100644 --- a/trains/stable/nextcloud/1.4.0/app.yaml +++ b/trains/stable/nextcloud/1.4.1/app.yaml @@ -66,4 +66,4 @@ sources: - https://github.com/truenas/charts/tree/master/charts/nextcloud title: Nextcloud train: stable -version: 1.4.0 +version: 1.4.1 diff --git a/trains/stable/nextcloud/1.4.0/ix_values.yaml b/trains/stable/nextcloud/1.4.1/ix_values.yaml similarity index 100% rename from trains/stable/nextcloud/1.4.0/ix_values.yaml rename to trains/stable/nextcloud/1.4.1/ix_values.yaml diff --git a/trains/stable/nextcloud/1.4.0/migrations/migrate_from_kubernetes b/trains/stable/nextcloud/1.4.1/migrations/migrate_from_kubernetes similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migrate_from_kubernetes rename to trains/stable/nextcloud/1.4.1/migrations/migrate_from_kubernetes diff --git a/trains/stable/nextcloud/1.4.0/migrations/migration_helpers/__init__.py b/trains/stable/nextcloud/1.4.1/migrations/migration_helpers/__init__.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migration_helpers/__init__.py rename to trains/stable/nextcloud/1.4.1/migrations/migration_helpers/__init__.py diff --git a/trains/stable/nextcloud/1.4.0/migrations/migration_helpers/cpu.py b/trains/stable/nextcloud/1.4.1/migrations/migration_helpers/cpu.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migration_helpers/cpu.py rename to trains/stable/nextcloud/1.4.1/migrations/migration_helpers/cpu.py diff --git a/trains/stable/nextcloud/1.4.0/migrations/migration_helpers/dns_config.py b/trains/stable/nextcloud/1.4.1/migrations/migration_helpers/dns_config.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migration_helpers/dns_config.py rename to trains/stable/nextcloud/1.4.1/migrations/migration_helpers/dns_config.py diff --git a/trains/stable/nextcloud/1.4.0/migrations/migration_helpers/kubernetes_secrets.py b/trains/stable/nextcloud/1.4.1/migrations/migration_helpers/kubernetes_secrets.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migration_helpers/kubernetes_secrets.py rename to trains/stable/nextcloud/1.4.1/migrations/migration_helpers/kubernetes_secrets.py diff --git a/trains/stable/nextcloud/1.4.0/migrations/migration_helpers/memory.py b/trains/stable/nextcloud/1.4.1/migrations/migration_helpers/memory.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migration_helpers/memory.py rename to trains/stable/nextcloud/1.4.1/migrations/migration_helpers/memory.py diff --git a/trains/stable/nextcloud/1.4.0/migrations/migration_helpers/resources.py b/trains/stable/nextcloud/1.4.1/migrations/migration_helpers/resources.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migration_helpers/resources.py rename to trains/stable/nextcloud/1.4.1/migrations/migration_helpers/resources.py diff --git a/trains/stable/nextcloud/1.4.0/migrations/migration_helpers/storage.py b/trains/stable/nextcloud/1.4.1/migrations/migration_helpers/storage.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/migrations/migration_helpers/storage.py rename to trains/stable/nextcloud/1.4.1/migrations/migration_helpers/storage.py diff --git a/trains/stable/nextcloud/1.4.0/questions.yaml b/trains/stable/nextcloud/1.4.1/questions.yaml similarity index 100% rename from trains/stable/nextcloud/1.4.0/questions.yaml rename to trains/stable/nextcloud/1.4.1/questions.yaml diff --git a/trains/stable/nextcloud/1.4.0/templates/docker-compose.yaml b/trains/stable/nextcloud/1.4.1/templates/docker-compose.yaml similarity index 99% rename from trains/stable/nextcloud/1.4.0/templates/docker-compose.yaml rename to trains/stable/nextcloud/1.4.1/templates/docker-compose.yaml index 1bc1fd4634..ae46ca3c3a 100644 --- a/trains/stable/nextcloud/1.4.0/templates/docker-compose.yaml +++ b/trains/stable/nextcloud/1.4.1/templates/docker-compose.yaml @@ -100,7 +100,7 @@ {% if values.nextcloud.host %} {% do trusted_domains.x.append( values.nextcloud.host if ":" in values.nextcloud.host - else "%s:%d"|format(values.nextcloud.host, values.nextcloud.port) + else "%s:%d"|format(values.nextcloud.host, values.network.web_port) ) %} {% endif %} {% do nc_env.x.append(("NEXTCLOUD_TRUSTED_DOMAINS", trusted_domains.x|unique|list|join(" "))) %} diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/__init__.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/__init__.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/__init__.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/__init__.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/configs.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/configs.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/configs.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/configs.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/container.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/container.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/container.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/container.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/depends.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/depends.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/depends.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/depends.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/deploy.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/deploy.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/deploy.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/deploy.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/deps.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/deps.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/deps.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/deps.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/device.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/device.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/device.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/device.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/devices.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/devices.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/devices.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/devices.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/dns.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/dns.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/dns.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/dns.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/environment.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/environment.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/environment.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/environment.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/error.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/error.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/error.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/error.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/formatter.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/formatter.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/formatter.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/formatter.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/functions.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/functions.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/functions.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/functions.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/healthcheck.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/healthcheck.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/healthcheck.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/healthcheck.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/labels.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/labels.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/labels.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/labels.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/notes.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/notes.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/notes.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/notes.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/portal.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/portal.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/portal.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/portal.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/portals.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/portals.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/portals.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/portals.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/ports.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/ports.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/ports.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/ports.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/render.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/render.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/render.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/render.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/resources.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/resources.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/resources.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/resources.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/restart.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/restart.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/restart.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/restart.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/storage.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/storage.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/storage.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/storage.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/__init__.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/__init__.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/__init__.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/__init__.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_build_image.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_build_image.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_build_image.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_build_image.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_configs.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_configs.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_configs.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_configs.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_container.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_container.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_container.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_container.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_depends.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_depends.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_depends.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_depends.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_deps.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_deps.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_deps.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_deps.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_device.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_device.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_device.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_device.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_dns.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_dns.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_dns.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_dns.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_environment.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_environment.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_environment.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_environment.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_functions.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_functions.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_functions.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_functions.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_healthcheck.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_healthcheck.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_healthcheck.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_healthcheck.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_labels.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_labels.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_labels.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_labels.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_notes.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_notes.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_notes.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_notes.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_portal.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_portal.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_portal.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_portal.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_ports.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_ports.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_ports.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_ports.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_render.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_render.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_render.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_render.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_resources.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_resources.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_resources.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_resources.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_restart.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_restart.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_restart.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_restart.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_volumes.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_volumes.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/tests/test_volumes.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/tests/test_volumes.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/validations.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/validations.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/validations.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/validations.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_mount.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_mount.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_mount.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_mount.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_mount_types.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_mount_types.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_mount_types.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_mount_types.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_sources.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_sources.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_sources.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_sources.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_types.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_types.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volume_types.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volume_types.py diff --git a/trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volumes.py b/trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volumes.py similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/library/base_v2_0_3/volumes.py rename to trains/stable/nextcloud/1.4.1/templates/library/base_v2_0_3/volumes.py diff --git a/trains/stable/nextcloud/1.4.0/templates/macros/nc.jinja.conf b/trains/stable/nextcloud/1.4.1/templates/macros/nc.jinja.conf similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/macros/nc.jinja.conf rename to trains/stable/nextcloud/1.4.1/templates/macros/nc.jinja.conf diff --git a/trains/stable/nextcloud/1.4.0/templates/macros/nc.jinja.sh b/trains/stable/nextcloud/1.4.1/templates/macros/nc.jinja.sh similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/macros/nc.jinja.sh rename to trains/stable/nextcloud/1.4.1/templates/macros/nc.jinja.sh diff --git a/trains/stable/nextcloud/1.4.0/templates/test_values/basic-values.yaml b/trains/stable/nextcloud/1.4.1/templates/test_values/basic-values.yaml similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/test_values/basic-values.yaml rename to trains/stable/nextcloud/1.4.1/templates/test_values/basic-values.yaml diff --git a/trains/stable/nextcloud/1.4.0/templates/test_values/https-values.yaml b/trains/stable/nextcloud/1.4.1/templates/test_values/https-values.yaml similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/test_values/https-values.yaml rename to trains/stable/nextcloud/1.4.1/templates/test_values/https-values.yaml diff --git a/trains/stable/nextcloud/1.4.0/templates/test_values/no-cron-values.yaml b/trains/stable/nextcloud/1.4.1/templates/test_values/no-cron-values.yaml similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/test_values/no-cron-values.yaml rename to trains/stable/nextcloud/1.4.1/templates/test_values/no-cron-values.yaml diff --git a/trains/stable/nextcloud/1.4.0/templates/test_values/same-vol-values.yaml b/trains/stable/nextcloud/1.4.1/templates/test_values/same-vol-values.yaml similarity index 100% rename from trains/stable/nextcloud/1.4.0/templates/test_values/same-vol-values.yaml rename to trains/stable/nextcloud/1.4.1/templates/test_values/same-vol-values.yaml From 4c1d21c80240453cb918fa5f4efbb17b3ad7dd97 Mon Sep 17 00:00:00 2001 From: sonicaj Date: Thu, 31 Oct 2024 16:42:50 +0000 Subject: [PATCH 6/9] Update catalog changes [skip ci] --- catalog.json | 281 ++++++++++-------- .../community/actual-budget/app_versions.json | 2 +- .../community/adguard-home/app_versions.json | 2 +- .../audiobookshelf/app_versions.json | 2 +- trains/community/autobrr/app_versions.json | 2 +- trains/community/bazarr/app_versions.json | 2 +- .../community/briefkasten/app_versions.json | 2 +- trains/community/castopod/app_versions.json | 2 +- trains/community/chia/app_versions.json | 2 +- trains/community/clamav/app_versions.json | 2 +- .../community/cloudflared/app_versions.json | 2 +- trains/community/dashy/app_versions.json | 2 +- .../community/ddns-updater/app_versions.json | 2 +- trains/community/deluge/app_versions.json | 2 +- .../community/distribution/app_versions.json | 2 +- trains/community/dockge/app_versions.json | 2 +- trains/community/drawio/app_versions.json | 2 +- .../community/filebrowser/app_versions.json | 16 +- .../community/firefly-iii/app_versions.json | 2 +- trains/community/flame/app_versions.json | 2 +- .../community/flaresolverr/app_versions.json | 2 +- trains/community/freshrss/app_versions.json | 2 +- trains/community/frigate/app_versions.json | 2 +- trains/community/fscrawler/app_versions.json | 2 +- trains/community/gitea/app_versions.json | 2 +- trains/community/grafana/app_versions.json | 2 +- trains/community/handbrake/app_versions.json | 2 +- trains/community/homarr/app_versions.json | 2 +- trains/community/homepage/app_versions.json | 2 +- trains/community/homer/app_versions.json | 2 +- trains/community/immich/app_versions.json | 2 +- trains/community/invidious/app_versions.json | 2 +- trains/community/ipfs/app_versions.json | 2 +- trains/community/jellyfin/app_versions.json | 2 +- trains/community/jellyseerr/app_versions.json | 2 +- trains/community/jenkins/app_versions.json | 2 +- trains/community/joplin/app_versions.json | 2 +- trains/community/kapowarr/app_versions.json | 2 +- trains/community/kavita/app_versions.json | 2 +- trains/community/komga/app_versions.json | 2 +- trains/community/lidarr/app_versions.json | 2 +- trains/community/linkding/app_versions.json | 2 +- trains/community/listmonk/app_versions.json | 43 ++- trains/community/logseq/app_versions.json | 2 +- trains/community/mealie/app_versions.json | 2 +- trains/community/metube/app_versions.json | 2 +- trains/community/minecraft/app_versions.json | 2 +- trains/community/mineos/app_versions.json | 2 +- trains/community/mumble/app_versions.json | 2 +- trains/community/n8n/app_versions.json | 2 +- trains/community/navidrome/app_versions.json | 2 +- trains/community/netbootxyz/app_versions.json | 2 +- .../nginx-proxy-manager/app_versions.json | 2 +- trains/community/node-red/app_versions.json | 2 +- trains/community/odoo/app_versions.json | 2 +- trains/community/ollama/app_versions.json | 2 +- .../omada-controller/app_versions.json | 2 +- trains/community/open-webui/app_versions.json | 2 +- trains/community/organizr/app_versions.json | 2 +- trains/community/overseerr/app_versions.json | 2 +- trains/community/palworld/app_versions.json | 2 +- .../community/paperless-ngx/app_versions.json | 2 +- trains/community/passbolt/app_versions.json | 2 +- trains/community/penpot/app_versions.json | 2 +- trains/community/pgadmin/app_versions.json | 2 +- trains/community/pigallery2/app_versions.json | 2 +- trains/community/piwigo/app_versions.json | 2 +- trains/community/planka/app_versions.json | 2 +- .../plex-auto-languages/app_versions.json | 2 +- trains/community/portainer/app_versions.json | 2 +- trains/community/prowlarr/app_versions.json | 2 +- .../community/qbittorrent/app_versions.json | 2 +- trains/community/radarr/app_versions.json | 2 +- trains/community/readarr/app_versions.json | 2 +- trains/community/recyclarr/app_versions.json | 2 +- trains/community/redis/app_versions.json | 2 +- trains/community/roundcube/app_versions.json | 2 +- trains/community/rsyncd/app_versions.json | 2 +- trains/community/rust-desk/app_versions.json | 2 +- trains/community/sabnzbd/app_versions.json | 2 +- trains/community/searxng/app_versions.json | 2 +- trains/community/sftpgo/app_versions.json | 2 +- trains/community/sonarr/app_versions.json | 2 +- trains/community/tailscale/app_versions.json | 2 +- trains/community/tautulli/app_versions.json | 2 +- trains/community/tdarr/app_versions.json | 2 +- trains/community/terraria/app_versions.json | 2 +- trains/community/tftpd-hpa/app_versions.json | 2 +- .../tiny-media-manager/app_versions.json | 2 +- .../community/transmission/app_versions.json | 2 +- .../twofactor-auth/app_versions.json | 2 +- .../unifi-controller/app_versions.json | 2 +- .../unifi-protect-backup/app_versions.json | 2 +- .../community/vaultwarden/app_versions.json | 2 +- trains/community/vikunja/app_versions.json | 2 +- trains/community/webdav/app_versions.json | 2 +- trains/community/whoogle/app_versions.json | 2 +- trains/community/wordpress/app_versions.json | 2 +- trains/community/zerotier/app_versions.json | 2 +- .../asigra-ds-system/app_versions.json | 2 +- trains/enterprise/minio/app_versions.json | 2 +- trains/enterprise/syncthing/app_versions.json | 2 +- trains/stable/collabora/app_versions.json | 2 +- trains/stable/diskoverdata/app_versions.json | 2 +- .../stable/elastic-search/app_versions.json | 2 +- trains/stable/emby/app_versions.json | 2 +- .../stable/home-assistant/app_versions.json | 2 +- trains/stable/ix-app/app_versions.json | 2 +- trains/stable/minio/app_versions.json | 2 +- trains/stable/netdata/app_versions.json | 2 +- trains/stable/nextcloud/app_versions.json | 12 +- trains/stable/photoprism/app_versions.json | 2 +- trains/stable/pihole/app_versions.json | 2 +- trains/stable/plex/app_versions.json | 2 +- trains/stable/prometheus/app_versions.json | 2 +- trains/stable/storj/app_versions.json | 2 +- trains/stable/syncthing/app_versions.json | 2 +- trains/stable/wg-easy/app_versions.json | 2 +- trains/test/nginx/app_versions.json | 8 +- trains/test/other-nginx/app_versions.json | 4 +- 120 files changed, 317 insertions(+), 275 deletions(-) diff --git a/catalog.json b/catalog.json index ef77812c5c..cb08d6c444 100644 --- a/catalog.json +++ b/catalog.json @@ -13,7 +13,7 @@ "latest_version": "1.1.16", "latest_app_version": "24.04.9.1.1", "latest_human_version": "24.04.9.1.1_1.1.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "collabora", "recommended": false, "title": "Collabora", @@ -102,7 +102,7 @@ "latest_version": "1.0.24", "latest_app_version": "1.40.2.8395-c67dce28e", "latest_human_version": "1.40.2.8395-c67dce28e_1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "plex", "recommended": false, "title": "Plex", @@ -180,7 +180,7 @@ "latest_version": "1.2.23", "latest_app_version": "2024.10.4", "latest_human_version": "2024.10.4_1.2.23", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "home-assistant", "recommended": false, "title": "Home Assistant", @@ -255,7 +255,7 @@ "latest_version": "1.1.14", "latest_app_version": "240915", "latest_human_version": "240915_1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "photoprism", "recommended": false, "title": "Photoprism", @@ -328,7 +328,7 @@ "latest_version": "1.1.15", "latest_app_version": "RELEASE.2024-10-29T16-01-48Z", "latest_human_version": "RELEASE.2024-10-29T16-01-48Z_1.1.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "minio", "recommended": false, "title": "MinIO", @@ -377,7 +377,7 @@ "latest_version": "1.1.13", "latest_app_version": "4.8.10.0", "latest_human_version": "4.8.10.0_1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "emby", "recommended": false, "title": "Emby Server", @@ -456,7 +456,7 @@ "latest_version": "1.1.15", "latest_app_version": "v1.47.5", "latest_human_version": "v1.47.5_1.1.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "netdata", "recommended": false, "title": "Netdata", @@ -533,7 +533,7 @@ "latest_version": "1.0.20", "latest_app_version": "14", "latest_human_version": "14_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "wg-easy", "recommended": false, "title": "WG Easy", @@ -593,7 +593,7 @@ "latest_version": "1.1.12", "latest_app_version": "v2.55.0", "latest_human_version": "v2.55.0_1.1.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "prometheus", "recommended": false, "title": "Prometheus", @@ -641,7 +641,7 @@ "latest_version": "1.0.28", "latest_app_version": "1.28.0", "latest_human_version": "1.28.0_1.0.28", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "syncthing", "recommended": false, "title": "Syncthing", @@ -726,7 +726,7 @@ "latest_version": "1.0.11", "latest_app_version": "1.0.0", "latest_human_version": "1.0.0_1.0.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "ix-app", "recommended": false, "title": "iX App", @@ -754,10 +754,10 @@ "healthy_error": null, "home": "https://nextcloud.com/", "location": "/__w/apps/apps/trains/stable/nextcloud", - "latest_version": "1.4.0", + "latest_version": "1.4.1", "latest_app_version": "30.0.1", - "latest_human_version": "30.0.1_1.4.0", - "last_update": "2024-10-31 13:02:23", + "latest_human_version": "30.0.1_1.4.1", + "last_update": "2024-10-31 16:42:28", "name": "nextcloud", "recommended": false, "title": "Nextcloud", @@ -861,7 +861,7 @@ "latest_version": "1.1.10", "latest_app_version": "6f87ea801-v1.71.2-go1.18.8", "latest_human_version": "6f87ea801-v1.71.2-go1.18.8_1.1.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "storj", "recommended": false, "title": "Storj", @@ -922,7 +922,7 @@ "latest_version": "1.3.9", "latest_app_version": "2.3.0", "latest_human_version": "2.3.0_1.3.9", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "diskoverdata", "recommended": false, "title": "Diskover Data", @@ -1005,7 +1005,7 @@ "latest_version": "1.1.11", "latest_app_version": "2024.07.0", "latest_human_version": "2024.07.0_1.1.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "pihole", "recommended": false, "title": "Pi-hole", @@ -1101,7 +1101,7 @@ "latest_version": "1.1.13", "latest_app_version": "8.15.3", "latest_human_version": "8.15.3_1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "elastic-search", "recommended": false, "title": "Elastic Search", @@ -1149,7 +1149,7 @@ "latest_version": "1.0.1", "latest_app_version": "v1", "latest_human_version": "v1_1.0.1", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "other-nginx", "recommended": false, "title": "Other Nginx", @@ -1197,7 +1197,7 @@ "latest_version": "1.0.6", "latest_app_version": "v1", "latest_human_version": "v1_1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "nginx", "recommended": false, "title": "Nginx", @@ -1247,7 +1247,7 @@ "latest_version": "1.1.8", "latest_app_version": "RELEASE.2024-08-26T15-33-07Z", "latest_human_version": "RELEASE.2024-08-26T15-33-07Z_1.1.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "minio", "recommended": false, "title": "MinIO", @@ -1296,7 +1296,7 @@ "latest_version": "1.0.15", "latest_app_version": "1.27.10", "latest_human_version": "1.27.10_1.0.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "syncthing", "recommended": false, "title": "Syncthing", @@ -1380,7 +1380,7 @@ "latest_version": "1.0.2", "latest_app_version": "14.2.0.8", "latest_human_version": "14.2.0.8_1.0.2", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "asigra-ds-system", "recommended": false, "title": "Asigra DS-System", @@ -1454,7 +1454,7 @@ "latest_version": "1.0.20", "latest_app_version": "0.9.0", "latest_human_version": "0.9.0_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "whoogle", "recommended": false, "title": "Whoogle", @@ -1502,7 +1502,7 @@ "latest_version": "1.0.15", "latest_app_version": "1.0.0", "latest_human_version": "1.0.0_1.0.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "rsyncd", "recommended": false, "title": "Rsync Daemon", @@ -1570,10 +1570,10 @@ "healthy_error": null, "home": "https://listmonk.app/", "location": "/__w/apps/apps/trains/community/listmonk", - "latest_version": "1.0.9", - "latest_app_version": "v3.0.0", - "latest_human_version": "v3.0.0_1.0.9", - "last_update": "2024-10-31 13:01:07", + "latest_version": "1.0.10", + "latest_app_version": "v4.0.1", + "latest_human_version": "v4.0.1_1.0.10", + "last_update": "2024-10-31 16:42:28", "name": "listmonk", "recommended": false, "title": "Listmonk", @@ -1598,13 +1598,34 @@ "https://github.com/knadh/listmonk" ], "icon_url": "https://media.sys.truenas.net/apps/listmonk/icons/icon.svg", - "capabilities": [], + "capabilities": [ + { + "description": "Listmonk is able to chown files.", + "name": "CHOWN" + }, + { + "description": "Listmonk is able to bypass permission checks.", + "name": "DAC_OVERRIDE" + }, + { + "description": "Listmonk is able bypass permission checks for it's sub-processes.", + "name": "FOWNER" + }, + { + "description": "Listmonk is able to set group ID for it's sub-processes.", + "name": "SETGID" + }, + { + "description": "Listmonk is able to set user ID for it's sub-processes.", + "name": "SETUID" + } + ], "run_as_context": [ { - "description": "Listmonk runs as any non-root user.", - "gid": 568, + "description": "Listmonk runs as root user.", + "gid": 0, "group_name": "listmonk", - "uid": 568, + "uid": 0, "user_name": "listmonk" }, { @@ -1629,7 +1650,7 @@ "latest_version": "1.11.19", "latest_app_version": "2024.10.2", "latest_human_version": "2024.10.2_1.11.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "minecraft", "recommended": false, "title": "Minecraft", @@ -1695,7 +1716,7 @@ "latest_version": "1.0.10", "latest_app_version": "2.27.02", "latest_human_version": "2.27.02_1.0.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "tdarr", "recommended": false, "title": "Tdarr", @@ -1761,7 +1782,7 @@ "latest_version": "1.0.22", "latest_app_version": "v2.14.6", "latest_human_version": "v2.14.6_1.0.22", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "tautulli", "recommended": false, "title": "Tautulli", @@ -1812,7 +1833,7 @@ "latest_version": "1.1.15", "latest_app_version": "v1.48.0", "latest_human_version": "v1.48.0_1.1.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "autobrr", "recommended": false, "title": "Autobrr", @@ -1861,7 +1882,7 @@ "latest_version": "1.0.18", "latest_app_version": "1.33.2", "latest_human_version": "1.33.2_1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "overseerr", "recommended": false, "title": "Overseerr", @@ -1905,7 +1926,7 @@ "latest_version": "1.0.17", "latest_app_version": "1.4.2", "latest_human_version": "1.4.2_1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "dockge", "recommended": false, "title": "Dockge", @@ -1985,7 +2006,7 @@ "latest_version": "1.1.14", "latest_app_version": "2024.10.1", "latest_human_version": "2024.10.1_1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "cloudflared", "recommended": false, "title": "Cloudflared", @@ -2031,7 +2052,7 @@ "latest_version": "1.0.1", "latest_app_version": "2.2.1", "latest_human_version": "2.2.1_1.0.1", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "penpot", "recommended": false, "title": "Penpot", @@ -2116,7 +2137,7 @@ "latest_version": "1.0.19", "latest_app_version": "0.7.3-nbxyz1", "latest_human_version": "0.7.3-nbxyz1_1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "netbootxyz", "recommended": false, "title": "Netboot.xyz", @@ -2200,7 +2221,7 @@ "latest_version": "1.2.11", "latest_app_version": "8.5.6", "latest_human_version": "8.5.6_1.2.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "unifi-controller", "recommended": false, "title": "Unifi Controller", @@ -2247,7 +2268,7 @@ "latest_version": "1.0.9", "latest_app_version": "0.14.1", "latest_human_version": "0.14.1_1.0.9", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "frigate", "recommended": false, "title": "Frigate", @@ -2316,7 +2337,7 @@ "latest_version": "1.0.23", "latest_app_version": "v0.9.11", "latest_human_version": "v0.9.11_1.0.23", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "homepage", "recommended": false, "title": "Homepage", @@ -2367,7 +2388,7 @@ "latest_version": "1.0.19", "latest_app_version": "2.3.1", "latest_human_version": "2.3.1_1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "flame", "recommended": false, "title": "Flame", @@ -2426,7 +2447,7 @@ "latest_version": "1.0.15", "latest_app_version": "2.13.2", "latest_human_version": "2.13.2_1.0.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "paperless-ngx", "recommended": false, "title": "Paperless-ngx", @@ -2515,7 +2536,7 @@ "latest_version": "1.0.11", "latest_app_version": "1.0.0", "latest_human_version": "1.0.0_1.0.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "tftpd-hpa", "recommended": false, "title": "TFTP Server", @@ -2577,7 +2598,7 @@ "latest_version": "1.0.6", "latest_app_version": "4.0.6", "latest_human_version": "4.0.6_1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "transmission", "recommended": false, "title": "Transmission", @@ -2623,7 +2644,7 @@ "latest_version": "1.0.18", "latest_app_version": "latest", "latest_human_version": "latest_1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "logseq", "recommended": false, "title": "Logseq", @@ -2667,7 +2688,7 @@ "latest_version": "1.1.11", "latest_app_version": "24.7.17", "latest_human_version": "24.7.17_1.1.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "drawio", "recommended": false, "title": "Draw.io", @@ -2717,7 +2738,7 @@ "latest_version": "1.1.12", "latest_app_version": "3.0.1-beta", "latest_human_version": "3.0.1-beta_1.1.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "joplin", "recommended": false, "title": "Joplin", @@ -2772,7 +2793,7 @@ "latest_version": "1.0.0", "latest_app_version": "v3.3.21", "latest_human_version": "v3.3.21_1.0.0", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "flaresolverr", "recommended": false, "title": "FlareSolverr", @@ -2813,7 +2834,7 @@ "latest_version": "1.0.19", "latest_app_version": "0.8.3", "latest_human_version": "0.8.3_1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "kavita", "recommended": false, "title": "Kavita", @@ -2885,7 +2906,7 @@ "latest_version": "1.0.7", "latest_app_version": "4.3.3", "latest_human_version": "4.3.3_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "sabnzbd", "recommended": false, "title": "SABnzbd", @@ -2935,7 +2956,7 @@ "latest_version": "1.1.13", "latest_app_version": "24.10.1", "latest_human_version": "24.10.1_1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "actual-budget", "recommended": false, "title": "Actual Budget", @@ -2985,7 +3006,7 @@ "latest_version": "1.0.7", "latest_app_version": "5.0.13", "latest_human_version": "5.0.13_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "tiny-media-manager", "recommended": false, "title": "Tiny Media Manager", @@ -3049,7 +3070,7 @@ "latest_version": "1.2.15", "latest_app_version": "0.24.4", "latest_human_version": "0.24.4_1.2.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "vikunja", "recommended": false, "title": "Vikunja", @@ -3117,7 +3138,7 @@ "latest_version": "1.0.7", "latest_app_version": "4.9.1-1-ce-non-root", "latest_human_version": "4.9.1-1-ce-non-root_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "passbolt", "recommended": false, "title": "Passbolt", @@ -3174,7 +3195,7 @@ "latest_version": "1.0.20", "latest_app_version": "2.0.1", "latest_human_version": "2.0.1_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "jellyseerr", "recommended": false, "title": "Jellyseerr", @@ -3220,7 +3241,7 @@ "latest_version": "1.0.8", "latest_app_version": "1.32.3", "latest_human_version": "1.32.3_1.0.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "vaultwarden", "recommended": false, "title": "Vaultwarden", @@ -3273,7 +3294,7 @@ "latest_version": "1.0.12", "latest_app_version": "1.13.0", "latest_human_version": "1.13.0_1.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "castopod", "recommended": false, "title": "Castopod", @@ -3354,7 +3375,7 @@ "latest_version": "1.3.27", "latest_app_version": "1.65.2", "latest_human_version": "1.65.2_1.3.27", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "n8n", "recommended": false, "title": "n8n", @@ -3418,7 +3439,7 @@ "latest_version": "1.0.19", "latest_app_version": "2.8.3", "latest_human_version": "2.8.3_1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "distribution", "recommended": false, "title": "Distribution", @@ -3464,7 +3485,7 @@ "latest_version": "1.0.17", "latest_app_version": "2.10-SNAPSHOT-ocr-es7", "latest_human_version": "2.10-SNAPSHOT-ocr-es7_1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "fscrawler", "recommended": false, "title": "FSCrawler", @@ -3510,7 +3531,7 @@ "latest_version": "1.0.20", "latest_app_version": "v0.31.0", "latest_human_version": "v0.31.0_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "ipfs", "recommended": false, "title": "IPFS", @@ -3560,7 +3581,7 @@ "latest_version": "1.0.24", "latest_app_version": "0.53.3", "latest_human_version": "0.53.3_1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "navidrome", "recommended": false, "title": "Navidrome", @@ -3608,7 +3629,7 @@ "latest_version": "1.0.6", "latest_app_version": "2.0.0", "latest_human_version": "2.0.0_1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "pigallery2", "recommended": false, "title": "PiGallery2", @@ -3664,7 +3685,7 @@ "latest_version": "1.0.18", "latest_app_version": "latest", "latest_human_version": "latest_1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "mineos", "recommended": false, "title": "MineOS", @@ -3731,7 +3752,7 @@ "latest_version": "1.1.13", "latest_app_version": "11.3.0", "latest_human_version": "11.3.0_1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "grafana", "recommended": false, "title": "Grafana", @@ -3782,7 +3803,7 @@ "latest_version": "1.1.16", "latest_app_version": "v1.74.1", "latest_human_version": "v1.74.1_1.1.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "tailscale", "recommended": false, "title": "Tailscale", @@ -3852,7 +3873,7 @@ "latest_version": "1.0.1", "latest_app_version": "0.3.35", "latest_human_version": "0.3.35_1.0.1", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "open-webui", "recommended": false, "title": "Open WebUI", @@ -3900,7 +3921,7 @@ "latest_version": "1.0.20", "latest_app_version": "2024.10.29-e39289257", "latest_human_version": "2024.10.29-e39289257_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "searxng", "recommended": false, "title": "SearXNG", @@ -3953,7 +3974,7 @@ "latest_version": "1.0.22", "latest_app_version": "1.4.5", "latest_human_version": "1.4.5_1.0.22", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "bazarr", "recommended": false, "title": "Bazarr", @@ -4001,7 +4022,7 @@ "latest_version": "1.0.4", "latest_app_version": "17.0", "latest_human_version": "17.0_1.0.4", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "odoo", "recommended": false, "title": "Odoo", @@ -4049,7 +4070,7 @@ "latest_version": "1.0.7", "latest_app_version": "15.0.0", "latest_human_version": "15.0.0_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "piwigo", "recommended": false, "title": "Piwigo", @@ -4128,7 +4149,7 @@ "latest_version": "1.2.15", "latest_app_version": "1.25.4.4818", "latest_human_version": "1.25.4.4818_1.2.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "prowlarr", "recommended": false, "title": "Prowlarr", @@ -4174,7 +4195,7 @@ "latest_version": "1.0.19", "latest_app_version": "v1.0.0", "latest_human_version": "v1.0.0_1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "kapowarr", "recommended": false, "title": "Kapowarr", @@ -4222,7 +4243,7 @@ "latest_version": "1.0.20", "latest_app_version": "1.14.1", "latest_human_version": "1.14.1_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "zerotier", "recommended": false, "title": "Zerotier", @@ -4312,7 +4333,7 @@ "latest_version": "1.2.17", "latest_app_version": "2.16.2", "latest_human_version": "2.16.2_1.2.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "audiobookshelf", "recommended": false, "title": "Audiobookshelf", @@ -4363,7 +4384,7 @@ "latest_version": "1.0.18", "latest_app_version": "latest", "latest_human_version": "latest_1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "organizr", "recommended": false, "title": "Organizr", @@ -4432,7 +4453,7 @@ "latest_version": "1.0.8", "latest_app_version": "5.3.2", "latest_human_version": "5.3.2_1.0.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "twofactor-auth", "recommended": false, "title": "2FAuth", @@ -4480,7 +4501,7 @@ "latest_version": "1.0.17", "latest_app_version": "0.11.0", "latest_human_version": "0.11.0_1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "unifi-protect-backup", "recommended": false, "title": "Unifi Protect Backup", @@ -4542,7 +4563,7 @@ "latest_version": "1.0.20", "latest_app_version": "4.0.10.2544", "latest_human_version": "4.0.10.2544_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "sonarr", "recommended": false, "title": "Sonarr", @@ -4591,7 +4612,7 @@ "latest_version": "1.0.22", "latest_app_version": "v0.107.53", "latest_human_version": "v0.107.53_1.0.22", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "adguard-home", "recommended": false, "title": "AdGuard Home", @@ -4657,7 +4678,7 @@ "latest_version": "1.1.14", "latest_app_version": "2.7.1.4417", "latest_human_version": "2.7.1.4417_1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "lidarr", "recommended": false, "title": "Lidarr", @@ -4706,7 +4727,7 @@ "latest_version": "1.2.14", "latest_app_version": "2.23.0", "latest_human_version": "2.23.0_1.2.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "portainer", "recommended": false, "title": "Portainer", @@ -4788,7 +4809,7 @@ "latest_version": "1.1.10", "latest_app_version": "v1.5.634", "latest_human_version": "v1.5.634_1.1.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "mumble", "recommended": false, "title": "Mumble", @@ -4832,7 +4853,7 @@ "latest_version": "1.1.13", "latest_app_version": "5.14.0.9383", "latest_human_version": "5.14.0.9383_1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "radarr", "recommended": false, "title": "Radarr", @@ -4882,7 +4903,7 @@ "latest_version": "1.0.6", "latest_app_version": "tshock-1.4.4.9-5.2.0-3", "latest_human_version": "tshock-1.4.4.9-5.2.0-3_1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "terraria", "recommended": false, "title": "Terraria", @@ -4930,7 +4951,7 @@ "latest_version": "1.0.16", "latest_app_version": "2.4.4", "latest_human_version": "2.4.4_1.0.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "chia", "recommended": false, "title": "Chia", @@ -4976,7 +4997,7 @@ "latest_version": "1.0.18", "latest_app_version": "3.1.0", "latest_human_version": "3.1.0_1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "dashy", "recommended": false, "title": "Dashy", @@ -5023,7 +5044,7 @@ "latest_version": "1.0.7", "latest_app_version": "6.6.2", "latest_human_version": "6.6.2_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "wordpress", "recommended": false, "title": "Wordpress", @@ -5084,7 +5105,7 @@ "latest_version": "1.0.12", "latest_app_version": "1.22.3", "latest_human_version": "1.22.3_1.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "gitea", "recommended": false, "title": "Gitea", @@ -5135,7 +5156,7 @@ "latest_version": "1.1.11", "latest_app_version": "5.14", "latest_human_version": "5.14_1.1.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "omada-controller", "recommended": false, "title": "Omada Controller", @@ -5203,7 +5224,7 @@ "latest_version": "1.1.14", "latest_app_version": "1.14.1", "latest_human_version": "1.14.1_1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "komga", "recommended": false, "title": "Komga", @@ -5252,7 +5273,7 @@ "latest_version": "1.0.7", "latest_app_version": "2.6.2", "latest_human_version": "2.6.2_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "sftpgo", "recommended": false, "title": "SFTPGo", @@ -5296,7 +5317,7 @@ "latest_version": "1.0.5", "latest_app_version": "8.12", "latest_human_version": "8.12_1.0.5", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "pgadmin", "recommended": false, "title": "pgAdmin", @@ -5350,7 +5371,7 @@ "latest_version": "1.0.13", "latest_app_version": "2.4.62", "latest_human_version": "2.4.62_1.0.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "webdav", "recommended": false, "title": "WebDAV", @@ -5394,7 +5415,7 @@ "latest_version": "1.0.13", "latest_app_version": "2.462.1-jdk17", "latest_human_version": "2.462.1-jdk17_1.0.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "jenkins", "recommended": false, "title": "Jenkins", @@ -5443,7 +5464,7 @@ "latest_version": "1.0.16", "latest_app_version": "v1.2.3", "latest_human_version": "v1.2.3_1.0.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "plex-auto-languages", "recommended": false, "title": "Plex Auto Languages", @@ -5488,7 +5509,7 @@ "latest_version": "1.1.18", "latest_app_version": "2024-10-23", "latest_human_version": "2024-10-23_1.1.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "metube", "recommended": false, "title": "MeTube", @@ -5535,7 +5556,7 @@ "latest_version": "1.0.7", "latest_app_version": "1.1.11-1", "latest_human_version": "1.1.11-1_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "rust-desk", "recommended": false, "title": "Rust Desk", @@ -5580,7 +5601,7 @@ "latest_version": "1.0.20", "latest_app_version": "palworld", "latest_human_version": "palworld_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "palworld", "recommended": false, "title": "Palworld", @@ -5658,7 +5679,7 @@ "latest_version": "1.0.19", "latest_app_version": "latest", "latest_human_version": "latest_1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "briefkasten", "recommended": false, "title": "Briefkasten", @@ -5713,7 +5734,7 @@ "latest_version": "1.6.24", "latest_app_version": "v1.119.1", "latest_human_version": "v1.119.1_1.6.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "immich", "recommended": false, "title": "Immich", @@ -5785,7 +5806,7 @@ "latest_version": "1.0.24", "latest_app_version": "v2.8.1", "latest_human_version": "v2.8.1_1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "ddns-updater", "recommended": false, "title": "DDNS Updater", @@ -5833,7 +5854,7 @@ "latest_version": "1.0.11", "latest_app_version": "2.20240825.2", "latest_human_version": "2.20240825.2_1.0.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "invidious", "recommended": false, "title": "Invidious", @@ -5891,7 +5912,7 @@ "latest_version": "1.0.19", "latest_app_version": "0.3.32.2587", "latest_human_version": "0.3.32.2587_1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "readarr", "recommended": false, "title": "Readarr", @@ -5941,7 +5962,7 @@ "latest_version": "1.0.9", "latest_app_version": "7.3.0", "latest_human_version": "7.3.0_1.0.9", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "recyclarr", "recommended": false, "title": "Recyclarr", @@ -5987,7 +6008,7 @@ "latest_version": "2.0.12", "latest_app_version": "v24.09.1", "latest_human_version": "v24.09.1_2.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "handbrake", "recommended": false, "title": "Handbrake", @@ -6062,7 +6083,7 @@ "latest_version": "2.0.12", "latest_app_version": "v24.10.2", "latest_human_version": "v24.10.2_2.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "homer", "recommended": false, "title": "Homer", @@ -6106,7 +6127,7 @@ "latest_version": "1.1.10", "latest_app_version": "1.1.2-2", "latest_human_version": "1.1.2-2_1.1.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "clamav", "recommended": false, "title": "ClamAV", @@ -6172,7 +6193,7 @@ "latest_version": "1.0.0", "latest_app_version": "v0.3.14", "latest_human_version": "v0.3.14_1.0.0", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "ollama", "recommended": false, "title": "Ollama", @@ -6216,7 +6237,7 @@ "latest_version": "1.0.33", "latest_app_version": "4.0.5", "latest_human_version": "4.0.5_1.0.33", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "node-red", "recommended": false, "title": "Node-RED", @@ -6259,10 +6280,10 @@ "healthy_error": null, "home": "https://filebrowser.org", "location": "/__w/apps/apps/trains/community/filebrowser", - "latest_version": "1.1.11", + "latest_version": "1.1.12", "latest_app_version": "v2.31.2", - "latest_human_version": "v2.31.2_1.1.11", - "last_update": "2024-10-31 13:01:07", + "latest_human_version": "v2.31.2_1.1.12", + "last_update": "2024-10-31 16:42:28", "name": "filebrowser", "recommended": false, "title": "File Browser", @@ -6312,7 +6333,7 @@ "latest_version": "1.0.17", "latest_app_version": "1.36.0", "latest_human_version": "1.36.0_1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "linkding", "recommended": false, "title": "Linkding", @@ -6365,7 +6386,7 @@ "latest_version": "1.0.13", "latest_app_version": "1.23.4", "latest_human_version": "1.23.4_1.0.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "planka", "recommended": false, "title": "Planka", @@ -6420,7 +6441,7 @@ "latest_version": "1.0.29", "latest_app_version": "5.0.1", "latest_human_version": "5.0.1_1.0.29", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "qbittorrent", "recommended": false, "title": "qBittorrent", @@ -6468,7 +6489,7 @@ "latest_version": "1.2.19", "latest_app_version": "version-6.1.21", "latest_human_version": "version-6.1.21_1.2.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "firefly-iii", "recommended": false, "title": "Firefly III", @@ -6555,7 +6576,7 @@ "latest_version": "1.0.8", "latest_app_version": "7.4.1", "latest_human_version": "7.4.1_1.0.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "redis", "recommended": false, "title": "Redis", @@ -6600,7 +6621,7 @@ "latest_version": "1.0.27", "latest_app_version": "10.10.0", "latest_human_version": "10.10.0_1.0.27", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "jellyfin", "recommended": false, "title": "Jellyfin", @@ -6652,7 +6673,7 @@ "latest_version": "1.0.18", "latest_app_version": "2.1.1", "latest_human_version": "2.1.1_1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "deluge", "recommended": false, "title": "Deluge", @@ -6718,7 +6739,7 @@ "latest_version": "1.2.12", "latest_app_version": "v2.1.0", "latest_human_version": "v2.1.0_1.2.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "mealie", "recommended": false, "title": "Mealie", @@ -6771,7 +6792,7 @@ "latest_version": "1.1.14", "latest_app_version": "1.24.3", "latest_human_version": "1.24.3_1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "freshrss", "recommended": false, "title": "FreshRSS", @@ -6839,7 +6860,7 @@ "latest_version": "1.0.20", "latest_app_version": "0.15.6", "latest_human_version": "0.15.6_1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "homarr", "recommended": false, "title": "Homarr", @@ -6887,7 +6908,7 @@ "latest_version": "1.0.7", "latest_app_version": "1.6.9-apache", "latest_human_version": "1.6.9-apache_1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "roundcube", "recommended": false, "title": "Roundcube", @@ -6966,7 +6987,7 @@ "latest_version": "1.0.24", "latest_app_version": "2.12.1", "latest_human_version": "2.12.1_1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "name": "nginx-proxy-manager", "recommended": false, "title": "Nginx Proxy Manager", diff --git a/trains/community/actual-budget/app_versions.json b/trains/community/actual-budget/app_versions.json index b88dd66b17..441aa6f1db 100644 --- a/trains/community/actual-budget/app_versions.json +++ b/trains/community/actual-budget/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/actual-budget/1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "24.10.1_1.1.13", "version": "1.1.13", diff --git a/trains/community/adguard-home/app_versions.json b/trains/community/adguard-home/app_versions.json index a3cb83adda..2a49312fdb 100644 --- a/trains/community/adguard-home/app_versions.json +++ b/trains/community/adguard-home/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/adguard-home/1.0.22", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v0.107.53_1.0.22", "version": "1.0.22", diff --git a/trains/community/audiobookshelf/app_versions.json b/trains/community/audiobookshelf/app_versions.json index 174a166ced..c13017fff6 100644 --- a/trains/community/audiobookshelf/app_versions.json +++ b/trains/community/audiobookshelf/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/audiobookshelf/1.2.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.16.2_1.2.17", "version": "1.2.17", diff --git a/trains/community/autobrr/app_versions.json b/trains/community/autobrr/app_versions.json index ba74f5a5f5..7c9f9e97c0 100644 --- a/trains/community/autobrr/app_versions.json +++ b/trains/community/autobrr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/autobrr/1.1.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1.48.0_1.1.15", "version": "1.1.15", diff --git a/trains/community/bazarr/app_versions.json b/trains/community/bazarr/app_versions.json index ca54d36b50..5fc2d62148 100644 --- a/trains/community/bazarr/app_versions.json +++ b/trains/community/bazarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/bazarr/1.0.22", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.4.5_1.0.22", "version": "1.0.22", diff --git a/trains/community/briefkasten/app_versions.json b/trains/community/briefkasten/app_versions.json index 672d5aa48f..6c52753884 100644 --- a/trains/community/briefkasten/app_versions.json +++ b/trains/community/briefkasten/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/briefkasten/1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "latest_1.0.19", "version": "1.0.19", diff --git a/trains/community/castopod/app_versions.json b/trains/community/castopod/app_versions.json index 75cc0b400a..ef0411b137 100644 --- a/trains/community/castopod/app_versions.json +++ b/trains/community/castopod/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/castopod/1.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.13.0_1.0.12", "version": "1.0.12", diff --git a/trains/community/chia/app_versions.json b/trains/community/chia/app_versions.json index 6c8f3b68d4..6c0b8ed14a 100644 --- a/trains/community/chia/app_versions.json +++ b/trains/community/chia/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/chia/1.0.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.4.4_1.0.16", "version": "1.0.16", diff --git a/trains/community/clamav/app_versions.json b/trains/community/clamav/app_versions.json index 998f7ebbed..e356e76fe8 100644 --- a/trains/community/clamav/app_versions.json +++ b/trains/community/clamav/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/clamav/1.1.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.1.2-2_1.1.10", "version": "1.1.10", diff --git a/trains/community/cloudflared/app_versions.json b/trains/community/cloudflared/app_versions.json index e69c0006f0..de8f1de246 100644 --- a/trains/community/cloudflared/app_versions.json +++ b/trains/community/cloudflared/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/cloudflared/1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2024.10.1_1.1.14", "version": "1.1.14", diff --git a/trains/community/dashy/app_versions.json b/trains/community/dashy/app_versions.json index 260e0ee857..8b88f5c6e3 100644 --- a/trains/community/dashy/app_versions.json +++ b/trains/community/dashy/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/dashy/1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "3.1.0_1.0.18", "version": "1.0.18", diff --git a/trains/community/ddns-updater/app_versions.json b/trains/community/ddns-updater/app_versions.json index 47b54dbf39..2c3cc4b06f 100644 --- a/trains/community/ddns-updater/app_versions.json +++ b/trains/community/ddns-updater/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/ddns-updater/1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v2.8.1_1.0.24", "version": "1.0.24", diff --git a/trains/community/deluge/app_versions.json b/trains/community/deluge/app_versions.json index a50fb886cc..e4253a9493 100644 --- a/trains/community/deluge/app_versions.json +++ b/trains/community/deluge/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/deluge/1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.1.1_1.0.18", "version": "1.0.18", diff --git a/trains/community/distribution/app_versions.json b/trains/community/distribution/app_versions.json index 80554f5051..9022b074cc 100644 --- a/trains/community/distribution/app_versions.json +++ b/trains/community/distribution/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/distribution/1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.8.3_1.0.19", "version": "1.0.19", diff --git a/trains/community/dockge/app_versions.json b/trains/community/dockge/app_versions.json index 3761c1e937..09e1722358 100644 --- a/trains/community/dockge/app_versions.json +++ b/trains/community/dockge/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/dockge/1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.4.2_1.0.17", "version": "1.0.17", diff --git a/trains/community/drawio/app_versions.json b/trains/community/drawio/app_versions.json index 35284b9074..19e4e10ede 100644 --- a/trains/community/drawio/app_versions.json +++ b/trains/community/drawio/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/drawio/1.1.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "24.7.17_1.1.11", "version": "1.1.11", diff --git a/trains/community/filebrowser/app_versions.json b/trains/community/filebrowser/app_versions.json index 151a1fbb3a..32ebba0847 100644 --- a/trains/community/filebrowser/app_versions.json +++ b/trains/community/filebrowser/app_versions.json @@ -1,13 +1,13 @@ { - "1.1.11": { + "1.1.12": { "healthy": true, "supported": true, "healthy_error": null, - "location": "/__w/apps/apps/trains/community/filebrowser/1.1.11", - "last_update": "2024-10-31 13:01:07", + "location": "/__w/apps/apps/trains/community/filebrowser/1.1.12", + "last_update": "2024-10-31 16:42:28", "required_features": [], - "human_version": "v2.31.2_1.1.11", - "version": "1.1.11", + "human_version": "v2.31.2_1.1.12", + "version": "1.1.12", "app_metadata": { "app_version": "v2.31.2", "capabilities": [], @@ -53,7 +53,7 @@ ], "title": "File Browser", "train": "community", - "version": "1.1.11" + "version": "1.1.12" }, "schema": { "groups": [ @@ -138,7 +138,7 @@ "description": "The user id that File Browser files will be owned by.", "schema": { "type": "int", - "min": 568, + "min": 0, "default": 568, "required": true } @@ -149,7 +149,7 @@ "description": "The group id that File Browser files will be owned by.", "schema": { "type": "int", - "min": 568, + "min": 0, "default": 568, "required": true } diff --git a/trains/community/firefly-iii/app_versions.json b/trains/community/firefly-iii/app_versions.json index f9a3dcd100..0fb6ce8340 100644 --- a/trains/community/firefly-iii/app_versions.json +++ b/trains/community/firefly-iii/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/firefly-iii/1.2.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "version-6.1.21_1.2.19", "version": "1.2.19", diff --git a/trains/community/flame/app_versions.json b/trains/community/flame/app_versions.json index 640b8e9e1f..b9d83732f6 100644 --- a/trains/community/flame/app_versions.json +++ b/trains/community/flame/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/flame/1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.3.1_1.0.19", "version": "1.0.19", diff --git a/trains/community/flaresolverr/app_versions.json b/trains/community/flaresolverr/app_versions.json index e46f9ace43..429ab1414a 100644 --- a/trains/community/flaresolverr/app_versions.json +++ b/trains/community/flaresolverr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/flaresolverr/1.0.0", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v3.3.21_1.0.0", "version": "1.0.0", diff --git a/trains/community/freshrss/app_versions.json b/trains/community/freshrss/app_versions.json index 2a9c6f7d40..9786f2364c 100644 --- a/trains/community/freshrss/app_versions.json +++ b/trains/community/freshrss/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/freshrss/1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.24.3_1.1.14", "version": "1.1.14", diff --git a/trains/community/frigate/app_versions.json b/trains/community/frigate/app_versions.json index 2af75c8e91..dab39e4b3a 100644 --- a/trains/community/frigate/app_versions.json +++ b/trains/community/frigate/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/frigate/1.0.9", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.14.1_1.0.9", "version": "1.0.9", diff --git a/trains/community/fscrawler/app_versions.json b/trains/community/fscrawler/app_versions.json index d87169a0ba..d3bb53d9ca 100644 --- a/trains/community/fscrawler/app_versions.json +++ b/trains/community/fscrawler/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/fscrawler/1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.10-SNAPSHOT-ocr-es7_1.0.17", "version": "1.0.17", diff --git a/trains/community/gitea/app_versions.json b/trains/community/gitea/app_versions.json index ccb00e2bd1..162ae0d0d4 100644 --- a/trains/community/gitea/app_versions.json +++ b/trains/community/gitea/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/gitea/1.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.22.3_1.0.12", "version": "1.0.12", diff --git a/trains/community/grafana/app_versions.json b/trains/community/grafana/app_versions.json index 1b7784f394..80cc1a0add 100644 --- a/trains/community/grafana/app_versions.json +++ b/trains/community/grafana/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/grafana/1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "11.3.0_1.1.13", "version": "1.1.13", diff --git a/trains/community/handbrake/app_versions.json b/trains/community/handbrake/app_versions.json index 8bd8054182..be351fd70b 100644 --- a/trains/community/handbrake/app_versions.json +++ b/trains/community/handbrake/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/handbrake/2.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v24.09.1_2.0.12", "version": "2.0.12", diff --git a/trains/community/homarr/app_versions.json b/trains/community/homarr/app_versions.json index 0622d85c9e..758518debb 100644 --- a/trains/community/homarr/app_versions.json +++ b/trains/community/homarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/homarr/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.15.6_1.0.20", "version": "1.0.20", diff --git a/trains/community/homepage/app_versions.json b/trains/community/homepage/app_versions.json index 086eec758c..2f58484492 100644 --- a/trains/community/homepage/app_versions.json +++ b/trains/community/homepage/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/homepage/1.0.23", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v0.9.11_1.0.23", "version": "1.0.23", diff --git a/trains/community/homer/app_versions.json b/trains/community/homer/app_versions.json index 109cca43e0..a65b0f6f60 100644 --- a/trains/community/homer/app_versions.json +++ b/trains/community/homer/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/homer/2.0.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v24.10.2_2.0.12", "version": "2.0.12", diff --git a/trains/community/immich/app_versions.json b/trains/community/immich/app_versions.json index 1cbd599698..eb95ebf9da 100644 --- a/trains/community/immich/app_versions.json +++ b/trains/community/immich/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/immich/1.6.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1.119.1_1.6.24", "version": "1.6.24", diff --git a/trains/community/invidious/app_versions.json b/trains/community/invidious/app_versions.json index affa1bf7a7..61d4fab42d 100644 --- a/trains/community/invidious/app_versions.json +++ b/trains/community/invidious/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/invidious/1.0.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.20240825.2_1.0.11", "version": "1.0.11", diff --git a/trains/community/ipfs/app_versions.json b/trains/community/ipfs/app_versions.json index 50f4915b0c..56f8be6fa8 100644 --- a/trains/community/ipfs/app_versions.json +++ b/trains/community/ipfs/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/ipfs/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v0.31.0_1.0.20", "version": "1.0.20", diff --git a/trains/community/jellyfin/app_versions.json b/trains/community/jellyfin/app_versions.json index f15ecbdf22..c12826420e 100644 --- a/trains/community/jellyfin/app_versions.json +++ b/trains/community/jellyfin/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/jellyfin/1.0.27", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "10.10.0_1.0.27", "version": "1.0.27", diff --git a/trains/community/jellyseerr/app_versions.json b/trains/community/jellyseerr/app_versions.json index c39481d375..1c7f4429d6 100644 --- a/trains/community/jellyseerr/app_versions.json +++ b/trains/community/jellyseerr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/jellyseerr/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.0.1_1.0.20", "version": "1.0.20", diff --git a/trains/community/jenkins/app_versions.json b/trains/community/jenkins/app_versions.json index d677e55c1e..f0da32a881 100644 --- a/trains/community/jenkins/app_versions.json +++ b/trains/community/jenkins/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/jenkins/1.0.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.462.1-jdk17_1.0.13", "version": "1.0.13", diff --git a/trains/community/joplin/app_versions.json b/trains/community/joplin/app_versions.json index ece882b0c9..0ea13bf4e1 100644 --- a/trains/community/joplin/app_versions.json +++ b/trains/community/joplin/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/joplin/1.1.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "3.0.1-beta_1.1.12", "version": "1.1.12", diff --git a/trains/community/kapowarr/app_versions.json b/trains/community/kapowarr/app_versions.json index 4173fa6842..19803c5211 100644 --- a/trains/community/kapowarr/app_versions.json +++ b/trains/community/kapowarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/kapowarr/1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1.0.0_1.0.19", "version": "1.0.19", diff --git a/trains/community/kavita/app_versions.json b/trains/community/kavita/app_versions.json index f8ab04475a..ba86d8c0da 100644 --- a/trains/community/kavita/app_versions.json +++ b/trains/community/kavita/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/kavita/1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.8.3_1.0.19", "version": "1.0.19", diff --git a/trains/community/komga/app_versions.json b/trains/community/komga/app_versions.json index 4bc60a26a9..5717e8e851 100644 --- a/trains/community/komga/app_versions.json +++ b/trains/community/komga/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/komga/1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.14.1_1.1.14", "version": "1.1.14", diff --git a/trains/community/lidarr/app_versions.json b/trains/community/lidarr/app_versions.json index dbb1a2dd7a..69255d1a36 100644 --- a/trains/community/lidarr/app_versions.json +++ b/trains/community/lidarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/lidarr/1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.7.1.4417_1.1.14", "version": "1.1.14", diff --git a/trains/community/linkding/app_versions.json b/trains/community/linkding/app_versions.json index c5849b2826..57b005edb2 100644 --- a/trains/community/linkding/app_versions.json +++ b/trains/community/linkding/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/linkding/1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.36.0_1.0.17", "version": "1.0.17", diff --git a/trains/community/listmonk/app_versions.json b/trains/community/listmonk/app_versions.json index 551fc585b8..31ac3413d1 100644 --- a/trains/community/listmonk/app_versions.json +++ b/trains/community/listmonk/app_versions.json @@ -1,16 +1,37 @@ { - "1.0.9": { + "1.0.10": { "healthy": true, "supported": true, "healthy_error": null, - "location": "/__w/apps/apps/trains/community/listmonk/1.0.9", - "last_update": "2024-10-31 13:01:07", + "location": "/__w/apps/apps/trains/community/listmonk/1.0.10", + "last_update": "2024-10-31 16:42:28", "required_features": [], - "human_version": "v3.0.0_1.0.9", - "version": "1.0.9", + "human_version": "v4.0.1_1.0.10", + "version": "1.0.10", "app_metadata": { - "app_version": "v3.0.0", - "capabilities": [], + "app_version": "v4.0.1", + "capabilities": [ + { + "description": "Listmonk is able to chown files.", + "name": "CHOWN" + }, + { + "description": "Listmonk is able to bypass permission checks.", + "name": "DAC_OVERRIDE" + }, + { + "description": "Listmonk is able bypass permission checks for it's sub-processes.", + "name": "FOWNER" + }, + { + "description": "Listmonk is able to set group ID for it's sub-processes.", + "name": "SETGID" + }, + { + "description": "Listmonk is able to set user ID for it's sub-processes.", + "name": "SETUID" + } + ], "categories": [ "productivity" ], @@ -34,10 +55,10 @@ "name": "listmonk", "run_as_context": [ { - "description": "Listmonk runs as any non-root user.", - "gid": 568, + "description": "Listmonk runs as root user.", + "gid": 0, "group_name": "listmonk", - "uid": 568, + "uid": 0, "user_name": "listmonk" }, { @@ -59,7 +80,7 @@ ], "title": "Listmonk", "train": "community", - "version": "1.0.9" + "version": "1.0.10" }, "schema": { "groups": [ diff --git a/trains/community/logseq/app_versions.json b/trains/community/logseq/app_versions.json index 10f27f3fbd..8d2c556014 100644 --- a/trains/community/logseq/app_versions.json +++ b/trains/community/logseq/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/logseq/1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "latest_1.0.18", "version": "1.0.18", diff --git a/trains/community/mealie/app_versions.json b/trains/community/mealie/app_versions.json index e44e8a2967..d61260376e 100644 --- a/trains/community/mealie/app_versions.json +++ b/trains/community/mealie/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/mealie/1.2.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v2.1.0_1.2.12", "version": "1.2.12", diff --git a/trains/community/metube/app_versions.json b/trains/community/metube/app_versions.json index c0c9f7763e..210c3ec404 100644 --- a/trains/community/metube/app_versions.json +++ b/trains/community/metube/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/metube/1.1.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2024-10-23_1.1.18", "version": "1.1.18", diff --git a/trains/community/minecraft/app_versions.json b/trains/community/minecraft/app_versions.json index 90451653d8..cec238be8e 100644 --- a/trains/community/minecraft/app_versions.json +++ b/trains/community/minecraft/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/minecraft/1.11.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2024.10.2_1.11.19", "version": "1.11.19", diff --git a/trains/community/mineos/app_versions.json b/trains/community/mineos/app_versions.json index 01dc6014e5..608f4b0be1 100644 --- a/trains/community/mineos/app_versions.json +++ b/trains/community/mineos/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/mineos/1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "latest_1.0.18", "version": "1.0.18", diff --git a/trains/community/mumble/app_versions.json b/trains/community/mumble/app_versions.json index 6ccc95d11e..92da92e898 100644 --- a/trains/community/mumble/app_versions.json +++ b/trains/community/mumble/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/mumble/1.1.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1.5.634_1.1.10", "version": "1.1.10", diff --git a/trains/community/n8n/app_versions.json b/trains/community/n8n/app_versions.json index 9c4afec207..00a0b3615f 100644 --- a/trains/community/n8n/app_versions.json +++ b/trains/community/n8n/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/n8n/1.3.27", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.65.2_1.3.27", "version": "1.3.27", diff --git a/trains/community/navidrome/app_versions.json b/trains/community/navidrome/app_versions.json index 5d82f6151f..9f610fdff2 100644 --- a/trains/community/navidrome/app_versions.json +++ b/trains/community/navidrome/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/navidrome/1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.53.3_1.0.24", "version": "1.0.24", diff --git a/trains/community/netbootxyz/app_versions.json b/trains/community/netbootxyz/app_versions.json index 387d08fb60..f4f46cf6bf 100644 --- a/trains/community/netbootxyz/app_versions.json +++ b/trains/community/netbootxyz/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/netbootxyz/1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.7.3-nbxyz1_1.0.19", "version": "1.0.19", diff --git a/trains/community/nginx-proxy-manager/app_versions.json b/trains/community/nginx-proxy-manager/app_versions.json index 4ad22a4a96..de925b677c 100644 --- a/trains/community/nginx-proxy-manager/app_versions.json +++ b/trains/community/nginx-proxy-manager/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/nginx-proxy-manager/1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.12.1_1.0.24", "version": "1.0.24", diff --git a/trains/community/node-red/app_versions.json b/trains/community/node-red/app_versions.json index a4dae21b45..dce5964614 100644 --- a/trains/community/node-red/app_versions.json +++ b/trains/community/node-red/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/node-red/1.0.33", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "4.0.5_1.0.33", "version": "1.0.33", diff --git a/trains/community/odoo/app_versions.json b/trains/community/odoo/app_versions.json index 2e93a81349..a697c8e609 100644 --- a/trains/community/odoo/app_versions.json +++ b/trains/community/odoo/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/odoo/1.0.4", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "17.0_1.0.4", "version": "1.0.4", diff --git a/trains/community/ollama/app_versions.json b/trains/community/ollama/app_versions.json index e9fcb49fb5..f35b651ce6 100644 --- a/trains/community/ollama/app_versions.json +++ b/trains/community/ollama/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/ollama/1.0.0", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v0.3.14_1.0.0", "version": "1.0.0", diff --git a/trains/community/omada-controller/app_versions.json b/trains/community/omada-controller/app_versions.json index 371c6cdffe..4c62701170 100644 --- a/trains/community/omada-controller/app_versions.json +++ b/trains/community/omada-controller/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/omada-controller/1.1.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "5.14_1.1.11", "version": "1.1.11", diff --git a/trains/community/open-webui/app_versions.json b/trains/community/open-webui/app_versions.json index 89240e8394..52c744311e 100644 --- a/trains/community/open-webui/app_versions.json +++ b/trains/community/open-webui/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/open-webui/1.0.1", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.3.35_1.0.1", "version": "1.0.1", diff --git a/trains/community/organizr/app_versions.json b/trains/community/organizr/app_versions.json index 0b0868d959..224a7c8cd2 100644 --- a/trains/community/organizr/app_versions.json +++ b/trains/community/organizr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/organizr/1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "latest_1.0.18", "version": "1.0.18", diff --git a/trains/community/overseerr/app_versions.json b/trains/community/overseerr/app_versions.json index 22232c22c5..dfb075a6ec 100644 --- a/trains/community/overseerr/app_versions.json +++ b/trains/community/overseerr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/overseerr/1.0.18", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.33.2_1.0.18", "version": "1.0.18", diff --git a/trains/community/palworld/app_versions.json b/trains/community/palworld/app_versions.json index 1ebb5c6bbc..bc771313ec 100644 --- a/trains/community/palworld/app_versions.json +++ b/trains/community/palworld/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/palworld/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "palworld_1.0.20", "version": "1.0.20", diff --git a/trains/community/paperless-ngx/app_versions.json b/trains/community/paperless-ngx/app_versions.json index 9d57ff3223..53706f653c 100644 --- a/trains/community/paperless-ngx/app_versions.json +++ b/trains/community/paperless-ngx/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/paperless-ngx/1.0.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.13.2_1.0.15", "version": "1.0.15", diff --git a/trains/community/passbolt/app_versions.json b/trains/community/passbolt/app_versions.json index 0392767d61..ba5616a563 100644 --- a/trains/community/passbolt/app_versions.json +++ b/trains/community/passbolt/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/passbolt/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "4.9.1-1-ce-non-root_1.0.7", "version": "1.0.7", diff --git a/trains/community/penpot/app_versions.json b/trains/community/penpot/app_versions.json index f5826c689a..92f0b36bb0 100644 --- a/trains/community/penpot/app_versions.json +++ b/trains/community/penpot/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/penpot/1.0.1", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.2.1_1.0.1", "version": "1.0.1", diff --git a/trains/community/pgadmin/app_versions.json b/trains/community/pgadmin/app_versions.json index b8d3365175..b333546467 100644 --- a/trains/community/pgadmin/app_versions.json +++ b/trains/community/pgadmin/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/pgadmin/1.0.5", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "8.12_1.0.5", "version": "1.0.5", diff --git a/trains/community/pigallery2/app_versions.json b/trains/community/pigallery2/app_versions.json index af10e80f00..212a545668 100644 --- a/trains/community/pigallery2/app_versions.json +++ b/trains/community/pigallery2/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/pigallery2/1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.0.0_1.0.6", "version": "1.0.6", diff --git a/trains/community/piwigo/app_versions.json b/trains/community/piwigo/app_versions.json index c108fba7ba..ae387e76bf 100644 --- a/trains/community/piwigo/app_versions.json +++ b/trains/community/piwigo/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/piwigo/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "15.0.0_1.0.7", "version": "1.0.7", diff --git a/trains/community/planka/app_versions.json b/trains/community/planka/app_versions.json index 5c66a1c49e..679fc1575d 100644 --- a/trains/community/planka/app_versions.json +++ b/trains/community/planka/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/planka/1.0.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.23.4_1.0.13", "version": "1.0.13", diff --git a/trains/community/plex-auto-languages/app_versions.json b/trains/community/plex-auto-languages/app_versions.json index a3c6972f88..f04749cff8 100644 --- a/trains/community/plex-auto-languages/app_versions.json +++ b/trains/community/plex-auto-languages/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/plex-auto-languages/1.0.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1.2.3_1.0.16", "version": "1.0.16", diff --git a/trains/community/portainer/app_versions.json b/trains/community/portainer/app_versions.json index f78af1eb11..3311f935c5 100644 --- a/trains/community/portainer/app_versions.json +++ b/trains/community/portainer/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/portainer/1.2.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.23.0_1.2.14", "version": "1.2.14", diff --git a/trains/community/prowlarr/app_versions.json b/trains/community/prowlarr/app_versions.json index 42caf7af4a..d253f806e3 100644 --- a/trains/community/prowlarr/app_versions.json +++ b/trains/community/prowlarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/prowlarr/1.2.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.25.4.4818_1.2.15", "version": "1.2.15", diff --git a/trains/community/qbittorrent/app_versions.json b/trains/community/qbittorrent/app_versions.json index 80c93f3240..bbb05ad9d8 100644 --- a/trains/community/qbittorrent/app_versions.json +++ b/trains/community/qbittorrent/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/qbittorrent/1.0.29", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "5.0.1_1.0.29", "version": "1.0.29", diff --git a/trains/community/radarr/app_versions.json b/trains/community/radarr/app_versions.json index 57899e6f22..6b28ac4f6c 100644 --- a/trains/community/radarr/app_versions.json +++ b/trains/community/radarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/radarr/1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "5.14.0.9383_1.1.13", "version": "1.1.13", diff --git a/trains/community/readarr/app_versions.json b/trains/community/readarr/app_versions.json index d9f4a11870..22a9b8e123 100644 --- a/trains/community/readarr/app_versions.json +++ b/trains/community/readarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/readarr/1.0.19", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.3.32.2587_1.0.19", "version": "1.0.19", diff --git a/trains/community/recyclarr/app_versions.json b/trains/community/recyclarr/app_versions.json index c6f682042f..254f4a96cd 100644 --- a/trains/community/recyclarr/app_versions.json +++ b/trains/community/recyclarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/recyclarr/1.0.9", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "7.3.0_1.0.9", "version": "1.0.9", diff --git a/trains/community/redis/app_versions.json b/trains/community/redis/app_versions.json index 301d35c5a7..fd3143e84c 100644 --- a/trains/community/redis/app_versions.json +++ b/trains/community/redis/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/redis/1.0.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "7.4.1_1.0.8", "version": "1.0.8", diff --git a/trains/community/roundcube/app_versions.json b/trains/community/roundcube/app_versions.json index f5a0d62682..ea0d46a41c 100644 --- a/trains/community/roundcube/app_versions.json +++ b/trains/community/roundcube/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/roundcube/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.6.9-apache_1.0.7", "version": "1.0.7", diff --git a/trains/community/rsyncd/app_versions.json b/trains/community/rsyncd/app_versions.json index 0f96079318..5bed0479e3 100644 --- a/trains/community/rsyncd/app_versions.json +++ b/trains/community/rsyncd/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/rsyncd/1.0.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.0.0_1.0.15", "version": "1.0.15", diff --git a/trains/community/rust-desk/app_versions.json b/trains/community/rust-desk/app_versions.json index c91d6f3fd5..8184c45183 100644 --- a/trains/community/rust-desk/app_versions.json +++ b/trains/community/rust-desk/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/rust-desk/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.1.11-1_1.0.7", "version": "1.0.7", diff --git a/trains/community/sabnzbd/app_versions.json b/trains/community/sabnzbd/app_versions.json index 3bc3e5678a..324f4d0db2 100644 --- a/trains/community/sabnzbd/app_versions.json +++ b/trains/community/sabnzbd/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/sabnzbd/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "4.3.3_1.0.7", "version": "1.0.7", diff --git a/trains/community/searxng/app_versions.json b/trains/community/searxng/app_versions.json index 3216ce8934..072b38ee92 100644 --- a/trains/community/searxng/app_versions.json +++ b/trains/community/searxng/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/searxng/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2024.10.29-e39289257_1.0.20", "version": "1.0.20", diff --git a/trains/community/sftpgo/app_versions.json b/trains/community/sftpgo/app_versions.json index 8b273b68f2..6e122bcb60 100644 --- a/trains/community/sftpgo/app_versions.json +++ b/trains/community/sftpgo/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/sftpgo/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.6.2_1.0.7", "version": "1.0.7", diff --git a/trains/community/sonarr/app_versions.json b/trains/community/sonarr/app_versions.json index c3cd178236..87d1a92c4e 100644 --- a/trains/community/sonarr/app_versions.json +++ b/trains/community/sonarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/sonarr/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "4.0.10.2544_1.0.20", "version": "1.0.20", diff --git a/trains/community/tailscale/app_versions.json b/trains/community/tailscale/app_versions.json index fa7f890a71..9351b61b0f 100644 --- a/trains/community/tailscale/app_versions.json +++ b/trains/community/tailscale/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/tailscale/1.1.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1.74.1_1.1.16", "version": "1.1.16", diff --git a/trains/community/tautulli/app_versions.json b/trains/community/tautulli/app_versions.json index 5c58b80ac6..9837d69e1b 100644 --- a/trains/community/tautulli/app_versions.json +++ b/trains/community/tautulli/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/tautulli/1.0.22", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v2.14.6_1.0.22", "version": "1.0.22", diff --git a/trains/community/tdarr/app_versions.json b/trains/community/tdarr/app_versions.json index 74589da533..b37de3c52a 100644 --- a/trains/community/tdarr/app_versions.json +++ b/trains/community/tdarr/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/tdarr/1.0.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.27.02_1.0.10", "version": "1.0.10", diff --git a/trains/community/terraria/app_versions.json b/trains/community/terraria/app_versions.json index 365053a589..b0d4b5f5b7 100644 --- a/trains/community/terraria/app_versions.json +++ b/trains/community/terraria/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/terraria/1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "tshock-1.4.4.9-5.2.0-3_1.0.6", "version": "1.0.6", diff --git a/trains/community/tftpd-hpa/app_versions.json b/trains/community/tftpd-hpa/app_versions.json index 5e2ff0132b..613e1dfed1 100644 --- a/trains/community/tftpd-hpa/app_versions.json +++ b/trains/community/tftpd-hpa/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/tftpd-hpa/1.0.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.0.0_1.0.11", "version": "1.0.11", diff --git a/trains/community/tiny-media-manager/app_versions.json b/trains/community/tiny-media-manager/app_versions.json index d6fdcdf1b6..817f022f2a 100644 --- a/trains/community/tiny-media-manager/app_versions.json +++ b/trains/community/tiny-media-manager/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/tiny-media-manager/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "5.0.13_1.0.7", "version": "1.0.7", diff --git a/trains/community/transmission/app_versions.json b/trains/community/transmission/app_versions.json index 1807251034..615aac0aa8 100644 --- a/trains/community/transmission/app_versions.json +++ b/trains/community/transmission/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/transmission/1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "4.0.6_1.0.6", "version": "1.0.6", diff --git a/trains/community/twofactor-auth/app_versions.json b/trains/community/twofactor-auth/app_versions.json index 425136d17a..3d8dcb18a6 100644 --- a/trains/community/twofactor-auth/app_versions.json +++ b/trains/community/twofactor-auth/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/twofactor-auth/1.0.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "5.3.2_1.0.8", "version": "1.0.8", diff --git a/trains/community/unifi-controller/app_versions.json b/trains/community/unifi-controller/app_versions.json index f229df711c..99ba94604e 100644 --- a/trains/community/unifi-controller/app_versions.json +++ b/trains/community/unifi-controller/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/unifi-controller/1.2.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "8.5.6_1.2.11", "version": "1.2.11", diff --git a/trains/community/unifi-protect-backup/app_versions.json b/trains/community/unifi-protect-backup/app_versions.json index 7a0b4f17c7..f2bae747aa 100644 --- a/trains/community/unifi-protect-backup/app_versions.json +++ b/trains/community/unifi-protect-backup/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/unifi-protect-backup/1.0.17", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.11.0_1.0.17", "version": "1.0.17", diff --git a/trains/community/vaultwarden/app_versions.json b/trains/community/vaultwarden/app_versions.json index 0b9f006f5d..5d5126f02e 100644 --- a/trains/community/vaultwarden/app_versions.json +++ b/trains/community/vaultwarden/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/vaultwarden/1.0.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.32.3_1.0.8", "version": "1.0.8", diff --git a/trains/community/vikunja/app_versions.json b/trains/community/vikunja/app_versions.json index 1697a2fc15..bffdd874d0 100644 --- a/trains/community/vikunja/app_versions.json +++ b/trains/community/vikunja/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/vikunja/1.2.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.24.4_1.2.15", "version": "1.2.15", diff --git a/trains/community/webdav/app_versions.json b/trains/community/webdav/app_versions.json index 23d4a30dc7..3eea0a4a4a 100644 --- a/trains/community/webdav/app_versions.json +++ b/trains/community/webdav/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/webdav/1.0.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.4.62_1.0.13", "version": "1.0.13", diff --git a/trains/community/whoogle/app_versions.json b/trains/community/whoogle/app_versions.json index 24abe6ad74..f3e7a7ec2f 100644 --- a/trains/community/whoogle/app_versions.json +++ b/trains/community/whoogle/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/whoogle/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "0.9.0_1.0.20", "version": "1.0.20", diff --git a/trains/community/wordpress/app_versions.json b/trains/community/wordpress/app_versions.json index 92bb74493b..0ab5422f41 100644 --- a/trains/community/wordpress/app_versions.json +++ b/trains/community/wordpress/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/wordpress/1.0.7", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "6.6.2_1.0.7", "version": "1.0.7", diff --git a/trains/community/zerotier/app_versions.json b/trains/community/zerotier/app_versions.json index 0e61059c78..e971d6fe01 100644 --- a/trains/community/zerotier/app_versions.json +++ b/trains/community/zerotier/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/community/zerotier/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.14.1_1.0.20", "version": "1.0.20", diff --git a/trains/enterprise/asigra-ds-system/app_versions.json b/trains/enterprise/asigra-ds-system/app_versions.json index 211235c6c3..0106e6c35f 100644 --- a/trains/enterprise/asigra-ds-system/app_versions.json +++ b/trains/enterprise/asigra-ds-system/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/enterprise/asigra-ds-system/1.0.2", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "14.2.0.8_1.0.2", "version": "1.0.2", diff --git a/trains/enterprise/minio/app_versions.json b/trains/enterprise/minio/app_versions.json index 2d9fd955b0..ed11c40b42 100644 --- a/trains/enterprise/minio/app_versions.json +++ b/trains/enterprise/minio/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/enterprise/minio/1.1.8", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "RELEASE.2024-08-26T15-33-07Z_1.1.8", "version": "1.1.8", diff --git a/trains/enterprise/syncthing/app_versions.json b/trains/enterprise/syncthing/app_versions.json index 66a318c411..6d3f43e5d8 100644 --- a/trains/enterprise/syncthing/app_versions.json +++ b/trains/enterprise/syncthing/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/enterprise/syncthing/1.0.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.27.10_1.0.15", "version": "1.0.15", diff --git a/trains/stable/collabora/app_versions.json b/trains/stable/collabora/app_versions.json index f0fced3851..9c868b43c8 100644 --- a/trains/stable/collabora/app_versions.json +++ b/trains/stable/collabora/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/collabora/1.1.16", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "24.04.9.1.1_1.1.16", "version": "1.1.16", diff --git a/trains/stable/diskoverdata/app_versions.json b/trains/stable/diskoverdata/app_versions.json index ef9ea9c3b1..29fde05df0 100644 --- a/trains/stable/diskoverdata/app_versions.json +++ b/trains/stable/diskoverdata/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/diskoverdata/1.3.9", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2.3.0_1.3.9", "version": "1.3.9", diff --git a/trains/stable/elastic-search/app_versions.json b/trains/stable/elastic-search/app_versions.json index 10a29cec02..a59cb32223 100644 --- a/trains/stable/elastic-search/app_versions.json +++ b/trains/stable/elastic-search/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/elastic-search/1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "8.15.3_1.1.13", "version": "1.1.13", diff --git a/trains/stable/emby/app_versions.json b/trains/stable/emby/app_versions.json index d0b506f76b..a918f61de0 100644 --- a/trains/stable/emby/app_versions.json +++ b/trains/stable/emby/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/emby/1.1.13", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "4.8.10.0_1.1.13", "version": "1.1.13", diff --git a/trains/stable/home-assistant/app_versions.json b/trains/stable/home-assistant/app_versions.json index 5ced12db8d..b7848c0aab 100644 --- a/trains/stable/home-assistant/app_versions.json +++ b/trains/stable/home-assistant/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/home-assistant/1.2.23", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2024.10.4_1.2.23", "version": "1.2.23", diff --git a/trains/stable/ix-app/app_versions.json b/trains/stable/ix-app/app_versions.json index ce5aca1a5a..4a13887f38 100644 --- a/trains/stable/ix-app/app_versions.json +++ b/trains/stable/ix-app/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/ix-app/1.0.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.0.0_1.0.11", "version": "1.0.11", diff --git a/trains/stable/minio/app_versions.json b/trains/stable/minio/app_versions.json index 4f429a9b30..4a2f6699d4 100644 --- a/trains/stable/minio/app_versions.json +++ b/trains/stable/minio/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/minio/1.1.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "RELEASE.2024-10-29T16-01-48Z_1.1.15", "version": "1.1.15", diff --git a/trains/stable/netdata/app_versions.json b/trains/stable/netdata/app_versions.json index 73b57f9049..5c0f450d3b 100644 --- a/trains/stable/netdata/app_versions.json +++ b/trains/stable/netdata/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/netdata/1.1.15", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1.47.5_1.1.15", "version": "1.1.15", diff --git a/trains/stable/nextcloud/app_versions.json b/trains/stable/nextcloud/app_versions.json index 1b84984ae0..5218f5eb16 100644 --- a/trains/stable/nextcloud/app_versions.json +++ b/trains/stable/nextcloud/app_versions.json @@ -1,13 +1,13 @@ { - "1.4.0": { + "1.4.1": { "healthy": true, "supported": true, "healthy_error": null, - "location": "/__w/apps/apps/trains/stable/nextcloud/1.4.0", - "last_update": "2024-10-31 13:02:23", + "location": "/__w/apps/apps/trains/stable/nextcloud/1.4.1", + "last_update": "2024-10-31 16:42:28", "required_features": [], - "human_version": "30.0.1_1.4.0", - "version": "1.4.0", + "human_version": "30.0.1_1.4.1", + "version": "1.4.1", "app_metadata": { "app_version": "30.0.1", "capabilities": [ @@ -107,7 +107,7 @@ ], "title": "Nextcloud", "train": "stable", - "version": "1.4.0" + "version": "1.4.1" }, "schema": { "groups": [ diff --git a/trains/stable/photoprism/app_versions.json b/trains/stable/photoprism/app_versions.json index 6127be5e77..1faf0e86f9 100644 --- a/trains/stable/photoprism/app_versions.json +++ b/trains/stable/photoprism/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/photoprism/1.1.14", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "240915_1.1.14", "version": "1.1.14", diff --git a/trains/stable/pihole/app_versions.json b/trains/stable/pihole/app_versions.json index 8cf7e57946..824fafe285 100644 --- a/trains/stable/pihole/app_versions.json +++ b/trains/stable/pihole/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/pihole/1.1.11", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "2024.07.0_1.1.11", "version": "1.1.11", diff --git a/trains/stable/plex/app_versions.json b/trains/stable/plex/app_versions.json index f6bf8828c7..dcae0d62a5 100644 --- a/trains/stable/plex/app_versions.json +++ b/trains/stable/plex/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/plex/1.0.24", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.40.2.8395-c67dce28e_1.0.24", "version": "1.0.24", diff --git a/trains/stable/prometheus/app_versions.json b/trains/stable/prometheus/app_versions.json index 524433457d..8172d25b59 100644 --- a/trains/stable/prometheus/app_versions.json +++ b/trains/stable/prometheus/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/prometheus/1.1.12", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v2.55.0_1.1.12", "version": "1.1.12", diff --git a/trains/stable/storj/app_versions.json b/trains/stable/storj/app_versions.json index 47cf4887f1..fd4b2ae84e 100644 --- a/trains/stable/storj/app_versions.json +++ b/trains/stable/storj/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/storj/1.1.10", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "6f87ea801-v1.71.2-go1.18.8_1.1.10", "version": "1.1.10", diff --git a/trains/stable/syncthing/app_versions.json b/trains/stable/syncthing/app_versions.json index b2c5c05659..7df2039e83 100644 --- a/trains/stable/syncthing/app_versions.json +++ b/trains/stable/syncthing/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/syncthing/1.0.28", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "1.28.0_1.0.28", "version": "1.0.28", diff --git a/trains/stable/wg-easy/app_versions.json b/trains/stable/wg-easy/app_versions.json index 988388ecef..7267d17f0f 100644 --- a/trains/stable/wg-easy/app_versions.json +++ b/trains/stable/wg-easy/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/stable/wg-easy/1.0.20", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "14_1.0.20", "version": "1.0.20", diff --git a/trains/test/nginx/app_versions.json b/trains/test/nginx/app_versions.json index d6eada84bc..77814bda4f 100644 --- a/trains/test/nginx/app_versions.json +++ b/trains/test/nginx/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/test/nginx/1.0.6", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1_1.0.6", "version": "1.0.6", @@ -107,7 +107,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/test/nginx/1.0.5", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1_1.0.5", "version": "1.0.5", @@ -200,7 +200,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/test/nginx/1.0.4", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1_1.0.4", "version": "1.0.4", @@ -293,7 +293,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/test/nginx/1.0.3", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1_1.0.3", "version": "1.0.3", diff --git a/trains/test/other-nginx/app_versions.json b/trains/test/other-nginx/app_versions.json index 8bbb842f03..3a158f8014 100644 --- a/trains/test/other-nginx/app_versions.json +++ b/trains/test/other-nginx/app_versions.json @@ -4,7 +4,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/test/other-nginx/1.0.1", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1_1.0.1", "version": "1.0.1", @@ -97,7 +97,7 @@ "supported": true, "healthy_error": null, "location": "/__w/apps/apps/trains/test/other-nginx/1.0.0", - "last_update": "2024-10-31 13:01:07", + "last_update": "2024-10-31 16:39:37", "required_features": [], "human_version": "v1_1.0.0", "version": "1.0.0", From c3ffb4bea1cd86a225b0c4bc6e9a9bfaa1091712 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:42:58 +0200 Subject: [PATCH 7/9] v1-lib: fix $ escaping (#756) * fix $ symbol escaping * we dont need to escape it here, it is done globally --- library/{1.1.4 => 1.1.5}/__init__.py | 0 library/{1.1.4 => 1.1.5}/environment.py | 0 library/{1.1.4 => 1.1.5}/healthchecks.py | 0 library/{1.1.4 => 1.1.5}/mariadb.py | 0 library/{1.1.4 => 1.1.5}/metadata.py | 0 library/{1.1.4 => 1.1.5}/network.py | 0 library/{1.1.4 => 1.1.5}/permissions.py | 0 library/{1.1.4 => 1.1.5}/ports.py | 0 library/{1.1.4 => 1.1.5}/postgres.py | 0 library/{1.1.4 => 1.1.5}/redis.py | 0 library/{1.1.4 => 1.1.5}/resources.py | 0 library/{1.1.4 => 1.1.5}/security.py | 0 library/{1.1.4 => 1.1.5}/storage.py | 0 library/{1.1.4 => 1.1.5}/utils.py | 21 +++++---------------- library/hashes.yaml | 2 +- 15 files changed, 6 insertions(+), 17 deletions(-) rename library/{1.1.4 => 1.1.5}/__init__.py (100%) rename library/{1.1.4 => 1.1.5}/environment.py (100%) rename library/{1.1.4 => 1.1.5}/healthchecks.py (100%) rename library/{1.1.4 => 1.1.5}/mariadb.py (100%) rename library/{1.1.4 => 1.1.5}/metadata.py (100%) rename library/{1.1.4 => 1.1.5}/network.py (100%) rename library/{1.1.4 => 1.1.5}/permissions.py (100%) rename library/{1.1.4 => 1.1.5}/ports.py (100%) rename library/{1.1.4 => 1.1.5}/postgres.py (100%) rename library/{1.1.4 => 1.1.5}/redis.py (100%) rename library/{1.1.4 => 1.1.5}/resources.py (100%) rename library/{1.1.4 => 1.1.5}/security.py (100%) rename library/{1.1.4 => 1.1.5}/storage.py (100%) rename library/{1.1.4 => 1.1.5}/utils.py (77%) diff --git a/library/1.1.4/__init__.py b/library/1.1.5/__init__.py similarity index 100% rename from library/1.1.4/__init__.py rename to library/1.1.5/__init__.py diff --git a/library/1.1.4/environment.py b/library/1.1.5/environment.py similarity index 100% rename from library/1.1.4/environment.py rename to library/1.1.5/environment.py diff --git a/library/1.1.4/healthchecks.py b/library/1.1.5/healthchecks.py similarity index 100% rename from library/1.1.4/healthchecks.py rename to library/1.1.5/healthchecks.py diff --git a/library/1.1.4/mariadb.py b/library/1.1.5/mariadb.py similarity index 100% rename from library/1.1.4/mariadb.py rename to library/1.1.5/mariadb.py diff --git a/library/1.1.4/metadata.py b/library/1.1.5/metadata.py similarity index 100% rename from library/1.1.4/metadata.py rename to library/1.1.5/metadata.py diff --git a/library/1.1.4/network.py b/library/1.1.5/network.py similarity index 100% rename from library/1.1.4/network.py rename to library/1.1.5/network.py diff --git a/library/1.1.4/permissions.py b/library/1.1.5/permissions.py similarity index 100% rename from library/1.1.4/permissions.py rename to library/1.1.5/permissions.py diff --git a/library/1.1.4/ports.py b/library/1.1.5/ports.py similarity index 100% rename from library/1.1.4/ports.py rename to library/1.1.5/ports.py diff --git a/library/1.1.4/postgres.py b/library/1.1.5/postgres.py similarity index 100% rename from library/1.1.4/postgres.py rename to library/1.1.5/postgres.py diff --git a/library/1.1.4/redis.py b/library/1.1.5/redis.py similarity index 100% rename from library/1.1.4/redis.py rename to library/1.1.5/redis.py diff --git a/library/1.1.4/resources.py b/library/1.1.5/resources.py similarity index 100% rename from library/1.1.4/resources.py rename to library/1.1.5/resources.py diff --git a/library/1.1.4/security.py b/library/1.1.5/security.py similarity index 100% rename from library/1.1.4/security.py rename to library/1.1.5/security.py diff --git a/library/1.1.4/storage.py b/library/1.1.5/storage.py similarity index 100% rename from library/1.1.4/storage.py rename to library/1.1.5/storage.py diff --git a/library/1.1.4/utils.py b/library/1.1.5/utils.py similarity index 77% rename from library/1.1.4/utils.py rename to library/1.1.5/utils.py index 3d76208ff1..8a7c0815c6 100644 --- a/library/1.1.4/utils.py +++ b/library/1.1.5/utils.py @@ -27,12 +27,8 @@ def basic_auth_header(username, password): return f"Basic {security.basic_auth(username, password)}" -def bcrypt_hash(password, escape=True): - hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") - if escape: - # Docker compose will try to expand the value, so we need to escape it - return hashed.replace("$", "$$") - return hashed +def bcrypt_hash(password): + return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") def match_regex(value, regex): @@ -88,9 +84,7 @@ def get_image(images={}, name=""): if name not in images: throw_error(f"Expected [images.{name}] to be set") if not images[name].get("repository") or not images[name].get("tag"): - throw_error( - f"Expected [images.{name}.repository] and [images.{name}.tag] to be set" - ) + throw_error(f"Expected [images.{name}.repository] and [images.{name}.tag] to be set") return f"{images[name]['repository']}:{images[name]['tag']}" @@ -109,13 +103,8 @@ def copy_dict(dict): return dict.copy() -# Replaces all single dollar signs with double dollar signs -# Docker tries to expand shell variables, so we need to -# escape them in multiple places -# It will not replace dollar signs that are already escaped -def escape_dollar(text): - # https://regex101.com/r/tdbI7y/1 - return re.sub(r"(? str: + return text.replace("$", "$$") def auto_cast(value): diff --git a/library/hashes.yaml b/library/hashes.yaml index 006d1d119f..524ecfe187 100644 --- a/library/hashes.yaml +++ b/library/hashes.yaml @@ -1,3 +1,3 @@ 0.0.1: f074617a82a86d2a6cc78a4c8a4296fc9d168e456f12713e50c696557b302133 -1.1.4: 6e32ff5969906d9c3a10fea2b17fdd3197afb052d3432344da03188d8a907113 +1.1.5: 51332f2b032a0c473693458cd93daa96d56604241878e538c07cb85b8be66727 2.0.4: 0e79e3390d3ea73649ee2ac05a4af9ed944a02e95289b5c7e2eb047d475a4651 From 5f5cf31d36dc47579430d960dad62f8cbb3dc156 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:49:24 +0200 Subject: [PATCH 8/9] v2-lib: fix $ escaping (#759) * auto add labels from a top level structure * improve message * fix escaping * fix escaping * clean --- library/{2.0.4 => 2.0.5}/__init__.py | 0 library/{2.0.4 => 2.0.5}/configs.py | 0 library/{2.0.4 => 2.0.5}/container.py | 0 library/{2.0.4 => 2.0.5}/depends.py | 0 library/{2.0.4 => 2.0.5}/deploy.py | 0 library/{2.0.4 => 2.0.5}/deps.py | 0 library/{2.0.4 => 2.0.5}/device.py | 0 library/{2.0.4 => 2.0.5}/devices.py | 0 library/{2.0.4 => 2.0.5}/dns.py | 0 library/{2.0.4 => 2.0.5}/environment.py | 0 library/{2.0.4 => 2.0.5}/error.py | 0 library/{2.0.4 => 2.0.5}/formatter.py | 10 ++-------- library/{2.0.4 => 2.0.5}/functions.py | 0 library/{2.0.4 => 2.0.5}/healthcheck.py | 0 library/{2.0.4 => 2.0.5}/labels.py | 0 library/{2.0.4 => 2.0.5}/notes.py | 0 library/{2.0.4 => 2.0.5}/portal.py | 0 library/{2.0.4 => 2.0.5}/portals.py | 0 library/{2.0.4 => 2.0.5}/ports.py | 0 library/{2.0.4 => 2.0.5}/render.py | 0 library/{2.0.4 => 2.0.5}/resources.py | 0 library/{2.0.4 => 2.0.5}/restart.py | 0 library/{2.0.4 => 2.0.5}/storage.py | 0 library/{2.0.4 => 2.0.5}/tests/__init__.py | 0 library/{2.0.4 => 2.0.5}/tests/test_build_image.py | 0 library/{2.0.4 => 2.0.5}/tests/test_configs.py | 0 library/{2.0.4 => 2.0.5}/tests/test_container.py | 0 library/{2.0.4 => 2.0.5}/tests/test_depends.py | 0 library/{2.0.4 => 2.0.5}/tests/test_deps.py | 0 library/{2.0.4 => 2.0.5}/tests/test_device.py | 0 library/{2.0.4 => 2.0.5}/tests/test_dns.py | 0 library/{2.0.4 => 2.0.5}/tests/test_environment.py | 0 library/2.0.5/tests/test_formatter.py | 13 +++++++++++++ library/{2.0.4 => 2.0.5}/tests/test_functions.py | 0 library/{2.0.4 => 2.0.5}/tests/test_healthcheck.py | 0 library/{2.0.4 => 2.0.5}/tests/test_labels.py | 0 library/{2.0.4 => 2.0.5}/tests/test_notes.py | 0 library/{2.0.4 => 2.0.5}/tests/test_portal.py | 0 library/{2.0.4 => 2.0.5}/tests/test_ports.py | 0 library/{2.0.4 => 2.0.5}/tests/test_render.py | 0 library/{2.0.4 => 2.0.5}/tests/test_resources.py | 0 library/{2.0.4 => 2.0.5}/tests/test_restart.py | 0 library/{2.0.4 => 2.0.5}/tests/test_volumes.py | 0 library/{2.0.4 => 2.0.5}/validations.py | 0 library/{2.0.4 => 2.0.5}/volume_mount.py | 0 library/{2.0.4 => 2.0.5}/volume_mount_types.py | 0 library/{2.0.4 => 2.0.5}/volume_sources.py | 0 library/{2.0.4 => 2.0.5}/volume_types.py | 0 library/{2.0.4 => 2.0.5}/volumes.py | 0 library/hashes.yaml | 2 +- 50 files changed, 16 insertions(+), 9 deletions(-) rename library/{2.0.4 => 2.0.5}/__init__.py (100%) rename library/{2.0.4 => 2.0.5}/configs.py (100%) rename library/{2.0.4 => 2.0.5}/container.py (100%) rename library/{2.0.4 => 2.0.5}/depends.py (100%) rename library/{2.0.4 => 2.0.5}/deploy.py (100%) rename library/{2.0.4 => 2.0.5}/deps.py (100%) rename library/{2.0.4 => 2.0.5}/device.py (100%) rename library/{2.0.4 => 2.0.5}/devices.py (100%) rename library/{2.0.4 => 2.0.5}/dns.py (100%) rename library/{2.0.4 => 2.0.5}/environment.py (100%) rename library/{2.0.4 => 2.0.5}/error.py (100%) rename library/{2.0.4 => 2.0.5}/formatter.py (67%) rename library/{2.0.4 => 2.0.5}/functions.py (100%) rename library/{2.0.4 => 2.0.5}/healthcheck.py (100%) rename library/{2.0.4 => 2.0.5}/labels.py (100%) rename library/{2.0.4 => 2.0.5}/notes.py (100%) rename library/{2.0.4 => 2.0.5}/portal.py (100%) rename library/{2.0.4 => 2.0.5}/portals.py (100%) rename library/{2.0.4 => 2.0.5}/ports.py (100%) rename library/{2.0.4 => 2.0.5}/render.py (100%) rename library/{2.0.4 => 2.0.5}/resources.py (100%) rename library/{2.0.4 => 2.0.5}/restart.py (100%) rename library/{2.0.4 => 2.0.5}/storage.py (100%) rename library/{2.0.4 => 2.0.5}/tests/__init__.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_build_image.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_configs.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_container.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_depends.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_deps.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_device.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_dns.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_environment.py (100%) create mode 100644 library/2.0.5/tests/test_formatter.py rename library/{2.0.4 => 2.0.5}/tests/test_functions.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_healthcheck.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_labels.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_notes.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_portal.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_ports.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_render.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_resources.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_restart.py (100%) rename library/{2.0.4 => 2.0.5}/tests/test_volumes.py (100%) rename library/{2.0.4 => 2.0.5}/validations.py (100%) rename library/{2.0.4 => 2.0.5}/volume_mount.py (100%) rename library/{2.0.4 => 2.0.5}/volume_mount_types.py (100%) rename library/{2.0.4 => 2.0.5}/volume_sources.py (100%) rename library/{2.0.4 => 2.0.5}/volume_types.py (100%) rename library/{2.0.4 => 2.0.5}/volumes.py (100%) diff --git a/library/2.0.4/__init__.py b/library/2.0.5/__init__.py similarity index 100% rename from library/2.0.4/__init__.py rename to library/2.0.5/__init__.py diff --git a/library/2.0.4/configs.py b/library/2.0.5/configs.py similarity index 100% rename from library/2.0.4/configs.py rename to library/2.0.5/configs.py diff --git a/library/2.0.4/container.py b/library/2.0.5/container.py similarity index 100% rename from library/2.0.4/container.py rename to library/2.0.5/container.py diff --git a/library/2.0.4/depends.py b/library/2.0.5/depends.py similarity index 100% rename from library/2.0.4/depends.py rename to library/2.0.5/depends.py diff --git a/library/2.0.4/deploy.py b/library/2.0.5/deploy.py similarity index 100% rename from library/2.0.4/deploy.py rename to library/2.0.5/deploy.py diff --git a/library/2.0.4/deps.py b/library/2.0.5/deps.py similarity index 100% rename from library/2.0.4/deps.py rename to library/2.0.5/deps.py diff --git a/library/2.0.4/device.py b/library/2.0.5/device.py similarity index 100% rename from library/2.0.4/device.py rename to library/2.0.5/device.py diff --git a/library/2.0.4/devices.py b/library/2.0.5/devices.py similarity index 100% rename from library/2.0.4/devices.py rename to library/2.0.5/devices.py diff --git a/library/2.0.4/dns.py b/library/2.0.5/dns.py similarity index 100% rename from library/2.0.4/dns.py rename to library/2.0.5/dns.py diff --git a/library/2.0.4/environment.py b/library/2.0.5/environment.py similarity index 100% rename from library/2.0.4/environment.py rename to library/2.0.5/environment.py diff --git a/library/2.0.4/error.py b/library/2.0.5/error.py similarity index 100% rename from library/2.0.4/error.py rename to library/2.0.5/error.py diff --git a/library/2.0.4/formatter.py b/library/2.0.5/formatter.py similarity index 67% rename from library/2.0.4/formatter.py rename to library/2.0.5/formatter.py index a256da61c8..24e882f47a 100644 --- a/library/2.0.4/formatter.py +++ b/library/2.0.5/formatter.py @@ -1,15 +1,9 @@ -import re import json import hashlib -# Replaces all single dollar signs with double dollar signs -# Docker tries to expand shell variables, so we need to -# escape them in multiple places -# It will not replace dollar signs that are already escaped -def escape_dollar(text): - # https://regex101.com/r/tdbI7y/1 - return re.sub(r"(? str: + return text.replace("$", "$$") def get_hashed_name_for_volume(prefix: str, config: dict): diff --git a/library/2.0.4/functions.py b/library/2.0.5/functions.py similarity index 100% rename from library/2.0.4/functions.py rename to library/2.0.5/functions.py diff --git a/library/2.0.4/healthcheck.py b/library/2.0.5/healthcheck.py similarity index 100% rename from library/2.0.4/healthcheck.py rename to library/2.0.5/healthcheck.py diff --git a/library/2.0.4/labels.py b/library/2.0.5/labels.py similarity index 100% rename from library/2.0.4/labels.py rename to library/2.0.5/labels.py diff --git a/library/2.0.4/notes.py b/library/2.0.5/notes.py similarity index 100% rename from library/2.0.4/notes.py rename to library/2.0.5/notes.py diff --git a/library/2.0.4/portal.py b/library/2.0.5/portal.py similarity index 100% rename from library/2.0.4/portal.py rename to library/2.0.5/portal.py diff --git a/library/2.0.4/portals.py b/library/2.0.5/portals.py similarity index 100% rename from library/2.0.4/portals.py rename to library/2.0.5/portals.py diff --git a/library/2.0.4/ports.py b/library/2.0.5/ports.py similarity index 100% rename from library/2.0.4/ports.py rename to library/2.0.5/ports.py diff --git a/library/2.0.4/render.py b/library/2.0.5/render.py similarity index 100% rename from library/2.0.4/render.py rename to library/2.0.5/render.py diff --git a/library/2.0.4/resources.py b/library/2.0.5/resources.py similarity index 100% rename from library/2.0.4/resources.py rename to library/2.0.5/resources.py diff --git a/library/2.0.4/restart.py b/library/2.0.5/restart.py similarity index 100% rename from library/2.0.4/restart.py rename to library/2.0.5/restart.py diff --git a/library/2.0.4/storage.py b/library/2.0.5/storage.py similarity index 100% rename from library/2.0.4/storage.py rename to library/2.0.5/storage.py diff --git a/library/2.0.4/tests/__init__.py b/library/2.0.5/tests/__init__.py similarity index 100% rename from library/2.0.4/tests/__init__.py rename to library/2.0.5/tests/__init__.py diff --git a/library/2.0.4/tests/test_build_image.py b/library/2.0.5/tests/test_build_image.py similarity index 100% rename from library/2.0.4/tests/test_build_image.py rename to library/2.0.5/tests/test_build_image.py diff --git a/library/2.0.4/tests/test_configs.py b/library/2.0.5/tests/test_configs.py similarity index 100% rename from library/2.0.4/tests/test_configs.py rename to library/2.0.5/tests/test_configs.py diff --git a/library/2.0.4/tests/test_container.py b/library/2.0.5/tests/test_container.py similarity index 100% rename from library/2.0.4/tests/test_container.py rename to library/2.0.5/tests/test_container.py diff --git a/library/2.0.4/tests/test_depends.py b/library/2.0.5/tests/test_depends.py similarity index 100% rename from library/2.0.4/tests/test_depends.py rename to library/2.0.5/tests/test_depends.py diff --git a/library/2.0.4/tests/test_deps.py b/library/2.0.5/tests/test_deps.py similarity index 100% rename from library/2.0.4/tests/test_deps.py rename to library/2.0.5/tests/test_deps.py diff --git a/library/2.0.4/tests/test_device.py b/library/2.0.5/tests/test_device.py similarity index 100% rename from library/2.0.4/tests/test_device.py rename to library/2.0.5/tests/test_device.py diff --git a/library/2.0.4/tests/test_dns.py b/library/2.0.5/tests/test_dns.py similarity index 100% rename from library/2.0.4/tests/test_dns.py rename to library/2.0.5/tests/test_dns.py diff --git a/library/2.0.4/tests/test_environment.py b/library/2.0.5/tests/test_environment.py similarity index 100% rename from library/2.0.4/tests/test_environment.py rename to library/2.0.5/tests/test_environment.py diff --git a/library/2.0.5/tests/test_formatter.py b/library/2.0.5/tests/test_formatter.py new file mode 100644 index 0000000000..843cf65d2e --- /dev/null +++ b/library/2.0.5/tests/test_formatter.py @@ -0,0 +1,13 @@ +from formatter import escape_dollar + + +def test_escape_dollar(): + cases = [ + {"input": "test", "expected": "test"}, + {"input": "$test", "expected": "$$test"}, + {"input": "$$test", "expected": "$$$$test"}, + {"input": "$$$test", "expected": "$$$$$$test"}, + {"input": "$test$", "expected": "$$test$$"}, + ] + for case in cases: + assert escape_dollar(case["input"]) == case["expected"] diff --git a/library/2.0.4/tests/test_functions.py b/library/2.0.5/tests/test_functions.py similarity index 100% rename from library/2.0.4/tests/test_functions.py rename to library/2.0.5/tests/test_functions.py diff --git a/library/2.0.4/tests/test_healthcheck.py b/library/2.0.5/tests/test_healthcheck.py similarity index 100% rename from library/2.0.4/tests/test_healthcheck.py rename to library/2.0.5/tests/test_healthcheck.py diff --git a/library/2.0.4/tests/test_labels.py b/library/2.0.5/tests/test_labels.py similarity index 100% rename from library/2.0.4/tests/test_labels.py rename to library/2.0.5/tests/test_labels.py diff --git a/library/2.0.4/tests/test_notes.py b/library/2.0.5/tests/test_notes.py similarity index 100% rename from library/2.0.4/tests/test_notes.py rename to library/2.0.5/tests/test_notes.py diff --git a/library/2.0.4/tests/test_portal.py b/library/2.0.5/tests/test_portal.py similarity index 100% rename from library/2.0.4/tests/test_portal.py rename to library/2.0.5/tests/test_portal.py diff --git a/library/2.0.4/tests/test_ports.py b/library/2.0.5/tests/test_ports.py similarity index 100% rename from library/2.0.4/tests/test_ports.py rename to library/2.0.5/tests/test_ports.py diff --git a/library/2.0.4/tests/test_render.py b/library/2.0.5/tests/test_render.py similarity index 100% rename from library/2.0.4/tests/test_render.py rename to library/2.0.5/tests/test_render.py diff --git a/library/2.0.4/tests/test_resources.py b/library/2.0.5/tests/test_resources.py similarity index 100% rename from library/2.0.4/tests/test_resources.py rename to library/2.0.5/tests/test_resources.py diff --git a/library/2.0.4/tests/test_restart.py b/library/2.0.5/tests/test_restart.py similarity index 100% rename from library/2.0.4/tests/test_restart.py rename to library/2.0.5/tests/test_restart.py diff --git a/library/2.0.4/tests/test_volumes.py b/library/2.0.5/tests/test_volumes.py similarity index 100% rename from library/2.0.4/tests/test_volumes.py rename to library/2.0.5/tests/test_volumes.py diff --git a/library/2.0.4/validations.py b/library/2.0.5/validations.py similarity index 100% rename from library/2.0.4/validations.py rename to library/2.0.5/validations.py diff --git a/library/2.0.4/volume_mount.py b/library/2.0.5/volume_mount.py similarity index 100% rename from library/2.0.4/volume_mount.py rename to library/2.0.5/volume_mount.py diff --git a/library/2.0.4/volume_mount_types.py b/library/2.0.5/volume_mount_types.py similarity index 100% rename from library/2.0.4/volume_mount_types.py rename to library/2.0.5/volume_mount_types.py diff --git a/library/2.0.4/volume_sources.py b/library/2.0.5/volume_sources.py similarity index 100% rename from library/2.0.4/volume_sources.py rename to library/2.0.5/volume_sources.py diff --git a/library/2.0.4/volume_types.py b/library/2.0.5/volume_types.py similarity index 100% rename from library/2.0.4/volume_types.py rename to library/2.0.5/volume_types.py diff --git a/library/2.0.4/volumes.py b/library/2.0.5/volumes.py similarity index 100% rename from library/2.0.4/volumes.py rename to library/2.0.5/volumes.py diff --git a/library/hashes.yaml b/library/hashes.yaml index 524ecfe187..1ea6b75bb8 100644 --- a/library/hashes.yaml +++ b/library/hashes.yaml @@ -1,3 +1,3 @@ 0.0.1: f074617a82a86d2a6cc78a4c8a4296fc9d168e456f12713e50c696557b302133 1.1.5: 51332f2b032a0c473693458cd93daa96d56604241878e538c07cb85b8be66727 -2.0.4: 0e79e3390d3ea73649ee2ac05a4af9ed944a02e95289b5c7e2eb047d475a4651 +2.0.5: 0a65beb80aa50e867204d9c6720f8c841047d9e2a0e03d2c949534688c2832e1 From eec95a1586cc8424bd1316faf284217a148adad5 Mon Sep 17 00:00:00 2001 From: Stavros kois Date: Thu, 31 Oct 2024 18:52:02 +0200 Subject: [PATCH 9/9] rm --- library/2.0.5/__init__.py | 0 library/2.0.5/configs.py | 86 ---- library/2.0.5/container.py | 244 ---------- library/2.0.5/depends.py | 34 -- library/2.0.5/deploy.py | 24 - library/2.0.5/deps.py | 336 ------------- library/2.0.5/device.py | 31 -- library/2.0.5/devices.py | 45 -- library/2.0.5/dns.py | 79 --- library/2.0.5/environment.py | 106 ----- library/2.0.5/error.py | 4 - library/2.0.5/formatter.py | 26 - library/2.0.5/functions.py | 105 ---- library/2.0.5/healthcheck.py | 193 -------- library/2.0.5/labels.py | 37 -- library/2.0.5/notes.py | 70 --- library/2.0.5/portal.py | 22 - library/2.0.5/portals.py | 28 -- library/2.0.5/ports.py | 68 --- library/2.0.5/render.py | 86 ---- library/2.0.5/resources.py | 111 ----- library/2.0.5/restart.py | 25 - library/2.0.5/storage.py | 96 ---- library/2.0.5/tests/__init__.py | 0 library/2.0.5/tests/test_build_image.py | 49 -- library/2.0.5/tests/test_configs.py | 63 --- library/2.0.5/tests/test_container.py | 213 --------- library/2.0.5/tests/test_depends.py | 54 --- library/2.0.5/tests/test_deps.py | 311 ------------ library/2.0.5/tests/test_device.py | 92 ---- library/2.0.5/tests/test_dns.py | 64 --- library/2.0.5/tests/test_environment.py | 182 ------- library/2.0.5/tests/test_formatter.py | 13 - library/2.0.5/tests/test_functions.py | 65 --- library/2.0.5/tests/test_healthcheck.py | 187 -------- library/2.0.5/tests/test_labels.py | 88 ---- library/2.0.5/tests/test_notes.py | 213 --------- library/2.0.5/tests/test_portal.py | 75 --- library/2.0.5/tests/test_ports.py | 110 ----- library/2.0.5/tests/test_render.py | 37 -- library/2.0.5/tests/test_resources.py | 138 ------ library/2.0.5/tests/test_restart.py | 57 --- library/2.0.5/tests/test_volumes.py | 607 ------------------------ library/2.0.5/validations.py | 196 -------- library/2.0.5/volume_mount.py | 81 ---- library/2.0.5/volume_mount_types.py | 72 --- library/2.0.5/volume_sources.py | 106 ----- library/2.0.5/volume_types.py | 132 ------ library/2.0.5/volumes.py | 66 --- 49 files changed, 5127 deletions(-) delete mode 100644 library/2.0.5/__init__.py delete mode 100644 library/2.0.5/configs.py delete mode 100644 library/2.0.5/container.py delete mode 100644 library/2.0.5/depends.py delete mode 100644 library/2.0.5/deploy.py delete mode 100644 library/2.0.5/deps.py delete mode 100644 library/2.0.5/device.py delete mode 100644 library/2.0.5/devices.py delete mode 100644 library/2.0.5/dns.py delete mode 100644 library/2.0.5/environment.py delete mode 100644 library/2.0.5/error.py delete mode 100644 library/2.0.5/formatter.py delete mode 100644 library/2.0.5/functions.py delete mode 100644 library/2.0.5/healthcheck.py delete mode 100644 library/2.0.5/labels.py delete mode 100644 library/2.0.5/notes.py delete mode 100644 library/2.0.5/portal.py delete mode 100644 library/2.0.5/portals.py delete mode 100644 library/2.0.5/ports.py delete mode 100644 library/2.0.5/render.py delete mode 100644 library/2.0.5/resources.py delete mode 100644 library/2.0.5/restart.py delete mode 100644 library/2.0.5/storage.py delete mode 100644 library/2.0.5/tests/__init__.py delete mode 100644 library/2.0.5/tests/test_build_image.py delete mode 100644 library/2.0.5/tests/test_configs.py delete mode 100644 library/2.0.5/tests/test_container.py delete mode 100644 library/2.0.5/tests/test_depends.py delete mode 100644 library/2.0.5/tests/test_deps.py delete mode 100644 library/2.0.5/tests/test_device.py delete mode 100644 library/2.0.5/tests/test_dns.py delete mode 100644 library/2.0.5/tests/test_environment.py delete mode 100644 library/2.0.5/tests/test_formatter.py delete mode 100644 library/2.0.5/tests/test_functions.py delete mode 100644 library/2.0.5/tests/test_healthcheck.py delete mode 100644 library/2.0.5/tests/test_labels.py delete mode 100644 library/2.0.5/tests/test_notes.py delete mode 100644 library/2.0.5/tests/test_portal.py delete mode 100644 library/2.0.5/tests/test_ports.py delete mode 100644 library/2.0.5/tests/test_render.py delete mode 100644 library/2.0.5/tests/test_resources.py delete mode 100644 library/2.0.5/tests/test_restart.py delete mode 100644 library/2.0.5/tests/test_volumes.py delete mode 100644 library/2.0.5/validations.py delete mode 100644 library/2.0.5/volume_mount.py delete mode 100644 library/2.0.5/volume_mount_types.py delete mode 100644 library/2.0.5/volume_sources.py delete mode 100644 library/2.0.5/volume_types.py delete mode 100644 library/2.0.5/volumes.py diff --git a/library/2.0.5/__init__.py b/library/2.0.5/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/library/2.0.5/configs.py b/library/2.0.5/configs.py deleted file mode 100644 index b76f4b169c..0000000000 --- a/library/2.0.5/configs.py +++ /dev/null @@ -1,86 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .formatter import escape_dollar - from .validations import valid_octal_mode_or_raise, valid_fs_path_or_raise -except ImportError: - from error import RenderError - from formatter import escape_dollar - from validations import valid_octal_mode_or_raise, valid_fs_path_or_raise - - -class Configs: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._configs: dict[str, dict] = {} - - def add(self, name: str, data: str): - if not isinstance(data, str): - raise RenderError(f"Expected [data] to be a string, got [{type(data)}]") - - if name not in self._configs: - self._configs[name] = {"name": name, "data": data} - return - - if data == self._configs[name]["data"]: - return - - raise RenderError(f"Config [{name}] already added with different data") - - def has_configs(self): - return bool(self._configs) - - def render(self): - return { - c["name"]: {"content": escape_dollar(c["data"])} - for c in sorted(self._configs.values(), key=lambda c: c["name"]) - } - - -class ContainerConfigs: - def __init__(self, render_instance: "Render", configs: Configs): - self._render_instance = render_instance - self.top_level_configs: Configs = configs - self.container_configs: set[ContainerConfig] = set() - - def add(self, name: str, data: str, target: str, mode: str = ""): - self.top_level_configs.add(name, data) - - if target == "": - raise RenderError(f"Expected [target] to be set for config [{name}]") - if mode != "": - mode = valid_octal_mode_or_raise(mode) - - if target in [c.target for c in self.container_configs]: - raise RenderError(f"Target [{target}] already used for another config") - target = valid_fs_path_or_raise(target) - self.container_configs.add(ContainerConfig(self._render_instance, name, target, mode)) - - def has_configs(self): - return bool(self.container_configs) - - def render(self): - return [c.render() for c in sorted(self.container_configs, key=lambda c: c.source)] - - -class ContainerConfig: - def __init__(self, render_instance: "Render", source: str, target: str, mode: str): - self._render_instance = render_instance - self.source = source - self.target = target - self.mode = mode - - def render(self): - result: dict[str, str | int] = { - "source": self.source, - "target": self.target, - } - - if self.mode: - result["mode"] = int(self.mode, 8) - - return result diff --git a/library/2.0.5/container.py b/library/2.0.5/container.py deleted file mode 100644 index 8089be4f75..0000000000 --- a/library/2.0.5/container.py +++ /dev/null @@ -1,244 +0,0 @@ -from typing import Any, TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - from storage import IxStorage - -try: - from .configs import ContainerConfigs - from .depends import Depends - from .deploy import Deploy - from .devices import Devices - from .dns import Dns - from .environment import Environment - from .error import RenderError - from .formatter import escape_dollar, get_image_with_hashed_data - from .healthcheck import Healthcheck - from .labels import Labels - from .ports import Ports - from .restart import RestartPolicy - from .validations import valid_network_mode_or_raise, valid_cap_or_raise - from .storage import Storage -except ImportError: - from configs import ContainerConfigs - from depends import Depends - from deploy import Deploy - from devices import Devices - from dns import Dns - from environment import Environment - from error import RenderError - from formatter import escape_dollar, get_image_with_hashed_data - from healthcheck import Healthcheck - from labels import Labels - from ports import Ports - from restart import RestartPolicy - from validations import valid_network_mode_or_raise, valid_cap_or_raise - from storage import Storage - - -class Container: - def __init__(self, render_instance: "Render", name: str, image: str): - self._render_instance = render_instance - - self._name: str = name - self._image: str = self._resolve_image(image) # TODO: account for inline dockerfile - self._build_image: str = "" - self._user: str = "" - self._tty: bool = False - self._stdin_open: bool = False - self._hostname: str = "" - self._cap_drop: set[str] = set(["ALL"]) # Drop all capabilities by default and add caps granularly - self._cap_add: set[str] = set() - self._security_opt: set[str] = set(["no-new-privileges"]) - self._network_mode: str = "" - self._entrypoint: list[str] = [] - self._command: list[str] = [] - self._grace_period: int | None = None - self._storage: Storage = Storage(self._render_instance) - self.configs: ContainerConfigs = ContainerConfigs(self._render_instance, self._render_instance.configs) - self.deploy: Deploy = Deploy(self._render_instance) - self.networks: set[str] = set() - self.devices: Devices = Devices(self._render_instance) - self.environment: Environment = Environment(self._render_instance, self.deploy.resources) - self.dns: Dns = Dns(self._render_instance) - self.depends: Depends = Depends(self._render_instance) - self.healthcheck: Healthcheck = Healthcheck(self._render_instance) - # TODO: have a known labels dict in the config and parse it automatically - # at render time so all the containers are defined - self.labels: Labels = Labels(self._render_instance) - self.restart: RestartPolicy = RestartPolicy(self._render_instance) - self.ports: Ports = Ports(self._render_instance) - - self._auto_set_network_mode() - self._auto_add_labels() - - def _auto_set_network_mode(self): - if self._render_instance.values.get("network", {}).get("host_network", False): - self.set_network_mode("host") - - def _auto_add_labels(self): - labels = self._render_instance.values.get("labels", []) - if not labels: - return - - for label in labels: - containers = label.get("containers", []) - if not containers: - raise RenderError(f'Label [{label.get("key", "")}] must have at least one container') - - if self._name in containers: - self.labels.add_label(label["key"], label["value"]) - - def _resolve_image(self, image: str): - images = self._render_instance.values["images"] - if image not in images: - raise RenderError( - f"Image [{image}] not found in values. " f"Available images: [{', '.join(images.keys())}]" - ) - repo = images[image].get("repository", "") - tag = images[image].get("tag", "") - - if not repo: - raise RenderError(f"Repository not found for image [{image}]") - if not tag: - raise RenderError(f"Tag not found for image [{image}]") - - return f"{repo}:{tag}" - - def build_image(self, content: list[str | None]): - dockerfile = f"FROM {self._image}\n" - for line in content: - if not line: - continue - if line.startswith("FROM"): - # TODO: This will also block multi-stage builds - # We can revisit this later if we need it - raise RenderError( - "FROM cannot be used in build image. Define the base image when creating the container." - ) - dockerfile += line + "\n" - - self._build_image = dockerfile - self._image = get_image_with_hashed_data(self._image, dockerfile) - - def set_user(self, user: int, group: int): - for i in (user, group): - if not isinstance(i, int) or i < 0: - raise RenderError(f"User/Group [{i}] is not valid") - self._user = f"{user}:{group}" - - def set_tty(self, enabled: bool = False): - self._tty = enabled - - def set_stdin(self, enabled: bool = False): - self._stdin_open = enabled - - def set_hostname(self, hostname: str): - self._hostname = hostname - - def set_grace_period(self, grace_period: int): - if grace_period < 0: - raise RenderError(f"Grace period [{grace_period}] cannot be negative") - self._grace_period = grace_period - - def add_caps(self, caps: list[str]): - for c in caps: - if c in self._cap_add: - raise RenderError(f"Capability [{c}] already added") - self._cap_add.add(valid_cap_or_raise(c)) - - def add_security_opt(self, opt: str): - if opt in self._security_opt: - raise RenderError(f"Security Option [{opt}] already added") - self._security_opt.add(opt) - - def remove_security_opt(self, opt: str): - self._security_opt.remove(opt) - - def set_network_mode(self, mode: str): - self._network_mode = valid_network_mode_or_raise(mode, self._render_instance.container_names()) - - def set_entrypoint(self, entrypoint: list[str]): - self._entrypoint = [escape_dollar(e) for e in entrypoint] - - def set_command(self, command: list[str]): - self._command = [escape_dollar(e) for e in command] - - def add_storage(self, mount_path: str, config: "IxStorage"): - self._storage.add(mount_path, config) - - def render(self) -> dict[str, Any]: - if self._network_mode and self.networks: - raise RenderError("Cannot set both [network_mode] and [networks]") - - result = { - "image": self._image, - "tty": self._tty, - "stdin_open": self._stdin_open, - "restart": self.restart.render(), - "cap_drop": sorted(self._cap_drop), - "healthcheck": self.healthcheck.render(), - } - - if self._hostname: - result["hostname"] = self._hostname - - if self._build_image: - result["build"] = {"tags": [self._image], "dockerfile_inline": self._build_image} - - if self.configs.has_configs(): - result["configs"] = self.configs.render() - - if self._grace_period is not None: - result["stop_grace_period"] = self._grace_period - - if self._user: - result["user"] = self._user - - if self._cap_add: - result["cap_add"] = sorted(self._cap_add) - - if self._security_opt: - result["security_opt"] = sorted(self._security_opt) - - if self._network_mode: - result["network_mode"] = self._network_mode - - if self._network_mode != "host": - if self.ports.has_ports(): - result["ports"] = self.ports.render() - - if self._entrypoint: - result["entrypoint"] = self._entrypoint - - if self._command: - result["command"] = self._command - - if self.devices.has_devices(): - result["devices"] = self.devices.render() - - if self.deploy.has_deploy(): - result["deploy"] = self.deploy.render() - - if self.environment.has_variables(): - result["environment"] = self.environment.render() - - if self.labels.has_labels(): - result["labels"] = self.labels.render() - - if self.dns.has_dns_nameservers(): - result["dns"] = self.dns.render_dns_nameservers() - - if self.dns.has_dns_searches(): - result["dns_search"] = self.dns.render_dns_searches() - - if self.dns.has_dns_opts(): - result["dns_opt"] = self.dns.render_dns_opts() - - if self.depends.has_dependencies(): - result["depends_on"] = self.depends.render() - - if self._storage.has_mounts(): - result["volumes"] = self._storage.render() - - return result diff --git a/library/2.0.5/depends.py b/library/2.0.5/depends.py deleted file mode 100644 index 4e057cf085..0000000000 --- a/library/2.0.5/depends.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .validations import valid_depend_condition_or_raise -except ImportError: - from error import RenderError - from validations import valid_depend_condition_or_raise - - -class Depends: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._dependencies: dict[str, str] = {} - - def add_dependency(self, name: str, condition: str): - condition = valid_depend_condition_or_raise(condition) - if name in self._dependencies.keys(): - raise RenderError(f"Dependency [{name}] already added") - if name not in self._render_instance.container_names(): - raise RenderError( - f"Dependency [{name}] not found in defined containers. " - f"Available containers: [{', '.join(self._render_instance.container_names())}]" - ) - self._dependencies[name] = condition - - def has_dependencies(self): - return len(self._dependencies) > 0 - - def render(self): - return {d: {"condition": c} for d, c in self._dependencies.items()} diff --git a/library/2.0.5/deploy.py b/library/2.0.5/deploy.py deleted file mode 100644 index 894dbc643b..0000000000 --- a/library/2.0.5/deploy.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .resources import Resources -except ImportError: - from resources import Resources - - -class Deploy: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self.resources: Resources = Resources(self._render_instance) - - def has_deploy(self): - return self.resources.has_resources() - - def render(self): - if self.resources.has_resources(): - return {"resources": self.resources.render()} - - return {} diff --git a/library/2.0.5/deps.py b/library/2.0.5/deps.py deleted file mode 100644 index c8207aec3d..0000000000 --- a/library/2.0.5/deps.py +++ /dev/null @@ -1,336 +0,0 @@ -import os -import json -from typing import TYPE_CHECKING, TypedDict, NotRequired - -if TYPE_CHECKING: - from render import Render - from storage import IxStorage - -try: - from .error import RenderError - from .validations import valid_port_or_raise, valid_fs_path_or_raise, valid_octal_mode_or_raise -except ImportError: - from error import RenderError - from validations import valid_port_or_raise, valid_fs_path_or_raise, valid_octal_mode_or_raise - - -class PostgresConfig(TypedDict): - user: str - password: str - database: str - port: NotRequired[int] - volume: "IxStorage" - - -class MariadbConfig(TypedDict): - user: str - password: str - database: str - root_password: NotRequired[str] - port: NotRequired[int] - auto_upgrade: NotRequired[bool] - volume: "IxStorage" - - -class RedisConfig(TypedDict): - password: str - port: NotRequired[int] - volume: "IxStorage" - - -class PermsContainer: - def __init__(self, render_instance: "Render", name: str): - self._render_instance = render_instance - self._name = name - self.actions: set[str] = set() - self.parsed_configs: list[dict] = [] - - def add_or_skip_action(self, identifier: str, volume_config: "IxStorage", action_config: dict): - identifier = self.normalize_identifier_for_path(identifier) - if identifier in self.actions: - raise RenderError(f"Action with id [{identifier}] already used for another permission action") - - parsed_action = self.parse_action(identifier, volume_config, action_config) - if parsed_action: - self.parsed_configs.append(parsed_action) - self.actions.add(identifier) - - def parse_action(self, identifier: str, volume_config: "IxStorage", action_config: dict): - valid_modes = ["always", "check"] - mode = action_config.get("mode", "check") - uid = action_config.get("uid", None) - gid = action_config.get("gid", None) - chmod = action_config.get("chmod", None) - mount_path = os.path.join("/mnt/permission", identifier) - - auto_perms = volume_config.get("auto_permissions", False) - is_temporary = False - # If it is a temporary volume, we force auto permissions - # and set is_temporary to True, so it will be cleaned up - if volume_config.get("type", "") == "temporary": - is_temporary = True - auto_perms = True - - # Skip ACL enabled volumes - if volume_config.get("ix_volume_config", {}).get("acl_enable", False): - return None - if volume_config.get("host_path_config", {}).get("acl_enable", False): - return None - - # On ix_volumes, we set auto permissions with "check" mode - if volume_config.get("ix_volume_config", {}): - auto_perms = True - mode = "check" - - # Skip volumes that do not have auto permissions set - if not auto_perms: - return None - - if mode not in valid_modes: - raise RenderError(f"Expected [mode] to be one of [{', '.join(valid_modes)}], got [{mode}]") - if not isinstance(uid, int) or not isinstance(gid, int): - raise RenderError("Expected [uid] and [gid] to be set when [auto_permissions] is enabled") - if chmod is not None: - chmod = valid_octal_mode_or_raise(chmod) - - mount_path = valid_fs_path_or_raise(mount_path) - return { - "mount_path": mount_path, - "volume_config": volume_config, - "action_data": { - "mount_path": mount_path, - "is_temporary": is_temporary, - "identifier": identifier, - "mode": mode, - "uid": uid, - "gid": gid, - "chmod": chmod, - }, - } - - def normalize_identifier_for_path(self, identifier: str): - return identifier.rstrip("/").lstrip("/").lower().replace("/", "_").replace(".", "-").replace(" ", "-") - - def has_actions(self): - return bool(self.actions) - - def activate(self): - if len(self.parsed_configs) != len(self.actions): - raise RenderError("Number of actions and parsed configs does not match") - - if not self.has_actions(): - raise RenderError("No actions added. Check if there are actions before activating") - - # Add the container and set it up - c = self._render_instance.add_container(self._name, "python_permissions_image") - c.set_user(0, 0) - c.add_caps(["CHOWN", "FOWNER", "DAC_OVERRIDE"]) - - # Don't attach any devices - c.deploy.resources.remove_devices() - c.deploy.resources.set_profile("medium") - c.restart.set_policy("on-failure", maximum_retry_count=1) - c.healthcheck.disable() - - c.set_entrypoint(["python3", "/script/run.py"]) - script = "#!/usr/bin/env python3\n" - script += get_script() - c.configs.add("permissions_run_script", script, "/script/run.py", "0700") - - actions_data: list[dict] = [] - for parsed in self.parsed_configs: - c.add_storage(parsed["mount_path"], parsed["volume_config"]) - actions_data.append(parsed["action_data"]) - - actions_data_json = json.dumps(actions_data) - c.configs.add("permissions_actions_data", actions_data_json, "/script/actions.json", "0500") - - -def get_script(): - return """ -import os -import json -import time -import shutil - -with open("/script/actions.json", "r") as f: - actions_data = json.load(f) - -if not actions_data: - # If this script is called, there should be actions data - raise ValueError("No actions data found") - -def fix_perms(path, chmod): - print(f"Changing permissions to {chmod} on: [{path}]") - os.chmod(path, int(chmod, 8)) - print("Permissions after changes:") - print_chmod_stat() - -def fix_owner(path, uid, gid): - print(f"Changing ownership to {uid}:{gid} on: [{path}]") - os.chown(path, uid, gid) - print("Ownership after changes:") - print_chown_stat() - -def print_chown_stat(): - curr_stat = os.stat(action["mount_path"]) - print(f"Ownership: [{curr_stat.st_uid}:{curr_stat.st_gid}]") - -def print_chmod_stat(): - curr_stat = os.stat(action["mount_path"]) - print(f"Permissions: [{oct(curr_stat.st_mode)[3:]}]") - -def print_chown_diff(curr_stat, uid, gid): - print(f"Ownership: wanted [{uid}:{gid}], got [{curr_stat.st_uid}:{curr_stat.st_gid}].") - -def print_chmod_diff(curr_stat, mode): - print(f"Permissions: wanted [{mode}], got [{oct(curr_stat.st_mode)[3:]}].") - -def perform_action(action): - start_time = time.time() - print(f"=== Applying configuration on volume with identifier [{action['identifier']}] ===") - - if not os.path.isdir(action["mount_path"]): - print(f"Path [{action['mount_path']}] is not a directory, skipping...") - return - - if action["is_temporary"]: - print(f"Path [{action['mount_path']}] is a temporary directory, ensuring it is empty...") - for item in os.listdir(action["mount_path"]): - item_path = os.path.join(action["mount_path"], item) - - # Exclude the safe directory, where we can use to mount files temporarily - if os.path.basename(item_path) == "ix-safe": - continue - if os.path.isdir(item_path): - shutil.rmtree(item_path) - else: - os.remove(item_path) - - if not action["is_temporary"] and os.listdir(action["mount_path"]): - print(f"Path [{action['mount_path']}] is not empty, skipping...") - return - - print(f"Current Ownership and Permissions on [{action['mount_path']}]:") - curr_stat = os.stat(action["mount_path"]) - print_chown_diff(curr_stat, action["uid"], action["gid"]) - print_chmod_diff(curr_stat, action["chmod"]) - print("---") - - if action["mode"] == "always": - fix_owner(action["mount_path"], action["uid"], action["gid"]) - if not action["chmod"]: - print("Skipping permissions check, chmod is falsy") - else: - fix_perms(action["mount_path"], action["chmod"]) - return - - elif action["mode"] == "check": - if curr_stat.st_uid != action["uid"] or curr_stat.st_gid != action["gid"]: - print("Ownership is incorrect. Fixing...") - fix_owner(action["mount_path"], action["uid"], action["gid"]) - else: - print("Ownership is correct. Skipping...") - - if not action["chmod"]: - print("Skipping permissions check, chmod is falsy") - else: - if oct(curr_stat.st_mode)[3:] != action["chmod"]: - print("Permissions are incorrect. Fixing...") - fix_perms(action["mount_path"], action["chmod"]) - else: - print("Permissions are correct. Skipping...") - - print(f"Time taken: {(time.time() - start_time) * 1000:.2f}ms") - print(f"=== Finished applying configuration on volume with identifier [{action['identifier']}] ==") - print() - -if __name__ == "__main__": - start_time = time.time() - for action in actions_data: - perform_action(action) - print(f"Total time taken: {(time.time() - start_time) * 1000:.2f}ms") -""" - - -class Deps: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - - def perms(self, name: str): - return PermsContainer(self._render_instance, name) - - def postgres(self, name: str, image: str, config: PostgresConfig, perms_instance: PermsContainer): - for key in ("user", "password", "database", "volume"): - if key not in config: - raise RenderError(f"Expected [{key}] to be set for postgres") - - port = valid_port_or_raise(config.get("port") or 5432) - - c = self._render_instance.add_container(name, image) - c.set_user(999, 999) - c.healthcheck.set_test("postgres") - c.deploy.resources.remove_devices() - - c.add_storage("/var/lib/postgresql/data", config["volume"]) - perms_instance.add_or_skip_action("postgres_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}) - - c.environment.add_env("POSTGRES_USER", config["user"]) - c.environment.add_env("POSTGRES_PASSWORD", config["password"]) - c.environment.add_env("POSTGRES_DB", config["database"]) - c.environment.add_env("POSTGRES_PORT", port) - - # Return container for further configuration - # For example: c.depends.add_dependency("other_container", "service_started") - return c - - def redis(self, name: str, image: str, config: RedisConfig, perms_instance: PermsContainer): - for key in ("password", "volume"): - if key not in config: - raise RenderError(f"Expected [{key}] to be set for redis") - - port = valid_port_or_raise(config.get("port") or 6379) - - c = self._render_instance.add_container(name, image) - c.set_user(1001, 0) - c.healthcheck.set_test("redis") - c.deploy.resources.remove_devices() - - c.add_storage("/bitnami/redis/data", config["volume"]) - perms_instance.add_or_skip_action("redis_data", config["volume"], {"uid": 1001, "gid": 0, "mode": "check"}) - - c.environment.add_env("ALLOW_EMPTY_PASSWORD", "no") - c.environment.add_env("REDIS_PASSWORD", config["password"]) - c.environment.add_env("REDIS_PORT_NUMBER", port) - - # Return container for further configuration - # For example: c.depends.add_dependency("other_container", "service_started") - return c - - def mariadb(self, name: str, image: str, config: MariadbConfig, perms_instance: PermsContainer): - for key in ("user", "password", "database", "volume"): - if key not in config: - raise RenderError(f"Expected [{key}] to be set for mariadb") - - port = valid_port_or_raise(config.get("port") or 3306) - root_password = config.get("root_password") or config["password"] - auto_upgrade = config.get("auto_upgrade", True) - - c = self._render_instance.add_container(name, image) - c.set_user(999, 999) - c.healthcheck.set_test("mariadb") - c.deploy.resources.remove_devices() - - c.add_storage("/var/lib/mysql", config["volume"]) - perms_instance.add_or_skip_action("mariadb_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}) - - c.environment.add_env("MARIADB_USER", config["user"]) - c.environment.add_env("MARIADB_PASSWORD", config["password"]) - c.environment.add_env("MARIADB_ROOT_PASSWORD", root_password) - c.environment.add_env("MARIADB_DATABASE", config["database"]) - c.environment.add_env("MARIADB_AUTO_UPGRADE", str(auto_upgrade).lower()) - c.set_command(["--port", str(port)]) - - # Return container for further configuration - # For example: c.depends.add_dependency("other_container", "service_started") - return c diff --git a/library/2.0.5/device.py b/library/2.0.5/device.py deleted file mode 100644 index bfe97097cb..0000000000 --- a/library/2.0.5/device.py +++ /dev/null @@ -1,31 +0,0 @@ -try: - from .error import RenderError - from .validations import valid_fs_path_or_raise, allowed_device_or_raise, valid_cgroup_perm_or_raise -except ImportError: - from error import RenderError - from validations import valid_fs_path_or_raise, allowed_device_or_raise, valid_cgroup_perm_or_raise - - -class Device: - def __init__(self, host_device: str, container_device: str, cgroup_perm: str = "", allow_disallowed=False): - hd = valid_fs_path_or_raise(host_device.rstrip("/")) - cd = valid_fs_path_or_raise(container_device.rstrip("/")) - if not hd or not cd: - raise RenderError( - "Expected [host_device] and [container_device] to be set. " - f"Got host_device [{host_device}] and container_device [{container_device}]" - ) - - cgroup_perm = valid_cgroup_perm_or_raise(cgroup_perm) - if not allow_disallowed: - hd = allowed_device_or_raise(hd) - - self.cgroup_perm: str = cgroup_perm - self.host_device: str = hd - self.container_device: str = cd - - def render(self): - result = f"{self.host_device}:{self.container_device}" - if self.cgroup_perm: - result += f":{self.cgroup_perm}" - return result diff --git a/library/2.0.5/devices.py b/library/2.0.5/devices.py deleted file mode 100644 index 5cfb84a899..0000000000 --- a/library/2.0.5/devices.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .device import Device -except ImportError: - from error import RenderError - from device import Device - - -class Devices: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._devices: set[Device] = set() - - # Tracks all container device paths to make sure they are not duplicated - self._container_device_paths: set[str] = set() - # Scan values for devices we should automatically add - # for example /dev/dri for gpus - self._auto_add_devices_from_values() - - def _auto_add_devices_from_values(self): - resources = self._render_instance.values.get("resources", {}) - - if resources.get("gpus", {}).get("use_all_gpus", False): - self.add_device("/dev/dri", "/dev/dri", allow_disallowed=True) - self._container_device_paths.add("/dev/dri") - - def add_device(self, host_device: str, container_device: str, cgroup_perm: str = "", allow_disallowed=False): - # Host device can be mapped to multiple container devices, - # so we only make sure container devices are not duplicated - if container_device in self._container_device_paths: - raise RenderError(f"Device with container path [{container_device}] already added") - - self._devices.add(Device(host_device, container_device, cgroup_perm, allow_disallowed)) - self._container_device_paths.add(container_device) - - def has_devices(self): - return len(self._devices) > 0 - - def render(self) -> list[str]: - return sorted([d.render() for d in self._devices]) diff --git a/library/2.0.5/dns.py b/library/2.0.5/dns.py deleted file mode 100644 index d3ae7b19fa..0000000000 --- a/library/2.0.5/dns.py +++ /dev/null @@ -1,79 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .validations import allowed_dns_opt_or_raise -except ImportError: - from error import RenderError - from validations import allowed_dns_opt_or_raise - - -class Dns: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._dns_options: set[str] = set() - self._dns_searches: set[str] = set() - self._dns_nameservers: set[str] = set() - - self._auto_add_dns_opts_from_values() - self._auto_add_dns_searches_from_values() - self._auto_add_dns_nameservers_from_values() - - def _get_dns_opt_keys(self): - return [self._get_key_from_opt(opt) for opt in self._dns_options] - - def _get_key_from_opt(self, opt): - return opt.split(":")[0] - - def _auto_add_dns_opts_from_values(self): - values = self._render_instance.values - for dns_opt in values.get("network", {}).get("dns_opts", []): - self.add_dns_opt(dns_opt) - - def _auto_add_dns_searches_from_values(self): - values = self._render_instance.values - for dns_search in values.get("network", {}).get("dns_searches", []): - self.add_dns_search(dns_search) - - def _auto_add_dns_nameservers_from_values(self): - values = self._render_instance.values - for dns_nameserver in values.get("network", {}).get("dns_nameservers", []): - self.add_dns_nameserver(dns_nameserver) - - def add_dns_search(self, dns_search): - if dns_search in self._dns_searches: - raise RenderError(f"DNS Search [{dns_search}] already added") - self._dns_searches.add(dns_search) - - def add_dns_nameserver(self, dns_nameserver): - if dns_nameserver in self._dns_nameservers: - raise RenderError(f"DNS Nameserver [{dns_nameserver}] already added") - self._dns_nameservers.add(dns_nameserver) - - def add_dns_opt(self, dns_opt): - # eg attempts:3 - key = allowed_dns_opt_or_raise(self._get_key_from_opt(dns_opt)) - if key in self._get_dns_opt_keys(): - raise RenderError(f"DNS Option [{key}] already added") - self._dns_options.add(dns_opt) - - def has_dns_opts(self): - return len(self._dns_options) > 0 - - def has_dns_searches(self): - return len(self._dns_searches) > 0 - - def has_dns_nameservers(self): - return len(self._dns_nameservers) > 0 - - def render_dns_searches(self): - return sorted(self._dns_searches) - - def render_dns_opts(self): - return sorted(self._dns_options) - - def render_dns_nameservers(self): - return sorted(self._dns_nameservers) diff --git a/library/2.0.5/environment.py b/library/2.0.5/environment.py deleted file mode 100644 index 0ae3f4cc1b..0000000000 --- a/library/2.0.5/environment.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Any, TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render -try: - from .error import RenderError - from .formatter import escape_dollar - from .resources import Resources -except ImportError: - from error import RenderError - from formatter import escape_dollar - from resources import Resources - - -class Environment: - def __init__(self, render_instance: "Render", resources: Resources): - self._render_instance = render_instance - self._resources = resources - # Stores variables that user defined - self._user_vars: dict[str, Any] = {} - # Stores variables that are automatically added (based on values) - self._auto_variables: dict[str, Any] = {} - # Stores variables that are added by the application developer - self._app_dev_variables: dict[str, Any] = {} - - self._auto_add_variables_from_values() - - def _auto_add_variables_from_values(self): - self._add_generic_variables() - self._add_nvidia_variables() - - def _add_generic_variables(self): - self._auto_variables["TZ"] = self._render_instance.values.get("TZ", "Etc/UTC") - run_as = self._render_instance.values.get("run_as", {}) - user = run_as.get("user") - group = run_as.get("group") - if user: - self._auto_variables["PUID"] = user - self._auto_variables["UID"] = user - self._auto_variables["USER_ID"] = user - if group: - self._auto_variables["PGID"] = group - self._auto_variables["GID"] = group - self._auto_variables["GROUP_ID"] = group - - def _add_nvidia_variables(self): - if self._resources._nvidia_ids: - self._auto_variables["NVIDIA_DRIVER_CAPABILITIES"] = "all" - self._auto_variables["NVIDIA_VISIBLE_DEVICES"] = ",".join(sorted(self._resources._nvidia_ids)) - else: - self._auto_variables["NVIDIA_VISIBLE_DEVICES"] = "void" - - def _format_value(self, v: Any) -> str: - value = str(v) - - # str(bool) returns "True" or "False", - # but we want "true" or "false" - if isinstance(v, bool): - value = value.lower() - return value - - def add_env(self, name: str, value: Any): - if not name: - raise RenderError(f"Environment variable name cannot be empty. [{name}]") - if name in self._app_dev_variables.keys(): - raise RenderError( - f"Found duplicate environment variable [{name}] in application developer environment variables." - ) - self._app_dev_variables[name] = value - - def add_user_envs(self, user_env: list[dict]): - for item in user_env: - if not item.get("name"): - raise RenderError(f"Environment variable name cannot be empty. [{item}]") - if item["name"] in self._user_vars.keys(): - raise RenderError( - f"Found duplicate environment variable [{item['name']}] in user environment variables." - ) - self._user_vars[item["name"]] = item.get("value") - - def has_variables(self): - return len(self._auto_variables) > 0 or len(self._user_vars) > 0 or len(self._app_dev_variables) > 0 - - def render(self): - result: dict[str, str] = {} - - # Add envs from auto variables - result.update({k: self._format_value(v) for k, v in self._auto_variables.items()}) - - # Track defined keys for faster lookup - defined_keys = set(result.keys()) - - # Add envs from application developer (prohibit overwriting auto variables) - for k, v in self._app_dev_variables.items(): - if k in defined_keys: - raise RenderError(f"Environment variable [{k}] is already defined automatically from the library.") - result[k] = self._format_value(v) - defined_keys.add(k) - - # Add envs from user (prohibit overwriting app developer envs and auto variables) - for k, v in self._user_vars.items(): - if k in defined_keys: - raise RenderError(f"Environment variable [{k}] is already defined from the application developer.") - result[k] = self._format_value(v) - - return {k: escape_dollar(v) for k, v in result.items()} diff --git a/library/2.0.5/error.py b/library/2.0.5/error.py deleted file mode 100644 index aef48d3b02..0000000000 --- a/library/2.0.5/error.py +++ /dev/null @@ -1,4 +0,0 @@ -class RenderError(Exception): - """Base class for exceptions in this module.""" - - pass diff --git a/library/2.0.5/formatter.py b/library/2.0.5/formatter.py deleted file mode 100644 index 24e882f47a..0000000000 --- a/library/2.0.5/formatter.py +++ /dev/null @@ -1,26 +0,0 @@ -import json -import hashlib - - -def escape_dollar(text: str) -> str: - return text.replace("$", "$$") - - -def get_hashed_name_for_volume(prefix: str, config: dict): - config_hash = hashlib.sha256(json.dumps(config).encode("utf-8")).hexdigest() - return f"{prefix}_{config_hash}" - - -def get_hash_with_prefix(prefix: str, data: str): - return f"{prefix}_{hashlib.sha256(data.encode('utf-8')).hexdigest()}" - - -def merge_dicts_no_overwrite(dict1, dict2): - overlapping_keys = dict1.keys() & dict2.keys() - if overlapping_keys: - raise ValueError(f"Merging of dicts failed. Overlapping keys: {overlapping_keys}") - return {**dict1, **dict2} - - -def get_image_with_hashed_data(image: str, data: str): - return get_hash_with_prefix(f"ix-{image}", data) diff --git a/library/2.0.5/functions.py b/library/2.0.5/functions.py deleted file mode 100644 index adcf9520b0..0000000000 --- a/library/2.0.5/functions.py +++ /dev/null @@ -1,105 +0,0 @@ -import re -import bcrypt -import secrets -from base64 import b64encode -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError -except ImportError: - from error import RenderError - - -class Functions: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - - def _bcrypt_hash(self, password): - hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") - return hashed - - def _htpasswd(self, username, password): - hashed = self._bcrypt_hash(password) - return username + ":" + hashed - - def _secure_string(self, length): - return secrets.token_urlsafe(length) - - def _basic_auth(self, username, password): - return b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8") - - def _basic_auth_header(self, username, password): - return f"Basic {self._basic_auth(username, password)}" - - def _fail(self, message): - raise RenderError(message) - - def _camel_case(self, string): - return string.title() - - def _auto_cast(self, value): - try: - return int(value) - except ValueError: - pass - - try: - return float(value) - except ValueError: - pass - - if value.lower() in ["true", "false"]: - return value.lower() == "true" - - return value - - def _match_regex(self, value, regex): - if not re.match(regex, value): - return False - return True - - def _must_match_regex(self, value, regex): - if not self._match_regex(value, regex): - raise RenderError(f"Expected [{value}] to match [{regex}]") - return value - - def _is_boolean(self, string): - return string.lower() in ["true", "false"] - - def _is_number(self, string): - try: - float(string) - return True - except ValueError: - return False - - def _copy_dict(self, dict): - return dict.copy() - - def _merge_dicts(self, *dicts): - merged_dict = {} - for dictionary in dicts: - merged_dict.update(dictionary) - return merged_dict - - def func_map(self): - # TODO: Check what is no longer used and remove - return { - "auto_cast": self._auto_cast, - "basic_auth_header": self._basic_auth_header, - "basic_auth": self._basic_auth, - "bcrypt_hash": self._bcrypt_hash, - "camel_case": self._camel_case, - "copy_dict": self._copy_dict, - "fail": self._fail, - "htpasswd": self._htpasswd, - "is_boolean": self._is_boolean, - "is_number": self._is_number, - "match_regex": self._match_regex, - "merge_dicts": self._merge_dicts, - "must_match_regex": self._must_match_regex, - "secure_string": self._secure_string, - } diff --git a/library/2.0.5/healthcheck.py b/library/2.0.5/healthcheck.py deleted file mode 100644 index a54f3f3133..0000000000 --- a/library/2.0.5/healthcheck.py +++ /dev/null @@ -1,193 +0,0 @@ -from typing import Any, TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .formatter import escape_dollar - from .validations import valid_http_path_or_raise -except ImportError: - from error import RenderError - from formatter import escape_dollar - from validations import valid_http_path_or_raise - - -class Healthcheck: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._test: str | list[str] = "" - self._interval_sec: int = 10 - self._timeout_sec: int = 5 - self._retries: int = 30 - self._start_period_sec: int = 10 - self._disabled: bool = False - - def _get_test(self): - if isinstance(self._test, str): - return escape_dollar(self._test) - - return [escape_dollar(t) for t in self._test] - - def disable(self): - self._disabled = True - - def set_custom_test(self, test: str | list[str]): - if self._disabled: - raise RenderError("Cannot set custom test when healthcheck is disabled") - self._test = test - - def set_test(self, variant: str, config: dict | None = None): - config = config or {} - self.set_custom_test(test_mapping(variant, config)) - - def set_interval(self, interval: int): - self._interval_sec = interval - - def set_timeout(self, timeout: int): - self._timeout_sec = timeout - - def set_retries(self, retries: int): - self._retries = retries - - def set_start_period(self, start_period: int): - self._start_period_sec = start_period - - def render(self): - if self._disabled: - return {"disable": True} - - if not self._test: - raise RenderError("Healthcheck test is not set") - - return { - "test": self._get_test(), - "interval": f"{self._interval_sec}s", - "timeout": f"{self._timeout_sec}s", - "retries": self._retries, - "start_period": f"{self._start_period_sec}s", - } - - -def test_mapping(variant: str, config: dict | None = None) -> str: - config = config or {} - tests = { - "curl": curl_test, - "wget": wget_test, - "http": http_test, - "netcat": netcat_test, - "tcp": tcp_test, - "redis": redis_test, - "postgres": postgres_test, - "mariadb": mariadb_test, - } - - if variant not in tests: - raise RenderError(f"Test variant [{variant}] is not valid. Valid options are: [{', '.join(tests.keys())}]") - - return tests[variant](config) - - -def get_key(config: dict, key: str, default: Any, required: bool): - if not config.get(key): - if not required: - return default - raise RenderError(f"Expected [{key}] to be set") - return config[key] - - -def curl_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", None, True) - path = valid_http_path_or_raise(get_key(config, "path", "/", False)) - scheme = get_key(config, "scheme", "http", False) - host = get_key(config, "host", "127.0.0.1", False) - headers = get_key(config, "headers", [], False) - - opts = [] - if scheme == "https": - opts.append("--insecure") - - for header in headers: - if not header[0] or not header[1]: - raise RenderError("Expected [header] to be a list of two items for curl test") - opts.append(f'--header "{header[0]}: {header[1]}"') - - cmd = "curl --silent --output /dev/null --show-error --fail" - if opts: - cmd += f" {' '.join(opts)}" - cmd += f" {scheme}://{host}:{port}{path}" - return cmd - - -def wget_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", None, True) - path = valid_http_path_or_raise(get_key(config, "path", "/", False)) - scheme = get_key(config, "scheme", "http", False) - host = get_key(config, "host", "127.0.0.1", False) - headers = get_key(config, "headers", [], False) - - opts = [] - if scheme == "https": - opts.append("--no-check-certificate") - - for header in headers: - if not header[0] or not header[1]: - raise RenderError("Expected [header] to be a list of two items for wget test") - opts.append(f'--header "{header[0]}: {header[1]}"') - - cmd = "wget --spider --quiet" - if opts: - cmd += f" {' '.join(opts)}" - cmd += f" {scheme}://{host}:{port}{path}" - return cmd - - -def http_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", None, True) - path = valid_http_path_or_raise(get_key(config, "path", "/", False)) - host = get_key(config, "host", "127.0.0.1", False) - - return f"""/bin/bash -c 'exec {{hc_fd}}<>/dev/tcp/{host}/{port} && echo -e "GET {path} HTTP/1.1\\r\\nHost: {host}\\r\\nConnection: close\\r\\n\\r\\n" >&${{hc_fd}} && cat <&${{hc_fd}} | grep -q "200 OK"'""" # noqa - - -def netcat_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", None, True) - host = get_key(config, "host", "127.0.0.1", False) - - return f"nc -z -w 1 {host} {port}" - - -def tcp_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", None, True) - host = get_key(config, "host", "127.0.0.1", False) - - return f"timeout 1 bash -c 'cat < /dev/null > /dev/tcp/{host}/{port}'" - - -def redis_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", 6379, False) - host = get_key(config, "host", "127.0.0.1", False) - - return f"redis-cli -h {host} -p {port} -a $REDIS_PASSWORD ping | grep -q PONG" - - -def postgres_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", 5432, False) - host = get_key(config, "host", "127.0.0.1", False) - - return f"pg_isready -h {host} -p {port} -U $POSTGRES_USER -d $POSTGRES_DB" - - -def mariadb_test(config: dict) -> str: - config = config or {} - port = get_key(config, "port", 3306, False) - host = get_key(config, "host", "127.0.0.1", False) - - return f"mariadb-admin --user=root --host={host} --port={port} --password=$MARIADB_ROOT_PASSWORD ping" diff --git a/library/2.0.5/labels.py b/library/2.0.5/labels.py deleted file mode 100644 index f1e667ba00..0000000000 --- a/library/2.0.5/labels.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .formatter import escape_dollar -except ImportError: - from error import RenderError - from formatter import escape_dollar - - -class Labels: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._labels: dict[str, str] = {} - - def add_label(self, key: str, value: str): - if not key: - raise RenderError("Labels must have a key") - - if key.startswith("com.docker.compose"): - raise RenderError(f"Label [{key}] cannot start with [com.docker.compose] as it is reserved") - - if key in self._labels.keys(): - raise RenderError(f"Label [{key}] already added") - - self._labels[key] = escape_dollar(str(value)) - - def has_labels(self) -> bool: - return bool(self._labels) - - def render(self) -> dict[str, str]: - if not self.has_labels(): - return {} - return {label: value for label, value in sorted(self._labels.items())} diff --git a/library/2.0.5/notes.py b/library/2.0.5/notes.py deleted file mode 100644 index 4adc50c3d8..0000000000 --- a/library/2.0.5/notes.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - - -class Notes: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._app_name: str = "" - self._warnings: list[str] = [] - self._deprecations: list[str] = [] - self._header: str = "" - self._body: str = "" - self._footer: str = "" - - self._auto_set_app_name() - self._auto_set_header() - self._auto_set_footer() - - def _auto_set_app_name(self): - app_name = self._render_instance.values.get("ix_context", {}).get("app_metadata", {}).get("name", "") - self._app_name = app_name or "" - - def _auto_set_header(self): - head = "# Welcome to TrueNAS SCALE\n\n" - head += f"Thank you for installing {self._app_name}!\n\n" - self._header = head - - def _auto_set_footer(self): - footer = "## Documentation\n\n" - footer += f"Documentation for {self._app_name} can be found at https://www.truenas.com/docs.\n\n" - footer += "## Bug reports\n\n" - footer += "If you find a bug in this app, please file an issue at\n" - footer += "https://ixsystems.atlassian.net or https://github.com/truenas/apps\n\n" - footer += "## Feature requests or improvements\n\n" - footer += "If you find a feature request for this app, please file an issue at\n" - footer += "https://ixsystems.atlassian.net or https://github.com/truenas/apps\n" - self._footer = footer - - def add_warning(self, warning: str): - self._warnings.append(warning) - - def add_deprecation(self, deprecation: str): - self._deprecations.append(deprecation) - - def set_body(self, body: str): - self._body = body - - def render(self): - result = self._header - - if self._warnings: - result += "## Warnings\n\n" - for warning in self._warnings: - result += f"- {warning}\n" - result += "\n" - - if self._deprecations: - result += "## Deprecations\n\n" - for deprecation in self._deprecations: - result += f"- {deprecation}\n" - result += "\n" - - if self._body: - result += self._body.strip() + "\n\n" - - result += self._footer - - return result diff --git a/library/2.0.5/portal.py b/library/2.0.5/portal.py deleted file mode 100644 index cf47163439..0000000000 --- a/library/2.0.5/portal.py +++ /dev/null @@ -1,22 +0,0 @@ -try: - from .validations import valid_portal_scheme_or_raise, valid_http_path_or_raise, valid_port_or_raise -except ImportError: - from validations import valid_portal_scheme_or_raise, valid_http_path_or_raise, valid_port_or_raise - - -class Portal: - def __init__(self, name: str, config: dict): - self._name = name - self._scheme = valid_portal_scheme_or_raise(config.get("scheme", "http")) - self._host = config.get("host", "0.0.0.0") or "0.0.0.0" - self._port = valid_port_or_raise(config.get("port", 0)) - self._path = valid_http_path_or_raise(config.get("path", "/")) - - def render(self): - return { - "name": self._name, - "scheme": self._scheme, - "host": self._host, - "port": self._port, - "path": self._path, - } diff --git a/library/2.0.5/portals.py b/library/2.0.5/portals.py deleted file mode 100644 index e106d231e6..0000000000 --- a/library/2.0.5/portals.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .portal import Portal -except ImportError: - from error import RenderError - from portal import Portal - - -class Portals: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._portals: set[Portal] = set() - - def add_portal(self, config: dict): - name = config.get("name", "Web UI") - - if name in [p._name for p in self._portals]: - raise RenderError(f"Portal [{name}] already added") - - self._portals.add(Portal(name, config)) - - def render(self): - return [p.render() for _, p in sorted([(p._name, p) for p in self._portals])] diff --git a/library/2.0.5/ports.py b/library/2.0.5/ports.py deleted file mode 100644 index f11e1481b4..0000000000 --- a/library/2.0.5/ports.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .validations import ( - valid_port_or_raise, - valid_port_protocol_or_raise, - valid_port_mode_or_raise, - valid_ip_or_raise, - ) -except ImportError: - from error import RenderError - from validations import ( - valid_port_or_raise, - valid_port_protocol_or_raise, - valid_port_mode_or_raise, - valid_ip_or_raise, - ) - - -class Ports: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._ports: dict[str, dict] = {} - - def add_port(self, host_port: int, container_port: int, config: dict | None = None): - config = config or {} - host_port = valid_port_or_raise(host_port) - container_port = valid_port_or_raise(container_port) - proto = valid_port_protocol_or_raise(config.get("protocol", "tcp")) - mode = valid_port_mode_or_raise(config.get("mode", "ingress")) - host_ip = valid_ip_or_raise(config.get("host_ip", "0.0.0.0")) - - key = f"{host_port}_{host_ip}_{proto}" - if key in self._ports.keys(): - raise RenderError(f"Port [{host_port}/{proto}] already added for [{host_ip}]") - - if host_ip != "0.0.0.0": - # If the port we are adding is not going to use 0.0.0.0 - # Make sure that we don't have already added that port/proto to 0.0.0.0 - search_key = f"{host_port}_0.0.0.0_{proto}" - if search_key in self._ports.keys(): - raise RenderError(f"Cannot bind port [{host_port}/{proto}] to [{host_ip}], already bound to [0.0.0.0]") - elif host_ip == "0.0.0.0": - # If the port we are adding is going to use 0.0.0.0 - # Make sure that we don't have already added that port/proto to a specific ip - for p in self._ports.values(): - if p["published"] == host_port and p["protocol"] == proto: - raise RenderError( - f"Cannot bind port [{host_port}/{proto}] to [{host_ip}], already bound to [{p['host_ip']}]" - ) - - self._ports[key] = { - "published": host_port, - "target": container_port, - "protocol": proto, - "mode": mode, - "host_ip": host_ip, - } - - def has_ports(self): - return len(self._ports) > 0 - - def render(self): - return [config for _, config in sorted(self._ports.items())] diff --git a/library/2.0.5/render.py b/library/2.0.5/render.py deleted file mode 100644 index 8421a833c6..0000000000 --- a/library/2.0.5/render.py +++ /dev/null @@ -1,86 +0,0 @@ -import copy - -try: - from .container import Container - from .configs import Configs - from .deps import Deps - from .error import RenderError - from .functions import Functions - from .notes import Notes - from .portals import Portals - from .volumes import Volumes -except ImportError: - from container import Container - from configs import Configs - from deps import Deps - from error import RenderError - from functions import Functions - from notes import Notes - from portals import Portals - from volumes import Volumes - - -class Render(object): - def __init__(self, values): - self._containers: dict[str, Container] = {} - self.values = values - self._add_images_internal_use() - # Make a copy after we inject the images - self._original_values: dict = copy.deepcopy(self.values) - - self.deps: Deps = Deps(self) - - self.configs = Configs(render_instance=self) - self.funcs = Functions(render_instance=self).func_map() - self.portals: Portals = Portals(render_instance=self) - self.notes: Notes = Notes(render_instance=self) - self.volumes = Volumes(render_instance=self) - - def _add_images_internal_use(self): - if not self.values.get("images"): - self.values["images"] = {} - - if "python_permissions_image" not in self.values["images"]: - self.values["images"]["python_permissions_image"] = {"repository": "python", "tag": "3.13.0-slim-bookworm"} - - def container_names(self): - return list(self._containers.keys()) - - def add_container(self, name: str, image: str): - container = Container(self, name, image) - if name in self._containers: - raise RenderError(f"Container {name} already exists.") - self._containers[name] = container - return container - - def render(self): - if self.values != self._original_values: - raise RenderError("Values have been modified since the renderer was created.") - - if not self._containers: - raise RenderError("No containers added.") - - result: dict = { - "x-notes": self.notes.render(), - "x-portals": self.portals.render(), - "services": {c._name: c.render() for c in self._containers.values()}, - } - - # Make sure that after services are rendered - # there are no labels that target a non-existent container - # This is to prevent typos - for label in self.values.get("labels", []): - for c in label.get("containers", []): - if c not in self.container_names(): - raise RenderError(f"Label [{label['key']}] references container [{c}] which does not exist") - - if self.volumes.has_volumes(): - result["volumes"] = self.volumes.render() - - if self.configs.has_configs(): - result["configs"] = self.configs.render() - - # if self.networks: - # result["networks"] = {...} - - return result diff --git a/library/2.0.5/resources.py b/library/2.0.5/resources.py deleted file mode 100644 index 825b6c8001..0000000000 --- a/library/2.0.5/resources.py +++ /dev/null @@ -1,111 +0,0 @@ -import re -from typing import Any, TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError -except ImportError: - from error import RenderError - -DEFAULT_CPUS = 2.0 -DEFAULT_MEMORY = 4096 - - -class Resources: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._limits: dict = {} - self._reservations: dict = {} - self._nvidia_ids: set[str] = set() - self._auto_add_cpu_from_values() - self._auto_add_memory_from_values() - self._auto_add_gpus_from_values() - - def _set_cpu(self, cpus: Any): - c = str(cpus) - if not re.match(r"^[1-9][0-9]*(\.[0-9]+)?$", c): - raise RenderError(f"Expected cpus to be a number or a float (minimum 1.0), got [{cpus}]") - self._limits.update({"cpus": c}) - - def _set_memory(self, memory: Any): - m = str(memory) - if not re.match(r"^[1-9][0-9]*$", m): - raise RenderError(f"Expected memory to be a number, got [{memory}]") - self._limits.update({"memory": f"{m}M"}) - - def _auto_add_cpu_from_values(self): - resources = self._render_instance.values.get("resources", {}) - self._set_cpu(resources.get("limits", {}).get("cpus", DEFAULT_CPUS)) - - def _auto_add_memory_from_values(self): - resources = self._render_instance.values.get("resources", {}) - self._set_memory(resources.get("limits", {}).get("memory", DEFAULT_MEMORY)) - - def _auto_add_gpus_from_values(self): - resources = self._render_instance.values.get("resources", {}) - gpus = resources.get("gpus", {}).get("nvidia_gpu_selection", {}) - if not gpus: - return - - for pci, gpu in gpus.items(): - if gpu.get("use_gpu", False): - if not gpu.get("uuid"): - raise RenderError(f"Expected [uuid] to be set for GPU in slot [{pci}] in [nvidia_gpu_selection]") - self._nvidia_ids.add(gpu["uuid"]) - - if self._nvidia_ids: - if not self._reservations: - self._reservations["devices"] = [] - self._reservations["devices"].append( - { - "capabilities": ["gpu"], - "driver": "nvidia", - "device_ids": sorted(self._nvidia_ids), - } - ) - - # This is only used on ix-app that we allow - # disabling cpus and memory. GPUs are only added - # if the user has requested them. - def remove_cpus_and_memory(self): - self._limits.pop("cpus", None) - self._limits.pop("memory", None) - - # Mainly will be used from dependencies - # There is no reason to pass devices to - # redis or postgres for example - def remove_devices(self): - self._reservations.pop("devices", None) - - def set_profile(self, profile: str): - cpu, memory = profile_mapping(profile) - self._set_cpu(cpu) - self._set_memory(memory) - - def has_resources(self): - return len(self._limits) > 0 or len(self._reservations) > 0 - - def render(self): - result = {} - if self._limits: - result["limits"] = self._limits - if self._reservations: - result["reservations"] = self._reservations - - return result - - -def profile_mapping(profile: str): - profiles = { - "low": (1, 512), - "medium": (2, 1024), - } - - if profile not in profiles: - raise RenderError( - f"Resource profile [{profile}] is not valid. Valid options are: [{', '.join(profiles.keys())}]" - ) - - return profiles[profile] diff --git a/library/2.0.5/restart.py b/library/2.0.5/restart.py deleted file mode 100644 index 2f6281af48..0000000000 --- a/library/2.0.5/restart.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - -try: - from .validations import valid_restart_policy_or_raise -except ImportError: - from validations import valid_restart_policy_or_raise - - -class RestartPolicy: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._policy: str = "unless-stopped" - self._maximum_retry_count: int = 0 - - def set_policy(self, policy: str, maximum_retry_count: int = 0): - self._policy = valid_restart_policy_or_raise(policy, maximum_retry_count) - self._maximum_retry_count = maximum_retry_count - - def render(self): - if self._policy == "on-failure" and self._maximum_retry_count > 0: - return f"{self._policy}:{self._maximum_retry_count}" - return self._policy diff --git a/library/2.0.5/storage.py b/library/2.0.5/storage.py deleted file mode 100644 index 4503793319..0000000000 --- a/library/2.0.5/storage.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import TYPE_CHECKING, TypedDict, Literal, NotRequired, Union - -if TYPE_CHECKING: - from render import Render - -try: - from .error import RenderError - from .validations import valid_fs_path_or_raise - from .volume_mount import VolumeMount -except ImportError: - from error import RenderError - from validations import valid_fs_path_or_raise - from volume_mount import VolumeMount - - -class IxStorageTmpfsConfig(TypedDict): - size: NotRequired[int] - mode: NotRequired[str] - - -class AclConfig(TypedDict, total=False): - path: str - - -class IxStorageHostPathConfig(TypedDict): - path: NotRequired[str] # Either this or acl.path must be set - acl_enable: NotRequired[bool] - acl: NotRequired[AclConfig] - create_host_path: NotRequired[bool] - propagation: NotRequired[Literal["shared", "slave", "private", "rshared", "rslave", "rprivate"]] - - -class IxStorageIxVolumeConfig(TypedDict): - dataset_name: str - acl_enable: NotRequired[bool] - acl_entries: NotRequired[AclConfig] - create_host_path: NotRequired[bool] - propagation: NotRequired[Literal["shared", "slave", "private", "rshared", "rslave", "rprivate"]] - - -class IxStorageVolumeConfig(TypedDict): - volume_name: str - nocopy: NotRequired[bool] - - -class IxStorageNfsConfig(TypedDict): - server: str - path: str - options: NotRequired[list[str]] - - -class IxStorageCifsConfig(TypedDict): - server: str - path: str - username: str - password: str - domain: NotRequired[str] - options: NotRequired[list[str]] - - -IxStorageVolumeLikeConfigs = Union[IxStorageVolumeConfig, IxStorageNfsConfig, IxStorageCifsConfig, IxStorageTmpfsConfig] -IxStorageBindLikeConfigs = Union[IxStorageHostPathConfig, IxStorageIxVolumeConfig] -IxStorageLikeConfigs = Union[IxStorageBindLikeConfigs, IxStorageVolumeLikeConfigs] - - -class IxStorage(TypedDict): - type: Literal["ix_volume", "host_path", "tmpfs", "volume", "anonymous"] - read_only: NotRequired[bool] - auto_permissions: NotRequired[bool] - - ix_volume_config: NotRequired[IxStorageIxVolumeConfig] - host_path_config: NotRequired[IxStorageHostPathConfig] - tmpfs_config: NotRequired[IxStorageTmpfsConfig] - volume_config: NotRequired[IxStorageVolumeConfig] - nfs_config: NotRequired[IxStorageNfsConfig] - cifs_config: NotRequired[IxStorageCifsConfig] - - -class Storage: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._volume_mounts: set[VolumeMount] = set() - - def add(self, mount_path: str, config: "IxStorage"): - mount_path = valid_fs_path_or_raise(mount_path) - if mount_path in [m.mount_path for m in self._volume_mounts]: - raise RenderError(f"Mount path [{mount_path}] already used for another volume mount") - - volume_mount = VolumeMount(self._render_instance, mount_path, config) - self._volume_mounts.add(volume_mount) - - def has_mounts(self) -> bool: - return bool(self._volume_mounts) - - def render(self): - return [vm.render() for vm in sorted(self._volume_mounts, key=lambda vm: vm.mount_path)] diff --git a/library/2.0.5/tests/__init__.py b/library/2.0.5/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/library/2.0.5/tests/test_build_image.py b/library/2.0.5/tests/test_build_image.py deleted file mode 100644 index f30c1210ed..0000000000 --- a/library/2.0.5/tests/test_build_image.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_build_image_with_from(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.build_image(["FROM test_image"]) - - -def test_build_image(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.build_image( - [ - "RUN echo hello", - None, - "", - "RUN echo world", - ] - ) - output = render.render() - assert ( - output["services"]["test_container"]["image"] - == "ix-nginx:latest_4a127145ea4c25511707e57005dd0ed457fe2f4932082c8f9faa339a450b6a99" - ) - assert output["services"]["test_container"]["build"] == { - "tags": ["ix-nginx:latest_4a127145ea4c25511707e57005dd0ed457fe2f4932082c8f9faa339a450b6a99"], - "dockerfile_inline": """FROM nginx:latest -RUN echo hello -RUN echo world -""", - } diff --git a/library/2.0.5/tests/test_configs.py b/library/2.0.5/tests/test_configs.py deleted file mode 100644 index 9049e473ea..0000000000 --- a/library/2.0.5/tests/test_configs.py +++ /dev/null @@ -1,63 +0,0 @@ -import pytest - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_duplicate_config_with_different_data(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.configs.add("test_config", "test_data", "/some/path") - with pytest.raises(Exception): - c1.configs.add("test_config", "test_data2", "/some/path") - - -def test_add_config_with_empty_target(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.configs.add("test_config", "test_data", "") - - -def test_add_duplicate_target(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.configs.add("test_config", "test_data", "/some/path") - with pytest.raises(Exception): - c1.configs.add("test_config2", "test_data2", "/some/path") - - -def test_add_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.configs.add("test_config", "$test_data", "/some/path") - output = render.render() - assert output["configs"]["test_config"]["content"] == "$$test_data" - assert output["services"]["test_container"]["configs"] == [{"source": "test_config", "target": "/some/path"}] - - -def test_add_config_with_mode(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.configs.add("test_config", "test_data", "/some/path", "0777") - output = render.render() - assert output["configs"]["test_config"]["content"] == "test_data" - assert output["services"]["test_container"]["configs"] == [ - {"source": "test_config", "target": "/some/path", "mode": 511} - ] diff --git a/library/2.0.5/tests/test_container.py b/library/2.0.5/tests/test_container.py deleted file mode 100644 index 6c8fb42fd2..0000000000 --- a/library/2.0.5/tests/test_container.py +++ /dev/null @@ -1,213 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_resolve_image(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["image"] == "nginx:latest" - - -def test_missing_repo(mock_values): - mock_values["images"]["test_image"]["repository"] = "" - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_missing_tag(mock_values): - mock_values["images"]["test_image"]["tag"] = "" - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_non_existing_image(mock_values): - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "non_existing_image") - - -def test_tty(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.set_tty(True) - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["tty"] is True - - -def test_stdin(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.set_stdin(True) - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["stdin_open"] is True - - -def test_hostname(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.set_hostname("test_hostname") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["hostname"] == "test_hostname" - - -def test_grace_period(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.set_grace_period(10) - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["stop_grace_period"] == 10 - - -def test_user(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.set_user(1000, 1000) - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["user"] == "1000:1000" - - -def test_invalid_user(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.set_user(-100, 1000) - - -def test_valid_caps(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.add_caps(["ALL", "NET_ADMIN"]) - output = render.render() - assert output["services"]["test_container"]["cap_add"] == ["ALL", "NET_ADMIN"] - assert output["services"]["test_container"]["cap_drop"] == ["ALL"] - - -def test_add_duplicate_caps(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.add_caps(["ALL", "NET_ADMIN", "NET_ADMIN"]) - - -def test_invalid_caps(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.add_caps(["invalid_cap"]) - - -def test_remove_security_opt(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.remove_security_opt("no-new-privileges") - output = render.render() - assert "security_opt" not in output["services"]["test_container"] - - -def test_add_security_opt(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.add_security_opt("seccomp=unconfined") - output = render.render() - assert output["services"]["test_container"]["security_opt"] == [ - "no-new-privileges", - "seccomp=unconfined", - ] - - -def test_add_duplicate_security_opt(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.add_security_opt("no-new-privileges") - - -def test_network_mode(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.set_network_mode("host") - output = render.render() - assert output["services"]["test_container"]["network_mode"] == "host" - - -def test_auto_network_mode_with_host_network(mock_values): - mock_values["network"] = {"host_network": True} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["network_mode"] == "host" - - -def test_network_mode_with_container(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.set_network_mode("service:test_container") - output = render.render() - assert output["services"]["test_container"]["network_mode"] == "service:test_container" - - -def test_network_mode_with_container_missing(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.set_network_mode("service:missing_container") - - -def test_invalid_network_mode(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.set_network_mode("invalid_mode") - - -def test_entrypoint(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.set_entrypoint(["/bin/bash", "-c", "echo hello $MY_ENV"]) - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["entrypoint"] == ["/bin/bash", "-c", "echo hello $$MY_ENV"] - - -def test_command(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.set_command(["echo", "hello $MY_ENV"]) - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["command"] == ["echo", "hello $$MY_ENV"] diff --git a/library/2.0.5/tests/test_depends.py b/library/2.0.5/tests/test_depends.py deleted file mode 100644 index a1d8373927..0000000000 --- a/library/2.0.5/tests/test_depends.py +++ /dev/null @@ -1,54 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_dependency(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c2 = render.add_container("test_container2", "test_image") - c1.healthcheck.disable() - c2.healthcheck.disable() - c1.depends.add_dependency("test_container2", "service_started") - output = render.render() - assert output["services"]["test_container"]["depends_on"]["test_container2"] == {"condition": "service_started"} - - -def test_add_dependency_invalid_condition(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - render.add_container("test_container2", "test_image") - with pytest.raises(Exception): - c1.depends.add_dependency("test_container2", "invalid_condition") - - -def test_add_dependency_missing_container(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.depends.add_dependency("test_container2", "service_started") - - -def test_add_dependency_duplicate(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - render.add_container("test_container2", "test_image") - c1.depends.add_dependency("test_container2", "service_started") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.depends.add_dependency("test_container2", "service_started") diff --git a/library/2.0.5/tests/test_deps.py b/library/2.0.5/tests/test_deps.py deleted file mode 100644 index 68eef635f8..0000000000 --- a/library/2.0.5/tests/test_deps.py +++ /dev/null @@ -1,311 +0,0 @@ -import json -import pytest - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_postgres_missing_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - render.deps.postgres( - "test_container", - "test_image", - {"user": "test_user", "password": "test_password", "database": "test_database"}, # type: ignore - ) - - -def test_add_postgres(mock_values): - mock_values["images"]["pg_image"] = {"repository": "postgres", "tag": "latest"} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - perms_container = render.deps.perms("perms_container") - p = render.deps.postgres( - "pg_container", - "pg_image", - { - "user": "test_user", - "password": "test_password", - "database": "test_database", - "volume": {"type": "volume", "volume_config": {"volume_name": "test_volume"}, "auto_permissions": True}, - }, - perms_container, - ) - if perms_container.has_actions(): - perms_container.activate() - p.depends.add_dependency("perms_container", "service_completed_successfully") - output = render.render() - assert "devices" not in output["services"]["pg_container"] - assert "reservations" not in output["services"]["pg_container"]["deploy"]["resources"] - assert output["services"]["pg_container"]["image"] == "postgres:latest" - assert output["services"]["pg_container"]["user"] == "999:999" - assert output["services"]["pg_container"]["deploy"]["resources"]["limits"]["cpus"] == "2.0" - assert output["services"]["pg_container"]["deploy"]["resources"]["limits"]["memory"] == "4096M" - assert output["services"]["pg_container"]["healthcheck"] == { - "test": "pg_isready -h 127.0.0.1 -p 5432 -U $$POSTGRES_USER -d $$POSTGRES_DB", - "interval": "10s", - "timeout": "5s", - "retries": 30, - "start_period": "10s", - } - assert output["services"]["pg_container"]["volumes"] == [ - { - "type": "volume", - "source": "test_volume", - "target": "/var/lib/postgresql/data", - "read_only": False, - "volume": {"nocopy": False}, - } - ] - assert output["services"]["pg_container"]["environment"] == { - "TZ": "Etc/UTC", - "NVIDIA_VISIBLE_DEVICES": "void", - "POSTGRES_USER": "test_user", - "POSTGRES_PASSWORD": "test_password", - "POSTGRES_DB": "test_database", - "POSTGRES_PORT": "5432", - } - assert output["services"]["pg_container"]["depends_on"] == { - "perms_container": {"condition": "service_completed_successfully"} - } - assert output["services"]["perms_container"]["restart"] == "on-failure:1" - - -def test_add_redis_missing_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - render.deps.redis( - "test_container", - "test_image", - {"password": "test_password", "volume": {}}, # type: ignore - ) - - -def test_add_redis(mock_values): - mock_values["images"]["redis_image"] = {"repository": "redis", "tag": "latest"} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - perms_container = render.deps.perms("perms_container") - r = render.deps.redis( - "redis_container", - "redis_image", - { - "password": "test_password", - "volume": {"type": "volume", "volume_config": {"volume_name": "test_volume"}, "auto_permissions": True}, - }, - perms_container, - ) - if perms_container.has_actions(): - perms_container.activate() - r.depends.add_dependency("perms_container", "service_completed_successfully") - output = render.render() - assert "devices" not in output["services"]["redis_container"] - assert "reservations" not in output["services"]["redis_container"]["deploy"]["resources"] - assert output["services"]["redis_container"]["image"] == "redis:latest" - assert output["services"]["redis_container"]["user"] == "1001:0" - assert output["services"]["redis_container"]["deploy"]["resources"]["limits"]["cpus"] == "2.0" - assert output["services"]["redis_container"]["deploy"]["resources"]["limits"]["memory"] == "4096M" - assert output["services"]["redis_container"]["healthcheck"] == { - "test": "redis-cli -h 127.0.0.1 -p 6379 -a $$REDIS_PASSWORD ping | grep -q PONG", - "interval": "10s", - "timeout": "5s", - "retries": 30, - "start_period": "10s", - } - assert output["services"]["redis_container"]["volumes"] == [ - { - "type": "volume", - "source": "test_volume", - "target": "/bitnami/redis/data", - "read_only": False, - "volume": {"nocopy": False}, - } - ] - assert output["services"]["redis_container"]["environment"] == { - "TZ": "Etc/UTC", - "NVIDIA_VISIBLE_DEVICES": "void", - "ALLOW_EMPTY_PASSWORD": "no", - "REDIS_PASSWORD": "test_password", - "REDIS_PORT_NUMBER": "6379", - } - assert output["services"]["redis_container"]["depends_on"] == { - "perms_container": {"condition": "service_completed_successfully"} - } - - -def test_add_mariadb_missing_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - render.deps.mariadb( - "test_container", - "test_image", - {"user": "test_user", "password": "test_password", "database": "test_database"}, # type: ignore - ) - - -def test_add_mariadb(mock_values): - mock_values["images"]["mariadb_image"] = {"repository": "mariadb", "tag": "latest"} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - perms_container = render.deps.perms("perms_container") - m = render.deps.mariadb( - "mariadb_container", - "mariadb_image", - { - "user": "test_user", - "password": "test_password", - "database": "test_database", - "volume": {"type": "volume", "volume_config": {"volume_name": "test_volume"}, "auto_permissions": True}, - }, - perms_container, - ) - if perms_container.has_actions(): - perms_container.activate() - m.depends.add_dependency("perms_container", "service_completed_successfully") - output = render.render() - assert "devices" not in output["services"]["mariadb_container"] - assert "reservations" not in output["services"]["mariadb_container"]["deploy"]["resources"] - assert output["services"]["mariadb_container"]["image"] == "mariadb:latest" - assert output["services"]["mariadb_container"]["user"] == "999:999" - assert output["services"]["mariadb_container"]["deploy"]["resources"]["limits"]["cpus"] == "2.0" - assert output["services"]["mariadb_container"]["deploy"]["resources"]["limits"]["memory"] == "4096M" - assert output["services"]["mariadb_container"]["healthcheck"] == { - "test": "mariadb-admin --user=root --host=127.0.0.1 --port=3306 --password=$$MARIADB_ROOT_PASSWORD ping", - "interval": "10s", - "timeout": "5s", - "retries": 30, - "start_period": "10s", - } - assert output["services"]["mariadb_container"]["volumes"] == [ - { - "type": "volume", - "source": "test_volume", - "target": "/var/lib/mysql", - "read_only": False, - "volume": {"nocopy": False}, - } - ] - assert output["services"]["mariadb_container"]["environment"] == { - "TZ": "Etc/UTC", - "NVIDIA_VISIBLE_DEVICES": "void", - "MARIADB_USER": "test_user", - "MARIADB_PASSWORD": "test_password", - "MARIADB_ROOT_PASSWORD": "test_password", - "MARIADB_DATABASE": "test_database", - "MARIADB_AUTO_UPGRADE": "true", - } - assert output["services"]["mariadb_container"]["depends_on"] == { - "perms_container": {"condition": "service_completed_successfully"} - } - - -def test_add_perms_container(mock_values): - mock_values["ix_volumes"] = { - "test_dataset1": "/mnt/test/1", - "test_dataset2": "/mnt/test/2", - "test_dataset3": "/mnt/test/3", - } - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - - # fmt: off - volume_perms = {"type": "volume", "volume_config": {"volume_name": "test_volume"}, "auto_permissions": True} - volume_no_perms = {"type": "volume", "volume_config": {"volume_name": "test_volume"}} - host_path_perms = {"type": "host_path", "host_path_config": {"path": "/mnt/test"}, "auto_permissions": True} - host_path_no_perms = {"type": "host_path", "host_path_config": {"path": "/mnt/test"}} - host_path_acl_perms = {"type": "host_path", "host_path_config": {"acl":{"path": "/mnt/test"}, "acl_enable": True}, "auto_permissions": True} # noqa - ix_volume_no_perms = {"type": "ix_volume", "ix_volume_config": {"dataset_name": "test_dataset1"}} - ix_volume_perms = {"type": "ix_volume", "ix_volume_config": {"dataset_name": "test_dataset2"}, "auto_permissions": True} # noqa - ix_volume_acl_perms = {"type": "ix_volume", "ix_volume_config": {"dataset_name": "test_dataset3", "acl_enable": True}, "auto_permissions": True} # noqa - temp_volume = {"type": "temporary", "volume_config": {"volume_name": "test_temp_volume"}} - # fmt: on - - c1.add_storage("/some/path1", volume_perms) - c1.add_storage("/some/path2", volume_no_perms) - c1.add_storage("/some/path3", host_path_perms) - c1.add_storage("/some/path4", host_path_no_perms) - c1.add_storage("/some/path5", host_path_acl_perms) - c1.add_storage("/some/path6", ix_volume_no_perms) - c1.add_storage("/some/path7", ix_volume_perms) - c1.add_storage("/some/path8", ix_volume_acl_perms) - c1.add_storage("/some/path9", temp_volume) - - perms_container = render.deps.perms("test_perms_container") - perms_container.add_or_skip_action("data", volume_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data2", volume_no_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data3", host_path_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data4", host_path_no_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data5", host_path_acl_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data6", ix_volume_no_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data7", ix_volume_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data8", ix_volume_acl_perms, {"uid": 1000, "gid": 1000, "mode": "check"}) - perms_container.add_or_skip_action("data9", temp_volume, {"uid": 1000, "gid": 1000, "mode": "check"}) - - if perms_container.has_actions(): - perms_container.activate() - c1.depends.add_dependency("test_perms_container", "service_completed_successfully") - output = render.render() - assert output["services"]["test_container"]["depends_on"] == { - "test_perms_container": {"condition": "service_completed_successfully"} - } - assert output["configs"]["permissions_run_script"]["content"] != "" - # fmt: off - content = [ - {"mount_path": "/mnt/permission/data", "is_temporary": False, "identifier": "data", "mode": "check", "uid": 1000, "gid": 1000, "chmod": None}, # noqa - {"mount_path": "/mnt/permission/data3", "is_temporary": False, "identifier": "data3", "mode": "check", "uid": 1000, "gid": 1000, "chmod": None}, # noqa - {"mount_path": "/mnt/permission/data6", "is_temporary": False, "identifier": "data6", "mode": "check", "uid": 1000, "gid": 1000, "chmod": None}, # noqa - {"mount_path": "/mnt/permission/data7", "is_temporary": False, "identifier": "data7", "mode": "check", "uid": 1000, "gid": 1000, "chmod": None}, # noqa - {"mount_path": "/mnt/permission/data9", "is_temporary": True, "identifier": "data9", "mode": "check", "uid": 1000, "gid": 1000, "chmod": None}, # noqa - ] - # fmt: on - assert output["configs"]["permissions_actions_data"]["content"] == json.dumps(content) - - -def test_add_duplicate_perms_action(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "volume", "volume_config": {"volume_name": "test_volume"}, "auto_permissions": True} - c1.add_storage("/some/path", vol_config) - perms_container = render.deps.perms("test_perms_container") - perms_container.add_or_skip_action("data", vol_config, {"uid": 1000, "gid": 1000, "mode": "check"}) - with pytest.raises(Exception): - perms_container.add_or_skip_action("data", vol_config, {"uid": 1000, "gid": 1000, "mode": "check"}) - - -def test_add_perm_action_without_auto_perms_enabled(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "volume", "volume_config": {"volume_name": "test_volume"}, "auto_permissions": False} - c1.add_storage("/some/path", vol_config) - perms_container = render.deps.perms("test_perms_container") - perms_container.add_or_skip_action("data", vol_config, {"uid": 1000, "gid": 1000, "mode": "check"}) - if perms_container.has_actions(): - perms_container.activate() - c1.depends.add_dependency("test_perms_container", "service_completed_successfully") - output = render.render() - assert "configs" not in output - assert "ix-test_perms_container" not in output["services"] - assert "depends_on" not in output["services"]["test_container"] diff --git a/library/2.0.5/tests/test_device.py b/library/2.0.5/tests/test_device.py deleted file mode 100644 index fd61baf985..0000000000 --- a/library/2.0.5/tests/test_device.py +++ /dev/null @@ -1,92 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_device(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.devices.add_device("/h/dev/sda", "/c/dev/sda") - c1.devices.add_device("/h/dev/sdb", "/c/dev/sdb", "rwm") - output = render.render() - assert output["services"]["test_container"]["devices"] == ["/h/dev/sda:/c/dev/sda", "/h/dev/sdb:/c/dev/sdb:rwm"] - - -def test_devices_without_host(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.devices.add_device("", "/c/dev/sda") - - -def test_devices_without_container(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.devices.add_device("/h/dev/sda", "") - - -def test_add_duplicate_device(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.devices.add_device("/h/dev/sda", "/c/dev/sda") - with pytest.raises(Exception): - c1.devices.add_device("/h/dev/sda", "/c/dev/sda") - - -def test_add_device_with_invalid_container_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.devices.add_device("/h/dev/sda", "c/dev/sda") - - -def test_add_device_with_invalid_host_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.devices.add_device("h/dev/sda", "/c/dev/sda") - - -def test_add_disallowed_device(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.devices.add_device("/dev/dri", "/c/dev/sda") - - -def test_add_device_with_invalid_cgroup_perm(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.devices.add_device("/h/dev/sda", "/c/dev/sda", "invalid") - - -def test_automatically_add_gpu_devices(mock_values): - mock_values["resources"] = {"gpus": {"use_all_gpus": True}} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["devices"] == ["/dev/dri:/dev/dri"] diff --git a/library/2.0.5/tests/test_dns.py b/library/2.0.5/tests/test_dns.py deleted file mode 100644 index fe6b21e34f..0000000000 --- a/library/2.0.5/tests/test_dns.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_auto_add_dns_opts(mock_values): - mock_values["network"] = {"dns_opts": ["attempts:3", "opt1", "opt2"]} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["dns_opt"] == ["attempts:3", "opt1", "opt2"] - - -def test_auto_add_dns_searches(mock_values): - mock_values["network"] = {"dns_searches": ["search1", "search2"]} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["dns_search"] == ["search1", "search2"] - - -def test_auto_add_dns_nameservers(mock_values): - mock_values["network"] = {"dns_nameservers": ["nameserver1", "nameserver2"]} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["dns"] == ["nameserver1", "nameserver2"] - - -def test_add_duplicate_dns_nameservers(mock_values): - mock_values["network"] = {"dns_nameservers": ["nameserver1", "nameserver1"]} - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_add_duplicate_dns_searches(mock_values): - mock_values["network"] = {"dns_searches": ["search1", "search1"]} - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_add_duplicate_dns_opts(mock_values): - mock_values["network"] = {"dns_opts": ["attempts:3", "attempts:5"]} - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") diff --git a/library/2.0.5/tests/test_environment.py b/library/2.0.5/tests/test_environment.py deleted file mode 100644 index ba6f850e9b..0000000000 --- a/library/2.0.5/tests/test_environment.py +++ /dev/null @@ -1,182 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_auto_add_vars(mock_values): - mock_values["TZ"] = "Etc/UTC" - mock_values["run_as"] = {"user": "1000", "group": "1000"} - mock_values["resources"] = { - "gpus": { - "nvidia_gpu_selection": { - "pci_slot_0": {"uuid": "uuid_0", "use_gpu": True}, - "pci_slot_1": {"uuid": "uuid_1", "use_gpu": True}, - }, - } - } - - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - envs = output["services"]["test_container"]["environment"] - assert len(envs) == 9 - assert envs["TZ"] == "Etc/UTC" - assert envs["PUID"] == "1000" - assert envs["UID"] == "1000" - assert envs["USER_ID"] == "1000" - assert envs["PGID"] == "1000" - assert envs["GID"] == "1000" - assert envs["GROUP_ID"] == "1000" - assert envs["NVIDIA_DRIVER_CAPABILITIES"] == "all" - assert envs["NVIDIA_VISIBLE_DEVICES"] == "uuid_0,uuid_1" - - -def test_add_from_all_sources(mock_values): - mock_values["TZ"] = "Etc/UTC" - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.environment.add_env("APP_ENV", "test_value") - c1.environment.add_user_envs( - [ - {"name": "USER_ENV", "value": "test_value2"}, - ] - ) - output = render.render() - envs = output["services"]["test_container"]["environment"] - assert envs["APP_ENV"] == "test_value" - assert envs["USER_ENV"] == "test_value2" - assert envs["TZ"] == "Etc/UTC" - - -def test_user_add_vars(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.environment.add_user_envs( - [ - {"name": "MY_ENV", "value": "test_value"}, - {"name": "MY_ENV2", "value": "test_value2"}, - ] - ) - output = render.render() - envs = output["services"]["test_container"]["environment"] - assert envs["MY_ENV"] == "test_value" - assert envs["MY_ENV2"] == "test_value2" - - -def test_user_add_duplicate_vars(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.environment.add_user_envs( - [ - {"name": "MY_ENV", "value": "test_value"}, - {"name": "MY_ENV", "value": "test_value2"}, - ] - ) - - -def test_user_env_without_name(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.environment.add_user_envs( - [ - {"name": "", "value": "test_value"}, - ] - ) - - -def test_user_env_try_to_overwrite_auto_vars(mock_values): - mock_values["TZ"] = "Etc/UTC" - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.environment.add_user_envs( - [ - {"name": "TZ", "value": "test_value"}, - ] - ) - with pytest.raises(Exception): - render.render() - - -def test_user_env_try_to_overwrite_app_dev_vars(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.environment.add_user_envs( - [ - {"name": "PORT", "value": "test_value"}, - ] - ) - c1.environment.add_env("PORT", "test_value2") - with pytest.raises(Exception): - render.render() - - -def test_app_dev_vars_try_to_overwrite_auto_vars(mock_values): - mock_values["TZ"] = "Etc/UTC" - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.environment.add_env("TZ", "test_value") - with pytest.raises(Exception): - render.render() - - -def test_app_dev_no_name(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.environment.add_env("", "test_value") - - -def test_app_dev_duplicate_vars(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.environment.add_env("PORT", "test_value") - with pytest.raises(Exception): - c1.environment.add_env("PORT", "test_value2") - - -def test_format_vars(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.environment.add_env("APP_ENV", "test_$value") - c1.environment.add_env("APP_ENV_BOOL", True) - c1.environment.add_env("APP_ENV_INT", 10) - c1.environment.add_env("APP_ENV_FLOAT", 10.5) - c1.environment.add_user_envs( - [ - {"name": "USER_ENV", "value": "test_$value2"}, - ] - ) - - output = render.render() - envs = output["services"]["test_container"]["environment"] - assert envs["APP_ENV"] == "test_$$value" - assert envs["USER_ENV"] == "test_$$value2" - assert envs["APP_ENV_BOOL"] == "true" - assert envs["APP_ENV_INT"] == "10" - assert envs["APP_ENV_FLOAT"] == "10.5" diff --git a/library/2.0.5/tests/test_formatter.py b/library/2.0.5/tests/test_formatter.py deleted file mode 100644 index 843cf65d2e..0000000000 --- a/library/2.0.5/tests/test_formatter.py +++ /dev/null @@ -1,13 +0,0 @@ -from formatter import escape_dollar - - -def test_escape_dollar(): - cases = [ - {"input": "test", "expected": "test"}, - {"input": "$test", "expected": "$$test"}, - {"input": "$$test", "expected": "$$$$test"}, - {"input": "$$$test", "expected": "$$$$$$test"}, - {"input": "$test$", "expected": "$$test$$"}, - ] - for case in cases: - assert escape_dollar(case["input"]) == case["expected"] diff --git a/library/2.0.5/tests/test_functions.py b/library/2.0.5/tests/test_functions.py deleted file mode 100644 index a75e7c4084..0000000000 --- a/library/2.0.5/tests/test_functions.py +++ /dev/null @@ -1,65 +0,0 @@ -import re -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_funcs(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - - tests = [ - {"func": "auto_cast", "values": ["1"], "expected": 1}, - {"func": "basic_auth_header", "values": ["my_user", "my_pass"], "expected": "Basic bXlfdXNlcjpteV9wYXNz"}, - {"func": "basic_auth", "values": ["my_user", "my_pass"], "expected": "bXlfdXNlcjpteV9wYXNz"}, - { - "func": "bcrypt_hash", - "values": ["my_pass"], - "expect_regex": r"^\$2b\$12\$[a-zA-Z0-9-_\.\/]+$", - }, - {"func": "camel_case", "values": ["my_user"], "expected": "My_User"}, - {"func": "copy_dict", "values": [{"a": 1}], "expected": {"a": 1}}, - {"func": "fail", "values": ["my_message"], "expect_raise": True}, - { - "func": "htpasswd", - "values": ["my_user", "my_pass"], - "expect_regex": r"^my_user:\$2b\$12\$[a-zA-Z0-9-_\.\/]+$", - }, - {"func": "is_boolean", "values": ["true"], "expected": True}, - {"func": "is_boolean", "values": ["false"], "expected": True}, - {"func": "is_number", "values": ["1"], "expected": True}, - {"func": "is_number", "values": ["1.1"], "expected": True}, - {"func": "match_regex", "values": ["value", "^[a-zA-Z0-9]+$"], "expected": True}, - {"func": "match_regex", "values": ["value", "^[0-9]+$"], "expected": False}, - {"func": "merge_dicts", "values": [{"a": 1}, {"b": 2}], "expected": {"a": 1, "b": 2}}, - {"func": "must_match_regex", "values": ["my_user", "^[0-9]$"], "expect_raise": True}, - {"func": "must_match_regex", "values": ["1", "^[0-9]$"], "expected": "1"}, - {"func": "secure_string", "values": [10], "expect_regex": r"^[a-zA-Z0-9-_]+$"}, - ] - - for test in tests: - print(test["func"], test) - func = render.funcs[test["func"]] - if test.get("expect_raise", False): - with pytest.raises(Exception): - func(*test["values"]) - elif test.get("expect_regex"): - r = func(*test["values"]) - assert re.match(test["expect_regex"], r) is not None - else: - r = func(*test["values"]) - assert r == test["expected"] diff --git a/library/2.0.5/tests/test_healthcheck.py b/library/2.0.5/tests/test_healthcheck.py deleted file mode 100644 index 8267b986b4..0000000000 --- a/library/2.0.5/tests/test_healthcheck.py +++ /dev/null @@ -1,187 +0,0 @@ -import pytest - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_disable_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["healthcheck"] == {"disable": True} - - -def test_set_custom_test(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_custom_test("echo $1") - output = render.render() - assert output["services"]["test_container"]["healthcheck"] == { - "test": "echo $$1", - "interval": "10s", - "timeout": "5s", - "retries": 30, - "start_period": "10s", - } - - -def test_set_custom_test_array(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_custom_test(["CMD", "echo", "$1"]) - output = render.render() - assert output["services"]["test_container"]["healthcheck"] == { - "test": ["CMD", "echo", "$$1"], - "interval": "10s", - "timeout": "5s", - "retries": 30, - "start_period": "10s", - } - - -def test_set_options(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_custom_test(["CMD", "echo", "$1"]) - c1.healthcheck.set_interval(9) - c1.healthcheck.set_timeout(8) - c1.healthcheck.set_retries(7) - c1.healthcheck.set_start_period(6) - output = render.render() - assert output["services"]["test_container"]["healthcheck"] == { - "test": ["CMD", "echo", "$$1"], - "interval": "9s", - "timeout": "8s", - "retries": 7, - "start_period": "6s", - } - - -def test_adding_test_when_disabled(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.healthcheck.set_custom_test("echo $1") - - -def test_not_adding_test(mock_values): - render = Render(mock_values) - render.add_container("test_container", "test_image") - with pytest.raises(Exception): - render.render() - - -def test_invalid_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - with pytest.raises(Exception): - c1.healthcheck.set_test("http", {"port": 8080, "path": "invalid"}) - - -def test_http_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("http", {"port": 8080}) - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == """/bin/bash -c 'exec {hc_fd}<>/dev/tcp/127.0.0.1/8080 && echo -e "GET / HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n" >&$${hc_fd} && cat <&$${hc_fd} | grep -q "200 OK"'""" # noqa - ) - - -def test_curl_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("curl", {"port": 8080, "path": "/health"}) - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == "curl --silent --output /dev/null --show-error --fail http://127.0.0.1:8080/health" - ) - - -def test_curl_healthcheck_with_headers(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("curl", {"port": 8080, "path": "/health", "headers": [("X-Test", "$test")]}) - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == 'curl --silent --output /dev/null --show-error --fail --header "X-Test: $$test" http://127.0.0.1:8080/health' - ) - - -def test_wget_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("wget", {"port": 8080, "path": "/health"}) - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == "wget --spider --quiet http://127.0.0.1:8080/health" - ) - - -def test_netcat_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("netcat", {"port": 8080}) - output = render.render() - assert output["services"]["test_container"]["healthcheck"]["test"] == "nc -z -w 1 127.0.0.1 8080" - - -def test_tcp_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("tcp", {"port": 8080}) - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == "timeout 1 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/8080'" - ) - - -def test_redis_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("redis") - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == "redis-cli -h 127.0.0.1 -p 6379 -a $$REDIS_PASSWORD ping | grep -q PONG" - ) - - -def test_postgres_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("postgres") - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == "pg_isready -h 127.0.0.1 -p 5432 -U $$POSTGRES_USER -d $$POSTGRES_DB" - ) - - -def test_mariadb_healthcheck(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.set_test("mariadb") - output = render.render() - assert ( - output["services"]["test_container"]["healthcheck"]["test"] - == "mariadb-admin --user=root --host=127.0.0.1 --port=3306 --password=$$MARIADB_ROOT_PASSWORD ping" - ) diff --git a/library/2.0.5/tests/test_labels.py b/library/2.0.5/tests/test_labels.py deleted file mode 100644 index ffa21eceac..0000000000 --- a/library/2.0.5/tests/test_labels.py +++ /dev/null @@ -1,88 +0,0 @@ -import pytest - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_disallowed_label(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.labels.add_label("com.docker.compose.service", "test_service") - - -def test_add_duplicate_label(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.labels.add_label("my.custom.label", "test_value") - with pytest.raises(Exception): - c1.labels.add_label("my.custom.label", "test_value1") - - -def test_add_label_on_non_existing_container(mock_values): - mock_values["labels"] = [ - { - "key": "my.custom.label1", - "value": "test_value1", - "containers": ["test_container", "test_container2"], - }, - ] - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - render.render() - - -def test_add_label(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.labels.add_label("my.custom.label1", "test_value1") - c1.labels.add_label("my.custom.label2", "test_value2") - output = render.render() - assert output["services"]["test_container"]["labels"] == { - "my.custom.label1": "test_value1", - "my.custom.label2": "test_value2", - } - - -def test_auto_add_labels(mock_values): - mock_values["labels"] = [ - { - "key": "my.custom.label1", - "value": "test_value1", - "containers": ["test_container", "test_container2"], - }, - { - "key": "my.custom.label2", - "value": "test_value2", - "containers": ["test_container"], - }, - ] - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c2 = render.add_container("test_container2", "test_image") - c1.healthcheck.disable() - c2.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["labels"] == { - "my.custom.label1": "test_value1", - "my.custom.label2": "test_value2", - } - assert output["services"]["test_container2"]["labels"] == { - "my.custom.label1": "test_value1", - } diff --git a/library/2.0.5/tests/test_notes.py b/library/2.0.5/tests/test_notes.py deleted file mode 100644 index 3613445385..0000000000 --- a/library/2.0.5/tests/test_notes.py +++ /dev/null @@ -1,213 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "ix_context": { - "app_metadata": { - "name": "test_app", - } - }, - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_notes(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert ( - output["x-notes"] - == """# Welcome to TrueNAS SCALE - -Thank you for installing test_app! - -## Documentation - -Documentation for test_app can be found at https://www.truenas.com/docs. - -## Bug reports - -If you find a bug in this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps - -## Feature requests or improvements - -If you find a feature request for this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps -""" - ) - - -def test_notes_with_warnings(mock_values): - render = Render(mock_values) - render.notes.add_warning("this is not properly configured. fix it now!") - render.notes.add_warning("that is not properly configured. fix it later!") - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert ( - output["x-notes"] - == """# Welcome to TrueNAS SCALE - -Thank you for installing test_app! - -## Warnings - -- this is not properly configured. fix it now! -- that is not properly configured. fix it later! - -## Documentation - -Documentation for test_app can be found at https://www.truenas.com/docs. - -## Bug reports - -If you find a bug in this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps - -## Feature requests or improvements - -If you find a feature request for this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps -""" - ) - - -def test_notes_with_deprecations(mock_values): - render = Render(mock_values) - render.notes.add_deprecation("this is will be removed later. fix it now!") - render.notes.add_deprecation("that is will be removed later. fix it later!") - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert ( - output["x-notes"] - == """# Welcome to TrueNAS SCALE - -Thank you for installing test_app! - -## Deprecations - -- this is will be removed later. fix it now! -- that is will be removed later. fix it later! - -## Documentation - -Documentation for test_app can be found at https://www.truenas.com/docs. - -## Bug reports - -If you find a bug in this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps - -## Feature requests or improvements - -If you find a feature request for this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps -""" - ) - - -def test_notes_with_body(mock_values): - render = Render(mock_values) - render.notes.set_body( - """## Additional info - -Some info -some other info. -""" - ) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert ( - output["x-notes"] - == """# Welcome to TrueNAS SCALE - -Thank you for installing test_app! - -## Additional info - -Some info -some other info. - -## Documentation - -Documentation for test_app can be found at https://www.truenas.com/docs. - -## Bug reports - -If you find a bug in this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps - -## Feature requests or improvements - -If you find a feature request for this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps -""" - ) - - -def test_notes_all(mock_values): - render = Render(mock_values) - render.notes.add_warning("this is not properly configured. fix it now!") - render.notes.add_warning("that is not properly configured. fix it later!") - render.notes.add_deprecation("this is will be removed later. fix it now!") - render.notes.add_deprecation("that is will be removed later. fix it later!") - render.notes.set_body( - """## Additional info - -Some info -some other info. -""" - ) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert ( - output["x-notes"] - == """# Welcome to TrueNAS SCALE - -Thank you for installing test_app! - -## Warnings - -- this is not properly configured. fix it now! -- that is not properly configured. fix it later! - -## Deprecations - -- this is will be removed later. fix it now! -- that is will be removed later. fix it later! - -## Additional info - -Some info -some other info. - -## Documentation - -Documentation for test_app can be found at https://www.truenas.com/docs. - -## Bug reports - -If you find a bug in this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps - -## Feature requests or improvements - -If you find a feature request for this app, please file an issue at -https://ixsystems.atlassian.net or https://github.com/truenas/apps -""" - ) diff --git a/library/2.0.5/tests/test_portal.py b/library/2.0.5/tests/test_portal.py deleted file mode 100644 index aebd9425c9..0000000000 --- a/library/2.0.5/tests/test_portal.py +++ /dev/null @@ -1,75 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_no_portals(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["x-portals"] == [] - - -def test_add_portal(mock_values): - render = Render(mock_values) - render.portals.add_portal({"scheme": "http", "path": "/", "port": 8080}) - render.portals.add_portal({"name": "Other Portal", "scheme": "https", "path": "/", "port": 8443}) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["x-portals"] == [ - {"name": "Other Portal", "scheme": "https", "host": "0.0.0.0", "port": 8443, "path": "/"}, - {"name": "Web UI", "scheme": "http", "host": "0.0.0.0", "port": 8080, "path": "/"}, - ] - - -def test_add_duplicate_portal(mock_values): - render = Render(mock_values) - render.portals.add_portal({"scheme": "http", "path": "/", "port": 8080}) - with pytest.raises(Exception): - render.portals.add_portal({"scheme": "http", "path": "/", "port": 8080}) - - -def test_add_duplicate_portal_with_explicit_name(mock_values): - render = Render(mock_values) - render.portals.add_portal({"name": "Some Portal", "scheme": "http", "path": "/", "port": 8080}) - with pytest.raises(Exception): - render.portals.add_portal({"name": "Some Portal", "scheme": "http", "path": "/", "port": 8080}) - - -def test_add_portal_with_invalid_scheme(mock_values): - render = Render(mock_values) - with pytest.raises(Exception): - render.portals.add_portal({"scheme": "invalid_scheme", "path": "/", "port": 8080}) - - -def test_add_portal_with_invalid_path(mock_values): - render = Render(mock_values) - with pytest.raises(Exception): - render.portals.add_portal({"scheme": "http", "path": "invalid_path", "port": 8080}) - - -def test_add_portal_with_invalid_path_double_slash(mock_values): - render = Render(mock_values) - with pytest.raises(Exception): - render.portals.add_portal({"scheme": "http", "path": "/some//path", "port": 8080}) - - -def test_add_portal_with_invalid_port(mock_values): - render = Render(mock_values) - with pytest.raises(Exception): - render.portals.add_portal({"scheme": "http", "path": "/", "port": -1}) diff --git a/library/2.0.5/tests/test_ports.py b/library/2.0.5/tests/test_ports.py deleted file mode 100644 index a4c923ca1d..0000000000 --- a/library/2.0.5/tests/test_ports.py +++ /dev/null @@ -1,110 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_ports(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.ports.add_port(8081, 8080) - c1.ports.add_port(8082, 8080, {"protocol": "udp"}) - output = render.render() - assert output["services"]["test_container"]["ports"] == [ - {"published": 8081, "target": 8080, "protocol": "tcp", "mode": "ingress", "host_ip": "0.0.0.0"}, - {"published": 8082, "target": 8080, "protocol": "udp", "mode": "ingress", "host_ip": "0.0.0.0"}, - ] - - -def test_add_duplicate_ports(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.ports.add_port(8081, 8080) - c1.ports.add_port(8081, 8080, {"protocol": "udp"}) # This should not raise - with pytest.raises(Exception): - c1.ports.add_port(8081, 8080) - - -def test_add_duplicate_ports_with_different_host_ip(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.ports.add_port(8081, 8080, {"host_ip": "192.168.1.10"}) - c1.ports.add_port(8081, 8080, {"host_ip": "192.168.1.11"}) - output = render.render() - assert output["services"]["test_container"]["ports"] == [ - {"published": 8081, "target": 8080, "protocol": "tcp", "mode": "ingress", "host_ip": "192.168.1.10"}, - {"published": 8081, "target": 8080, "protocol": "tcp", "mode": "ingress", "host_ip": "192.168.1.11"}, - ] - - -def test_add_duplicate_ports_to_specific_host_ip_binds_to_0_0_0_0(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.ports.add_port(8081, 8080, {"host_ip": "192.168.1.10"}) - with pytest.raises(Exception): - c1.ports.add_port(8081, 8080, {"host_ip": "0.0.0.0"}) - - -def test_add_duplicate_ports_to_0_0_0_0_binds_to_specific_host_ip(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.ports.add_port(8081, 8080, {"host_ip": "0.0.0.0"}) - with pytest.raises(Exception): - c1.ports.add_port(8081, 8080, {"host_ip": "192.168.1.10"}) - - -def test_add_ports_with_invalid_protocol(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.ports.add_port(8081, 8080, {"protocol": "invalid_protocol"}) - - -def test_add_ports_with_invalid_mode(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.ports.add_port(8081, 8080, {"mode": "invalid_mode"}) - - -def test_add_ports_with_invalid_ip(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.ports.add_port(8081, 8080, {"host_ip": "invalid_ip"}) - - -def test_add_ports_with_invalid_host_port(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.ports.add_port(-1, 8080) - - -def test_add_ports_with_invalid_container_port(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.ports.add_port(8081, -1) diff --git a/library/2.0.5/tests/test_render.py b/library/2.0.5/tests/test_render.py deleted file mode 100644 index 60dc00679e..0000000000 --- a/library/2.0.5/tests/test_render.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_values_cannot_be_modified(mock_values): - render = Render(mock_values) - render.values["test"] = "test" - with pytest.raises(Exception): - render.render() - - -def test_duplicate_containers(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_no_containers(mock_values): - render = Render(mock_values) - with pytest.raises(Exception): - render.render() diff --git a/library/2.0.5/tests/test_resources.py b/library/2.0.5/tests/test_resources.py deleted file mode 100644 index bba70e4309..0000000000 --- a/library/2.0.5/tests/test_resources.py +++ /dev/null @@ -1,138 +0,0 @@ -import pytest - - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_automatically_add_cpu(mock_values): - mock_values["resources"] = {"limits": {"cpus": 1.0}} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["deploy"]["resources"]["limits"]["cpus"] == "1.0" - - -def test_invalid_cpu(mock_values): - mock_values["resources"] = {"limits": {"cpus": "invalid"}} - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_automatically_add_memory(mock_values): - mock_values["resources"] = {"limits": {"memory": 1024}} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - assert output["services"]["test_container"]["deploy"]["resources"]["limits"]["memory"] == "1024M" - - -def test_invalid_memory(mock_values): - mock_values["resources"] = {"limits": {"memory": "invalid"}} - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_automatically_add_gpus(mock_values): - mock_values["resources"] = { - "gpus": { - "nvidia_gpu_selection": { - "pci_slot_0": {"uuid": "uuid_0", "use_gpu": True}, - "pci_slot_1": {"uuid": "uuid_1", "use_gpu": True}, - }, - } - } - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - output = render.render() - devices = output["services"]["test_container"]["deploy"]["resources"]["reservations"]["devices"] - assert len(devices) == 1 - assert devices[0] == { - "capabilities": ["gpu"], - "driver": "nvidia", - "device_ids": ["uuid_0", "uuid_1"], - } - - -def test_gpu_without_uuid(mock_values): - mock_values["resources"] = { - "gpus": { - "nvidia_gpu_selection": { - "pci_slot_0": {"uuid": "", "use_gpu": True}, - "pci_slot_1": {"uuid": "uuid_1", "use_gpu": True}, - }, - } - } - render = Render(mock_values) - with pytest.raises(Exception): - render.add_container("test_container", "test_image") - - -def test_remove_cpus_and_memory_with_gpus(mock_values): - mock_values["resources"] = {"gpus": {"nvidia_gpu_selection": {"pci_slot_0": {"uuid": "uuid_1", "use_gpu": True}}}} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.deploy.resources.remove_cpus_and_memory() - output = render.render() - assert "limits" not in output["services"]["test_container"]["deploy"]["resources"] - devices = output["services"]["test_container"]["deploy"]["resources"]["reservations"]["devices"] - assert len(devices) == 1 - assert devices[0] == { - "capabilities": ["gpu"], - "driver": "nvidia", - "device_ids": ["uuid_1"], - } - - -def test_remove_cpus_and_memory(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.deploy.resources.remove_cpus_and_memory() - output = render.render() - assert "deploy" not in output["services"]["test_container"] - - -def test_remove_devices(mock_values): - mock_values["resources"] = {"gpus": {"nvidia_gpu_selection": {"pci_slot_0": {"uuid": "uuid_0", "use_gpu": True}}}} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.deploy.resources.remove_devices() - output = render.render() - assert "reservations" not in output["services"]["test_container"]["deploy"]["resources"] - - -def test_set_profile(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.deploy.resources.set_profile("low") - output = render.render() - assert output["services"]["test_container"]["deploy"]["resources"]["limits"]["cpus"] == "1" - assert output["services"]["test_container"]["deploy"]["resources"]["limits"]["memory"] == "512M" - - -def test_set_profile_invalid_profile(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.deploy.resources.set_profile("invalid_profile") diff --git a/library/2.0.5/tests/test_restart.py b/library/2.0.5/tests/test_restart.py deleted file mode 100644 index 06b2975590..0000000000 --- a/library/2.0.5/tests/test_restart.py +++ /dev/null @@ -1,57 +0,0 @@ -import pytest - -from render import Render - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_invalid_restart_policy(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.restart.set_policy("invalid_policy") - - -def test_valid_restart_policy(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.restart.set_policy("on-failure") - output = render.render() - assert output["services"]["test_container"]["restart"] == "on-failure" - - -def test_valid_restart_policy_with_maximum_retry_count(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.restart.set_policy("on-failure", 10) - output = render.render() - assert output["services"]["test_container"]["restart"] == "on-failure:10" - - -def test_invalid_restart_policy_with_maximum_retry_count(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.restart.set_policy("on-failure", maximum_retry_count=-1) - - -def test_invalid_restart_policy_with_maximum_retry_count_and_policy(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.restart.set_policy("always", maximum_retry_count=10) diff --git a/library/2.0.5/tests/test_volumes.py b/library/2.0.5/tests/test_volumes.py deleted file mode 100644 index 7e1b293c48..0000000000 --- a/library/2.0.5/tests/test_volumes.py +++ /dev/null @@ -1,607 +0,0 @@ -import pytest - - -from render import Render -from formatter import get_hashed_name_for_volume - - -@pytest.fixture -def mock_values(): - return { - "images": { - "test_image": { - "repository": "nginx", - "tag": "latest", - } - }, - } - - -def test_add_volume_invalid_type(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.add_storage("/some/path", {"type": "invalid_type"}) - - -def test_add_volume_empty_mount_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - with pytest.raises(Exception): - c1.add_storage("", {"type": "tmpfs"}) - - -def test_add_volume_duplicate_mount_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - c1.add_storage("/some/path", {"type": "tmpfs"}) - with pytest.raises(Exception): - c1.add_storage("/some/path", {"type": "tmpfs"}) - - -def test_add_volume_host_path_invalid_propagation(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = { - "type": "host_path", - "host_path_config": {"path": "/mnt/test", "propagation": "invalid_propagation"}, - } - with pytest.raises(Exception): - c1.add_storage("/some/path", host_path_config) - - -def test_add_host_path_volume_no_host_path_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = {"type": "host_path"} - with pytest.raises(Exception): - c1.add_storage("/some/path", host_path_config) - - -def test_add_host_path_volume_no_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = {"type": "host_path", "host_path_config": {"path": ""}} - with pytest.raises(Exception): - c1.add_storage("/some/path", host_path_config) - - -def test_add_host_path_with_acl_no_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = {"type": "host_path", "host_path_config": {"acl_enable": True, "acl": {"path": ""}}} - with pytest.raises(Exception): - c1.add_storage("/some/path", host_path_config) - - -def test_add_host_path_volume_mount(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = {"type": "host_path", "host_path_config": {"path": "/mnt/test"}} - c1.add_storage("/some/path", host_path_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "bind", - "source": "/mnt/test", - "target": "/some/path", - "read_only": False, - "bind": {"create_host_path": False, "propagation": "rprivate"}, - } - ] - - -def test_add_host_path_volume_mount_with_acl(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = { - "type": "host_path", - "host_path_config": {"path": "/mnt/test", "acl_enable": True, "acl": {"path": "/mnt/test/acl"}}, - } - c1.add_storage("/some/path", host_path_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "bind", - "source": "/mnt/test/acl", - "target": "/some/path", - "read_only": False, - "bind": {"create_host_path": False, "propagation": "rprivate"}, - } - ] - - -def test_add_host_path_volume_mount_with_propagation(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = {"type": "host_path", "host_path_config": {"path": "/mnt/test", "propagation": "slave"}} - c1.add_storage("/some/path", host_path_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "bind", - "source": "/mnt/test", - "target": "/some/path", - "read_only": False, - "bind": {"create_host_path": False, "propagation": "slave"}, - } - ] - - -def test_add_host_path_volume_mount_with_create_host_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = {"type": "host_path", "host_path_config": {"path": "/mnt/test", "create_host_path": True}} - c1.add_storage("/some/path", host_path_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "bind", - "source": "/mnt/test", - "target": "/some/path", - "read_only": False, - "bind": {"create_host_path": True, "propagation": "rprivate"}, - } - ] - - -def test_add_host_path_volume_mount_with_read_only(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - host_path_config = {"type": "host_path", "read_only": True, "host_path_config": {"path": "/mnt/test"}} - c1.add_storage("/some/path", host_path_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "bind", - "source": "/mnt/test", - "target": "/some/path", - "read_only": True, - "bind": {"create_host_path": False, "propagation": "rprivate"}, - } - ] - - -def test_add_ix_volume_invalid_dataset_name(mock_values): - mock_values["ix_volumes"] = {"test_dataset": "/mnt/test"} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - ix_volume_config = {"type": "ix_volume", "ix_volume_config": {"dataset_name": "invalid_dataset"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", ix_volume_config) - - -def test_add_ix_volume_no_ix_volume_config(mock_values): - mock_values["ix_volumes"] = {"test_dataset": "/mnt/test"} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - ix_volume_config = {"type": "ix_volume"} - with pytest.raises(Exception): - c1.add_storage("/some/path", ix_volume_config) - - -def test_add_ix_volume_volume_mount(mock_values): - mock_values["ix_volumes"] = {"test_dataset": "/mnt/test"} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - ix_volume_config = {"type": "ix_volume", "ix_volume_config": {"dataset_name": "test_dataset"}} - c1.add_storage("/some/path", ix_volume_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "bind", - "source": "/mnt/test", - "target": "/some/path", - "read_only": False, - "bind": {"create_host_path": False, "propagation": "rprivate"}, - } - ] - - -def test_add_ix_volume_volume_mount_with_options(mock_values): - mock_values["ix_volumes"] = {"test_dataset": "/mnt/test"} - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - ix_volume_config = { - "type": "ix_volume", - "ix_volume_config": {"dataset_name": "test_dataset", "propagation": "rslave", "create_host_path": True}, - } - c1.add_storage("/some/path", ix_volume_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "bind", - "source": "/mnt/test", - "target": "/some/path", - "read_only": False, - "bind": {"create_host_path": True, "propagation": "rslave"}, - } - ] - - -def test_cifs_volume_missing_server(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = {"type": "cifs", "cifs_config": {"path": "/path", "username": "user", "password": "password"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_missing_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = {"type": "cifs", "cifs_config": {"server": "server", "username": "user", "password": "password"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_missing_username(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = {"type": "cifs", "cifs_config": {"server": "server", "path": "/path", "password": "password"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_missing_password(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = {"type": "cifs", "cifs_config": {"server": "server", "path": "/path", "username": "user"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_without_cifs_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = {"type": "cifs"} - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_duplicate_option(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = { - "type": "cifs", - "cifs_config": { - "server": "server", - "path": "/path", - "username": "user", - "password": "pas$word", - "options": ["verbose=true", "verbose=true"], - }, - } - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_disallowed_option(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = { - "type": "cifs", - "cifs_config": { - "server": "server", - "path": "/path", - "username": "user", - "password": "pas$word", - "options": ["user=username"], - }, - } - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_invalid_options(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = { - "type": "cifs", - "cifs_config": { - "server": "server", - "path": "/path", - "username": "user", - "password": "pas$word", - "options": {"verbose": True}, - }, - } - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_cifs_volume_invalid_options2(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_config = { - "type": "cifs", - "cifs_config": { - "server": "server", - "path": "/path", - "username": "user", - "password": "pas$word", - "options": [{"verbose": True}], - }, - } - with pytest.raises(Exception): - c1.add_storage("/some/path", cifs_config) - - -def test_add_cifs_volume(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_inner_config = {"server": "server", "path": "/path", "username": "user", "password": "pas$word"} - cifs_config = {"type": "cifs", "cifs_config": cifs_inner_config} - c1.add_storage("/some/path", cifs_config) - output = render.render() - vol_name = get_hashed_name_for_volume("cifs", cifs_inner_config) - assert output["volumes"] == { - vol_name: {"driver_opts": {"type": "cifs", "device": "//server/path", "o": "password=pas$$word,user=user"}} - } - assert output["services"]["test_container"]["volumes"] == [ - {"type": "volume", "source": vol_name, "target": "/some/path", "read_only": False, "volume": {"nocopy": False}} - ] - - -def test_cifs_volume_with_options(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - cifs_inner_config = { - "server": "server", - "path": "/path", - "username": "user", - "password": "pas$word", - "options": ["vers=3.0", "verbose=true"], - } - cifs_config = {"type": "cifs", "cifs_config": cifs_inner_config} - c1.add_storage("/some/path", cifs_config) - output = render.render() - vol_name = get_hashed_name_for_volume("cifs", cifs_inner_config) - assert output["volumes"] == { - vol_name: { - "driver_opts": { - "type": "cifs", - "device": "//server/path", - "o": "password=pas$$word,user=user,verbose=true,vers=3.0", - } - } - } - assert output["services"]["test_container"]["volumes"] == [ - {"type": "volume", "source": vol_name, "target": "/some/path", "read_only": False, "volume": {"nocopy": False}} - ] - - -def test_nfs_volume_missing_server(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_config = {"type": "nfs", "nfs_config": {"path": "/path"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", nfs_config) - - -def test_nfs_volume_missing_path(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_config = {"type": "nfs", "nfs_config": {"server": "server"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", nfs_config) - - -def test_nfs_volume_without_nfs_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_config = {"type": "nfs"} - with pytest.raises(Exception): - c1.add_storage("/some/path", nfs_config) - - -def test_nfs_volume_duplicate_option(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_config = { - "type": "nfs", - "nfs_config": {"server": "server", "path": "/path", "options": ["verbose=true", "verbose=true"]}, - } - with pytest.raises(Exception): - c1.add_storage("/some/path", nfs_config) - - -def test_nfs_volume_disallowed_option(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_config = {"type": "nfs", "nfs_config": {"server": "server", "path": "/path", "options": ["addr=server"]}} - with pytest.raises(Exception): - c1.add_storage("/some/path", nfs_config) - - -def test_nfs_volume_invalid_options(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_config = {"type": "nfs", "nfs_config": {"server": "server", "path": "/path", "options": {"verbose": True}}} - with pytest.raises(Exception): - c1.add_storage("/some/path", nfs_config) - - -def test_nfs_volume_invalid_options2(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_config = {"type": "nfs", "nfs_config": {"server": "server", "path": "/path", "options": [{"verbose": True}]}} - with pytest.raises(Exception): - c1.add_storage("/some/path", nfs_config) - - -def test_add_nfs_volume(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_inner_config = {"server": "server", "path": "/path"} - nfs_config = {"type": "nfs", "nfs_config": nfs_inner_config} - c1.add_storage("/some/path", nfs_config) - output = render.render() - vol_name = get_hashed_name_for_volume("nfs", nfs_inner_config) - assert output["volumes"] == {vol_name: {"driver_opts": {"type": "nfs", "device": ":/path", "o": "addr=server"}}} - assert output["services"]["test_container"]["volumes"] == [ - {"type": "volume", "source": vol_name, "target": "/some/path", "read_only": False, "volume": {"nocopy": False}} - ] - - -def test_nfs_volume_with_options(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - nfs_inner_config = {"server": "server", "path": "/path", "options": ["vers=3.0", "verbose=true"]} - nfs_config = {"type": "nfs", "nfs_config": nfs_inner_config} - c1.add_storage("/some/path", nfs_config) - output = render.render() - vol_name = get_hashed_name_for_volume("nfs", nfs_inner_config) - assert output["volumes"] == { - vol_name: { - "driver_opts": { - "type": "nfs", - "device": ":/path", - "o": "addr=server,verbose=true,vers=3.0", - } - } - } - assert output["services"]["test_container"]["volumes"] == [ - {"type": "volume", "source": vol_name, "target": "/some/path", "read_only": False, "volume": {"nocopy": False}} - ] - - -def test_tmpfs_invalid_size(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "tmpfs", "tmpfs_config": {"size": "2"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", vol_config) - - -def test_tmpfs_zero_size(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "tmpfs", "tmpfs_config": {"size": 0}} - with pytest.raises(Exception): - c1.add_storage("/some/path", vol_config) - - -def test_tmpfs_invalid_mode(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "tmpfs", "tmpfs_config": {"mode": "invalid"}} - with pytest.raises(Exception): - c1.add_storage("/some/path", vol_config) - - -def test_tmpfs_volume(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "tmpfs"} - c1.add_storage("/some/path", vol_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "tmpfs", - "target": "/some/path", - "read_only": False, - } - ] - - -def test_temporary_volume(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "temporary", "volume_config": {"volume_name": "test_temp_volume"}} - c1.add_storage("/some/path", vol_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - {"type": "volume", "target": "/some/path", "read_only": False, "volume": {"nocopy": False}} - ] - - -def test_docker_volume_missing_config(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "volume", "volume_config": {}} - with pytest.raises(Exception): - c1.add_storage("/some/path", vol_config) - - -def test_docker_volume_missing_volume_name(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "volume", "volume_config": {"volume_name": ""}} - with pytest.raises(Exception): - c1.add_storage("/some/path", vol_config) - - -def test_docker_volume(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "volume", "volume_config": {"volume_name": "test_volume"}} - c1.add_storage("/some/path", vol_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - { - "type": "volume", - "source": "test_volume", - "target": "/some/path", - "read_only": False, - "volume": {"nocopy": False}, - } - ] - assert output["volumes"] == {"test_volume": {}} - - -def test_anonymous_volume(mock_values): - render = Render(mock_values) - c1 = render.add_container("test_container", "test_image") - c1.healthcheck.disable() - vol_config = {"type": "anonymous", "volume_config": {"nocopy": True}} - c1.add_storage("/some/path", vol_config) - output = render.render() - assert output["services"]["test_container"]["volumes"] == [ - {"type": "volume", "target": "/some/path", "read_only": False, "volume": {"nocopy": True}} - ] - assert "volumes" not in output diff --git a/library/2.0.5/validations.py b/library/2.0.5/validations.py deleted file mode 100644 index 424ec9488a..0000000000 --- a/library/2.0.5/validations.py +++ /dev/null @@ -1,196 +0,0 @@ -import re -import ipaddress - -try: - from .error import RenderError -except ImportError: - from error import RenderError - -OCTAL_MODE_REGEX = re.compile(r"^0[0-7]{3}$") - - -def valid_octal_mode_or_raise(mode: str): - mode = str(mode) - if not OCTAL_MODE_REGEX.match(mode): - raise RenderError(f"Expected [mode] to be a octal string, got [{mode}]") - return mode - - -def valid_host_path_propagation(propagation: str): - valid_propagations = ("shared", "slave", "private", "rshared", "rslave", "rprivate") - if propagation not in valid_propagations: - raise RenderError(f"Expected [propagation] to be one of [{', '.join(valid_propagations)}], got [{propagation}]") - return propagation - - -def valid_portal_scheme_or_raise(scheme: str): - schemes = ("http", "https") - if scheme not in schemes: - raise RenderError(f"Portal Scheme [{scheme}] is not valid. Valid options are: [{', '.join(schemes)}]") - return scheme - - -def valid_port_or_raise(port: int): - if port < 1 or port > 65535: - raise RenderError(f"Invalid port [{port}]. Valid ports are between 1 and 65535") - return port - - -def valid_ip_or_raise(ip: str): - try: - ipaddress.ip_address(ip) - except ValueError: - raise RenderError(f"Invalid IP address [{ip}]") - return ip - - -def valid_port_mode_or_raise(mode: str): - modes = ("ingress", "host") - if mode not in modes: - raise RenderError(f"Port Mode [{mode}] is not valid. Valid options are: [{', '.join(modes)}]") - return mode - - -def valid_port_protocol_or_raise(protocol: str): - protocols = ("tcp", "udp") - if protocol not in protocols: - raise RenderError(f"Port Protocol [{protocol}] is not valid. Valid options are: [{', '.join(protocols)}]") - return protocol - - -def valid_depend_condition_or_raise(condition: str): - valid_conditions = ("service_started", "service_healthy", "service_completed_successfully") - if condition not in valid_conditions: - raise RenderError( - f"Depend Condition [{condition}] is not valid. Valid options are: [{', '.join(valid_conditions)}]" - ) - return condition - - -def valid_cgroup_perm_or_raise(cgroup_perm: str): - valid_cgroup_perms = ("r", "w", "m", "rw", "rm", "wm", "rwm", "") - if cgroup_perm not in valid_cgroup_perms: - raise RenderError( - f"Cgroup Permission [{cgroup_perm}] is not valid. Valid options are: [{', '.join(valid_cgroup_perms)}]" - ) - return cgroup_perm - - -def allowed_dns_opt_or_raise(dns_opt: str): - disallowed_dns_opts = [] - if dns_opt in disallowed_dns_opts: - raise RenderError(f"DNS Option [{dns_opt}] is not allowed to added.") - return dns_opt - - -def valid_http_path_or_raise(path: str): - path = _valid_path_or_raise(path) - return path - - -def valid_fs_path_or_raise(path: str): - # There is no reason to allow / as a path, - # either on host or in a container side. - if path == "/": - raise RenderError(f"Path [{path}] cannot be [/]") - path = _valid_path_or_raise(path) - return path - - -def _valid_path_or_raise(path: str): - if path == "": - raise RenderError(f"Path [{path}] cannot be empty") - if not path.startswith("/"): - raise RenderError(f"Path [{path}] must start with /") - if "//" in path: - raise RenderError(f"Path [{path}] cannot contain [//]") - return path - - -def allowed_device_or_raise(path: str): - disallowed_devices = ["/dev/dri"] - if path in disallowed_devices: - raise RenderError(f"Device [{path}] is not allowed to be manually added.") - return path - - -def valid_network_mode_or_raise(mode: str, containers: list[str]): - valid_modes = ("host", "none") - if mode in valid_modes: - return mode - - if mode.startswith("service:"): - if mode[8:] not in containers: - raise RenderError(f"Service [{mode[8:]}] not found") - return mode - - raise RenderError( - f"Invalid network mode [{mode}]. Valid options are: [{', '.join(valid_modes)}] or [service:]" - ) - - -def valid_restart_policy_or_raise(policy: str, maximum_retry_count: int = 0): - valid_restart_policies = ("always", "on-failure", "unless-stopped", "no") - if policy not in valid_restart_policies: - raise RenderError( - f"Restart policy [{policy}] is not valid. Valid options are: [{', '.join(valid_restart_policies)}]" - ) - if policy != "on-failure" and maximum_retry_count != 0: - raise RenderError("Maximum retry count can only be set for [on-failure] restart policy") - - if maximum_retry_count < 0: - raise RenderError("Maximum retry count must be a positive integer") - - return policy - - -def valid_cap_or_raise(cap: str): - valid_policies = ( - "ALL", - "AUDIT_CONTROL", - "AUDIT_READ", - "AUDIT_WRITE", - "BLOCK_SUSPEND", - "BPF", - "CHECKPOINT_RESTORE", - "CHOWN", - "DAC_OVERRIDE", - "DAC_READ_SEARCH", - "FOWNER", - "FSETID", - "IPC_LOCK", - "IPC_OWNER", - "KILL", - "LEASE", - "LINUX_IMMUTABLE", - "MAC_ADMIN", - "MAC_OVERRIDE", - "MKNOD", - "NET_ADMIN", - "NET_BIND_SERVICE", - "NET_BROADCAST", - "NET_RAW", - "PERFMON", - "SETFCAP", - "SETGID", - "SETPCAP", - "SETUID", - "SYS_ADMIN", - "SYS_BOOT", - "SYS_CHROOT", - "SYS_MODULE", - "SYS_NICE", - "SYS_PACCT", - "SYS_PTRACE", - "SYS_RAWIO", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "SYSLOG", - "WAKE_ALARM", - ) - - if cap not in valid_policies: - raise RenderError(f"Capability [{cap}] is not valid. " f"Valid options are: [{', '.join(valid_policies)}]") - - return cap diff --git a/library/2.0.5/volume_mount.py b/library/2.0.5/volume_mount.py deleted file mode 100644 index d0894ac3e0..0000000000 --- a/library/2.0.5/volume_mount.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - from storage import IxStorage - -try: - from .error import RenderError - from .formatter import merge_dicts_no_overwrite - from .volume_mount_types import BindMountType, VolumeMountType, TmpfsMountType - from .volume_sources import HostPathSource, IxVolumeSource, CifsSource, NfsSource, VolumeSource -except ImportError: - from error import RenderError - from formatter import merge_dicts_no_overwrite - from volume_mount_types import BindMountType, VolumeMountType, TmpfsMountType - from volume_sources import HostPathSource, IxVolumeSource, CifsSource, NfsSource, VolumeSource - - -class VolumeMount: - def __init__(self, render_instance: "Render", mount_path: str, config: "IxStorage"): - self._render_instance = render_instance - self.mount_path: str = mount_path - - storage_type: str = config.get("type", "") - if not storage_type: - raise RenderError("Expected [type] to be set for volume mounts.") - - match storage_type: - case "host_path": - spec_type = "bind" - mount_config = config.get("host_path_config") - assert mount_config is not None - mount_type_specific_definition = BindMountType(self._render_instance, mount_config).render() - source = HostPathSource(self._render_instance, mount_config).get() - case "ix_volume": - spec_type = "bind" - mount_config = config.get("ix_volume_config") - assert mount_config is not None - mount_type_specific_definition = BindMountType(self._render_instance, mount_config).render() - source = IxVolumeSource(self._render_instance, mount_config).get() - case "tmpfs": - spec_type = "tmpfs" - mount_config = config.get("tmpfs_config", {}) - mount_type_specific_definition = TmpfsMountType(self._render_instance, mount_config).render() - source = None - case "nfs": - spec_type = "volume" - mount_config = config.get("nfs_config") - assert mount_config is not None - mount_type_specific_definition = VolumeMountType(self._render_instance, mount_config).render() - source = NfsSource(self._render_instance, mount_config).get() - case "cifs": - spec_type = "volume" - mount_config = config.get("cifs_config") - assert mount_config is not None - mount_type_specific_definition = VolumeMountType(self._render_instance, mount_config).render() - source = CifsSource(self._render_instance, mount_config).get() - case "volume": - spec_type = "volume" - mount_config = config.get("volume_config") - assert mount_config is not None - mount_type_specific_definition = VolumeMountType(self._render_instance, mount_config).render() - source = VolumeSource(self._render_instance, mount_config).get() - case "temporary" | "anonymous": - spec_type = "volume" - mount_config = config.get("volume_config") - assert mount_config is not None - mount_type_specific_definition = VolumeMountType(self._render_instance, mount_config).render() - source = None - case _: - raise RenderError(f"Storage type [{storage_type}] is not supported for volume mounts.") - - common_spec = {"type": spec_type, "target": self.mount_path, "read_only": config.get("read_only", False)} - if source is not None: - common_spec["source"] = source - self._render_instance.volumes.add_volume(source, storage_type, mount_config) # type: ignore - - self.volume_mount_spec = merge_dicts_no_overwrite(common_spec, mount_type_specific_definition) - - def render(self) -> dict: - return self.volume_mount_spec diff --git a/library/2.0.5/volume_mount_types.py b/library/2.0.5/volume_mount_types.py deleted file mode 100644 index 00a0ec3a18..0000000000 --- a/library/2.0.5/volume_mount_types.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - from storage import IxStorageTmpfsConfig, IxStorageVolumeConfig, IxStorageBindLikeConfigs - - -try: - from .error import RenderError - from .validations import valid_host_path_propagation, valid_octal_mode_or_raise -except ImportError: - from error import RenderError - from validations import valid_host_path_propagation, valid_octal_mode_or_raise - - -class TmpfsMountType: - def __init__(self, render_instance: "Render", config: "IxStorageTmpfsConfig"): - self._render_instance = render_instance - self.spec = {"tmpfs": {}} - size = config.get("size", None) - mode = config.get("mode", None) - - if size is not None: - if not isinstance(size, int): - raise RenderError(f"Expected [size] to be an integer for [tmpfs] type, got [{size}]") - if not size > 0: - raise RenderError(f"Expected [size] to be greater than 0 for [tmpfs] type, got [{size}]") - # Convert Mebibytes to Bytes - self.spec["tmpfs"]["size"] = size * 1024 * 1024 - - if mode is not None: - mode = valid_octal_mode_or_raise(mode) - self.spec["tmpfs"]["mode"] = int(mode, 8) - - if not self.spec["tmpfs"]: - self.spec.pop("tmpfs") - - def render(self) -> dict: - """Render the tmpfs mount specification.""" - return self.spec - - -class BindMountType: - def __init__(self, render_instance: "Render", config: "IxStorageBindLikeConfigs"): - self._render_instance = render_instance - self.spec: dict = {} - - propagation = valid_host_path_propagation(config.get("propagation", "rprivate")) - create_host_path = config.get("create_host_path", False) - - self.spec: dict = { - "bind": { - "create_host_path": create_host_path, - "propagation": propagation, - } - } - - def render(self) -> dict: - """Render the bind mount specification.""" - return self.spec - - -class VolumeMountType: - def __init__(self, render_instance: "Render", config: "IxStorageVolumeConfig"): - self._render_instance = render_instance - self.spec: dict = {} - - self.spec: dict = {"volume": {"nocopy": config.get("nocopy", False)}} - - def render(self) -> dict: - """Render the volume mount specification.""" - return self.spec diff --git a/library/2.0.5/volume_sources.py b/library/2.0.5/volume_sources.py deleted file mode 100644 index c33fe55ea1..0000000000 --- a/library/2.0.5/volume_sources.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - from storage import IxStorageHostPathConfig, IxStorageIxVolumeConfig, IxStorageVolumeConfig - -try: - from .error import RenderError - from .formatter import get_hashed_name_for_volume - from .validations import valid_fs_path_or_raise -except ImportError: - from error import RenderError - from formatter import get_hashed_name_for_volume - from validations import valid_fs_path_or_raise - - -class HostPathSource: - def __init__(self, render_instance: "Render", config: "IxStorageHostPathConfig"): - self._render_instance = render_instance - self.source: str = "" - - if not config: - raise RenderError("Expected [host_path_config] to be set for [host_path] type.") - - path = "" - if config.get("acl_enable", False): - acl_path = config.get("acl", {}).get("path") - if not acl_path: - raise RenderError("Expected [host_path_config.acl.path] to be set for [host_path] type.") - path = valid_fs_path_or_raise(acl_path) - else: - path = valid_fs_path_or_raise(config.get("path", "")) - - self.source = path.rstrip("/") - - def get(self): - return self.source - - -class IxVolumeSource: - def __init__(self, render_instance: "Render", config: "IxStorageIxVolumeConfig"): - self._render_instance = render_instance - self.source: str = "" - - if not config: - raise RenderError("Expected [ix_volume_config] to be set for [ix_volume] type.") - dataset_name = config.get("dataset_name") - if not dataset_name: - raise RenderError("Expected [ix_volume_config.dataset_name] to be set for [ix_volume] type.") - - ix_volumes = self._render_instance.values.get("ix_volumes", {}) - if dataset_name not in ix_volumes: - available = ", ".join(ix_volumes.keys()) - raise RenderError( - f"Expected the key [{dataset_name}] to be set in [ix_volumes] for [ix_volume] type. " - f"Available keys: [{available}]." - ) - - self.source = valid_fs_path_or_raise(ix_volumes[dataset_name].rstrip("/")) - - def get(self): - return self.source - - -class CifsSource: - def __init__(self, render_instance: "Render", config: dict): - self._render_instance = render_instance - self.source: str = "" - - if not config: - raise RenderError("Expected [cifs_config] to be set for [cifs] type.") - self.source = get_hashed_name_for_volume("cifs", config) - - def get(self): - return self.source - - -class NfsSource: - def __init__(self, render_instance: "Render", config: dict): - self._render_instance = render_instance - self.source: str = "" - - if not config: - raise RenderError("Expected [nfs_config] to be set for [nfs] type.") - self.source = get_hashed_name_for_volume("nfs", config) - - def get(self): - return self.source - - -class VolumeSource: - def __init__(self, render_instance: "Render", config: "IxStorageVolumeConfig"): - self._render_instance = render_instance - self.source: str = "" - - if not config: - raise RenderError("Expected [volume_config] to be set for [volume] type.") - - volume_name: str = config.get("volume_name", "") - if not volume_name: - raise RenderError("Expected [volume_config.volume_name] to be set for [volume] type.") - - self.source = volume_name - - def get(self): - return self.source diff --git a/library/2.0.5/volume_types.py b/library/2.0.5/volume_types.py deleted file mode 100644 index c2282937dd..0000000000 --- a/library/2.0.5/volume_types.py +++ /dev/null @@ -1,132 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - from storage import IxStorageNfsConfig, IxStorageCifsConfig, IxStorageVolumeConfig - - -try: - from .error import RenderError - from .formatter import escape_dollar - from .validations import valid_fs_path_or_raise -except ImportError: - from error import RenderError - from formatter import escape_dollar - from validations import valid_fs_path_or_raise - - -class NfsVolume: - def __init__(self, render_instance: "Render", config: "IxStorageNfsConfig"): - self._render_instance = render_instance - - if not config: - raise RenderError("Expected [nfs_config] to be set for [nfs] type") - - required_keys = ["server", "path"] - for key in required_keys: - if not config.get(key): - raise RenderError(f"Expected [{key}] to be set for [nfs] type") - - opts = [f"addr={config['server']}"] - cfg_options = config.get("options") - if cfg_options: - if not isinstance(cfg_options, list): - raise RenderError("Expected [nfs_config.options] to be a list for [nfs] type") - - tracked_keys: set[str] = set() - disallowed_opts = ["addr"] - for opt in cfg_options: - if not isinstance(opt, str): - raise RenderError("Options for [nfs] type must be a list of strings.") - - key = opt.split("=")[0] - if key in tracked_keys: - raise RenderError(f"Option [{key}] already added for [nfs] type.") - if key in disallowed_opts: - raise RenderError(f"Option [{key}] is not allowed for [nfs] type.") - opts.append(opt) - tracked_keys.add(key) - - opts.sort() - - path = valid_fs_path_or_raise(config["path"].rstrip("/")) - self.volume_spec = { - "driver_opts": { - "type": "nfs", - "device": f":{path}", - "o": f"{','.join([escape_dollar(opt) for opt in opts])}", - }, - } - - def get(self): - return self.volume_spec - - -class CifsVolume: - def __init__(self, render_instance: "Render", config: "IxStorageCifsConfig"): - self._render_instance = render_instance - self.volume_spec: dict = {} - - if not config: - raise RenderError("Expected [cifs_config] to be set for [cifs] type") - - required_keys = ["server", "path", "username", "password"] - for key in required_keys: - if not config.get(key): - raise RenderError(f"Expected [{key}] to be set for [cifs] type") - - opts = [ - f"user={config['username']}", - f"password={config['password']}", - ] - - domain = config.get("domain") - if domain: - opts.append(f"domain={domain}") - - cfg_options = config.get("options") - if cfg_options: - if not isinstance(cfg_options, list): - raise RenderError("Expected [cifs_config.options] to be a list for [cifs] type") - - tracked_keys: set[str] = set() - disallowed_opts = ["user", "password", "domain"] - for opt in cfg_options: - if not isinstance(opt, str): - raise RenderError("Options for [cifs] type must be a list of strings.") - - key = opt.split("=")[0] - if key in tracked_keys: - raise RenderError(f"Option [{key}] already added for [cifs] type.") - if key in disallowed_opts: - raise RenderError(f"Option [{key}] is not allowed for [cifs] type.") - for disallowed in disallowed_opts: - if key == disallowed: - raise RenderError(f"Option [{key}] is not allowed for [cifs] type.") - opts.append(opt) - tracked_keys.add(key) - opts.sort() - - server = config["server"].lstrip("/") - path = config["path"].strip("/") - path = valid_fs_path_or_raise("/" + path).lstrip("/") - - self.volume_spec = { - "driver_opts": { - "type": "cifs", - "device": f"//{server}/{path}", - "o": f"{','.join([escape_dollar(opt) for opt in opts])}", - }, - } - - def get(self): - return self.volume_spec - - -class DockerVolume: - def __init__(self, render_instance: "Render", config: "IxStorageVolumeConfig"): - self._render_instance = render_instance - self.volume_spec: dict = {} - - def get(self): - return self.volume_spec diff --git a/library/2.0.5/volumes.py b/library/2.0.5/volumes.py deleted file mode 100644 index e6925a402f..0000000000 --- a/library/2.0.5/volumes.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from render import Render - - -try: - from .error import RenderError - from .storage import IxStorageVolumeLikeConfigs - from .volume_types import NfsVolume, CifsVolume, DockerVolume -except ImportError: - from error import RenderError - from storage import IxStorageVolumeLikeConfigs - from volume_types import NfsVolume, CifsVolume, DockerVolume - - -class Volumes: - def __init__(self, render_instance: "Render"): - self._render_instance = render_instance - self._volumes: dict[str, Volume] = {} - - def add_volume( - self, - source: str, - storage_type: str, - config: "IxStorageVolumeLikeConfigs", - ): - # This method can be called many times from the volume mounts - # Only add the volume if it is not already added, but dont raise an error - if source == "": - raise RenderError(f"Volume source [{source}] cannot be empty") - - if source in self._volumes: - return - - self._volumes[source] = Volume(self._render_instance, storage_type, config) - - def has_volumes(self) -> bool: - return bool(self._volumes) - - def render(self): - return {name: v.render() for name, v in sorted(self._volumes.items()) if v.render() is not None} - - -class Volume: - def __init__( - self, - render_instance: "Render", - storage_type: str, - config: "IxStorageVolumeLikeConfigs", - ): - self._render_instance = render_instance - self.volume_spec: dict | None = {} - - match storage_type: - case "nfs": - self.volume_spec = NfsVolume(self._render_instance, config).get() # type: ignore - case "cifs": - self.volume_spec = CifsVolume(self._render_instance, config).get() # type: ignore - case "volume" | "temporary": - self.volume_spec = DockerVolume(self._render_instance, config).get() # type: ignore - case _: - self.volume_spec = None - - def render(self): - return self.volume_spec