Files
Christian Manivong 32a63411d3
CI / test (3.10) (push) Successful in 29s
CI / test (3.11) (push) Successful in 30s
CI / test (3.12) (push) Successful in 29s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 46s
CI / test (3.12) (pull_request) Successful in 42s
feat: say whether a host is still in its first boot
first_boot_pending() is true while /firstboot exists on FreeBSD. A FreeBSD
cloud image upgrades its base system on its first boot, starts sshd only
after that and restarts right away, and /etc/rc removes /firstboot just
before that restart. netOrk waits for this before it sets up a new VM
(NetOrk/netork#795).

OpenBSD has no such marker to ask yet, so it returns False there.
2026-10-08 12:58:00 +02:00

100 lines
5.6 KiB
Markdown

# napalm-bsd
NAPALM drivers for **FreeBSD** (`freebsd`) and **OpenBSD** (`openbsd`) hosts,
over SSH, built on [napalm-device-types](https://git.netork.io/NAPALM/napalm-device-types)'
`OSDriver`. Written for netOrk, which provisions and manages BSD VMs
(NetOrk/netork#792).
## Design
- One shared `BsdDriver` (`napalm_bsd/base.py`) holds the SSH layer and every
reader whose tools agree between the BSDs. `FreeBSDDriver` and
`OpenBSDDriver` name only the commands that differ (hostname, release,
kernel, hardware, `netstat -rnW` vs `-rn`).
- Every command runs on its own SSH **exec channel** (`run_on_transport` from
napalm-device-types): no PTY to parse a prompt from, stdout and stderr apart,
a real exit status. `run_command()` / `open_stream()` are public
(`CommandChannelMixin`).
- Root comes from `sudo -S` with the sudo password on stdin (never on a command
line), or `sudo -n` without one, as in napalm-linux. netOrk provisions `sudo`
on BSD VMs for that reason; `doas` cannot read a password from stdin.
- Parsing is in `napalm_bsd/parse.py`, pure functions tested on output recorded
from real systems (`tests/fixtures/`, each directory's `COMMANDS.txt` lists
what produced it).
## Supported getters
| Getter | FreeBSD | OpenBSD | Source |
|---|---|---|---|
| `get_facts` | ✓ | ✓ | `kern.hostname`/`hostname`, `freebsd-version`/`uname`, `kenv smbios.*`/`hw.*`, `kern.boottime` |
| `get_interfaces`, `get_interfaces_ip` | ✓ | ✓ | `ifconfig -a` |
| `get_route_to` | ✓ | ✓ | `netstat -rnW` / `netstat -rn` |
| `get_arp_table` | ✓ | ✓ | `arp -an` |
| `get_lldp_neighbors` | `{}` | `{}` | no LLDP daemon in either base system |
| `get_users`, `get_processes`, `get_cron_jobs` | ✓ | ✓ | `/etc/passwd`+`/etc/group`, `ps … lstart`, system crontab + `crontab -l` |
| `get_listening_sockets` | ✓ | ✓ | `sockstat -46lq` / `fstat -n` as root, `netstat -an` without |
| `get_packages`, `install_package`, `uninstall_package` | ✓ | ✓ | `pkg query` / `pkg install`, `pkg delete`; `pkg_info` / `pkg_add -I`, `pkg_delete` |
| `get_available_updates` | ✓ | ✓ | `pkg upgrade -n` + `pkg audit` (VuXML); `pkg_add -u -n -v` + `syspatch -c` |
| `get_services`, `manage_service` | ✓ | ✓ | `service -e` + `service … status` / `service … <action>`; `rcctl ls on` + `rcctl check` / `rcctl <action>` |
| `run_device_action("fix_snmp")`, `get_snmp_config` | ✓ | ✓ | net-snmp from packages: `/usr/local/etc/snmp/snmpd.conf` + `service snmpd`; `/etc/snmp/snmpd.conf` + `rcctl … netsnmpd` |
| `get_host_status` | ✓ | unknown | napalm-device-types' host status: on FreeBSD `freebsd-version -k` vs `-r` (4.1+) |
| `first_boot_pending` | ✓ | always `False` | whether `/firstboot` still exists |
`get_listening_sockets` has the shape of napalm-device-types'
`ListeningSocketsMixin` (whose `ss`/cgroup reading is Linux's) and its rule:
read as root first, without root when that brings nothing back
(`attributed: False`). FreeBSD's `sockstat` sees every socket either way;
OpenBSD's `fstat` shows a user only their own processes, so without root the
sockets come from `netstat -an`, unnamed.
There is deliberately no `get_kernel_facts`: `KernelFactsMixin` reports a
Linux kernel's modules and `CONFIG_*` options, which a BSD kernel does not
have, and `hasattr(driver, "get_kernel_facts")` has to stay truthful.
**Updates.** `security` on FreeBSD comes from VuXML (`pkg audit`): True for a
package it lists as vulnerable, False for one it does not, None when the audit
could not run. OpenBSD's tools do not say, so it is None there. Base-system
patches are one entry, `base-system` (NetOrk/netork#799): OpenBSD's
`syspatch` applies its patches together and in order; a classic FreeBSD base
reports what `freebsd-update` has fetched (`updatesready`). On FreeBSD with
pkgbase the base system is packages from the `FreeBSD-base` repository and
needs no extra entry.
A reader that cannot read **raises** rather than return an empty list: netOrk
takes `[]` as "no updates" and would close every patch clock on the host. That
covers a refused sudo, a failing `pkg`, `syspatch` or `freebsd-update`, and a pkg
database pkg cannot read (only pkg's "not installed" means no packages).
**Reboot.** `get_host_status` reports a FreeBSD host whose installed kernel
(`freebsd-version -k`) differs from the running one (`-r`) as needing a reboot.
OpenBSD has no such reading yet, so it stays unknown there.
**First boot.** A FreeBSD cloud image upgrades its base system on its first boot,
starts sshd only after that and restarts right away. `/etc/rc` removes `/firstboot`
just before the restart, so `first_boot_pending()` is true until then; netOrk waits
for it before it sets up a new VM. OpenBSD has no such marker to ask yet.
**Services** are the enabled ones. FreeBSD reads their status as root, as
root-only pidfiles hide a daemon from anyone else, and without root when sudo
refuses; OpenBSD's `rcctl check` needs no root. Actions run as root.
**SNMP** is net-snmp from packages (NetOrk/netork#800), configured as netOrk does
on Linux (v2c, community `public`, every address), so the agent answers
UCD-SNMP-MIB for netOrk's health metrics. Memory, swap and load are right on
both systems. CPU: FreeBSD reports `ssCpuIdle` about a minute after the
agent starts; on OpenBSD net-snmp's CPU figures are wrong (0 % idle on an idle
machine, also in `hrProcessorLoad`).
## Connection arguments
`optional_args`: `port` (22), `key_file`, `sudo_password`, `allow_agent`,
`look_for_keys` (both off by default).
## Development
```bash
pip install "napalm-device-types @ git+https://git.netork.io/NAPALM/napalm-device-types.git"
pip install -e ".[dev]"
pytest
```