How too data from comment fields in a config file via NQE
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
!
Page 1 / 1
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
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`;
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).