Section 4: Property Matchers

The filters you have used so far — role='spine', deploy_mode='deploy' — are exact equality matches. Property matchers extend that capability: they let you match on ranges, partial strings, the absence of a value, membership in a list, nested JSON structures, and combinations of all of the above.

Each property matcher is a function that replaces the right-hand side of a property filter. Instead of writing vlan_id=100, you write vlan_id=ge(100) to mean "a VLAN ID that is greater than or equal to 100".

Inequalities: eq, ne, gt, ge, lt, le

Use these when you want to match on a numeric range or an explicit non-equality.

Matcher Meaning Example

eq(x)

Equal to x (explicit; same as property=x)

vlan_id=eq(100)

ne(x)

Not equal to x

deploy_mode=ne('deploy')

gt(x)

Greater than x

member_count=gt(1)

ge(x)

Greater than or equal to x

vlan_id=ge(100)

lt(x)

Less than x

vlan_id=lt(200)

le(x)

Less than or equal to x

vlan_id=le(4094)

Example: find all VN instances in a specific VLAN range

node(type='vn_instance', vlan_id=ge(100))

This returns every VN instance with a VLAN ID of 100 or higher.

When would you use this? If your organisation uses VLAN ranges to segment environments — 100–199 for development, 200–299 for staging, 300–399 for production — you can query each range separately to audit VN instance assignments.

String globbing: aeq

aeq() performs a wildcard match on string properties. It supports * (match any sequence of characters) and ? (match any single character).

node(label=aeq('*spine*'))

Returns every node whose label contains the word spine, regardless of what comes before or after it.

node(type='interface', if_name=aeq('xe-*'))

Returns all interfaces whose name starts with xe-.

node(label=aeq('*test*'))

Returns every node — of any type — whose label contains the word test.

aeq() is not a full regular expression engine. It supports only and ? as wildcards. For case-insensitive matching, note that the match is case-sensitive by default — aeq('*Spine') will not match a node labelled spine-1.

When would you use this? The most practical use is finding objects by naming convention. If your team prefixes all development objects with dev_, the query node(label=aeq('dev_*')) finds all of them in one call — regardless of whether they are VNs, routing zones, or systems. This is far more efficient than making separate REST calls for each object type.

Presence and absence: is_none, not_none

Some node properties are optional — they exist on some nodes of a given type and not others. is_none() matches nodes where the property is absent or null. not_none() matches nodes where the property has any value.

node(type='system', system_id=not_none())

Returns only systems that have a system_id assigned — meaning they have been associated with a physical or virtual device. Systems without a system_id are in the blueprint design but have not yet been assigned to actual hardware.

node(type='interface', if_name=not_none())

Filters out logical interface nodes that have no assigned name. Useful when you only want to work with concrete physical interfaces.

When would you use this? In queries that target deployed devices, filtering on system_id=not_none() ensures you only process systems that are actually assigned to hardware. You will see this pattern throughout the predefined catalog queries.

Enum matching: is_in, not_in

Use is_in() when you want to match any value from a list. Use not_in() when you want to exclude a list of values.

node(type='system', role=is_in(['leaf', 'access']))

Returns all systems with a role of either leaf or access. This is equivalent to role='leaf' OR role='access' — but expressed in a single property filter.

node(type='system', deploy_mode=not_in(['deploy']))

Returns all systems that are not in deploy mode — effectively every system that has not yet been deployed to hardware.

node(type='system', role=is_in(['leaf', 'spine', 'access', 'superspine']), deploy_mode='deploy')

Returns all deployed switching infrastructure — the exact filter used in the DC - All managed devices (any role) predefined query.

When would you use this? Anywhere you need to match across a group of related values. is_in() is particularly useful for role-based filtering, where you want to target the switching fabric (spine + leaf) but exclude generic servers.

Tag matching: has_any, has_all

Tags in Apstra are multi-value labels that can be applied to systems, interfaces, and links. has_any() and has_all() let you filter on those tags.

node(type='system', tag=has_any(['production', 'critical']))

Returns all systems tagged with either production or critical (or both).

node(type='interface', tag=has_all(['uplink', 'monitored']))

Returns only interfaces tagged with both uplink and monitored.

When would you use this? Tags are the recommended way to target IBA probes and connectivity templates at specific subsets of your fabric. A graph query using has_all() or has_any() is the mechanism that translates a tag policy into a concrete set of objects.

Nested JSON values: has_keys, has_items

Some node properties are not simple strings or numbers — they are embedded JSON structures. The device_profile node, for example, carries a hardware_capabilities property that is itself a dictionary containing ASIC model, port counts, and other hardware details.

has_keys() checks whether a key exists inside the nested structure. has_items() checks whether a specific key-value pair exists inside the nested structure.

node(type='device_profile', hardware_capabilities=has_keys(['asic']))

Returns all device profiles that have an asic key within their hardware_capabilities structure.

node(type='device_profile', hardware_capabilities=has_items({'asic': 'T3'}))

Returns all device profiles where hardware_capabilities['asic'] equals 'T3'.

When would you use this? Filtering by hardware capability is the primary use case for has_items. If you want to target an IBA probe only at devices running a specific ASIC type, or if you want to audit which device profiles in your catalog belong to a particular hardware family, has_items is the right tool.

The nested JSON access provided by has_keys and has_items is a shallow single-level check. For deeper nested access or more complex logic, you will need a lambda — covered in Section 5.

Boolean combinators: _and, _or, _not

Use _and(), _or(), and _not() to compose multiple matchers into a single property filter.

node(type='vn_instance', vlan_id=_and(ge(100), le(199)))

Returns all VN instances with a VLAN ID between 100 and 199 inclusive. Both conditions must be true.

node(type='system', role=_or(eq('spine'), eq('leaf')))

Equivalent to role=is_in(['spine', 'leaf']) — returns systems with either role. is_in() is the cleaner form for list membership; _or() is more useful when combining heterogeneous matchers.

node(type='interface', if_name=_not(aeq('lo*')))

Returns interfaces whose name does not start with lo — excluding loopback interfaces.

_and(), _or(), and _not() operate on a single property. They combine multiple matchers on the same property value. They cannot combine conditions across different properties — for that, simply list multiple property=matcher pairs inside node(), which are always combined with AND.

So to find deployed spines with at least one assigned interface: node(type='system', role='spine', deploy_mode='deploy', system_id=not_none()). This is four separate property filters, all of which must be true simultaneously.

Combining matchers with traversals

Property matchers apply inside any node() expression, whether that node is the start, middle, or end of a traversal:

node(type='system', role=is_in(['leaf', 'access']), deploy_mode='deploy', system_id=not_none())
  .out('hosted_interfaces')
  .node(type='interface', if_name=not_none(), operation_state='up')
  .out('link')
  .node(type='link', link_type='ethernet')

This traversal finds all Ethernet links connecting deployed leaf/access switches to other devices, but only via interfaces that are operationally up and have an assigned name.

This is close to the DC - All non-fabric interfaces predefined catalog query.

Checkpoint

Run the following queries and confirm the results make sense:

All deployed leaf and access switches:

node(type='system', role=is_in(['leaf', 'access']), deploy_mode='deploy')

All VN instances with a VLAN in the range 200–299:

node(type='vn_instance', vlan_id=_and(ge(200), le(299)))

All interfaces that are operationally up and have a name assigned:

node(type='interface', operation_state='up', if_name=not_none())

Open the DC - All managed devices by ASIC model entry in the Predefined Query Catalog and identify which matcher is being used and what property it is applied to.

Continue to Section 5: Output Labels and Lambdas to learn how to get useful data back from your queries.