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:
| Type | Description |
|---|---|
command | A single configuration command |
section | A configuration block with children |
comment | A comment line |
virtual_root | Synthetic 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 = ast.find(n => n.id.startsWith('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'Parsing Configuration
Use the SchemaAwareParser class to parse configuration text into an AST:
import { SchemaAwareParser } from '@sentriflow/core';
const config = `
interface GigabitEthernet0/0
ip address 192.168.1.1 255.255.255.0
no shutdown
`;
const parser = new SchemaAwareParser();
const ast = parser.parse(config);Traversing the AST
Since the AST is returned as an array of ConfigNode objects, you can use standard JavaScript array methods to traverse and query it.
Finding Nodes
Use Array.filter() to find all nodes matching a condition:
// Find all interface sections
const interfaces = ast.filter(n =>
n.type === 'section' && n.id.startsWith('interface')
);
// Find all BGP router sections
const bgpRouters = ast.filter(n => n.id.startsWith('router bgp'));Use Array.find() to find the first matching node:
const interfaceNode = ast.find(n =>
n.type === 'section' && n.id.startsWith('interface')
);Query Helpers
SentriFlow provides helper functions for common node queries. Import them from @sentriflow/core:
import { hasChildCommand, getChildCommand, getChildCommands } from '@sentriflow/core';hasChildCommand()
Check if a node has a child matching a prefix:
import { hasChildCommand } from '@sentriflow/core';
const hasDescription = hasChildCommand(interfaceNode, 'description');
const isShutdown = hasChildCommand(interfaceNode, 'shutdown');getChildCommand()
Get a child node by prefix:
import { getChildCommand } from '@sentriflow/core';
const descNode = getChildCommand(interfaceNode, 'description');
if (descNode) {
const description = descNode.params.slice(1).join(' ');
}getChildCommands()
Get all children matching a prefix:
import { getChildCommands } from '@sentriflow/core';
const aclEntries = getChildCommands(aclNode, 'permit');
const denyEntries = getChildCommands(aclNode, 'deny');Working with Parameters
Access parameter values directly from the params array:
// For "ip address 192.168.1.1 255.255.255.0"
const ip = node.params[2]; // "192.168.1.1"
const mask = node.params[3]; // "255.255.255.0"For more complex parameter extraction, use standard array methods:
// Find value after a keyword
const params = node.params;
const vlanIndex = params.indexOf('vlan');
const vlanId = vlanIndex !== -1 ? params[vlanIndex + 1] : undefined;Selector Matching
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 ..."Example: Finding Security Issues
import { SchemaAwareParser, hasChildCommand, getChildCommand } 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 parser = new SchemaAwareParser();
const ast = parser.parse(config);
// Find interfaces without descriptions
const interfaces = ast.filter(n => n.id.startsWith('interface'));
const missingDescription = interfaces.filter(iface =>
!hasChildCommand(iface, 'description')
);
for (const iface of missingDescription) {
console.log(`Missing description: ${iface.id} (line ${iface.loc.startLine})`);
}Example: Cross-Reference Check
import { SchemaAwareParser } 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 = ast.filter(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 = ast.filter(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
- Parser API - Parsing configurations
- Rule Engine API - Running validation
- Helper Functions - Vendor-specific utilities