@Rohit_809 Kumar this appears to be nesting your data. You can project the data as a nested structure for use in a downstream API or as a table as rows and columns.
The task you are performing is very much like I describe in Approach 1 and Approach 2
If you keep your field selectors common you can keep the structure common to your needs.
Hi @GaryB this article is very helpful. but however i am using below pattern for Syslog config .
as you know some device use “ logging host “ or some “logging server “ how can i use these 2 in 1 pattern.
Also F5 and Forintet the pattern is different not sure how can i add
F5
pattern = ```
sys ntp
servers { server1:string} {server2:string}
Fortinet
pattern_fmg = ```
config system ntp
config ntpserver
edit 1
set server {ntp1:string}
```;
i am not able to create single pattern logic to above condition , Please help
@Rohit_809 Kumar if i’m understanding your request correctly, you’re trying to combine the results for these items -
"Server-Cisco1": cisco1(device),
"Server-Cisco/Arista": cisco2(device),
"F5_server": max(getServers(device))?.server1,
"F5_server2": max(getServers(device))?.server2,}
- into one singular output.
The main issue you’re having with trying to combine them, is data type mismatch. The variables you set when you defined the patterns all have different types.
The result of your functions now have different types as well
cisco1(device) = foreach command in device.outputs.commands let response = parseConfigBlocks(device.platform.os, command.response) foreach match in blockMatches(response, ciscontppattern) select match.data.server;
the above returns a List<String>
cisco2(device) = foreach command in device.outputs.commands let response = parseConfigBlocks(device.platform.os, command.response) foreach match in blockMatches(response, ciscontppattern2) select match.data.ser;
while this returns a List<IpAddress>
getServers(device) = foreach command in device.outputs.commands where command.commandText == "list sys ntp" let filtered_response = replace(command.response, "{", "") let filtered_response = replace(filtered_response, "}", "") let blocks = parseConfigBlocks(OS.F5, filtered_response) foreach match in blockMatches(blocks, patternf5) select { server1: match.data.server1, server2: match.data.server2 };
and here we’re getting List<{server1: String, server2: String}
This is where the trouble of combining them comes in. Here is what I would suggest to get you the results that you’re looking for -
// since this pattern searches for any string, it is also going to pick up vrf lines. This excludes vrf lines from the search and leaves it to patern2 ciscontppattern = ``` ntp server {server:(!"vrf" string)} ```;
ciscontppattern2 = ``` ntp server {vrf:string} {dir:string} {ser:ipv4Address} ```;
// instead of defining variables for two servers, you can use this to collect any amount of server addresses in a list patternf5 = ``` sys ntp servers {ser:(string*)} ```;
cisco1(device) = // foreach command in device.outputs.commands // let response = parseConfigBlocks(device.platform.os, command.response) foreach match in blockMatches(device.files.config, ciscontppattern) // changed to search only the config file select match.data.server;
cisco2(device) = // foreach command in device.outputs.commands // let response = parseConfigBlocks(device.platform.os, command.response) foreach match in blockMatches(device.files.config, ciscontppattern2) // changed to search only the config file // convert the server ip to string so data types match select toString(match.data.ser);
getServers(device) = foreach command in device.outputs.commands where command.commandText == "list sys ntp" let filtered_response = replace(command.response, "{", "") let filtered_response = replace(filtered_response, "}", "") let blocks = parseConfigBlocks(OS.F5, filtered_response) foreach match in blockMatches(blocks, patternf5) // (string*) returns the match as a list, this removes the extra list layer in the final results foreach server in match.data.ser select server;
foreach device in network.devices // now that the functions all have matching data types, you can easily combine them into one variable let servers = cisco1(device) + cisco2(device) + getServers(device) select { vendor: device.platform.vendor, "Device Name": device.name, "IP Address": device.snapshotInfo.collectionIp, Tags: device.tagNames, Location: device.locationName, "NTP Servers": servers, }
And if you need to add any additional patterns/functions just make sure the final output is a list of strings. You can verify this by hovering over the initial definition like so
@AricaFN thanks , its working what i need , but what is the scope to add new pattern in same query ?
example - if i need to add some other pattern for cisco.
Thanks
@Rohit_809 Kumar if you need to add a new pattern, you can use the same pattern and function format you have for the other cisco patterns. You just need the final data types to be strings. So you can either define the pattern as a string and add it to the cisco1 function, or you can define it as an ip address and add it to the cisco2 function where it uses toString() on the ip address.
Examples of how that would look -
ciscontppattern3 = ``` replace with new ntp server pattern {server:string} ```;
cisco1(device) = foreach match in blockMatches(device.files.config, ciscontppattern) + blockMatches(device.files.config, ciscontppattern3) select match.data.server;
or
ciscontppattern3 = ``` replace with new ntp server pattern {server:ipv4Address} ```;
cisco2(device) = foreach match in blockMatches(device.files.config, ciscontppattern2) + blockMatches(device.files.config, ciscontppattern3) select toString(match.data.ser);