Skip to content

Releases: k3l0-dev/niwaki

niwaki v1.3.1

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:16

Fixed

  • Subscription push silently never delivered. login()/_refresh_token()
    (sync and async) explicitly set the APIC-cookie cookie on the httpx
    client, on top of the cookie httpx already stores automatically from the
    APIC's own Set-Cookie response header. The explicit call didn't pin a
    domain, so it landed as a second, distinct jar entry (same name, empty
    domain) rather than overwriting the first. The APIC accepts the resulting
    malformed, duplicated Cookie: APIC-cookie=...; APIC-cookie=... header for
    ordinary reads/writes and even for the subscribe GET itself (a valid
    subscriptionId still comes back) — but it silently breaks the APIC's
    internal link between that request and the caller's already-open
    WebSocket, so no push ever arrives, and the subscribe response's initial
    imdata can come back empty even when matching objects exist. Fixed by
    pinning the cookie to the session's own host on every login()/
    _refresh_token() call, so it overwrites in place instead of duplicating.
    Only subscribe() was affected — every other read/write path was already
    correct, which is why this went unnoticed until a live subscription
    investigation traced the exact Cookie header sent on the wire.
    Re-validated live end to end on a freshly provisioned fabric: create,
    modify, delete, filtered subscriptions, forced reconnect, and refresh
    escalation all deliver correctly now.

niwaki v1.3.0

Choose a tag to compare

@github-actions github-actions released this 20 Jul 16:17

Native APIC object-subscription: a query becomes a live push stream instead
of a one-off read, over the same WebSocket mechanism the APIC GUI itself
uses. Purely additive — the configuration API and the query surface are
unchanged.

Added

  • Query.subscribe() / AsyncQuery.subscribe(). Any single-class query
    can be subscribed instead of fetched: .initial gives the synchronous
    snapshot, then the returned Subscription/AsyncSubscription iterates
    live push events for as long as it stays open. One shared WebSocket per
    session multiplexes every subscription; refresh and reconnect run
    automatically in the background — nothing here needs a caller-driven loop.

  • Typed events. Each item is a SubscriptionEvent: .kind
    (EventKind.CREATED/MODIFIED/DELETED/GAP/REFRESH_FAILED), .mo
    deserialised through the same readable field names a normal read uses,
    with .mo.model_fields_set reporting exactly what that push carried (the
    APIC sends sparse deltas on MODIFIED, dn only on DELETED).

  • Automatic recovery, never silent. The APIC has no replay mechanism at
    all, so a reconnect resubscribes everything from scratch and delivers a
    GAP event rather than continuing as if nothing happened. Two consecutive
    missed refreshes trigger the same kind of recovery for that one
    subscription. SubscriptionLostError — with .reason — is raised only
    once recovery itself has been tried and failed.

  • Bulk and single-subscription tools. aci.subscriptions.list() /
    .refresh_all() / .close_all() manage every subscription open on a
    session at once (close_all() stops them without tearing down the shared
    socket); sub.info / sub.refresh_now() do the same for one subscription.

  • Validated live against a real fabric: genuine create/modify/delete push
    payloads, a real subscriptionId, and the subscriptionRefresh endpoint
    accepting a real id.

niwaki v1.2.0

Choose a tag to compare

@github-actions github-actions released this 20 Jul 07:34

Discovery for the ~15,300 ACI classes the SDK does not generate a model for —
learned endpoints, stats, hardware, routing runtime. Purely additive: the
configuration API and the 1.1.0 query surface are unchanged.

