Skip to main content

The below are two lines that we are trying to pull data out of, but it appears utilizing device.file.config  from the data model wont return the information because they are comments?  Thoughts?

 

 

 

!

! Last configuration change at 13:58:30 ZULU Fri Oct 25 2024 by personx

! NVRAM config last updated at 14:01:13 ZULU Thu Aug 22 2024 by persony

!

P.S. we only care about the time, date, and person as far as what is returned from the NQE.


You could use a custom command. For example show run. Okay not ideal but if the built in output has a limitation.

In this case all output will be available to you.

If it is cisco devicw you can probably also do

Show run | inc !

Which (and i have not tested it) may get just those lines in the output with a comment.

Maybe you can try this regex as well

Show run  | inc (config)


Here’s a sample that I found from another customer.  I tested it and it runs in parallel so should go pretty quick.  the logic has several nested if-then-else statements which we could look at optimizing.

The first few lines should give you a reference for what Andy is saying, the rest should give you some good ideas on how to handle the different conditions for getting the date, etc.  By the way we’ve done more with date functions lately so perhaps could streamline the logic here too

/**
* @intent Saved config older than Current
* @description Compares the last configuration timestamp to last NVRAM timestamp - if newer then violation
*/
foreach device in network.devices
where device.platform.os in mOS.IOS, OS.IOS_XE, OS.IOS_XR, OS.NXOS]
foreach command in device.outputs.commands
where command.commandType == CommandType.CONFIG
let configTextWithComments = replace(command.response, "!! ", "")
let configTextWithComments2 = replace(configTextWithComments, "! ", "")
let configBlocksWithComments = parseConfigBlocks(device.platform.os, configTextWithComments2)
/*
This part is going to look like a duplicate.
need to go through one time without the "person" variable
Then a second time with the "person" variable
The problem is that a "by person" is not always present
*
* !Running configuration last done at: Tue Feb 7 06:33:51 2023 - discuss this pattern - device nxos**
*/
let lastIOSXE = max(foreach lastMatch
//! Last configuration change at 10:02:42 UTC Thu Jul 28 2022 by f-smartsncm << used to create pattern
//! Last configuration change at 20:16:59 UTC Sat Feb 4 2023 << breaks pattern
// Line for discussion 8-11 - pattern matches
in patternMatches(configBlocksWithComments, `Last configuration change at {time:string} {timezone:string} {dayofweek:string} {month:string} {dayofmonth:number} {year:number}`)
select lastMatch)
let lastIOSXR = max(foreach lastMatch
//!! Last configuration change at Tue Feb 7 16:54:27 2023 by ZILEA004 - IOSXR
// Line for discussion 8-11 - pattern matches
in patternMatches(configBlocksWithComments, `Last configuration change at {dayofweek:string} {month:string} {dayofmonth:number} {time:string} {year:number} {timezone:string}`)
select lastMatch)
let lastNull = {line: null:ConfigLine, data: {month:null:String, year: null:Number, dayofweek: null:String, time: null:String, timezone: null:String, dayofmonth: null:Number}}
let last = if isPresent(lastIOSXE)
then lastIOSXE
else if isPresent(lastIOSXR)
then lastIOSXR
else lastNull
/*
This part is going to look like a duplicate.
this is the second time through to get the "person" if present
*/
let lastIOSXEperson = max(foreach lastMatch
//! Last configuration change at 10:02:42 UTC Thu Jul 28 2022 by f-smartsncm << used to create pattern
//! Last configuration change at 20:16:59 UTC Sat Feb 4 2023 << breaks pattern
// Line for discussion 8-11 - pattern matches
in patternMatches(configBlocksWithComments, `Last configuration change at {time:string} {timezone:string} {dayofweek:string} {month:string} {dayofmonth:number} {year:number} by {person:string}`)
select lastMatch)
let lastIOSXRperson = max(foreach lastMatch
//!! Last configuration change at Tue Feb 7 16:54:27 2023 by ZILEA004 - IOSXR
// Line for discussion 8-11 - pattern matches
in patternMatches(configBlocksWithComments, `Last configuration change at {dayofweek:string} {month:string} {dayofmonth:number} {time:string} {year:number} by {person:string}`)
select lastMatch)
let lastNullperson = {line: null:ConfigLine, data: {month:null:String, year: null:Number, person: null:String, dayofweek: null:String, time: null:String, timezone: null:String, dayofmonth: null:Number}}
let lastperson = if isPresent(lastIOSXEperson)
then lastIOSXEperson
else if isPresent(lastIOSXRperson)
then lastIOSXRperson
else lastNullperson
/*
* need same duplication for nvram and nvramperson
*/
let nvram = max(foreach nvramMatch
in patternMatches(configBlocksWithComments, `NVRAM config last updated at {time:string} {timezone:string} {dayofweek:string} {month:string} {dayofmonth:number} {year:number}`)
select nvramMatch)

