Skip to content

Commit

Permalink
[#385][#390] JSON command output implementation for pgagroal-cli
Browse files Browse the repository at this point in the history
This commit introduces the capaiblity for `pgagroal-cli` to print out
the command results in a JSON format.

A new dependency on the library 'cJSON' has been introduced; see <https://github.com/DaveGamble/cJSON>.

A new set of functions, named 'json_xxx' have been added in a
separated file json.c (include file json.h) to handle JSON structures
in a more consistent way.

Each function that manipulates a JSON object has been named with the
'json_' prefix. Each management function that reads data out of the
protocol and creates or handles json data has been named with the
prefix 'pgagroal_management_json_'.
A few functions with a prefix name 'pgagroal_management_json_print'
are used to print out a JSON object as normal text.

Every command output, in the JSON format, is structured with a
'command' is the main object contained in the output, that in turns
has different attributes:
- 'output' is an object that contains the command specific
information (e.g., list of databases, messages, and so on);
- `error` a boolean-like value that indicates if the command
was in error or not (0 means success, 1 means error);
- `status` a string that contains either 'OK' or an error message in
the case the `error` flag is set;
- `exit-status` an integer value with the exit status of the command,
0 in case of success, a different value in case of failure.

The JSON object for a result includes also another object, named
'application', that can be used for introspection: such object
describes which executable has launched the command (so far, only
`pgagroal-cli`) and at which version.

In the 'output' object, every thing that has a list (e.g.,
connections, limits, databases, servers) will be wrapped into an
object with the attributes 'list' and 'count': the former contains the
details of every "thing", while the latter contains the count (i.e.,
the size of the array 'list' ).
Whenever possible, a 'state' string is placed to indicate the state of
the single entry.

The command `status` and `status details` have been unified on the
management side.
There is a single function that handles both the cases of reading the
answer for the `status` or the `status details` commands. This has
been done because `status details` includes the output of `status`.
The function `pgagroal_management_json_read_status_details` is in
charge of getting the answer of the management protocol (i.e., read
the socket) and invoke the printing function in the case the output is
of type text. The above `pgagroal_management_json_read_status_details`
returns always a JSON object, that can be converted back to the text
output via `pgagroal_management_json_print_status_details`. In this
way, the output between the JSON and the text formats are always
coherent for these commands.

The `ping` (i.e., `is-alive`) command does not print nothing by
default. In the JSON version it provides the numerical status of the
server and a string that represents a human-readable status.

The `conf get` command has been refactored to be symmetric with other
commands: the logic to print out the result is now within the
management function (pgagroal_management_read_config_get) as per other
commands. The JSON provides the `config-key` and the `config-value` as
strings. See #390

The `conf set` command has been refactored similarly to `conf get` in
order to have all the logic to print out the information into the
management read method (see #390).
The exit status provided by the command is now
the result of the matching within the JSON object of the expected
configuration value and the requested configuration value.

The `conf ls` command has been refactored to produce a JSON object
when reading data out of the management socket. Such JSON object is
then printed as normal text if required.

A new utility function, named 'pgagroal_server_status_as_string' has
been added to the utils.c stuff. The idea is to have a consistent way
to translate the numerical status representation into an human
readable string.

The text output format of commands has slightly changed due to the
refactoring of some internal methods.

Documentation updated.
CI workflow updated.

Close #385
Close #390
  • Loading branch information
fluca1978 committed Dec 7, 2023
1 parent 5988613 commit c9a0275
Show file tree
Hide file tree
Showing 11 changed files with 1,447 additions and 297 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
run: sudo apt update -y
- name: Install libev
run: sudo apt install -y libev4 libev-dev
- name: Install cJSON
run: sudo apt install -y libcjson1 libcjson-dev
- name: Install systemd
run: sudo apt install -y libsystemd-dev
- name: Install rst2man
Expand Down
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ else ()
message(FATAL_ERROR "rst2man needed")
endif()

# search for cJSON library
# <https://github.com/DaveGamble/cJSON>
find_package(cJSON)
if (cJSON_FOUND)
message(STATUS "cJSON found")
else ()
message(FATAL_ERROR "cJSON needed")
endif()


if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
find_package(Libatomic)
if (LIBATOMIC_FOUND)
Expand Down
206 changes: 206 additions & 0 deletions doc/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Available options are the following ones:
-U, --user USERNAME Set the user name
-P, --password PASSWORD Set the password
-L, --logfile FILE Set the log file
-F, --format text|json Set the output format
-v, --verbose Output text string of result
-V, --version Display version information
-?, --help Display help
Expand All @@ -30,6 +31,11 @@ Available options are the following ones:

Options can be specified either in short or long form, in any position of the command line.

By default the command output, if any, is reported as text. It is possible to specify JSON as the output format,
and this is the suggested format if there is the need to automtically parse the command output, since the text format
could be subject to changes in future releases. For more information about the JSON output format,
please see the [JSON Output Format](#json-output-format) section.

## Commands

### flush
Expand Down Expand Up @@ -380,3 +386,203 @@ pgagroal-cli reset-server 2>/dev/null

There is a minimal shell completion support for `pgagroal-cli`.
Please refer to the [Install pgagroal](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/01_install.md) tutorial for detailed information about how to enable and use shell completions.


## JSON Output Format

It is possible to obtain the output of a command in a JSON format by specyfing the `-F` (`--format`) option on the command line.
Supported output formats are:
- `text` (the default)
- `json`

As an example, the following are invocations of commands with different output formats:

```
pgagroal-cli status # defaults to text output format
pgagroal-cli status --format text # same as above
pgagroal-cli status -F text # same as above
pgagroal-cli status --format json # outputs as JSON text
pgagroal-cli status -F json # same as above
```

Whenever a command produces output, the latter can be obtained in a JSON format.
Every command output consists of an object that contains two other objects:
- a `command` object, with all the details about the command and its output;
- an `application` object, with all the details about the executable that launched the command (e.g., `pgagroal-cli`).

In the following, details about every object are provided:

### The `application` object

The `application` object is made by the following attributes:
- `name` a string representing the name of the executable that launched the command;
- `version` a string representing the version of the executable;
- `major`, `minor`, `patch` are integers representing every single part of the version of the application.

As an example, when `pgagroal-cli` launches a command, the output includes an `application` object like the following:

```
"application": {
"name": "pgagroal-cli",
"major": 1,
"minor": 6,
"patch": 0,
"version": "1.6.0"
}
```


### The `command` object

The `command` object represents the launched command and contains also the answer from the `pgagroal`.
The object is made by the following attributes:
- `name` a string representing the command launched (e.g., `status`);
- `status` a string that contains either "OK" or an error string if the command failed;
- `error` an interger value used as a flag to indicate if the command was in error or not, where `0` means success and `1` means error;
- `exit-status` an integer that contains zero if the command run succesfully, another value depending on the specific command in case of failure;
- `output` an object that contains the details of the executed command.

The `output` object is *the variable part* in the JSON command output, that means its effective content depends on the launched command.

Whenever the command output includes an array of stuff, for example a connection list, such array is wrapped into a `list` JSON array with a sibling named `count` that contains the integer size of the array (number of elements).


The following are a few examples of commands that provide output in JSON:


```
pgagroal-cli ping --format json
{
"command": {
"name": "ping",
"status": "OK",
"error": 0,
"exit-status": 0,
"output": {
"status": 1,
"message": "running"
}
},
"application": {
"name": "pgagroal-cli",
"major": 1,
"minor": 6,
"patch": 0,
"version": "1.6.0"
}
}
pgagroal-cli status --format json
{
"command": {
"name": "status",
"status": "OK",
"error": 0,
"exit-status": 0,
"output": {
"status": {
"message": "Running",
"status": 1
},
"connections": {
"active": 0,
"total": 2,
"max": 15
},
"databases": {
"disabled": {
"count": 0,
"state": "disabled",
"list": []
}
}
}
},
"application": {
"name": "pgagroal-cli",
"major": 1,
"minor": 6,
"patch": 0,
"version": "1.6.0"
}
}
```

As an example, the following is the output of a faulty `conf set` command (note the `status`, `error` and `exist-status` values):

```
pgagroal-cli conf set max_connections 1000 --format json
{
"command": {
"name": "conf set",
"status": "Current and expected values are different",
"error": true,
"exit-status": 2,
"output": {
"key": "max_connections",
"value": "15",
"expected": "1000"
}
},
"application": {
"name": "pgagroal-cli",
"major": 1,
"minor": 6,
"patch": 0,
"version": "1.6.0"
}
}
```


The `conf ls` command returns an array named `files` where each entry is made by a couple `description` and `path`, where the former
is the mnemonic name of the configuration file, and the latter is the value of the configuration file used:

```
$ pgagroal-cli conf ls --format json
{
"command": {
"name": "conf ls",
"status": "OK",
"error": 0,
"exit-status": 0,
"output": {
"files": {
"list": [{
"description": "Main Configuration file",
"path": "/etc/pgagroal/pgagroal.conf"
}, {
"description": "HBA File",
"path": "/etc/pgagroal/pgagroal_hba.conf"
}, {
"description": "Limit file",
"path": "/etc/pgagroal/pgagroal_databases.conf"
}, {
"description": "Frontend users file",
"path": "/etc/pgagroal/pgagroal_frontend_users.conf"
}, {
"description": "Admins file",
"path": "/etc/pgagroal/pgagroal_admins.conf"
}, {
"description": "Superuser file",
"path": ""
}, {
"description": "Users file",
"path": "/etc/pgagroal/pgagroal_users.conf"
}]
}
}
},
"application": {
"name": "pgagroal-cli",
"major": 1,
"minor": 6,
"patch": 0,
"version": "1.6.0"
}
}
```
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
${LIBEV_INCLUDE_DIRS}
${OPENSSL_INCLUDE_DIR}
${SYSTEMD_INCLUDE_DIRS}
${CJSON_INCLUDE_DIRS}
)

#
Expand All @@ -33,6 +34,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
${OPENSSL_SSL_LIBRARY}
${SYSTEMD_LIBRARIES}
${LIBATOMIC_LIBRARY}
${CJSON_LIBRARIES}
)

set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
Expand Down Expand Up @@ -314,3 +316,4 @@ endif()
target_link_libraries(pgagroal-admin-bin pgagroal)

install(TARGETS pgagroal-admin-bin DESTINATION ${CMAKE_INSTALL_BINDIR})

Loading

0 comments on commit c9a0275

Please sign in to comment.