Added

  • The read catalogue. A shipped, lazily-opened sqlite store (~31 MB) of
    read metadata for every readable ACI class, not just the 2,239 with generated
    models — so any class can be searched, described, and read with readable
    field names.

    Readable names are recomputed with the code generator's own naming logic, so
    a catalogue-served class reads with the same field names its model would
    use — except for ~0.07 % of properties on a handful of generated classes
    (11 properties across 7 of 2,211 on APIC 6.0(9c)), where the catalogue
    resolves name collisions over a class's whole readable property set while
    the model resolves over its configurable subset, so the two can pick
    different names (e.g. l3extOut.enforceRtctrlenforce_route_control
    vs enforce_rtctrl). This never affects a result object — a generated
    class is served by its typed model, never the catalogue — and is visible
    only when introspecting those classes.

  • niwaki.catalog — the public door to it: search(term),
    describe(class_name), prop_meta(class_name, name), find_prop(term),
    concrete_subclasses(class_name), class_meta(class_name), and
    fault_name(code) (a fault code to its rule name, independent of which class
    raised it). Entirely offline — no APIC connection needed.

  • Readable field access on any result object. A ManagedObject built
    from a class with no generated model now exposes readable attribute names
    (ep.infrastructure_ip, not just ep["address"]) via the catalogue, with
    the same per-property coercion (bool/int/float/flags/…) the wire
    boundary uses for generated models — reading is uniform across all ~15,300
    classes, generated or not.

  • Validated live: real non-generated classes (topSystem, fabricNode,
    lldpAdjEp, eqptSensor, faultInst), the abstract-class query fan-out
    (aci.query("fvEPg") resolving server-side to its concrete descendants), and
    fault_name against every fault code a live fabric actually raised.

niwaki v1.1.0

Choose a tag to compare

@github-actions github-actions released this 19 Jul 10:03

The observation / query surface — which 1.0.0 declared still-evolving — gets
its read foundation: the full APIC query grammar, expressed as a fluent, typed
API and validated against a live controller. The configuration API (design
DSL, push modes) is unchanged and remains stable; the breaking changes below are
confined to reading.

Added

  • Filter operators. anybit, allbit (emulated as and_(anybit, …)),
    xor and raw join eq/ne/lt/le/gt/ge/bw/wcard and
    and_/or_/not_. anybit closes the write-but-not-filter gap on bitmask
    (Flags) fields — you can now filter "where this flag is set".
  • Smart values in where(...). A list or tuple means membership, a * in a
    string means wildcard, a set stays bitmask equality; explicit wrappers
    any_of(...), like(pattern) and between(start, end) remove any ambiguity.
  • Response shaping — full GET grammar. self_only(), also(...),
    subtree_full(), and include_subtree(...) with the SubtreeInclude facets
    (faults, health, stats, audit/event/fault/health records, tasks, count, …);
    multi-key order_by.
  • Executors and Python protocols. A query is a lazy iterable
    (for mo in q, list(q)); q[:n] sets a server-side limit; .one() and
    .exists(); execute_raw(). count() and exists() honor the limit. New
    typed NoResultError / MultipleResultsError.
  • Uniform result access on every object, generated model or operational
    class alike: .dn, mo["wireName"], and .attrs (the full wire view).

Changed

  • with_faults() no longer filters. It embeds faults on each object without
    restricting the result; chain only_faulted() to return only faulted objects.
    (Previously with_faults() implicitly restricted to faulted objects.)
  • bool(query) / if query: now raises TypeError. A query is lazy, so
    truthiness would hide a network call — use .exists().
  • subtree_where(prop=value) qualifies the property with the included subtree
    class.
    include(fvSubnet).subtree_where(scope=...) filters fvSubnet.scope,
    not the queried class; when several classes are included, pass an explicitly
    qualified expression (subtree_where(eq("fvSubnet.scope", …))).

Removed

  • contains() and isdigit() filter operators — the APIC has no such filter
    types (verified on 6.0(9c)). Use like("*x*") (subject to the property's
    format) or raw(...).

niwaki v1.0.0

Choose a tag to compare

@github-actions github-actions released this 17 Jul 15:58

Niwaki leaves beta. The design-first configuration surface is proven against
a live Cisco APIC: the SDK expresses the ACI configuration model in depth, and a
real controller accepts what it produces.

Out of beta

  • Stable configuration API. The design DSL (design, tenant, infra,
    fabric, controller, aaa, the makers, bind, bind_dn, ref, the verbs),
    the push modes (strict, staged, plan) and the observation façade are now
    stable; breaking changes will land in a new major version with a migration note.
  • Development-status classifier moved from 4 - Beta to 5 -
    Production/Stable
    .