let nvram_nxos = max(foreach nvramMatch
in patternMatches(configBlocksWithComments, `Running configuration last done at: Tue Feb 7 06:33:51 2023`)
select nvramMatch)

/*
* need same duplication for nvram and nvramperson
*/
let nvramperson = max(foreach nvramMatch
in patternMatches(configBlocksWithComments, `NVRAM config last updated at {time:string} {timezone:string} {dayofweek:string} {month:string} {dayofmonth:number} {year:number} by {person:string}`)
select nvramMatch)
let lasthour = prefix(last.data.time, 2)
let lastminute = substring(last.data.time, 3, 5)
let lastsecond = suffix(last.data.time, 2)
let nvramhour = prefix(nvram.data.time, 2)
let nvramminute = substring(nvram.data.time, 3, 5)
let nvramsecond = suffix(nvram.data.time, 2)
let lastmonth = last.data.month
let lastmonnum = if matches(lastmonth, "Jan")
then 1
else if matches(lastmonth, "Feb")
then 2
else if matches(lastmonth, "Mar")
then 3
else if matches(lastmonth, "Apr")
then 4
else if matches(lastmonth, "May")
then 5
else if matches(lastmonth, "Jun")
then 6
else if matches(lastmonth, "Jul")
then 7
else if matches(lastmonth, "Aug")
then 8
else if matches(lastmonth, "Sep")
then 9
else if matches(lastmonth,
"Oct")
then 10
else if matches(lastmonth,
"Nov")
then 11
else if matches(lastmonth,
"Dec")
then 12
else 99
let nvrammonth = nvram.data.month
let nvrammonnum = if matches(nvrammonth, "Jan")
then 1
else if matches(nvrammonth, "Feb")
then 2
else if matches(nvrammonth, "Mar")
then 3
else if matches(nvrammonth, "Apr")
then 4
else if matches(nvrammonth, "May")
then 5
else if matches(nvrammonth, "Jun")
then 6
else if matches(nvrammonth, "Jul")
then 7
else if matches(nvrammonth, "Aug")
then 8
else if matches(nvrammonth,
"Sep")
then 9
else if matches(nvrammonth,
"Oct")
then 10
else if matches(nvrammonth,
"Nov")
then 11
else if matches(nvrammonth,
"Dec")
then 12
else 0
let violation = if !isPresent(last?.data?.year)
then true
else if !isPresent(nvram?.data?.year)
then true
else if last.data.year > nvram.data.year
then true
else if last.data.year < nvram.data.year
then false
else if lastmonnum > nvrammonnum
then true
else if lastmonnum < nvrammonnum
then false
else if last.data.dayofmonth > nvram.data.dayofmonth
then true
else if last.data.dayofmonth < nvram.data.dayofmonth
then false
else if lasthour > nvramhour
then true
else false



let stringLast = if isPresent(last?.data?.timezone) && last.data.timezone == "by" //IOS_XR does not have timezone currently
then
toString(last.data.time) + " " /*+ toString(last?.data?.timezone)+ " "*/+toString(last.data.dayofweek) + " "+toString(last.data.month)+ " " + " "+toString(last.data.dayofmonth) +" " +toString(last.data.year)
else if isPresent(last?.data?.time)
then
toString(last.data.time) + " " + toString(last?.data?.timezone)+ " "+toString(last.data.dayofweek) + " "+toString(last.data.month)+ " " + " "+toString(last.data.dayofmonth) +" " +toString(last.data.year)
else null:String

