diff --git a/.gitignore b/.gitignore index 749a792..a87345b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ __pycache__ .coverage *,cover +build dist zeek_client.egg-info diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec868ab..185e383 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,7 @@ repos: hooks: - id: pylint additional_dependencies: + - argparse_manpage - websocket-client - repo: https://github.com/pre-commit/pre-commit-hooks @@ -18,3 +19,14 @@ repos: - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files + +- repo: local + hooks: + - id: generate-manpage + name: generate manpage + language: python + additional_dependencies: + - argparse_manpage + - websocket-client + entry: ./man/build.py + files: (zeekclient/cli\.py|man/build\.py)$ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6eb5d58..e961369 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,13 @@ configure_file( install(FILES ${CMAKE_CURRENT_BINARY_DIR}/zeekclient/consts.py DESTINATION ${PY_MOD_INSTALL_DIR}/zeekclient) +# This mirrors what we do in zkg: +if ( NOT ZEEK_MAN_INSTALL_PATH ) + set(ZEEK_MAN_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/share/man) +endif () + +install(FILES man/zeek-client.1 DESTINATION ${ZEEK_MAN_INSTALL_PATH}/man1) + message( "\n==================| zeek-client Build Summary |===============" "\n" diff --git a/Makefile b/Makefile index ea776c1..24d4c2f 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,10 @@ test-coverage: test_types.py \ && coverage report -m +.PHONY: man +man: + ./man/build.py + .PHONY: dist dist: rm -rf dist/*.tar.gz diff --git a/man/build.py b/man/build.py new file mode 100755 index 0000000..a418b8b --- /dev/null +++ b/man/build.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# +# A helper script to derive a zeek-client manpage from its argparse state. +# +import os +import sys + +try: + from argparse_manpage.manpage import Manpage +except ImportError: + print("The manpage builder needs the argparse_manpage package.") + sys.exit(1) + +LOCALDIR = os.path.dirname(os.path.realpath(__file__)) +ROOTDIR = os.path.normpath(os.path.join(LOCALDIR, "..")) + +# Prepend the project's toplevel directory to the search path so we import the +# zeekclient package locally. +sys.path.insert(0, ROOTDIR) + +import zeekclient.cli # pylint: disable=wrong-import-position,import-error + + +def main(): + # Set a fixed number of columns to avoid output discrepancies between + # invocation at the shell vs via CI tooling. This only affects how the + # manpage is written to disk, not how it renders at the terminal. + os.environ["COLUMNS"] = "80" + + # Change the program name so the parsers report zeek-client, not build.py. + sys.argv[0] = "zeek-client" + + parser = zeekclient.cli.create_parser() + + # Expand the description: + parser.description = """A command-line client for Zeek's Management Framework. + +Use this client to push cluster configurations to a cluster controller, retrieve +running state from the system, restart nodes, and more. + +For details about Zeek's Management Framework, please consult the documentation +at https://docs.zeek.org/en/master/frameworks/management.html. +""" + # Remove the epilog -- it's more suitable for an ENVIRONMENT section. + environment = parser.epilog + parser.epilog = None + + # The 'single-commands-section' formatter spells out the details of every + # subcommand as it lists each of those commands. + manpage = Manpage(parser, format="single-commands-section") + + # The Manpage class cannot handle ":" in the usage string ("HOST:PORT"), + # so replace the synopsis. Must yield a list of strings. + # https://github.com/praiskup/argparse-manpage/pull/102 + manpage.synopsis = parser.format_usage().split(":", 1)[-1].split() + + # The manpage contains a date, which breaks our comparison logic when + # running this script produces otherwise identical output. + manpage.date = "" + + # It's just another user command, more meaningful than "Generated Python Manual" + manpage.manual = "User Commands" + + manpage.add_section( + "EXIT STATUS", + ">", + """The client exits with 0 on +success and 1 if a problem arises, such as lack of a response from the +controller, unexpected response data, or the controller explicitly reporting an +error in its handling of a command.""", + ) + + # Create an environment section from the epilog in the parser. + # We replace the first line to be more manpage-suitable. + env = environment.splitlines() + env = ["zeek-client supports the following environment variables:"] + env[1:] + manpage.add_section("ENVIRONMENT", ">", "\n".join(env)) + + manpage.add_section( + "SUGGESTIONS AND BUG REPORTS", + ">", + """The Management Framework and this client are experimental +software. The Zeek team welcomes your feedback. Please file issues on Github at +https://github.com/zeek/zeek-client/issues, or contact us on Discourse or Slack: +https://zeek.org/community""", + ) + + with open(os.path.join(LOCALDIR, "zeek-client.1"), "w", encoding="ascii") as hdl: + hdl.write(str(manpage)) + + +if __name__ == "__main__": + main() diff --git a/man/zeek-client.1 b/man/zeek-client.1 new file mode 100644 index 0000000..71e9ed0 --- /dev/null +++ b/man/zeek-client.1 @@ -0,0 +1,166 @@ +.TH ZEEK\-CLIENT "1" "" "zeek\-client" "User Commands" +.SH NAME +zeek\-client +.SH SYNOPSIS +.B zeek\-client +[-h] [-c FILE] [--controller HOST:PORT] [--set SECTION.KEY=VAL] [--quiet | --verbose] [--version] {deploy,deploy-config,get-config,get-id-value,get-instances,get-nodes,monitor,restart,stage-config,show-settings,test-timeout} ... +.SH DESCRIPTION +A command\-line client for Zeek's Management Framework. + +Use this client to push cluster configurations to a cluster controller, retrieve +running state from the system, restart nodes, and more. + +For details about Zeek's Management Framework, please consult the documentation +at https://docs.zeek.org/en/master/frameworks/management.html. + +.SH OPTIONS +.TP +\fB\-c\fR \fI\,FILE\/\fR, \fB\-\-configfile\fR \fI\,FILE\/\fR +Path to zeek\-client config file. (Default: @ZEEK_CLIENT_CONFIG_FILE@) + +.TP +\fB\-\-controller\fR \fI\,HOST:PORT\/\fR +Address and port of the controller, either of which may be omitted (default: 127.0.0.1:2149) + +.TP +\fB\-\-set\fR \fI\,SECTION.KEY=VAL\/\fR +Adjust a configuration setting. Can use repeatedly. See show\-settings. + +.TP +\fB\-\-quiet\fR, \fB\-q\fR +Suppress informational output to stderr. + +.TP +\fB\-\-verbose\fR, \fB\-v\fR +Increase informational output to stderr. Repeat for more output (e.g. \-vvv). + +.TP +\fB\-\-version\fR +Show version number and exit. + +.SH +COMMANDS +.SS \fBzeek\-client deploy\fR +Deploy a staged cluster configuration. + +usage: zeek\-client deploy [\-h] +.SS \fBzeek\-client deploy\-config\fR +Upload a cluster configuration and deploy it. + +usage: zeek\-client deploy\-config [\-h] FILE + +arguments: +.RS 7 +.TP +\fBFILE\fR +Cluster configuration file, "\-" for stdin +.RE + +.SS \fBzeek\-client get\-config\fR +Retrieve staged or deployed cluster configuration. + +usage: zeek\-client get\-config [\-h] [\-\-filename FILE] [\-\-as\-json] + [\-\-deployed | \-\-staged] + +options: +.RS 7 +.TP +\fB\-\-filename\fR \fI\,FILE\/\fR, \fB\-f\fR \fI\,FILE\/\fR +Output file for the configuration, default stdout + +.TP +\fB\-\-as\-json\fR +Report in JSON instead of INI\-style config file + +.TP +\fB\-\-deployed\fR +Return deployed configuration + +.TP +\fB\-\-staged\fR +Return staged configuration (default) +.RE + +.SS \fBzeek\-client get\-id\-value\fR +Show the value of a given identifier in Zeek cluster nodes. + +usage: zeek\-client get\-id\-value [\-h] IDENTIFIER [NODES ...] + +arguments: +.RS 7 +.TP +\fBIDENTIFIER\fR +Name of the Zeek script identifier to retrieve. + +.TP +\fBNODES\fR +Name(s) of Zeek cluster nodes to query. When omitted, queries all nodes. +.RE + +.SS \fBzeek\-client get\-instances\fR +Show instances connected to the controller. + +usage: zeek\-client get\-instances [\-h] +.SS \fBzeek\-client get\-nodes\fR +Show active Zeek nodes at each instance. + +usage: zeek\-client get\-nodes [\-h] +.SS \fBzeek\-client monitor\fR +For troubleshooting: do nothing, just report events. + +usage: zeek\-client monitor [\-h] +.SS \fBzeek\-client restart\fR +Restart cluster nodes. + +usage: zeek\-client restart [\-h] [NODES ...] + +arguments: +.RS 7 +.TP +\fBNODES\fR +Name(s) of Zeek cluster nodes to restart. When omitted, restarts all nodes. +.RE + +.SS \fBzeek\-client stage\-config\fR +Upload a cluster configuration for later deployment. + +usage: zeek\-client stage\-config [\-h] FILE + +arguments: +.RS 7 +.TP +\fBFILE\fR +Cluster configuration file, "\-" for stdin +.RE + +.SS \fBzeek\-client show\-settings\fR +Show zeek-client's own configuration. + +usage: zeek\-client show\-settings [\-h] +.SS \fBzeek\-client test\-timeout\fR +Send timeout test event. + +usage: zeek\-client test\-timeout [\-h] [\-\-with\-state] + +options: +.RS 7 +.TP +\fB\-\-with\-state\fR +Make request stateful in the controller. +.RE + +.SH EXIT STATUS +The client exits with 0 on +success and 1 if a problem arises, such as lack of a response from the +controller, unexpected response data, or the controller explicitly reporting an +error in its handling of a command. +.SH ENVIRONMENT +zeek-client supports the following environment variables: + + ZEEK_CLIENT_CONFIG_FILE: Same as `--configfile` argument, but lower precedence. + ZEEK_CLIENT_CONFIG_SETTINGS: Same as a space-separated series of `--set` arguments, but lower precedence. +.SH SUGGESTIONS AND BUG REPORTS +The Management Framework and this client are experimental +software. The Zeek team welcomes your feedback. Please file issues on Github at +https://github.com/zeek/zeek-client/issues, or contact us on Discourse or Slack: +https://zeek.org/community