diff --git a/changelog.txt b/changelog.txt index 7b71da39..744fbef1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ jc changelog +20200xxxx vX.X.X +- Added ntpq command parser + 20200308 v1.8.1 - CLI and history parser optimizations by https://github.com/philippeitis - Refactored magic syntax function and added tests (https://github.com/philippeitis) diff --git a/docgen.sh b/docgen.sh index b37f7cde..a9746502 100755 --- a/docgen.sh +++ b/docgen.sh @@ -31,6 +31,7 @@ pydocmd simple jc.parsers.lsmod+ > ../docs/parsers/lsmod.md pydocmd simple jc.parsers.lsof+ > ../docs/parsers/lsof.md pydocmd simple jc.parsers.mount+ > ../docs/parsers/mount.md pydocmd simple jc.parsers.netstat+ > ../docs/parsers/netstat.md +pydocmd simple jc.parsers.ntpq+ > ../docs/parsers/ntpq.md pydocmd simple jc.parsers.passwd+ > ../docs/parsers/passwd.md pydocmd simple jc.parsers.pip_list+ > ../docs/parsers/pip_list.md pydocmd simple jc.parsers.pip_show+ > ../docs/parsers/pip_show.md diff --git a/docs/parsers/ntpq.md b/docs/parsers/ntpq.md new file mode 100644 index 00000000..d0062147 --- /dev/null +++ b/docs/parsers/ntpq.md @@ -0,0 +1,235 @@ +# jc.parsers.ntpq +jc - JSON CLI output utility ntpq Parser + +Usage: + + specify --ntpq as the first argument if the piped input is coming from ntpq -p + +Compatibility: + + 'linux' + +Examples: + + $ ntpq -p | jc --ntpq -p + [ + { + "selection_state": null, + "remote": "44.190.6.254", + "refid": "127.67.113.92", + "st": 2, + "t": "u", + "when": 1, + "poll": 64, + "reach": 1, + "delay": 23.399, + "offset": -2.805, + "jitter": 2.131 + }, + { + "selection_state": null, + "remote": "ntp.wdc1.us.lea", + "refid": "130.133.1.10", + "st": 2, + "t": "u", + "when": null, + "poll": 64, + "reach": 1, + "delay": 93.053, + "offset": -0.807, + "jitter": 2.839 + }, + { + "selection_state": null, + "remote": "clock.team-cymr", + "refid": "204.9.54.119", + "st": 2, + "t": "u", + "when": null, + "poll": 64, + "reach": 1, + "delay": 70.337, + "offset": -2.909, + "jitter": 2.6 + }, + { + "selection_state": null, + "remote": "mirror1.sjc02.s", + "refid": "216.218.254.202", + "st": 2, + "t": "u", + "when": 2, + "poll": 64, + "reach": 1, + "delay": 29.325, + "offset": 1.044, + "jitter": 4.069 + } + ] + + $ ntpq -pn| jc --ntpq -p + [ + { + "selection_state": "+", + "remote": "44.190.6.254", + "refid": "127.67.113.92", + "st": 2, + "t": "u", + "when": 66, + "poll": 64, + "reach": 377, + "delay": 22.69, + "offset": -0.392, + "jitter": 2.085 + }, + { + "selection_state": "-", + "remote": "108.59.2.24", + "refid": "130.133.1.10", + "st": 2, + "t": "u", + "when": 63, + "poll": 64, + "reach": 377, + "delay": 90.805, + "offset": 2.84, + "jitter": 1.908 + }, + { + "selection_state": "+", + "remote": "38.229.71.1", + "refid": "204.9.54.119", + "st": 2, + "t": "u", + "when": 64, + "poll": 64, + "reach": 377, + "delay": 68.699, + "offset": -0.61, + "jitter": 2.576 + }, + { + "selection_state": "*", + "remote": "72.5.72.15", + "refid": "216.218.254.202", + "st": 2, + "t": "u", + "when": 63, + "poll": 64, + "reach": 377, + "delay": 22.654, + "offset": 0.231, + "jitter": 1.964 + } + ] + + $ ntpq -pn| jc --ntpq -p -r + [ + { + "selection_state": "+", + "remote": "44.190.6.254", + "refid": "127.67.113.92", + "st": "2", + "t": "u", + "when": "66", + "poll": "64", + "reach": "377", + "delay": "22.690", + "offset": "-0.392", + "jitter": "2.085" + }, + { + "selection_state": "-", + "remote": "108.59.2.24", + "refid": "130.133.1.10", + "st": "2", + "t": "u", + "when": "63", + "poll": "64", + "reach": "377", + "delay": "90.805", + "offset": "2.840", + "jitter": "1.908" + }, + { + "selection_state": "+", + "remote": "38.229.71.1", + "refid": "204.9.54.119", + "st": "2", + "t": "u", + "when": "64", + "poll": "64", + "reach": "377", + "delay": "68.699", + "offset": "-0.610", + "jitter": "2.576" + }, + { + "selection_state": "*", + "remote": "72.5.72.15", + "refid": "216.218.254.202", + "st": "2", + "t": "u", + "when": "63", + "poll": "64", + "reach": "377", + "delay": "22.654", + "offset": "0.231", + "jitter": "1.964" + } + ] + +## info +```python +info(self, /, *args, **kwargs) +``` + +## process +```python +process(proc_data) +``` + +Final processing to conform to the schema. + +Parameters: + + proc_data: (dictionary) raw structured data to process + +Returns: + + List of dictionaries. Structured data with the following schema: + + [ + { + "selection_state": string, # space/~ converted to null + "remote": string, + "refid": string, + "st": integer, + "t": string, + "when": integer, # - converted to null + "poll": integer, + "reach": integer, + "delay": float, + "offset": float, + "jitter": float + }, + ] + + +## parse +```python +parse(data, raw=False, quiet=False) +``` + +Main text parsing function + +Parameters: + + data: (string) text data to parse + raw: (boolean) output preprocessed JSON if True + quiet: (boolean) suppress warning messages if True + +Returns: + + List of dictionaries. Raw or processed structured data. + diff --git a/jc/cli.py b/jc/cli.py index a89d4eed..c49f9d04 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -49,6 +49,7 @@ parsers = [ 'lsof', 'mount', 'netstat', + 'ntpq', 'passwd', 'pip-list', 'pip-show', diff --git a/jc/parsers/ntpq.py b/jc/parsers/ntpq.py new file mode 100644 index 00000000..466dbcd9 --- /dev/null +++ b/jc/parsers/ntpq.py @@ -0,0 +1,291 @@ +"""jc - JSON CLI output utility ntpq Parser + +Usage: + + specify --ntpq as the first argument if the piped input is coming from ntpq -p + +Compatibility: + + 'linux' + +Examples: + + $ ntpq -p | jc --ntpq -p + [ + { + "selection_state": null, + "remote": "44.190.6.254", + "refid": "127.67.113.92", + "st": 2, + "t": "u", + "when": 1, + "poll": 64, + "reach": 1, + "delay": 23.399, + "offset": -2.805, + "jitter": 2.131 + }, + { + "selection_state": null, + "remote": "ntp.wdc1.us.lea", + "refid": "130.133.1.10", + "st": 2, + "t": "u", + "when": null, + "poll": 64, + "reach": 1, + "delay": 93.053, + "offset": -0.807, + "jitter": 2.839 + }, + { + "selection_state": null, + "remote": "clock.team-cymr", + "refid": "204.9.54.119", + "st": 2, + "t": "u", + "when": null, + "poll": 64, + "reach": 1, + "delay": 70.337, + "offset": -2.909, + "jitter": 2.6 + }, + { + "selection_state": null, + "remote": "mirror1.sjc02.s", + "refid": "216.218.254.202", + "st": 2, + "t": "u", + "when": 2, + "poll": 64, + "reach": 1, + "delay": 29.325, + "offset": 1.044, + "jitter": 4.069 + } + ] + + $ ntpq -pn| jc --ntpq -p + [ + { + "selection_state": "+", + "remote": "44.190.6.254", + "refid": "127.67.113.92", + "st": 2, + "t": "u", + "when": 66, + "poll": 64, + "reach": 377, + "delay": 22.69, + "offset": -0.392, + "jitter": 2.085 + }, + { + "selection_state": "-", + "remote": "108.59.2.24", + "refid": "130.133.1.10", + "st": 2, + "t": "u", + "when": 63, + "poll": 64, + "reach": 377, + "delay": 90.805, + "offset": 2.84, + "jitter": 1.908 + }, + { + "selection_state": "+", + "remote": "38.229.71.1", + "refid": "204.9.54.119", + "st": 2, + "t": "u", + "when": 64, + "poll": 64, + "reach": 377, + "delay": 68.699, + "offset": -0.61, + "jitter": 2.576 + }, + { + "selection_state": "*", + "remote": "72.5.72.15", + "refid": "216.218.254.202", + "st": 2, + "t": "u", + "when": 63, + "poll": 64, + "reach": 377, + "delay": 22.654, + "offset": 0.231, + "jitter": 1.964 + } + ] + + $ ntpq -pn| jc --ntpq -p -r + [ + { + "selection_state": "+", + "remote": "44.190.6.254", + "refid": "127.67.113.92", + "st": "2", + "t": "u", + "when": "66", + "poll": "64", + "reach": "377", + "delay": "22.690", + "offset": "-0.392", + "jitter": "2.085" + }, + { + "selection_state": "-", + "remote": "108.59.2.24", + "refid": "130.133.1.10", + "st": "2", + "t": "u", + "when": "63", + "poll": "64", + "reach": "377", + "delay": "90.805", + "offset": "2.840", + "jitter": "1.908" + }, + { + "selection_state": "+", + "remote": "38.229.71.1", + "refid": "204.9.54.119", + "st": "2", + "t": "u", + "when": "64", + "poll": "64", + "reach": "377", + "delay": "68.699", + "offset": "-0.610", + "jitter": "2.576" + }, + { + "selection_state": "*", + "remote": "72.5.72.15", + "refid": "216.218.254.202", + "st": "2", + "t": "u", + "when": "63", + "poll": "64", + "reach": "377", + "delay": "22.654", + "offset": "0.231", + "jitter": "1.964" + } + ] +""" +import jc.utils +import jc.parsers.universal + + +class info(): + version = '1.0' + description = 'ntpq command parser' + author = 'Kelly Brazil' + author_email = 'kellyjonbrazil@gmail.com' + # details = 'enter any other details here' + + # compatible options: linux, darwin, cygwin, win32, aix, freebsd + compatible = ['linux'] + magic_commands = ['ntpq'] + + +__version__ = info.version + + +def process(proc_data): + """ + Final processing to conform to the schema. + + Parameters: + + proc_data: (dictionary) raw structured data to process + + Returns: + + List of dictionaries. Structured data with the following schema: + + [ + { + "selection_state": string, # space/~ converted to null + "remote": string, + "refid": string, + "st": integer, + "t": string, + "when": integer, # - converted to null + "poll": integer, + "reach": integer, + "delay": float, + "offset": float, + "jitter": float + }, + ] + + """ + for entry in proc_data: + + if entry['selection_state'] == '~': + entry['selection_state'] = None + + int_list = ['st', 'when', 'poll', 'reach'] + for key in int_list: + if key in entry: + try: + entry[key] = int(entry[key]) + except (ValueError): + entry[key] = None + + float_list = ['delay', 'offset', 'jitter'] + for key in float_list: + if key in entry: + try: + entry[key] = float(entry[key]) + except (ValueError): + entry[key] = None + + return proc_data + + +def parse(data, raw=False, quiet=False): + """ + Main text parsing function + + Parameters: + + data: (string) text data to parse + raw: (boolean) output preprocessed JSON if True + quiet: (boolean) suppress warning messages if True + + Returns: + + List of dictionaries. Raw or processed structured data. + """ + if not quiet: + jc.utils.compatibility(__name__, info.compatible) + + raw_output = [] + + cleandata = data.splitlines() + cleandata[0] = 'selection_state ' + cleandata[0] + cleandata[0] = cleandata[0].lower() + + # delete header delimiter + del cleandata[1] + + # separate first character with a space for easier parsing + for i, line in enumerate(cleandata[1:]): + if line[0] == ' ': + cleandata[i + 1] = '~ ' + line[1:] + else: + cleandata[i + 1] = line[:1] + ' ' + line[1:] + + raw_output = jc.parsers.universal.simple_table_parse(cleandata) + + if raw: + return raw_output + else: + return process(raw_output) diff --git a/tests/fixtures/centos-7.7/ntpq-p.out b/tests/fixtures/centos-7.7/ntpq-p.out new file mode 100644 index 00000000..5cefcac5 --- /dev/null +++ b/tests/fixtures/centos-7.7/ntpq-p.out @@ -0,0 +1,6 @@ + remote refid st t when poll reach delay offset jitter +============================================================================== + 44.190.6.254 127.67.113.92 2 u 1 64 1 23.399 -2.805 2.131 + ntp.wdc1.us.lea 130.133.1.10 2 u - 64 1 93.053 -0.807 2.839 + clock.team-cymr 204.9.54.119 2 u - 64 1 70.337 -2.909 2.600 + mirror1.sjc02.s 216.218.254.202 2 u 2 64 1 29.325 1.044 4.069 diff --git a/tests/fixtures/centos-7.7/ntpq-pn.out b/tests/fixtures/centos-7.7/ntpq-pn.out new file mode 100644 index 00000000..9bbce795 --- /dev/null +++ b/tests/fixtures/centos-7.7/ntpq-pn.out @@ -0,0 +1,6 @@ + remote refid st t when poll reach delay offset jitter +============================================================================== ++44.190.6.254 127.67.113.92 2 u 66 64 377 22.690 -0.392 2.085 +-108.59.2.24 130.133.1.10 2 u 63 64 377 90.805 2.840 1.908 ++38.229.71.1 204.9.54.119 2 u 64 64 377 68.699 -0.610 2.576 +*72.5.72.15 216.218.254.202 2 u 63 64 377 22.654 0.231 1.964