Skip to main content

How can i get output in single coloum . 

 

team ,

 

i am using below query to get the NTP Server data , however is tried all option to get the output in single coloum , but i am not sucess.

 

NQE Query 

ciscontppattern=

```



ntp server {server:string}

```;

ciscontppattern2=

```



ntp server {vrf:string} {dir:string} {ser:ipv4Address}

```;

patternf5 = ```

sys ntp

    servers { server1:string} {server2:string}

```;




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;

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;

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,

 };



foreach device in network.devices

select {

  vendor: device.platform.vendor,

  "Device Name": device.name,

  "IP Address": device.snapshotInfo.collectionIp,

  Tags: device.tagNames,

  Location: device.locationName,

  "Server-Cisco1": cisco1(device),

  "Server-Cisco/Arista": cisco2(device),

   "F5_server": max(getServers(device))?.server1,

    "F5_server2": max(getServers(device))?.server2,}

 

@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 .

 

pattern1 = `logging {tech:string}{"server" vrf:string server:string | server:string }`;

 

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);

 


Reply