Author SHA1 Message Date
Christian Manivong 472518576e feat: name no warnings and report no package proxy (0.5.0)
CI / test (3.10) (push) Successful in 28s
CI / test (3.11) (push) Successful in 26s
CI / test (3.12) (push) Successful in 27s
CI / test (3.10) (pull_request) Successful in 27s
CI / test (3.11) (pull_request) Successful in 26s
CI / test (3.12) (pull_request) Successful in 27s
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 10:35:10 +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
6 changed files with 151 additions and 14 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
+11 -2
View File
@@ -78,6 +78,13 @@ class QnapQtsDriver(StorageDriver, HypervisorDriver, LinuxDriver):
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 = [
@@ -150,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."""
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
]
+47 -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():
@@ -202,3 +224,23 @@ def test_updates_are_not_read_or_applied_here():
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))