Changed

  • bind() no longer climbs to an ancestor. A relation attaches to the
    object the cursor is on — declare each alias on the level that owns it
    (.bd("web").bind(vrf="prod").subnet(...), not
    .bd("web").subnet(...).bind(vrf="prod")). Binding an alias the current level
    does not curate now fails loud — at the type level (the typed cursor no longer
    exposes the alias) and at runtime (DesignError) — instead of silently placing
    the relation on a parent object.
  • Typed cursor bind() signatures expose only the aliases curated on that
    level
    , never an ancestor's, so the editor and type-checker reject a bind the
    controller would never accept.
  • Two references that resolve to the same relation now coexist instead of
    raising: vrf.bind(l3out="x") and l3out("x").bind(vrf="v") build the same
    l3extRsEctx and collapse to one; a same-relation collision with conflicting
    attributes still raises.

Proven on a live fabric

An exhaustive integration suite drives the SDK against a live APIC simulator,
organised as eight domain walkthroughs an operator would recognise —
fabric/access, fabric, tenant, contracts, external connectivity (L3Out / L2Out),
service graphs, observability and management. Together they:

  • push more than 10,000 configuration objects across 101 walkthrough files
    and 225 test functions — every one accepted by the controller, with zero
    rejections
    ;
  • sweep each object's configuration surface in depth — every enum value, the
    combinations of interacting fields, and every curated child of every parent,
    with mutually-exclusive settings factored across separate tenants, VRFs and
    bridge domains so both sides of each exclusion are covered;
  • encode the controller's real cross-field rules that the schema does not
    express (one SPAN destination per session, NetFlow v9-only, OSPF/EIGRP mutual
    exclusion on an L3Out, redistribution route-maps permit-only, a backup policy
    serving a single redirect, …), so every pushed object is accepted in context,
    not merely syntactically valid;
  • confirm that every declared object is present on the fabric with the
    attributes and children its design declared, and that every relation reads
    state=formed.

Residual faults a lab run may show are deployment-layer, not configuration
defects — a static path whose VLAN no domain binds yet, a routing peer with no
neighbour on the simulator, a VMM infra port-group without the fabric-wide
infra-VLAN scaffolding — and each is documented in its domain's walkthrough
README. The SDK expresses every knob; the controller accepts every object.

Scope

The walkthroughs run on a simulator, so they prove the configuration surface
the SDK expresses the ACI model and a real controller accepts it — not hardware
or data-plane behaviour. The configuration side of the SDK is
production-ready; the read / query and observation surfaces continue to grow.

Verifying it yourself

Point the suite at your own lab and watch it configure a fabric end to end
(uv run pytest tests/integration/<phase> -m integration -s), then confront the
result through an independent read path — for example a read-only oracle over
the APIC such as aci-mcp — to confirm
each object landed. See tests/integration/README.md.

niwaki v0.14.16

Choose a tag to compare

@github-actions github-actions released this 17 Jul 08:12

Added

  • tests/integration/README.md — explains what the live walkthroughs are
    for (evaluating the SDK against a lab APIC / simulator) and states plainly that
    they are not production configuration and not best-practice snippets:
    their values are illustrative and their job is to confirm the code runs against
    a real controller.

Fixed

  • The generated coverage-matrix intro no longer hardcodes the number of design
    domains — it read "the four design domains" after further roots (aaa,
    vmm_provider) were added.

niwaki v0.14.15

Choose a tag to compare

@github-actions github-actions released this 17 Jul 07:54