let stringNvram = if isPresent(nvram?.data?.time)
then
toString(nvram.data.time) + " " + toString(nvram.data.timezone)+ " "+toString(nvram.data.dayofweek) + " "+toString(nvram.data.month)+ " " + " "+toString(nvram.data.dayofmonth) +" " +toString(nvram.data.year)
else null:String
//where violation == true
select {
violation: violation,
device: device.name,
LastConfiguredBy: lastperson?.data?.person,
Last: stringLast, //if want whole config line - last.line.text
LastSavedBy: nvramperson?.data?.person,
NVRAM: stringNvram,
OS: device.platform.os
//timeStamp: toString(last.data.month)+ "-"+toString(last.data.dayofmonth) +"-"+toString(last.data.year)//if want whole config line - nvram.line.text
}

 


Tried that as well.  I get the error message adding the custom command, stating that “!” must not be included.

The information is in the default config file we pull, I just cant seem to extract it with an NQE.


@Scot Wilson - I will try that out...


@Scot Wilson - Banging and spot on for Cisco...but what about Junipers?


To make it a bit easier to follow the logic, this way you can extract the fields you want by working with the raw output then replacing the !! or ! then parsing that output as a config block and using pattern matches

pattern = `Last configuration change at Tue Oct 22 22:45:37 2024 by {person:string}`;

foreach device in network.devices
where device.platform.os in [OS.IOS, OS.IOS_XE, OS.IOS_XR, OS.NXOS]
foreach command in device.outputs.commands
where command.commandType == CommandType.CONFIG
let configTextWithComments = replace(command.response, "!! ", "")
let configTextWithComments2 = replace(configTextWithComments, "! ", "")
let configBlocksWithComments = parseConfigBlocks(device.platform.os, configTextWithComments2)
let lastperson = patternMatches(configBlocksWithComments, pattern)
select {
device: device.name,
LastConfiguredBy: (foreach l in lastperson select l?.data?.person),
OS: device.platform.os
}

 


Hi @gbaron @Scot Wilson ,

As you’ve noticed, device.files.config attribute strips out comment lines. So if you are trying to match/extract something in comment lines, you’ll need to use the CONFIG output under device.output.commands, as @Scot Wilson ‘s query does.

As  @Scot Wilson  alluded to, we’ve added some new capabilities recently that might make this query a bit easier now. Specifically, we’ve added date and time data types (24.9) and also regular expressions (24.10). 

Here is an example of what you can do now with regular expressions and dates:

regex =
re`! Last configuration change at (?<time>.+) (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?<month>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?<day:Number>\d+) (?<year>\d+)\s*(?:by (?<person>.+))?\n`;

monthNums =
"""csv
name,num
Jan,1
Feb,2
Mar,3
Apr,4
May,5
Jun,6
Jul,7
Aug,8
Sep,9
Oct,10
Nov,11
Dec,12
""";

getMonthNum(name) =
max(foreach monthNum in monthNums
where monthNum.name == name
select monthNum.num);

getDate(data) =
date(join("-", >data.year,
prepend0(getMonthNum(data.month)),
prepend0(data.day)
]));

prepend0(n: Number) = (if n < 10 then "0" else "") + toString(n);

foreach device in network.devices
foreach command in device.outputs.commands
where command.commandType == CommandType.CONFIG
let match = max(regexMatches(command.response, regex))
let date = if isPresent(match) then getDate(match.data) else null : Date
select {
Device: device.name,
"Has Match": isPresent(match),
Matched: if isPresent(match) then match.string else "",
Date: date,
Time: if isPresent(match) then match.data.time else "",
Person: if isPresent(match) then match.data.person else ""
}

In the above logic, we use a regular expression literal (re`...`) and regexMatches function to match the regular expression against the config. We access the captured data under the data field of the match. We also define the date variable as a Date value. That can be helpful if you need to do date logic on that value, such as find devices where that date is before some other date. See the “date(string)” function in the docs for how to construct dates (e.g. to compare against).

 

You will likely need to tweak the regular expression for different OSes (or use different regexes for different OSes).


Reply