Skip to Content
APIConfigNode API Reference

ConfigNode API

The ConfigNode interface represents a parsed configuration element in SentriFlow’s AST. This page covers how to navigate and query configuration nodes.

ConfigNode Interface

interface ConfigNode { /** Node identifier (command prefix) */ id: string; /** Node type */ type: 'command' | 'section' | 'comment' | 'virtual_root'; /** Command parameters (split by whitespace) */ params: string[]; /** Child nodes (for sections) */ children: ConfigNode[]; /** Parent node reference */ parent?: ConfigNode; /** Source location */ loc: { startLine: number; endLine: number; }; /** Raw text of the command */ raw: string; }

Properties

id

The node identifier, which is the command prefix used for selector matching.

// For "interface GigabitEthernet0/0" node.id === 'interface GigabitEthernet0/0' // For "ip address 192.168.1.1 255.255.255.0" node.id === 'ip address 192.168.1.1 255.255.255.0'

The id is the full command including all parameters, not just the keyword.

type

The node type:

TypeDescription
commandA single configuration command
sectionA configuration block with children
commentA comment line
virtual_rootSynthetic root node (internal use)

params

Command split by whitespace:

// For "ip address 192.168.1.1 255.255.255.0" node.params === ['ip', 'address', '192.168.1.1', '255.255.255.0'] // For "interface GigabitEthernet0/0" node.params === ['interface', 'GigabitEthernet0/0']

children

Child nodes for section-type nodes:

