Author SHA1 Message Date
Christian Manivong a97c460cc3 feat: name no warnings and report no package proxy (0.5.0)
CI / test (3.10) (push) Failing after 19s
CI / test (3.11) (push) Failing after 17s
CI / test (3.12) (push) Failing after 18s
CI / test (3.10) (pull_request) Failing after 18s
CI / test (3.11) (pull_request) Failing after 17s
CI / test (3.12) (pull_request) Failing after 19s
napalm-linux 0.9.0 names no warnings (netOrk #1111), so the override that
kept the inherited get_device_warnings from asking for updates goes. The
package proxy it now reports is switched off with the template that sets
it: QTS has no apt. Requires napalm-linux 0.9.0.
2026-10-17 09:44:51 +02:00
christianmanivong 375530ac1c Merge pull request 'feat: switch off the fix templates QTS cannot take (0.4.0)' (#8) from feat/opt-out-fixes into main
CI / test (3.10) (push) Successful in 29s
CI / test (3.11) (push) Successful in 29s
CI / test (3.12) (push) Successful in 33s
2026-10-16 23:22:10 +00:00
Christian Manivong 95ec6ad33b feat: switch off the fix templates QTS cannot take (0.4.0)
CI / test (3.10) (push) Successful in 38s
CI / test (3.11) (push) Successful in 35s
CI / test (3.12) (push) Successful in 35s
CI / test (3.10) (pull_request) Successful in 31s
CI / test (3.11) (pull_request) Successful in 30s
CI / test (3.12) (pull_request) Successful in 32s
napalm-linux 0.8 implements napalm-device-types' fix templates. QTS has
no apt, dnf or opkg and sets its SNMP agent in its own UI, so
configure_snmp_agent, configure_package_proxy and ensure_package are
None here: a capability check answers no instead of offering a fix
that fails, as the inherited fix_snmp and fix_apt_proxy did.

get_device_warnings returns nothing: the inherited one asked for
updates, which is None here, and logged a TypeError on every poll.

The role test follows napalm-linux's get_os_users.

Closes #6. Part of #7.
2026-10-17 01:17:37 +02:00
christianmanivong 088d47f47c Merge pull request 'refactor!: drop the _docker_bin override' (#5) from chore/drop-legacy-docker into main
CI / test (3.10) (push) Successful in 28s
CI / test (3.11) (push) Successful in 26s
CI / test (3.12) (push) Successful in 29s
2026-10-08 06:48:37 +00:00
Christian Manivong 912469bdc0 refactor!: drop the _docker_bin override
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 27s
CI / test (3.12) (pull_request) Successful in 29s
CI / test (3.10) (push) Successful in 28s
CI / test (3.11) (push) Successful in 26s
CI / test (3.12) (push) Successful in 28s
napalm-linux 0.3.0 drops the Docker CLI methods the override served.
_container_engine_binary("docker") returns Container Station's path
itself, which is all netOrk needs: it reaches the engine through
open_container_engine (NetOrk/netork#765, phase 9).

Requires napalm-linux >= 0.3.0. Version 0.3.0.
2026-10-08 08:43:57 +02:00
christianmanivong 39532471b7 Merge pull request 'feat: hand the container engine connection Container Station's docker' (#4) from feat/container-engine-binary into main
CI / test (3.10) (push) Successful in 27s
CI / test (3.11) (push) Successful in 26s
CI / test (3.12) (push) Successful in 29s
2026-10-07 16:10:44 +00:00
Christian Manivong 5838685690 feat: hand the container engine connection Container Station's docker
CI / test (3.11) (pull_request) Successful in 27s
CI / test (3.12) (pull_request) Successful in 30s
CI / test (3.10) (push) Successful in 29s
CI / test (3.11) (push) Successful in 27s
CI / test (3.12) (push) Successful in 28s
CI / test (3.10) (pull_request) Successful in 29s
napalm-device-types 2.6.0 opens a host's container engine through
`open_container_engine()`, and asks the driver for the binary through
`_container_engine_binary()`. Under QTS docker is not on PATH; the hook
now returns the path `open()` already discovers, so the Engine API stream
(`<path> system dial-stdio`) and the CLI reach Container Station while
callers only pass arguments (NetOrk/netork#765).

Version 0.2.0, requires napalm-device-types >= 2.6.0 and
napalm-linux >= 0.2.0.

Refs NAPALM/napalm-device-types#17
2026-10-07 17:59:55 +02:00
christianmanivong b868f53990 Merge pull request 'ci: run the tests and build the package on every push and pull request' (#3) from ci/workflow into main
CI / test (3.10) (push) Successful in 29s
CI / test (3.11) (push) Successful in 27s
CI / test (3.12) (push) Successful in 29s
2026-10-07 06:37:00 +00:00
Christian Manivong 8ad1c66ace ci: run the tests and build the package on every push and pull request
CI / test (3.10) (push) Successful in 27s
CI / test (3.11) (push) Successful in 26s
CI / test (3.12) (push) Successful in 28s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 27s
CI / test (3.12) (pull_request) Successful in 30s
The same job as napalm-fritzbox and napalm-opnsense: Python 3.10, 3.11 and
3.12, pytest, then wheel and sdist. napalm-device-types comes from
git.netork.io first, because PyPI has an unrelated package of that name.

napalm-linux, which this driver builds on, is installed from git as well:
it is not on PyPI either.
2026-10-07 07:52:44 +02:00
christianmanivong 0f4b4f56c4 Merge pull request 'fix: do not claim update reading or applying QTS cannot do' (#2) from fix/no-update-reader into main 2026-10-05 22:20:13 +00:00
Christian Manivong 5e371db3af fix: do not claim update reading or applying QTS cannot do
QTS has no apt, dnf or opkg, so the update readers inherited from
LinuxDriver find no package manager, and QPKG updates are not implemented.
get_available_updates, refresh_available_updates and apply_updates are None,
the same opt-out as manage_service: netOrk's patch groups and compliance
report include a driver only when these are callable.
2026-10-06 00:20:12 +02:00
christianmanivong 872e5718a3 Merge pull request 'fix: do not claim service control QTS cannot do' (#1) from fix/no-service-control into main 2026-10-05 11:12:16 +00:00
6 changed files with 167 additions and 15 deletions

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
name: CI
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install package with dev extras
run: |
python -m pip install --upgrade pip
# napalm-device-types lives in git.netork.io/NAPALM, not on PyPI: without this
# pip looks there, finds an unrelated 0.1.0 and the job dies before any test.
python -m pip install "napalm-device-types @ git+https://git.netork.io/NAPALM/napalm-device-types.git"
# napalm-linux, which this driver builds on, is not on PyPI either.
python -m pip install "napalm-linux @ git+https://git.netork.io/NAPALM/napalm-linux.git"
python -m pip install -e ".[dev]"
- name: Run unit tests
run: |
python -m pytest -q --tb=short
- name: Build wheel and sdist
run: |
python -m pip install build
python -m build
- name: Upload dist artifacts
# v4 refuses to run on Gitea ("not currently supported on GHES").
uses: actions/upload-artifact@v3
with:
name: dist-${{ matrix.python-version }}
path: dist/*
+35
View File
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.4.0] – 2026-10-17
### Changed
- Requires napalm-linux 0.8.0, whose fixes are napalm-device-types' templates.
`configure_snmp_agent`, `configure_package_proxy` and `ensure_package` are switched
off (`None`): QTS has no apt, dnf or opkg, and its SNMP agent is set in its own UI.
Before, the inherited SNMP and apt-proxy fixes were offered and failed.
### Fixed
- `get_device_warnings` no longer logs a TypeError on every poll: the inherited one
asked for updates, which QTS cannot list (#6).
## [0.3.0] – 2026-10-08
### Removed
- The `_docker_bin()` override: napalm-linux 0.3.0 drops the Docker CLI
methods it served. `_container_engine_binary("docker")` returns Container
Station's path itself.
### Changed
- Requires napalm-linux >= 0.3.0.
## [0.2.0] – 2026-10-07
### Added
- `_container_engine_binary` returns Container Station's docker path, so the
container engine connection from napalm-device-types 2.6.0
(`open_container_engine("docker")`) reaches it although it is not on PATH.
Callers pass arguments only and never see the path (NetOrk/netork#765).
### Changed
- Requires napalm-device-types >= 2.6.0 and napalm-linux >= 0.2.0.
## [0.1.0]
### Added
- Initial driver scaffold: `QnapQtsDriver(StorageDriver, LinuxDriver)` with
discovery fingerprints (SNMP enterprise OID 1.3.6.1.4.1.24681, HTTP, port
+5 -4
View File
@@ -32,7 +32,7 @@ output is how a driver ends up passing its own tests and failing on a real NAS.
| `start_vm`, `stop_vm`, `reboot_vm` | `virsh` | pending harvest |
| `set_service_enabled` | `setcfg`, `/etc/init.d` | pending harvest |
| `get_device_warnings` | derived from the above | pending harvest |
| `get_docker_info` | inherited, via `_docker_bin()` | ✅ |
| `container_engines`, `open_container_engine` | Container Station's docker, `system dial-stdio` | ✅ |
| `get_services`, `get_users`, `get_processes` | inherited from `LinuxDriver` | ✅ |
| `get_health_metrics` | inherited (UCD-MIB over SNMP) | ✅ |
| `ping`, `ping_sweep` | inherited from `LinuxDriver` | ✅ |
@@ -94,9 +94,10 @@ QNAP running Virtualization Station is a NAS *and* a hypervisor, and
### Docker
Container Station does not put `docker` on `PATH`; it lives under
`/share/<pool>/.qpkg/container-station/`. The Docker *logic* stays in
`LinuxDriver` and only the path is overridden here, via the `_docker_bin()`
hook.
`/share/<pool>/.qpkg/container-station/`. This driver only says where:
`_container_engine_binary("docker")` returns the path it found, and
`open_container_engine("docker")` from napalm-device-types reaches the Engine
API through it. What runs on the engine is netOrk's (NetOrk/netork#765).
## Harvesting fixtures
+17 -3
View File
@@ -73,6 +73,18 @@ class QnapQtsDriver(StorageDriver, HypervisorDriver, LinuxDriver):
#: makes every "can it control services?" check -- is ``manage_service``
#: callable -- answer no, rather than offering buttons that cannot work.
manage_service = None # type: ignore[assignment]
#: The same for updates: QTS has no apt, dnf or opkg, and QPKG updates are
#: not implemented. netOrk leaves a driver without them out of patching.
get_available_updates = None # type: ignore[assignment]
refresh_available_updates = None # type: ignore[assignment]
apply_updates = None # type: ignore[assignment]
#: The fix templates LinuxDriver carries, and the package proxy it reports,
#: need a package manager or net-snmp from packages; QTS has neither, and
#: its SNMP agent is set in its own UI.
configure_snmp_agent = None # type: ignore[assignment]
configure_package_proxy = None # type: ignore[assignment]
get_package_proxy = None # type: ignore[assignment]
ensure_package = None # type: ignore[assignment]
SNMP_OBJECT_ID_PREFIX = QNAP_ENTERPRISE_OID
SNMP_FINGERPRINT = [
@@ -145,9 +157,11 @@ class QnapQtsDriver(StorageDriver, HypervisorDriver, LinuxDriver):
return None
return found.splitlines()[0].strip() or None if found else None
def _docker_bin(self) -> str:
"""Override LinuxDriver's hook: docker is not on PATH under QTS."""
return getattr(self, "_docker_path", None) or "docker"
def _container_engine_binary(self, engine: str) -> str:
"""Container Station keeps docker off PATH; callers never see the path."""
if engine == "docker":
return getattr(self, "_docker_path", None) or "docker"
return super()._container_engine_binary(engine)
# ── QPKG, not apt ─────────────────────────────────────────────────────────
#
+3 -3
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "napalm-qnap-qts"
version = "0.1.0"
version = "0.5.0"
description = "NAPALM driver for QNAP NAS systems running QTS via SSH"
readme = "README.md"
requires-python = ">=3.9"
@@ -43,8 +43,8 @@ classifiers = [
# processes, Docker) from LinuxDriver rather than reimplementing it.
dependencies = [
"napalm>=4.0",
"napalm-device-types>=0.5.0",
"napalm-linux>=0.1.0",
"napalm-device-types>=2.6.0",
"napalm-linux>=0.9.0",
"netmiko>=4.0.0",
"paramiko>=5.0.0", # CVE-2026-44405
]
+57 -5
View File
@@ -10,8 +10,8 @@ from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
from napalm_device_types import primary_role_of, role_keys_of
from napalm_qnap_qts import QnapQtsDriver
#: Parsers need real command output to be written against. These tests are the
@@ -97,7 +97,7 @@ class TestRolesDoNotShadowLinux:
collision. There is nothing left to collide with.
"""
@pytest.mark.parametrize("method", ["get_services", "get_users", "get_docker_info"])
@pytest.mark.parametrize("method", ["get_services", "get_os_users"])
def test_os_surface_resolves_to_linux(self, method):
"""QTS really is Linux for these, so inheriting them is correct."""
from napalm_linux.linux import LinuxDriver
@@ -179,9 +179,31 @@ class TestDockerBinDiscovery:
with patch.object(driver, "_send", return_value=""):
assert driver._discover_docker_path() == "docker"
def test_docker_bin_hook_returns_the_discovered_path(self, driver):
driver._docker_path = "/opt/docker"
assert driver._docker_bin() == "/opt/docker"
def test_the_old_docker_methods_are_gone(self):
"""netOrk reaches Container Station over the Engine API (NetOrk/netork#765)."""
for name in ("get_docker_info", "get_docker_outdated", "_docker_bin"):
assert not hasattr(QnapQtsDriver, name), name
class TestContainerEngineBinary:
"""The container engine connection (napalm-device-types 2.6.0) reaches
Container Station's docker, not one on PATH: the driver decides where the
binary lives, and netOrk never sees it (NetOrk/netork#765)."""
def test_the_engine_binary_is_the_discovered_path(self, driver):
driver._docker_path = "/share/CACHEDEV1_DATA/.qpkg/container-station/bin/docker"
assert driver._container_engine_binary("docker") == driver._docker_path
def test_the_api_stream_runs_dial_stdio_from_that_path(self, driver):
driver._docker_path = "/share/CACHEDEV1_DATA/.qpkg/container-station/bin/docker"
opened = []
with patch.object(driver, "open_stream", side_effect=lambda cmd, **kw: opened.append(cmd)):
driver.open_container_engine("docker").open_api()
assert opened == [f"{driver._docker_path} system dial-stdio"]
def test_without_a_discovered_path_it_falls_back_to_plain_docker(self, driver):
driver._docker_path = None
assert driver._container_engine_binary("docker") == "docker"
def test_services_cannot_be_controlled():
@@ -192,3 +214,33 @@ def test_services_cannot_be_controlled():
assert callable(getattr(LinuxDriver, "manage_service", None))
assert not callable(getattr(QnapQtsDriver, "manage_service", None))
def test_updates_are_not_read_or_applied_here():
"""QTS has no apt, dnf or opkg: the readers inherited from LinuxDriver find no
package manager, and QPKG updates are not implemented. Saying so keeps QNAP out
of netOrk's patch groups and compliance report, which ask whether these
methods are callable."""
for method in ("get_available_updates", "refresh_available_updates", "apply_updates"):
assert not callable(getattr(QnapQtsDriver, method, None)), method
class TestFixesQtsCannotTake:
"""QTS has no apt, dnf or opkg, and its SNMP agent is configured in its own UI:
the fix templates inherited from LinuxDriver are switched off (#7), so a
capability check -- is it callable? -- answers no."""
@pytest.mark.parametrize(
"name",
["configure_snmp_agent", "configure_package_proxy", "get_package_proxy", "ensure_package"],
)
def test_switched_off(self, name):
assert getattr(QnapQtsDriver, name) is None
def test_no_warnings_of_its_own():
"""napalm-linux names no warnings any more (netOrk #1111), so neither does
this driver; the override that kept the inherited one from asking for updates
QTS cannot list (#6) is gone with it."""
assert not callable(getattr(QnapQtsDriver, "get_device_warnings", None))