Added

  • Fabric switch profiles, module cards and vPC protection. The fabric root
    gains leaf_path_selector, spine_path_selector, override_leaf_selector and
    override_spine_selector makers; leaf and spine switch profiles bind their
    module_profile (FEX card); the vPC explicit protection group binds its
    vpc_policy; and switch event/fault/health retention policies are declarable.
  • Management and DHCP node-group associations. Management, DHCP and
    deployment-zone pod groups bind their node groups (management_group,
    node_group); DHCP node groups bind their dhcp_relay policy; DNS server
    groups bind their epg; and bridge domains gain a dhcp_relay_label maker.
  • Observability — monitoring sources, syslog destinations, SPAN and
    retention.
    EPG monitoring policies gain the SNMP/syslog/callhome/TACACS
    sources plus lifecycle_policy and stats_limit_pol (the latter two across
    every monitoring policy); syslog groups gain console, file and
    protocol_profile destinations; tenants declare VSPAN sessions and destination
    groups, whose destinations bind an EPG, path, APIC node or virtual port.

Coverage

Declarable-surface coverage after these fixes — curated positions against the
remaining in-scope backlog, by design domain:

Domain Curated In-scope gaps Coverage
tenant 553 141 80 %
access 212 29 88 %
fabric 206 36 85 %
controller 12 1 92 %
aaa 14 1 93 %
vmm 33 1 97 %
Total 1030 209 83 %

The 322 detected gaps split into 209 in-scope, 26 deferred (VMM,
Intersight and on-switch third-party integrations — these need a live backend to
verify) and 87 out of scope (imperative actions, Cloud Network Controller,
Nexus Dashboard Orchestrator / multi-site, and SD-WAN). Anything outside the
curated vocabulary stays reachable through .mo(Class, ...) and
bind_dn(alias=dn).

niwaki v0.14.12

Choose a tag to compare

@github-actions github-actions released this 16 Jul 11:09

Fixed

  • A staged push now isolates a failure to its own subtree. When one object
    failed, push(mode="staged") marked every deeper object as "not attempted"
    — keyed on DN depth alone — so an unrelated sibling branch whose parent had
    succeeded was left half-built (e.g. a failure on BD-a skipped BD-b's
    subnet). The engine now skips only the descendants of a failed object;
    independent branches run to completion, and StagedPushError.not_run lists
    only objects whose ancestor genuinely failed.

niwaki v0.14.9

Choose a tag to compare

@github-actions github-actions released this 16 Jul 09:48

Fixed

  • Writes are no longer retried after a timeout. A read/write timeout can
    mean the APIC already accepted the POST/DELETE, so retrying risked a
    double-apply or a spurious NotFoundError on a delete that actually
    succeeded. Writes now retry only on pre-send errors (connection/pool); reads
    retry on any transport error as before.

niwaki v0.14.0

Choose a tag to compare

@github-actions github-actions released this 15 Jul 16:52

L4-L7 service graphs join the vocabulary — the last large domain of the ACI
configuration plane.

Added

  • Service graph templates. tenant(name).service_graph(...) with function
    nodes (+ function connectors carrying config folders and parameters, copy
    connectors), connections, and consumer/provider terminal nodes.
  • Logical devices. logical_device (the L4-L7 cluster) with concrete
    devices (+ their interfaces and parameters), logical interfaces, credentials
    and management interface; the graph's function node binds a logical device.
  • Device context. logical_device_context (keyed by contract/graph/node)
    selecting a device and router configuration, with per-connector interface
    contexts mapped to bridge domains and their virtual IPs.
  • Function profiles. Profile container → group → profile with function,
    device and group shared configs, and the abstract folder/parameter model.
  • Device manager, chassis and instance config. Device manager and chassis
    (with credentials), the deployed L4-L7 policy container with folder/parameter
    instances, and normalized firewall parameters.

Service graphs define the ACI-side topology and configuration; rendering a graph
needs a real L4-L7 appliance — see the "Hardware-dependent integrations" note.
The device-package metamodel and normalized LB/NAT requests stay uncurated.

Coverage

The declarable config plane across the five domains (operational, diagnostic and
out-of-scope families — cloud, multi-site/NDO, device-package meta — excluded):

Domain Declarable Curated %
Tenant 368 318 86 %
Access (infra) 164 141 85 %
Fabric 179 145 81 %
Controller 20 9 45 %
VMM 22 15 68 %
Global (union) 753 628 83 %

790 curated positions across 652 distinct classes.