const interfaceNode = ast.find(n => n.id.startsWith('interface')); for (const child of interfaceNode.children) { console.log(child.id); // "ip address ...", "no shutdown", etc. }

parent

Reference to parent node (undefined for root nodes):

const ipAddressNode = findNode(ast, 'ip address'); console.log(ipAddressNode.parent?.id); // "interface GigabitEthernet0/0"

loc

Source location in the original configuration:

const node = ast[0]; console.log(`Lines ${node.loc.startLine}-${node.loc.endLine}`);

raw

Original text of the configuration line:

// For indented command node.raw === ' ip address 192.168.1.1 255.255.255.0'

Traversal Functions

findNode()

Find the first node matching a predicate:

import { findNode } from '@sentriflow/core'; const interfaceNode = findNode(ast, node => node.type === 'section' && node.id.startsWith('interface') );

findAllNodes()

Find all nodes matching a predicate:

import { findAllNodes } from '@sentriflow/core'; const allInterfaces = findAllNodes(ast, node => node.type === 'section' && node.id.startsWith('interface') );

walkTree()

Walk the AST tree depth-first:

import { walkTree } from '@sentriflow/core'; walkTree(ast, (node, depth) => { console.log(' '.repeat(depth) + node.id); });

filterBySelector()

Get nodes matching a selector:

import { filterBySelector } from '@sentriflow/core'; // Get all interface nodes const interfaces = filterBySelector(ast, 'interface'); // Get all BGP neighbors const neighbors = filterBySelector(ast, 'router bgp', 'neighbor');

Query Helpers

hasChild()

Check if a node has a child matching a prefix:

import { hasChild } from '@sentriflow/core'; const hasDescription = hasChild(interfaceNode, 'description'); const isShutdown = hasChild(interfaceNode, 'shutdown');

getChild()

Get a child node by prefix:

import { getChild } from '@sentriflow/core'; const descNode = getChild(interfaceNode, 'description'); if (descNode) { const description = descNode.params.slice(1).join(' '); }

getChildren()

Get all children matching a prefix:

import { getChildren } from '@sentriflow/core'; const aclEntries = getChildren(aclNode, 'permit'); const denyEntries = getChildren(aclNode, 'deny');

getValue()

Get a parameter value by index:

import { getValue } from '@sentriflow/core'; // For "ip address 192.168.1.1 255.255.255.0" const ip = getValue(node, 2); // "192.168.1.1" const mask = getValue(node, 3); // "255.255.255.0"

getValueAfter()

Get value after a specific keyword:

import { getValueAfter } from '@sentriflow/core'; // For "switchport trunk native vlan 999" const vlanId = getValueAfter(node, 'vlan'); // "999" // For "logging host 10.1.1.100 transport udp port 514" const port = getValueAfter(node, 'port'); // "514"

Path-Based Queries

getPath()

Get the path from root to a node:

import { getPath } from '@sentriflow/core'; const path = getPath(deepNode); // ['interface GigabitEthernet0/0', 'ip address 192.168.1.1 255.255.255.0']

findByPath()

Find a node by path:

import { findByPath } from '@sentriflow/core'; const node = findByPath(ast, [ 'interface GigabitEthernet0/0', 'ip address' ]);

Selector Matching

matchesSelector()

Check if a node matches a selector:

import { matchesSelector } from '@sentriflow/core'; // Exact prefix match matchesSelector(node, 'interface') // true for "interface ..." // Multi-part selector matchesSelector(node, 'router bgp') // true for "router bgp 65000"

Selector Syntax

Selectors use case-insensitive prefix matching:

// Simple selectors 'interface' // Matches: "interface ...", "Interface ...", "INTERFACE ..." 'hostname' // Matches: "hostname ...", "Hostname ..." // Multi-word selectors 'router bgp' // Matches: "router bgp 65000" 'ip access-list' // Matches: "ip access-list extended ..." // Wildcards (future) 'interface Gi*' // Matches: "interface GigabitEthernet..."

Modification Helpers

cloneNode()

Create a deep copy of a node:

import { cloneNode } from '@sentriflow/core'; const copy = cloneNode(interfaceNode); // Modify copy without affecting original

createNode()

Create a new ConfigNode:

import { createNode } from '@sentriflow/core'; const newNode = createNode({ id: 'description Uplink to Core', type: 'command', params: ['description', 'Uplink', 'to', 'Core'], raw: ' description Uplink to Core', loc: { startLine: 0, endLine: 0 }, });

Serialization

toConfig()

Convert AST back to configuration text:

import { toConfig } from '@sentriflow/core'; const config = toConfig(ast, 'cisco-ios');

toJSON()

Serialize to JSON (excluding circular refs):

import { toJSON } from '@sentriflow/core'; const json = toJSON(ast);

Example: Finding Security Issues

import { parse, findAllNodes, hasChild, getChild } from '@sentriflow/core'; const config = ` interface GigabitEthernet0/0 ip address 192.168.1.1 255.255.255.0 no shutdown ! interface GigabitEthernet0/1 description Uplink ip address 10.0.0.1 255.255.255.0 no shutdown `; const ast = parse(config, 'cisco-ios'); // Find interfaces without descriptions const interfaces = findAllNodes(ast, n => n.type === 'section' && n.id.startsWith('interface') ); const missingDescription = interfaces.filter(iface => !hasChild(iface, 'description') ); for (const iface of missingDescription) { console.log(`Missing description: ${iface.id} (line ${iface.loc.startLine})`); }

Example: Cross-Reference Check

import { parse, findAllNodes, getValueAfter } from '@sentriflow/core'; // Check if referenced IPs exist on interfaces function checkIpReferences(ast: ConfigNode[]): string[] { const issues: string[] = []; // Get all interface IPs const interfaceIps = new Set<string>(); const interfaces = findAllNodes(ast, n => n.id.startsWith('interface')); for (const iface of interfaces) { const ipNode = iface.children.find(c => c.id.startsWith('ip address')); if (ipNode) { const ip = ipNode.params[2]; if (ip) interfaceIps.add(ip); } } // Check route next-hops const routes = findAllNodes(ast, n => n.id.startsWith('ip route')); for (const route of routes) { const nextHop = route.params[4]; // ip route <dest> <mask> <next-hop> if (nextHop && !interfaceIps.has(nextHop)) { // Next-hop not on local interface (might be external - just log) console.log(`Route next-hop ${nextHop} not on local interface`); } } return issues; }

Next Steps

Last updated on