← Interview Prep

Annet — Config Generation & Deploy

Annet end to end: generators with ACL-scoped partial config, the gen→diff→deploy workflow, multivendor dispatch, gnetcli transport over SSH or Telnet (streamer_type/dev_port), NetBox vs file inventory, and an offline fake-device self-test.

Annet (from the annetutil project) is a config-generation and deploy framework: you write Python generators that emit the target config, Annet fetches the device's running config, computes a vendor-aware diff, and deploys only the delta. Its signature idea is the ACL — each generator declares exactly which config it owns, so Annet does partial config management safely. This page follows a real stand that drives Cisco over Telnet via gnetcli.

Flow: annet gen (build target config from generators) → annet diff (fetch running-config, diff within each generator's ACL) → annet deploy (push the diff). Inventory comes from NetBox or a file; transport is pluggable (SSH or Telnet).

Annet doesn't collect facts

Unlike Ansible or Nornir, Annet has no operational-state / show collection step. It takes intent entirely from the SoT (NetBox or a file), and the only thing it pulls from the device is the running-config — purely to diff it against the generated target. If you need show version, interface counters, or any live facts, run gnetcli / netmiko / NAPALM alongside Annet. See the side-by-side in Frameworks Compared.

Architecture

PieceRole
StorageWhere the device list & metadata come from — netbox (production) or file (offline YAML).
GeneratorsPython classes that yield target config per vendor.
FetcherPulls running-config from the device (here the gnetcli adapter).
DeployerPushes the computed diff back.
Context (config.yaml)Wires storage + fetcher + deployer + generators into a named context you select.

Generators & the ACL

A generator is a PartialGenerator with two methods per vendor: acl_<vendor> (which lines it manages) and run_<vendor> (the lines it produces). Annet dispatches by the device's vendor and diffs only inside the ACL — config outside it is left untouched.

from annet.generators import PartialGenerator
from annet.storage import Device

class Hostname(PartialGenerator):
    TAGS = ["hostname"]

    def acl_cisco(self, _: Device):
        return "hostname"                 # this generator owns the 'hostname' line

    def run_cisco(self, device: Device):
        yield "hostname", device.name     # ...and sets it to the device name

Nested config uses self.block(). A trimmed interfaces generator (access vs trunk from NetBox tags):

class IfaceAccess(PartialGenerator):
    TAGS = ["access"]

    def acl_cisco(self, _: Device):
        return """
        interface
            description
            switchport mode ~
            switchport access ~
        """                                # '~' / '*' are ACL wildcards

    def run_cisco(self, device: Device):
        for iface in device.interfaces:
            if self.isAccess(iface.tags):
                vlan = iface.untagged_vlan.vid or 999
                with self.block(f"interface {iface.name}"):
                    yield "switchport mode access"
                    yield f"switchport access vlan {vlan}"

Why the ACL matters: it's the guardrail. Annet will only add/remove lines that match the ACL, so a hostname generator can never accidentally wipe your interfaces — the diff is scoped by construction.

The gen / diff / deploy workflow

annet gen    cisco-sw1.example.net    # print the generated target config
annet diff   cisco-sw1.example.net    # fetch running-config, show the delta
annet deploy cisco-sw1.example.net    # push the delta

A diff against a device whose hostname is fakesw:

# -------------------- cisco-sw1.cfg --------------------
- hostname fakesw
+ hostname cisco-sw1

Transport: gnetcli over SSH or Telnet

Annet reaches devices through an adapter. This stand uses gnetcli: a Go daemon (gnetcli_server) does the actual connection, a Python gRPC client (gnetclisdk) talks to it, and gnetcli_adapter plugs it into Annet as fetcher/deployer. Transport is chosen with two params:

# config.yaml — fetcher/deployer using the gnetcli adapter
fetcher:
  telnet:
    adapter: gnetcli
    params:
      streamer_type: telnet    # ssh (default) or telnet
      dev_port: 23
deployer:
  telnet:
    adapter: gnetcli
    params:
      streamer_type: telnet
      dev_port: 23

Credentials are never in the config — the adapter reads any dev_* param from GNETCLI_* environment variables:

export GNETCLI_DEV_LOGIN=admin
export GNETCLI_DEV_PASSWORD=secret

The Telnet caveat (why a fork)

Telnet support is spread across three repos and, as of 2026, not fully upstream:

ComponentTelnet status
annetTransport-agnostic ✅ (PyPI annet[netbox])
gnetcli_server (Go)Telnet merged upstream ✅ (≥ v1.3.6)
gnetclisdk (Python)Has StreamerType_telnet
gnetcli_adapterTelnet still in an open PR ❌ — install from the fork until merged

Only the adapter needs the fork (it lacks streamer_type/dev_port fields, so stock installs go SSH). Everything else is upstream.

Inventory: NetBox or a file

storage:
  netbox:
    adapter: netbox
    params: { url: http://netbox:8000/, token: REPLACE_ME }
  file:
    adapter: file
    params: { path: /file_inventory.yaml }
# file_inventory.yaml — offline testing, no NetBox
devices:
  - fqdn: cisco-sw1.example.net   # required, also the lookup key
    vendor: cisco                 # cisco, huawei, arista, routeros, ...
    breed: ios12                  # concrete breed needed for diff/deploy parsing

NetBox drives production (interfaces, tags, VLANs, cabling feed the generators); the file adapter is a minimal stand-in for CI — but it has no tags/VLANs, so only vendor-agnostic generators (like hostname) run.

Offline self-test (no real device)

The stand ships a fake Cisco telnet server so the whole path runs locally in Docker:

make build && make run && make shell    # build + start + enter the container
bash /test/run_annet_telnet_diff.sh      # annet diffs the fake Cisco over telnet
# => - hostname fakesw / + hostname cisco-sw1

That exercises generators → gnetcli_server → telnet → fake device → diff, end to end, with no credentials and no lab.

Annet vs Ansible / Nornir

Likely interview questions

Repos: annetutil/annet · annetutil/gnetcli. Related: Nornir · Ansible.