diff --git a/.github/workflows/build-tdnf-rpms.yml b/.github/workflows/build-tdnf-rpms.yml index 40795a9a..94d7192c 100644 --- a/.github/workflows/build-tdnf-rpms.yml +++ b/.github/workflows/build-tdnf-rpms.yml @@ -1,4 +1,4 @@ -name: tdnf CI +name: tdnf RPMs on: [pull_request, push, workflow_dispatch] diff --git a/CMakeLists.txt b/CMakeLists.txt index f0206876..9eccc23b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,10 +73,12 @@ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_IN set(LIB_TDNF "tdnf") set(LIB_TDNF_SOLV "tdnfsolv") set(LIB_TDNF_COMMON "common") +set(LIB_TDNF_JSONDUMP "jsondump") set(LIB_TDNF_CLI tdnfcli) add_subdirectory("${PROJECT_SOURCE_DIR}/common") add_subdirectory("${PROJECT_SOURCE_DIR}/client") +add_subdirectory("${PROJECT_SOURCE_DIR}/jsondump") add_subdirectory("${PROJECT_SOURCE_DIR}/plugins") add_subdirectory("${PROJECT_SOURCE_DIR}/python") add_subdirectory("${PROJECT_SOURCE_DIR}/etc") diff --git a/ci/docker-entrypoint.sh b/ci/docker-entrypoint.sh index 754b83c2..a5ba2376 100755 --- a/ci/docker-entrypoint.sh +++ b/ci/docker-entrypoint.sh @@ -4,9 +4,7 @@ rm -rf build mkdir -p build cd build || exit 1 -cmake .. && make -j32 && make python -j32 - -make check -j32 +cmake .. && make -j32 && make python -j32 && make check -j32 if ! flake8 ../pytests ; then echo "flake8 tests failed" diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index a36b5a5c..45d864ad 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (C) 2020-2021 VMware, Inc. All Rights Reserved. +# Copyright (C) 2020-2022 VMware, Inc. All Rights Reserved. # # Licensed under the GNU General Public License v2 (the "License"); # you may not use this file except in compliance with the License. The terms @@ -46,6 +46,7 @@ add_library(${LIB_TDNF} SHARED target_link_libraries(${LIB_TDNF} ${LIB_TDNF_COMMON} + ${LIB_TDNF_JSONDUMP} ${LIB_TDNF_SOLV} ${RPM_LIBRARIES} ${LibSolv_LIBRARIES} diff --git a/client/api.c b/client/api.c index 04c0d432..6b0ae7da 100644 --- a/client/api.c +++ b/client/api.c @@ -660,6 +660,7 @@ TDNFOpenHandle( IsTdnfAlreadyRunning(); GlobalSetQuiet(pArgs->nQuiet); + GlobalSetJson(pArgs->nJsonOutput); dwError = TDNFAllocateMemory(1, sizeof(TDNF), (void**)&pTdnf); BAIL_ON_TDNF_ERROR(dwError); diff --git a/client/init.c b/client/init.c index 89427035..2601fb7d 100644 --- a/client/init.c +++ b/client/init.c @@ -57,6 +57,7 @@ TDNFCloneCmdArgs( pCmdArgs->nDisableExcludes = pCmdArgsIn->nDisableExcludes; pCmdArgs->nDownloadOnly = pCmdArgsIn->nDownloadOnly; pCmdArgs->nNoAutoRemove = pCmdArgsIn->nNoAutoRemove; + pCmdArgs->nJsonOutput = pCmdArgsIn->nJsonOutput; dwError = TDNFAllocateString( pCmdArgsIn->pszInstallRoot, diff --git a/common/log.c b/common/log.c index 4a963dd8..6fac414a 100644 --- a/common/log.c +++ b/common/log.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2020 VMware, Inc. All Rights Reserved. + * Copyright (C) 2019-2022 VMware, Inc. All Rights Reserved. * * Licensed under the GNU Lesser General Public License v2.1 (the "License"); * you may not use this file except in compliance with the License. The terms @@ -9,6 +9,7 @@ #include "includes.h" static bool isQuiet = false; +static bool isJson = false; void GlobalSetQuiet(int32_t val) { @@ -18,6 +19,14 @@ void GlobalSetQuiet(int32_t val) } } +void GlobalSetJson(int32_t val) +{ + if (val > 0) + { + isJson = true; + } +} + void log_console(int32_t loglevel, const char *format, ...) { va_list args; @@ -34,6 +43,10 @@ void log_console(int32_t loglevel, const char *format, ...) { case LOG_INFO: case LOG_CRIT: + if (isJson) + { + goto end; + } if (loglevel == LOG_INFO && isQuiet) { goto end; diff --git a/common/prototypes.h b/common/prototypes.h index 5d389627..276654ac 100644 --- a/common/prototypes.h +++ b/common/prototypes.h @@ -326,6 +326,11 @@ GlobalSetQuiet( int32_t val ); +void +GlobalSetJson( + int32_t val + ); + void log_console( int32_t loglevel, diff --git a/include/tdnf-common-defines.h b/include/tdnf-common-defines.h index ada519d7..0ea78d8a 100644 --- a/include/tdnf-common-defines.h +++ b/include/tdnf-common-defines.h @@ -33,12 +33,36 @@ } \ } while(0) -#define BAIL_ON_TDNF_SYSTEM_ERROR_UNCOND(dwError) \ - do { \ - dwError = ERROR_TDNF_SYSTEM_BASE + dwError; \ - goto error; \ +#define BAIL_ON_TDNF_SYSTEM_ERROR_UNCOND(dwError) \ + do { \ + dwError = ERROR_TDNF_SYSTEM_BASE + dwError; \ + goto error; \ } while(0) +#define CHECK_JD_RC(rc) \ +{ \ + if ((rc) != 0) { \ + dwError = ERROR_TDNF_JSONDUMP; \ + BAIL_ON_CLI_ERROR(dwError); \ + } \ +} + +#define CHECK_JD_NULL(jd) \ +{ \ + if ((jd) == NULL) { \ + dwError = ERROR_TDNF_JSONDUMP; \ + BAIL_ON_CLI_ERROR(dwError); \ + } \ +} + +#define JD_SAFE_DESTROY(jd) \ +{ \ + if (jd) { \ + jd_destroy(jd); \ + jd = NULL; \ + } \ +} + #define TDNF_SAFE_FREE_MEMORY(pMemory) \ do { \ if (pMemory) { \ @@ -66,6 +90,12 @@ #define pr_err(fmt, ...) \ log_console(LOG_ERR, fmt, ##__VA_ARGS__) +#define pr_json(str) \ + fputs(str, stdout) + +#define pr_jsonf(fmt, ...) \ + fprintf(stdout, fmt, ##__VA_ARGS__) + /* * If something needs to be printed (a prompt for example) * irrespective of 'quiet' option diff --git a/include/tdnfcli.h b/include/tdnfcli.h index 5ab6709e..e4be1d50 100644 --- a/include/tdnfcli.h +++ b/include/tdnfcli.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 VMware, Inc. All Rights Reserved. + * Copyright (C) 2017-2022 VMware, Inc. All Rights Reserved. * * Licensed under the GNU Lesser General Public License v2.1 (the "License"); * you may not use this file except in compliance with the License. The terms @@ -105,7 +105,8 @@ TDNFCliParsePackageArgs( uint32_t TDNFCliPrintError( - uint32_t dwErrorCode + uint32_t dwErrorCode, + int doJson ); uint32_t @@ -285,6 +286,11 @@ PrintSolvedInfo( PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo ); +uint32_t +PrintSolvedInfoJson( + PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo + ); + uint32_t PrintNotAvailable( char** ppszPkgsNotAvailable diff --git a/include/tdnferror.h b/include/tdnferror.h index 611b161a..01410d49 100644 --- a/include/tdnferror.h +++ b/include/tdnferror.h @@ -164,6 +164,8 @@ extern "C" { #define ERROR_TDNF_ACCESS_DENIED (ERROR_TDNF_SYSTEM_BASE + EACCES) #define ERROR_TDNF_ALREADY_EXISTS (ERROR_TDNF_SYSTEM_BASE + EEXIST) +#define ERROR_TDNF_JSONDUMP 1700 + #define ERROR_TDNF_PLUGIN_BASE 2000 #define ERROR_TDNF_BASEURL_DOES_NOT_EXISTS 2500 diff --git a/include/tdnftypes.h b/include/tdnftypes.h index 89d8eff4..9400fdaf 100644 --- a/include/tdnftypes.h +++ b/include/tdnftypes.h @@ -222,6 +222,7 @@ typedef struct _TDNF_CMD_ARGS int nDisableExcludes; //disable excludes from tdnf.conf int nDownloadOnly; //download packages only, no install int nNoAutoRemove; //overide clean_requirements_on_remove config option + int nJsonOutput; //output in json format char* pszDownloadDir; //directory for download, if nDownloadOnly is set char* pszInstallRoot; //set install root char* pszConfFile; //set conf file location diff --git a/jsondump/CMakeLists.txt b/jsondump/CMakeLists.txt new file mode 100644 index 00000000..d3cb1c47 --- /dev/null +++ b/jsondump/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# Copyright (C) 2020-2022 VMware, Inc. All Rights Reserved. +# +# Licensed under the GNU General Public License v2 (the "License"); +# you may not use this file except in compliance with the License. The terms +# of the License are located in the COPYING file of this distribution. +# + +set(TDNF_JSON_BIN jsondumptest) + +add_executable(${TDNF_JSON_BIN} + test.c +) + +add_library(${LIB_TDNF_JSONDUMP} + jsondump.c +) + +target_link_libraries(${TDNF_JSON_BIN} + ${LIB_TDNF_JSONDUMP} +) diff --git a/jsondump/jsondump.c b/jsondump/jsondump.c new file mode 100644 index 00000000..b36ff37d --- /dev/null +++ b/jsondump/jsondump.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2022 VMware, Inc. All Rights Reserved. + * + * Licensed under the GNU General Public License v2 (the "License"); + * you may not use this file except in compliance with the License. The terms + * of the License are located in the COPYING file of this distribution. + */ + +#include +#include +#include +#include + +#include "jsondump.h" + +#define SIZE_INC 256 + +/* + * This is a very simple libary to simplify creating json strings. + * It only allows for creating json output. It is not intended + * for access of these json data in a structured way, and there is + * no parsing back into json data. There are other libraries + * for that purpose, but they are too complex if all you need is + * a simple dump of json. + * + * Look at test.c for usage examples. + */ + +/* Example 'make_message' from man 3 snprintf, + * modified to accept va_list. + * The allocated string returned must be free'd by the caller. + */ +static +char *_alloc_vsprintf(const char *fmt, va_list ap) +{ + int size = 0; + char *p = NULL; + va_list aq; + va_copy(aq, ap); + + /* Determine required size */ + size = vsnprintf(p, size, fmt, ap); + + if (size < 0) + return NULL; + + size++; /* For '\0' */ + p = (char *)calloc(1, size); + if (p == NULL) + return NULL; + + size = vsnprintf(p, size, fmt, aq); + va_end(aq); + + if (size < 0) { + free(p); + return NULL; + } + + return p; +} + +/* + * Create a context to hold a json buffer. After use it needs to be + * free'd using json_destroy(). + * The buffer will be allocated with size bytes, or SIZE_INC bytes if 0. + */ + +struct json_dump *jd_create(unsigned int size) +{ + struct json_dump *jd = NULL; + + jd = calloc(1, sizeof(struct json_dump)); + if (!jd) + return NULL; + + if (size == 0) + size = SIZE_INC; + + jd->buf = (char *)calloc(size, sizeof(char)); + if (!jd->buf) { + jd_destroy(jd); + return NULL; + } + jd->buf_size = size; + + return jd; +} + +void jd_destroy(struct json_dump *jd) +{ + if (jd) { + if (jd->buf) + free(jd->buf); + free(jd); + } +} + +/* make sure we have at least add_size bytes left in buffer by reallocating + * if needed */ +static +int _jd_realloc(struct json_dump *jd, unsigned int add_size) +{ + if (jd == NULL) + return -1; + + if (jd->pos + add_size >= jd->buf_size - 1) { + add_size += SIZE_INC; + jd->buf = (char *)realloc((void *)jd->buf, jd->buf_size + add_size); + if (!jd->buf) + return -1; + jd->buf_size = jd->buf_size + add_size; + } + return 0; +} + +/* + * Convert string to json format, escaping characters if needed and add + * quotes. + * The string will be allocated using calloc() and it's the caller's + * responsibilty to free it. + */ + +char *jsonify_string(const char *str) +{ + char *p; + const char *q = str; + char *buf; + + /* allocate for worst case - every char escaped plus quotes + nul */ + buf = calloc(strlen(str)*2+3, sizeof(char)); + if(!buf) + return NULL; + + p = buf; + *p++ = '\"'; + while(*q) { + switch (*q) { + case '"': + *p++ = '\\'; + *p++ = '"'; + break; + case '\\': + *p++ = '\\'; + *p++ = '\\'; + break; + case '\b': + *p++ = '\\'; + *p++ ='b'; + break; + case '\f': + *p++ = '\\'; + *p++ ='f'; + break; + case '\n': + *p++ = '\\'; + *p++ ='n'; + break; + case '\r': + *p++ = '\\'; + *p++ ='r'; + break; + case '\t': + *p++ = '\\'; + *p++ ='t'; + break; + default: + *p++ = *q; + } + q++; + } + *p++ = '\"'; + *p = 0; + return buf; +} + +/* add an empty map to the buffer */ +int jd_map_start(struct json_dump *jd) +{ + if(_jd_realloc(jd, 2)) + return -1; + jd->buf[jd->pos++] = '{'; + jd->buf[jd->pos++] = '}'; + jd->buf[jd->pos] = 0; + + return 0; +} + +/* before adding an item to a map, we need to rewind and replace the + * closing bracket with a comma. */ +static +void _jd_map_prep_append(struct json_dump *jd) +{ + if (jd->pos > 1 && jd->buf[jd->pos-1] == '}') { + jd->pos--; + if (jd->buf[jd->pos-1] != '{') + jd->buf[jd->pos++] = ','; + } +} + +/* helper to add a key value pair with the string 'as-is' + * (w/out jsonifying and quotes) + */ +static +int _jd_map_add_raw(struct json_dump *jd, const char *key, const char *value) +{ + int add_size = strlen(key) + strlen(value) + 5; /* lengths plus quotes plus colon plus closing bracket plus nul*/ + int l; + + if (jd == NULL) + return -1; + + if(_jd_realloc(jd, add_size)) + return -1; + _jd_map_prep_append(jd); + + l = snprintf(&(jd->buf[jd->pos]), jd->buf_size - jd->pos, "\"%s\":%s}", key, value); + if (l < 0) + return -1; + jd->pos += l; + return 0; +} + +/* + * Add a string value to a map with key. The string will be processed + * to convert it to json format. The string can be NULL, in which case + * a json 'null' will be added instead. + */ +int jd_map_add_string(struct json_dump *jd, const char *key, const char *value) +{ + int rc; + + if (!value) + return jd_map_add_null(jd, key); + + char *json_value = jsonify_string(value); + if (json_value == NULL) + return -1; + + rc = _jd_map_add_raw(jd, key, json_value); + + free(json_value); + + return rc; +} + +int jd_map_add_int(struct json_dump *jd, const char *key, int value) +{ + char buf[22]; /* 22 = length of 2^64 + 1 */ + + if (snprintf(buf, sizeof(buf), "%d", value) < 0) + return -1; + return _jd_map_add_raw(jd, key, buf); +} + +int jd_map_add_bool(struct json_dump *jd, const char *key, int value) +{ + return _jd_map_add_raw(jd, key, value ? "true": "false"); +} + +int jd_map_add_null(struct json_dump *jd, const char *key) +{ + return _jd_map_add_raw(jd, key, "null"); +} + +int jd_map_add_fmt(struct json_dump *jd, const char *key, const char *format, ...) +{ + va_list args; + char *buf; + int rc; + + va_start(args, format); + buf = _alloc_vsprintf(format, args); + va_end(args); + + if (buf == NULL) + return -1; + + rc = jd_map_add_string(jd, key, buf); + + free(buf); + + return rc; +} + +int jd_map_add_child(struct json_dump *jd, const char *key, struct json_dump *jd_child) +{ + return _jd_map_add_raw(jd, key, jd_child->buf); +} + +/* create an empty list */ +int jd_list_start(struct json_dump *jd) +{ + if(_jd_realloc(jd, 2)) + return -1; + jd->buf[jd->pos++] = '['; + jd->buf[jd->pos++] = ']'; + jd->buf[jd->pos] = 0; + + return 0; +} + +static +int _jd_list_prep_append(struct json_dump *jd) +{ + if (jd->pos > 1 && jd->buf[jd->pos-1] == ']') { + jd->pos--; + if (jd->buf[jd->pos-1] != '[') + jd->buf[jd->pos++] = ','; + } + return 0; +} + +/* similar to _jd_map_add_raw() but for a list */ +static +int _jd_list_add_raw(struct json_dump *jd, const char *value) +{ + int add_size = strlen(value) + 2; /* length plus closing bracket plus nul */ + int l; + + if (jd == NULL) + return -1; + + if(_jd_realloc(jd, add_size)) + return -1; + _jd_list_prep_append(jd); + + l = snprintf(&(jd->buf[jd->pos]), jd->buf_size - jd->pos, "%s]", value); + if (l < 0) + return -1; + jd->pos += l; + + return 0; +} + +/* similar to jd_map_add_string() but for a list */ +int jd_list_add_string(struct json_dump *jd, const char *value) +{ + char *json_value; + int rc; + + if (!value) + return jd_list_add_null(jd); + + json_value = jsonify_string(value); + if (json_value == NULL) + return -1; + + rc = _jd_list_add_raw(jd, json_value); + + free(json_value); + + return rc; +} + +int jd_list_add_int(struct json_dump *jd, int value) +{ + char buf[22]; /* 22 = length of 2^64 + 1 */ + + if (snprintf(buf, sizeof(buf), "%d", value) < 0) + return -1; + return _jd_list_add_raw(jd, buf); +} + +int jd_list_add_bool(struct json_dump *jd, int value) +{ + return _jd_list_add_raw(jd, value? "true" : "false"); +} + +int jd_list_add_null(struct json_dump *jd) +{ + return _jd_list_add_raw(jd, "null"); +} + +int jd_list_add_fmt(struct json_dump *jd, const char *format, ...) +{ + va_list args; + char *buf = NULL; + int rc; + + va_start(args, format); + buf = _alloc_vsprintf(format, args); + va_end(args); + + if (buf == NULL) + return -1; + + rc = jd_list_add_string(jd, buf); + + free(buf); + + return rc; +} + +int jd_list_add_child(struct json_dump *jd, struct json_dump *jd_child) +{ + return _jd_list_add_raw(jd, jd_child->buf); +} + diff --git a/jsondump/jsondump.h b/jsondump/jsondump.h new file mode 100644 index 00000000..90c4671d --- /dev/null +++ b/jsondump/jsondump.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 VMware, Inc. All Rights Reserved. + * + * Licensed under the GNU General Public License v2 (the "License"); + * you may not use this file except in compliance with the License. The terms + * of the License are located in the COPYING file of this distribution. + */ + +#pragma once + +struct json_dump{ + char *buf; + unsigned int buf_size; + unsigned int pos; +}; + +struct json_dump *jd_create(unsigned int size); +void jd_destroy(struct json_dump *jd); + +int jd_map_start(struct json_dump *jd); +int jd_map_add_string(struct json_dump *jd, const char *key, const char *value); +int jd_map_add_int(struct json_dump *jd, const char *key, int value); +int jd_map_add_bool(struct json_dump *jd, const char *key, int value); +int jd_map_add_null(struct json_dump *jd, const char *key); +int jd_map_add_fmt(struct json_dump *jd, const char *key, const char *format, ...); +int jd_map_add_child(struct json_dump *jd, const char *key, struct json_dump *jd_child); + +int jd_list_start(struct json_dump *jd); +int jd_list_add_string(struct json_dump *jd, const char *value); +int jd_list_add_int(struct json_dump *jd, int value); +int jd_list_add_bool(struct json_dump *jd, int value); +int jd_list_add_null(struct json_dump *jd); +int jd_list_add_fmt(struct json_dump *jd, const char *format, ...); +int jd_list_add_child(struct json_dump *jd, struct json_dump *jd_child); diff --git a/jsondump/test.c b/jsondump/test.c new file mode 100644 index 00000000..add6be50 --- /dev/null +++ b/jsondump/test.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 VMware, Inc. All Rights Reserved. + * + * Licensed under the GNU General Public License v2 (the "License"); + * you may not use this file except in compliance with the License. The terms + * of the License are located in the COPYING file of this distribution. + */ + +#include + +#include "jsondump.h" + +#define CHECK_RC(x) if (x) {fprintf(stderr, "FAIL: rc != 0 in line %d\n", __LINE__); } +#define CHECK_NULL(x) if (x == NULL) {fprintf(stderr, "FAIL: NULL returned in line %d\n", __LINE__); } + +int main(void) +{ + struct json_dump *jd, *jd1; + + /* flat map with all inds of values */ + jd = jd_create(0); + CHECK_NULL(jd); + + CHECK_RC(jd_map_start(jd)); + CHECK_RC(jd_map_add_string(jd, "foo", "bar")); + CHECK_RC(jd_map_add_string(jd, "goo", "car")); + CHECK_RC(jd_map_add_string(jd, "hoo", "\tdar\n")); + CHECK_RC(jd_map_add_fmt(jd, "ioo", "%d ears", 2)); + CHECK_RC(jd_map_add_null(jd, "nothing")); + CHECK_RC(jd_map_add_bool(jd, "yes", 1)); + CHECK_RC(jd_map_add_bool(jd, "no", 0)); + + printf("%s\n", jd->buf); + + /* nested map in map */ + jd1 = jd_create(0); + CHECK_NULL(jd1); + + CHECK_RC(jd_map_start(jd1)); + CHECK_RC(jd_map_add_child(jd1, "nested", jd)); + + printf("%s\n", jd1->buf); + + jd_destroy(jd); + jd_destroy(jd1); + + /* list with strings */ + jd = jd_create(0); + CHECK_NULL(jd); + + CHECK_RC(jd_list_start(jd)); + for(int i = 0; i < 10; i++) { + char buf[3]; + snprintf(buf, sizeof(buf), "%d", i); + jd_list_add_string(jd, buf); + } + jd_list_add_null(jd); + + printf("%s\n", jd->buf); + + jd_destroy(jd); + + /* list with format strings */ + jd = jd_create(0); + CHECK_NULL(jd); + + CHECK_RC(jd_list_start(jd)); + for(int i = 0; i < 10; i++) { + CHECK_RC(jd_list_add_fmt(jd, "i=%d", i)); + } + + printf("%s\n", jd->buf); + + jd_destroy(jd); + + /* list of ints */ + jd = jd_create(0); + CHECK_NULL(jd); + + CHECK_RC(jd_list_start(jd)); + for(int i = 0; i < 10; i++) { + CHECK_RC(jd_list_add_int(jd, i)); + } + + printf("%s\n", jd->buf); + + jd_destroy(jd); + + /* list of bools */ + jd = jd_create(0); + CHECK_NULL(jd); + + CHECK_RC(jd_list_start(jd)); + CHECK_RC(jd_list_add_bool(jd, 1)); + CHECK_RC(jd_list_add_bool(jd, 0)); + + printf("%s\n", jd->buf); + + jd_destroy(jd); + + return 0; +} \ No newline at end of file diff --git a/pytests/tests/test_json.py b/pytests/tests/test_json.py new file mode 100644 index 00000000..ebb5cd11 --- /dev/null +++ b/pytests/tests/test_json.py @@ -0,0 +1,147 @@ +# +# Copyright (C) 2022 VMware, Inc. All Rights Reserved. +# +# Licensed under the GNU General Public License v2 (the "License"); +# you may not use this file except in compliance with the License. The terms +# of the License are located in the COPYING file of this distribution. +# + +import pytest +import json +import os + + +@pytest.fixture(scope='function', autouse=True) +def setup_test(utils): + tdnfj = os.path.join(utils.config['build_dir'], 'bin/tdnfj') + if not os.path.lexists(tdnfj): + os.symlink('tdnf', tdnfj) + yield + teardown_test(utils) + + +def teardown_test(utils): + pkgname = utils.config["mulversion_pkgname"] + utils.erase_package(pkgname) + + +def test_list(utils): + ret = utils.run(['tdnf', '-j', 'list']) + infolist = json.loads("\n".join(ret['stdout'])) + + glibc_found = False + for info in infolist: + if info['Name'] == "glibc": + glibc_found = True + break + assert(glibc_found) + + +def test_list_tdnfj(utils): + tdnfj = os.path.join(utils.config['build_dir'], 'bin/tdnfj') + ret = utils.run([tdnfj, 'list']) + infolist = json.loads("\n".join(ret['stdout'])) + + glibc_found = False + for info in infolist: + if info['Name'] == "glibc": + glibc_found = True + break + assert(glibc_found) + + +def test_info(utils): + ret = utils.run(['tdnf', '-j', 'info']) + infolist = json.loads("\n".join(ret['stdout'])) + + glibc_found = False + for info in infolist: + if info['Name'] == "glibc": + glibc_found = True + break + assert(glibc_found) + + +def test_install(utils): + pkgname = utils.config["mulversion_pkgname"] + utils.erase_package(pkgname) + ret = utils.run(['tdnf', + '-j', '-y', '--nogpgcheck', + 'install', pkgname]) + assert(utils.check_package(pkgname)) + install_info = json.loads("\n".join(ret['stdout'])) + + pkg_found = False + install_pkgs = install_info["Install"] + for p in install_pkgs: + if p['Name'] == pkgname: + pkg_found = True + break + assert(pkg_found) + + +def test_erase(utils): + pkgname = utils.config["mulversion_pkgname"] + utils.install_package(pkgname) + ret = utils.run(['tdnf', + '-j', '-y', '--nogpgcheck', + 'erase', pkgname]) + assert(not utils.check_package(pkgname)) + install_info = json.loads("\n".join(ret['stdout'])) + + pkg_found = False + install_pkgs = install_info["Remove"] + for p in install_pkgs: + if p['Name'] == pkgname: + pkg_found = True + break + assert(pkg_found) + + +def test_check_update(utils): + ret = utils.run(['tdnf', '-j', 'check-update']) + d = json.loads("\n".join(ret['stdout'])) + assert(type(d) == list) + + +def test_repolist(utils): + ret = utils.run(['tdnf', '-j', 'repolist']) + repolist = json.loads("\n".join(ret['stdout'])) + + repo_found = False + for repo in repolist: + if repo['Repo'] == 'photon-test': + repo_found = True + assert(repo['Enabled']) + break + assert(repo_found) + + +def test_repoquery(utils): + ret = utils.run(['tdnf', '-j', 'repoquery']) + d = json.loads("\n".join(ret['stdout'])) + assert(type(d) == list) + + +def test_updateinfo(utils): + ret = utils.run(['tdnf', '-j', 'updateinfo']) + d = json.loads("\n".join(ret['stdout'])) + assert(type(d) == dict) + + +def test_updateinfo_info(utils): + ret = utils.run(['tdnf', '-j', 'updateinfo', '--info']) + d = json.loads("\n".join(ret['stdout'])) + assert(type(d) == list) + + +def test_jsondump(utils): + cmd = os.path.join(utils.config['build_dir'], 'bin/jsondumptest') + ret = utils.run([cmd]) + assert("FAIL" not in "\n".join(ret['stdout'])) + + +def test_jsondump_memcheck(utils): + cmd = os.path.join(utils.config['build_dir'], 'bin/jsondumptest') + ret = utils.run_memcheck([cmd]) + assert(ret['retval'] == 0) diff --git a/tdnf.spec.in b/tdnf.spec.in index c666421e..923238a8 100644 --- a/tdnf.spec.in +++ b/tdnf.spec.in @@ -122,6 +122,7 @@ find %{buildroot} -name '*.a' -delete mkdir -p %{buildroot}/var/cache/tdnf %{buildroot}%{_unitdir} ln -sf %{_bindir}/tdnf %{buildroot}%{_bindir}/tyum ln -sf %{_bindir}/tdnf %{buildroot}%{_bindir}/yum +ln -sf %{_bindir}/tdnf %{buildroot}%{_bindir}/tdnfj mv %{buildroot}%{_libdir}/pkgconfig/tdnfcli.pc %{buildroot}%{_libdir}/pkgconfig/tdnf-cli-libs.pc mkdir -p %{buildroot}%{_tdnfpluginsdir}/tdnfrepogpgcheck mv %{buildroot}%{_tdnfpluginsdir}/libtdnfrepogpgcheck.so %{buildroot}%{_tdnfpluginsdir}/tdnfrepogpgcheck/ @@ -181,6 +182,7 @@ systemctl try-restart tdnf-cache-updateinfo.timer >/dev/null 2>&1 || : %files %defattr(-,root,root,0755) %{_bindir}/tdnf +%{_bindir}/tdnfj %{_bindir}/tyum %{_bindir}/yum %{_bindir}/tdnf-cache-updateinfo diff --git a/tools/cli/includes.h b/tools/cli/includes.h index f9ad59e4..036105c2 100644 --- a/tools/cli/includes.h +++ b/tools/cli/includes.h @@ -42,5 +42,6 @@ #include "prototypes.h" #include "../common/structs.h" #include "../common/prototypes.h" +#include "../jsondump/jsondump.h" #endif /* __CLI_INCLUDES_H__ */ diff --git a/tools/cli/lib/api.c b/tools/cli/lib/api.c index e4a9c915..270ea6a5 100644 --- a/tools/cli/lib/api.c +++ b/tools/cli/lib/api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 VMware, Inc. All Rights Reserved. + * Copyright (C) 2017-2022 VMware, Inc. All Rights Reserved. * * Licensed under the GNU Lesser General Public License v2.1 (the "License"); * you may not use this file except in compliance with the License. The terms @@ -118,7 +118,12 @@ TDNFCliCountCommand( dwError = pContext->pFnCount(pContext, &dwCount); BAIL_ON_CLI_ERROR(dwError); - pr_crit("Package count = %u\n", dwCount); + if (pCmdArgs->nJsonOutput) + { + pr_jsonf("%u", dwCount); + } else { + pr_crit("Package count = %u\n", dwCount); + } cleanup: return dwError; @@ -139,6 +144,8 @@ TDNFCliListCommand( uint32_t dwCount = 0; uint32_t dwIndex = 0; PTDNF_LIST_ARGS pListArgs = NULL; + struct json_dump *jd = NULL; + struct json_dump *jd_pkg = NULL; #define MAX_COL_LEN 256 char szNameAndArch[MAX_COL_LEN] = {0}; @@ -159,47 +166,80 @@ TDNFCliListCommand( BAIL_ON_CLI_ERROR(dwError); dwError = pContext->pFnList(pContext, pListArgs, &pPkgInfo, &dwCount); + if (pCmdArgs->nJsonOutput && dwError == ERROR_TDNF_NO_MATCH) + { + dwError = 0; + } BAIL_ON_CLI_ERROR(dwError); - dwError = GetColumnWidths(COL_COUNT, nColPercents, nColWidths); - BAIL_ON_CLI_ERROR(dwError); - - for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + if (pCmdArgs->nJsonOutput) { - pPkg = &pPkgInfo[dwIndex]; + jd = jd_create(0); + CHECK_JD_NULL(jd); + + jd_list_start(jd); - memset(szNameAndArch, 0, MAX_COL_LEN); - if(snprintf( - szNameAndArch, - MAX_COL_LEN, - "%s.%s", - pPkg->pszName, - pPkg->pszArch) < 0) + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) { - dwError = errno; - BAIL_ON_CLI_ERROR(dwError); + jd_pkg = jd_create(0); + CHECK_JD_NULL(jd_pkg); + + CHECK_JD_RC(jd_map_start(jd_pkg)); + pPkg = &pPkgInfo[dwIndex]; + + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Name", pPkg->pszName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Arch", pPkg->pszArch)); + CHECK_JD_RC(jd_map_add_fmt(jd_pkg, "Evr", "%s-%s", pPkg->pszVersion, pPkg->pszRelease)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Repo", pPkg->pszRepoName)); + + CHECK_JD_RC(jd_list_add_child(jd, jd_pkg)); + JD_SAFE_DESTROY(jd_pkg); } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); + } + else + { + dwError = GetColumnWidths(COL_COUNT, nColPercents, nColWidths); + BAIL_ON_CLI_ERROR(dwError); - memset(szVersionAndRelease, 0, MAX_COL_LEN); - if(snprintf( - szVersionAndRelease, - MAX_COL_LEN, - "%s-%s", - pPkg->pszVersion, - pPkg->pszRelease) < 0) + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) { - dwError = errno; - BAIL_ON_CLI_ERROR(dwError); - } + pPkg = &pPkgInfo[dwIndex]; + + memset(szNameAndArch, 0, MAX_COL_LEN); + if(snprintf( + szNameAndArch, + MAX_COL_LEN, + "%s.%s", + pPkg->pszName, + pPkg->pszArch) < 0) + { + dwError = errno; + BAIL_ON_CLI_ERROR(dwError); + } + + memset(szVersionAndRelease, 0, MAX_COL_LEN); + if(snprintf( + szVersionAndRelease, + MAX_COL_LEN, + "%s-%s", + pPkg->pszVersion, + pPkg->pszRelease) < 0) + { + dwError = errno; + BAIL_ON_CLI_ERROR(dwError); + } - pr_crit( - "%-*s%-*s%*s\n", - nColWidths[0], - szNameAndArch, - nColWidths[1], - szVersionAndRelease, - nColWidths[2], - pPkg->pszRepoName); + pr_crit( + "%-*s%-*s%*s\n", + nColWidths[0], + szNameAndArch, + nColWidths[1], + szVersionAndRelease, + nColWidths[2], + pPkg->pszRepoName); + } } cleanup: @@ -214,6 +254,8 @@ TDNFCliListCommand( return dwError; error: + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_pkg); goto cleanup; } @@ -235,6 +277,9 @@ TDNFCliInfoCommand( uint32_t dwIndex = 0; uint64_t dwTotalSize = 0; + struct json_dump *jd = NULL; + struct json_dump *jd_pkg = NULL; + if(!pContext || !pContext->hTdnf || !pContext->pFnInfo) { dwError = ERROR_TDNF_CLI_INVALID_ARGUMENT; @@ -245,35 +290,73 @@ TDNFCliInfoCommand( BAIL_ON_CLI_ERROR(dwError); dwError = pContext->pFnInfo(pContext, pInfoArgs, &pPkgInfo, &dwCount); + if (pCmdArgs->nJsonOutput && dwError == ERROR_TDNF_NO_MATCH) + { + dwError = 0; + } BAIL_ON_CLI_ERROR(dwError); - for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + if (pCmdArgs->nJsonOutput) { - pPkg = &pPkgInfo[dwIndex]; - - pr_crit("Name : %s\n", pPkg->pszName); - pr_crit("Arch : %s\n", pPkg->pszArch); - pr_crit("Epoch : %d\n", pPkg->dwEpoch); - pr_crit("Version : %s\n", pPkg->pszVersion); - pr_crit("Release : %s\n", pPkg->pszRelease); - pr_crit("Install Size : %s (%u)\n", pPkg->pszFormattedSize, pPkg->dwInstallSizeBytes); - pr_crit("Repo : %s\n", pPkg->pszRepoName); - pr_crit("Summary : %s\n", pPkg->pszSummary); - pr_crit("URL : %s\n", pPkg->pszURL); - pr_crit("License : %s\n", pPkg->pszLicense); - pr_crit("Description : %s\n", pPkg->pszDescription); + jd = jd_create(1024); + CHECK_JD_NULL(jd); - pr_crit("\n"); + CHECK_JD_RC(jd_list_start(jd)); - dwTotalSize += pPkg->dwInstallSizeBytes; + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + { + jd_pkg = jd_create(0); + CHECK_JD_NULL(jd_pkg); + + CHECK_JD_RC(jd_map_start(jd_pkg)); + pPkg = &pPkgInfo[dwIndex]; + + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Name", pPkg->pszName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Arch", pPkg->pszArch)); + CHECK_JD_RC(jd_map_add_fmt(jd_pkg, "Evr", "%s-%s", pPkg->pszVersion, pPkg->pszRelease)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Repo", pPkg->pszRepoName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Url", pPkg->pszURL)); + CHECK_JD_RC(jd_map_add_int(jd_pkg, "InstallSize", pPkg->dwInstallSizeBytes)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Summary", pPkg->pszSummary)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "License", pPkg->pszLicense)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Description", pPkg->pszDescription)); + + CHECK_JD_RC(jd_list_add_child(jd, jd_pkg)); + JD_SAFE_DESTROY(jd_pkg); + } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); } + else + { + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + { + pPkg = &pPkgInfo[dwIndex]; + + pr_crit("Name : %s\n", pPkg->pszName); + pr_crit("Arch : %s\n", pPkg->pszArch); + pr_crit("Epoch : %d\n", pPkg->dwEpoch); + pr_crit("Version : %s\n", pPkg->pszVersion); + pr_crit("Release : %s\n", pPkg->pszRelease); + pr_crit("Install Size : %s (%u)\n", pPkg->pszFormattedSize, pPkg->dwInstallSizeBytes); + pr_crit("Repo : %s\n", pPkg->pszRepoName); + pr_crit("Summary : %s\n", pPkg->pszSummary); + pr_crit("URL : %s\n", pPkg->pszURL); + pr_crit("License : %s\n", pPkg->pszLicense); + pr_crit("Description : %s\n", pPkg->pszDescription); + + pr_crit("\n"); + + dwTotalSize += pPkg->dwInstallSizeBytes; + } - dwError = TDNFUtilsFormatSize(dwTotalSize, &pszFormattedSize); - BAIL_ON_CLI_ERROR(dwError); + dwError = TDNFUtilsFormatSize(dwTotalSize, &pszFormattedSize); + BAIL_ON_CLI_ERROR(dwError); - if(dwCount > 0) - { - pr_crit("\nTotal Size: %s (%lu)\n", pszFormattedSize, dwTotalSize); + if(dwCount > 0) + { + pr_crit("\nTotal Size: %s (%lu)\n", pszFormattedSize, dwTotalSize); + } } cleanup: @@ -289,6 +372,8 @@ TDNFCliInfoCommand( return dwError; error: + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_pkg); goto cleanup; } @@ -299,9 +384,11 @@ TDNFCliRepoListCommand( ) { uint32_t dwError = 0; - PTDNF_REPO_DATA pRepos = NULL; - PTDNF_REPO_DATA pReposTemp = NULL; + PTDNF_REPO_DATA pRepoList = NULL; + PTDNF_REPO_DATA pRepo = NULL; TDNF_REPOLISTFILTER nFilter = REPOLISTFILTER_ENABLED; + struct json_dump *jd = NULL; + struct json_dump *jd_repo = NULL; if(!pContext || !pContext->hTdnf || !pContext->pFnRepoList) { @@ -312,29 +399,55 @@ TDNFCliRepoListCommand( dwError = TDNFCliParseRepoListArgs(pCmdArgs, &nFilter); BAIL_ON_CLI_ERROR(dwError); - dwError = pContext->pFnRepoList(pContext, nFilter, &pRepos); + dwError = pContext->pFnRepoList(pContext, nFilter, &pRepoList); BAIL_ON_CLI_ERROR(dwError); - pReposTemp = pRepos; - if(pReposTemp) + if (pCmdArgs->nJsonOutput) { - pr_crit("%-20s%-40s%-10s\n", "repo id", "repo name", "status"); + jd = jd_create(0); + CHECK_JD_NULL(jd); + + jd_list_start(jd); + + for(pRepo = pRepoList; pRepo; pRepo = pRepo->pNext) + { + jd_repo = jd_create(0); + CHECK_JD_NULL(jd_repo); + CHECK_JD_RC(jd_map_start(jd_repo)); + + CHECK_JD_RC(jd_map_add_string(jd_repo, "Repo", pRepo->pszId)); + CHECK_JD_RC(jd_map_add_string(jd_repo, "RepoName", pRepo->pszName)); + CHECK_JD_RC(jd_map_add_bool(jd_repo, "Enabled", pRepo->nEnabled)); + + CHECK_JD_RC(jd_list_add_child(jd, jd_repo)); + JD_SAFE_DESTROY(jd_repo); + } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); } - while(pReposTemp) + else { - pr_crit( - "%-20s%-40s%-10s\n", - pReposTemp->pszId, - pReposTemp->pszName, - pReposTemp->nEnabled ? "enabled" : "disabled"); - pReposTemp = pReposTemp->pNext; + if(pRepoList) + { + pr_crit("%-20s%-40s%-10s\n", "repo id", "repo name", "status"); + } + for(pRepo = pRepoList; pRepo; pRepo = pRepo->pNext) + { + pr_crit( + "%-20s%-40s%-10s\n", + pRepo->pszId, + pRepo->pszName, + pRepo->nEnabled ? "enabled" : "disabled"); + } } cleanup: - TDNFFreeRepos(pRepos); + TDNFFreeRepos(pRepoList); return dwError; error: + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_repo); goto cleanup; } @@ -349,6 +462,8 @@ TDNFCliSearchCommand( uint32_t dwIndex = 0; PTDNF_PKG_INFO pPkgInfo = NULL; PTDNF_PKG_INFO pPkg = NULL; + struct json_dump *jd = NULL; + struct json_dump *jd_pkg = NULL; if(!pContext || !pContext->hTdnf || !pContext->pFnSearch) { @@ -357,12 +472,44 @@ TDNFCliSearchCommand( } dwError = pContext->pFnSearch(pContext, pCmdArgs, &pPkgInfo, &dwCount); + if (pCmdArgs->nJsonOutput && dwError == ERROR_TDNF_NO_SEARCH_RESULTS) + { + dwError = 0; + } BAIL_ON_CLI_ERROR(dwError); - for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + if (pCmdArgs->nJsonOutput) { - pPkg = &pPkgInfo[dwIndex]; - pr_crit("%s : %s\n", pPkg->pszName, pPkg->pszSummary); + jd = jd_create(0); + CHECK_JD_NULL(jd); + + jd_list_start(jd); + + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + { + jd_pkg = jd_create(0); + CHECK_JD_NULL(jd_pkg); + + CHECK_JD_RC(jd_map_start(jd_pkg)); + + pPkg = &pPkgInfo[dwIndex]; + + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Name", pPkg->pszName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Summary", pPkg->pszSummary)); + + CHECK_JD_RC(jd_list_add_child(jd, jd_pkg)); + JD_SAFE_DESTROY(jd_pkg); + } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); + } + else + { + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + { + pPkg = &pPkgInfo[dwIndex]; + pr_crit("%s : %s\n", pPkg->pszName, pPkg->pszSummary); + } } cleanup: @@ -370,6 +517,8 @@ TDNFCliSearchCommand( return dwError; error: + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_pkg); goto cleanup; } @@ -412,8 +561,10 @@ TDNFCliProvidesCommand( ) { uint32_t dwError = 0; - PTDNF_PKG_INFO pPkgInfo = NULL; + PTDNF_PKG_INFO pPkg = NULL; PTDNF_PKG_INFO pPkgInfos = NULL; + struct json_dump *jd = NULL; + struct json_dump *jd_pkg = NULL; if(!pContext || !pContext->hTdnf || !pCmdArgs || !pContext->pFnProvides) { @@ -432,19 +583,44 @@ TDNFCliProvidesCommand( &pPkgInfos); BAIL_ON_CLI_ERROR(dwError); - pPkgInfo = pPkgInfos; - while(pPkgInfo) + if (pCmdArgs->nJsonOutput) { - pr_crit("%s-%s-%s.%s : %s\n", - pPkgInfo->pszName, - pPkgInfo->pszVersion, - pPkgInfo->pszRelease, - pPkgInfo->pszArch, - pPkgInfo->pszSummary); - pr_crit("Repo\t : %s\n", pPkgInfo->pszRepoName); - pPkgInfo = pPkgInfo->pNext; - } + jd = jd_create(0); + CHECK_JD_NULL(jd); + + jd_list_start(jd); + + for(pPkg = pPkgInfos; pPkg; pPkg = pPkg->pNext) + { + jd_pkg = jd_create(0); + CHECK_JD_NULL(jd_pkg); + CHECK_JD_RC(jd_map_start(jd_pkg)); + + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Name", pPkg->pszName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Arch", pPkg->pszArch)); + CHECK_JD_RC(jd_map_add_fmt(jd_pkg, "Evr", "%s-%s", pPkg->pszVersion, pPkg->pszRelease)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Summary", pPkg->pszSummary)); + + CHECK_JD_RC(jd_list_add_child(jd, jd_pkg)); + JD_SAFE_DESTROY(jd_pkg); + } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); + } + else + { + for(pPkg = pPkgInfos; pPkg; pPkg = pPkg->pNext) + { + pr_crit("%s-%s-%s.%s : %s\n", + pPkg->pszName, + pPkg->pszVersion, + pPkg->pszRelease, + pPkg->pszArch, + pPkg->pszSummary); + pr_crit("Repo\t : %s\n", pPkg->pszRepoName); + } + } cleanup: if(pPkgInfos) { @@ -453,6 +629,8 @@ TDNFCliProvidesCommand( return dwError; error: + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_pkg); goto cleanup; } @@ -498,6 +676,10 @@ TDNFCliRepoQueryCommand( PTDNF_PKG_INFO pPkgInfos = NULL; int nCount = 0, i, j, k; char **ppszLines = NULL; + struct json_dump *jd = NULL; + struct json_dump *jd_pkg = NULL; + struct json_dump *jd_list = NULL; + struct json_dump *jd_entry = NULL; if(!pContext || !pContext->hTdnf || !pCmdArgs || !pContext->pFnRepoQuery) { @@ -510,86 +692,192 @@ TDNFCliRepoQueryCommand( dwError = pContext->pFnRepoQuery(pContext, pRepoqueryArgs, &pPkgInfos, &dwCount); BAIL_ON_CLI_ERROR(dwError); - for (i = 0; i < (int)dwCount; i++) + + if (pCmdArgs->nJsonOutput) { - pPkgInfo = &pPkgInfos[i]; + jd = jd_create(0); + CHECK_JD_NULL(jd); - if (pPkgInfo->ppszDependencies) - { - for (j = 0; pPkgInfo->ppszDependencies[j]; j++); - nCount += j; - } - else if (pPkgInfo->ppszFileList) - { - for (j = 0; pPkgInfo->ppszFileList[j]; j++); - nCount += j; - } - else if (pPkgInfo->pChangeLogEntries) + jd_list_start(jd); + + for (i = 0; i < (int)dwCount; i++) { - PTDNF_PKG_CHANGELOG_ENTRY pEntry; - for (pEntry = pPkgInfo->pChangeLogEntries; pEntry; pEntry = pEntry->pNext) + int j; + jd_pkg = jd_create(0); + CHECK_JD_NULL(jd_pkg); + + CHECK_JD_RC(jd_map_start(jd_pkg)); + + pPkgInfo = &pPkgInfos[i]; + + CHECK_JD_RC(jd_map_add_fmt(jd_pkg, "Nevra", "%s-%s-%s.%s", + pPkgInfo->pszName, + pPkgInfo->pszVersion, + pPkgInfo->pszRelease, + pPkgInfo->pszArch)); + + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Name", pPkgInfo->pszName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Arch", pPkgInfo->pszArch)); + CHECK_JD_RC(jd_map_add_fmt(jd_pkg, "Evr", "%s-%s", pPkgInfo->pszVersion, pPkgInfo->pszRelease)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Repo", pPkgInfo->pszRepoName)); + + if (pPkgInfo->ppszFileList) { - char szTime[20] = {0}; - if (strftime(szTime, 20, "%a %b %d %Y", localtime(&pEntry->timeTime))) + jd_list = jd_create(0); + CHECK_JD_NULL(jd_list); + + CHECK_JD_RC(jd_list_start(jd_list)); + + for (j = 0; pPkgInfo->ppszFileList[j]; j++) { - pr_crit("%s %s\n%s\n", - szTime, - pEntry->pszAuthor, - pEntry->pszText); + CHECK_JD_RC(jd_list_add_string(jd_list, pPkgInfo->ppszFileList[j])); } - else + CHECK_JD_RC(jd_map_add_child(jd_pkg, "Files", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if (pPkgInfo->ppszDependencies) + { + char *strDepKeys[] = {"Provides", "Obsoletes", "Conflicts", + "Requires", "Recommends", "Suggests", + "Supplements", "Enhances", "Depends", + "RequiresPre"}; + + jd_list = jd_create(0); + CHECK_JD_NULL(jd_list); + + jd_list_start(jd_list); + + for (j = 0; pPkgInfo->ppszDependencies[j]; j++) { - dwError = ERROR_TDNF_CLI_INVALID_ARGUMENT; - BAIL_ON_CLI_ERROR(dwError); + CHECK_JD_RC(jd_list_add_string(jd_list, pPkgInfo->ppszDependencies[j])); } + CHECK_JD_RC(jd_map_add_child(jd_pkg, strDepKeys[pRepoqueryArgs->depKey-1], jd_list)); + JD_SAFE_DESTROY(jd_list); } + if (pPkgInfo->pChangeLogEntries) + { + jd_list = jd_create(0); + CHECK_JD_NULL(jd_list); + + CHECK_JD_RC(jd_list_start(jd_list)); + + PTDNF_PKG_CHANGELOG_ENTRY pEntry; + for (pEntry = pPkgInfo->pChangeLogEntries; pEntry; pEntry = pEntry->pNext) + { + jd_entry = jd_create(0); + CHECK_JD_NULL(jd_entry); + + char szTime[20] = {0}; + + jd_map_start(jd_entry); + + if (strftime(szTime, 20, "%a %b %d %Y", localtime(&pEntry->timeTime))) + { + jd_map_add_string(jd_entry, "Time", szTime); + } + CHECK_JD_RC(jd_map_add_string(jd_entry, "Author", pEntry->pszAuthor)); + CHECK_JD_RC(jd_map_add_string(jd_entry, "Text", pEntry->pszText)); + + CHECK_JD_RC(jd_list_add_child(jd_list, jd_entry)); + JD_SAFE_DESTROY(jd_entry); + } + CHECK_JD_RC(jd_map_add_child(jd_pkg, "ChangeLogs", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if (pPkgInfo->pszSourcePkg) + { + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Source", pPkgInfo->pszSourcePkg)); + } + + CHECK_JD_RC(jd_list_add_child(jd, jd_pkg)); + JD_SAFE_DESTROY(jd_pkg); } - else if (pPkgInfo->pszSourcePkg) - { - pr_crit("%s\n", pPkgInfo->pszSourcePkg); - } - else - { - pr_crit("%s-%s-%s.%s\n", - pPkgInfo->pszName, - pPkgInfo->pszVersion, - pPkgInfo->pszRelease, - pPkgInfo->pszArch); - } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); } - - if (nCount > 0) + else { - dwError = TDNFAllocateMemory(nCount + 1, sizeof(char *), (void**)&ppszLines); - BAIL_ON_CLI_ERROR(dwError); - for (k = 0, i = 0; i < (int)dwCount; i++) + for (i = 0; i < (int)dwCount; i++) { pPkgInfo = &pPkgInfos[i]; if (pPkgInfo->ppszDependencies) { - for (j = 0; pPkgInfo->ppszDependencies[j]; j++) - { - ppszLines[k++] = pPkgInfo->ppszDependencies[j]; - } + for (j = 0; pPkgInfo->ppszDependencies[j]; j++); + nCount += j; } else if (pPkgInfo->ppszFileList) { - for (j = 0; pPkgInfo->ppszFileList[j]; j++) + for (j = 0; pPkgInfo->ppszFileList[j]; j++); + nCount += j; + } + else if (pPkgInfo->pChangeLogEntries) + { + PTDNF_PKG_CHANGELOG_ENTRY pEntry; + for (pEntry = pPkgInfo->pChangeLogEntries; pEntry; pEntry = pEntry->pNext) { - ppszLines[k++] = pPkgInfo->ppszFileList[j]; + char szTime[20] = {0}; + if (strftime(szTime, 20, "%a %b %d %Y", localtime(&pEntry->timeTime))) + { + pr_crit("%s %s\n%s\n", + szTime, + pEntry->pszAuthor, + pEntry->pszText); + } + else + { + dwError = ERROR_TDNF_CLI_INVALID_ARGUMENT; + BAIL_ON_CLI_ERROR(dwError); + } } } + else if (pPkgInfo->pszSourcePkg) + { + pr_crit("%s\n", pPkgInfo->pszSourcePkg); + } + else + { + pr_crit("%s-%s-%s.%s\n", + pPkgInfo->pszName, + pPkgInfo->pszVersion, + pPkgInfo->pszRelease, + pPkgInfo->pszArch); + } } - dwError = TDNFStringArraySort(ppszLines); - BAIL_ON_CLI_ERROR(dwError); - - for (j = 0; ppszLines[j]; j++) + if (nCount > 0) { - if (j == 0 || strcmp(ppszLines[j], ppszLines[j-1])) + dwError = TDNFAllocateMemory(nCount + 1, sizeof(char *), (void**)&ppszLines); + BAIL_ON_CLI_ERROR(dwError); + for (k = 0, i = 0; i < (int)dwCount; i++) { - pr_crit("%s\n", ppszLines[j]); + pPkgInfo = &pPkgInfos[i]; + + if (pPkgInfo->ppszDependencies) + { + for (j = 0; pPkgInfo->ppszDependencies[j]; j++) + { + ppszLines[k++] = pPkgInfo->ppszDependencies[j]; + } + } + else if (pPkgInfo->ppszFileList) + { + for (j = 0; pPkgInfo->ppszFileList[j]; j++) + { + ppszLines[k++] = pPkgInfo->ppszFileList[j]; + } + } + } + + dwError = TDNFStringArraySort(ppszLines); + BAIL_ON_CLI_ERROR(dwError); + + for (j = 0; ppszLines[j]; j++) + { + if (j == 0 || strcmp(ppszLines[j], ppszLines[j-1])) + { + pr_crit("%s\n", ppszLines[j]); + } } } } @@ -604,6 +892,10 @@ TDNFCliRepoQueryCommand( return dwError; error: + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_pkg); + JD_SAFE_DESTROY(jd_list); + JD_SAFE_DESTROY(jd_entry); goto cleanup; } @@ -620,6 +912,8 @@ TDNFCliCheckUpdateCommand( uint32_t dwIndex = 0; char** ppszPackageArgs = NULL; int nPackageCount = 0; + struct json_dump *jd = NULL; + struct json_dump *jd_pkg = NULL; if(!pContext || !pContext->hTdnf || !pCmdArgs || !pContext->pFnCheckUpdate) { @@ -639,13 +933,42 @@ TDNFCliCheckUpdateCommand( &dwCount); BAIL_ON_CLI_ERROR(dwError); - for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + if (pCmdArgs->nJsonOutput) + { + jd = jd_create(0); + CHECK_JD_NULL(jd); + + CHECK_JD_RC(jd_list_start(jd)); + + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + { + jd_pkg = jd_create(0); + CHECK_JD_NULL(jd_pkg); + + jd_map_start(jd_pkg); + pPkg = &pPkgInfo[dwIndex]; + + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Name", pPkg->pszName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Arch", pPkg->pszArch)); + CHECK_JD_RC(jd_map_add_fmt(jd_pkg, "Evr", "%s-%s", pPkg->pszVersion, pPkg->pszRelease)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Repo", pPkg->pszRepoName)); + + CHECK_JD_RC(jd_list_add_child(jd, jd_pkg)); + JD_SAFE_DESTROY(jd_pkg); + } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); + } + else { - pPkg = &pPkgInfo[dwIndex]; - pr_crit("%*s\r", 80, pPkg->pszRepoName); - pr_crit("%*s-%s\r", 50, pPkg->pszVersion, pPkg->pszRelease); - pr_crit("%s.%s", pPkg->pszName, pPkg->pszArch); - pr_crit("\n"); + for(dwIndex = 0; dwIndex < dwCount; ++dwIndex) + { + pPkg = &pPkgInfo[dwIndex]; + pr_crit("%*s\r", 80, pPkg->pszRepoName); + pr_crit("%*s-%s\r", 50, pPkg->pszVersion, pPkg->pszRelease); + pr_crit("%s.%s", pPkg->pszName, pPkg->pszArch); + pr_crit("\n"); + } } cleanup: @@ -657,6 +980,8 @@ TDNFCliCheckUpdateCommand( return dwError; error: + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_pkg); goto cleanup; } diff --git a/tools/cli/lib/includes.h b/tools/cli/lib/includes.h index 1f798122..5a440b24 100644 --- a/tools/cli/lib/includes.h +++ b/tools/cli/lib/includes.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2020 VMware, Inc. All Rights Reserved. + * Copyright (C) 2017-2022 VMware, Inc. All Rights Reserved. * * Licensed under the GNU General Public License v2 (the "License"); * you may not use this file except in compliance with the License. The terms @@ -41,5 +41,6 @@ #include "prototypes.h" #include "../common/structs.h" #include "../common/prototypes.h" +#include "../jsondump/jsondump.h" #endif /* __CLI_LIB_INCLUDES_H__ */ diff --git a/tools/cli/lib/installcmd.c b/tools/cli/lib/installcmd.c index 89a70f98..c3d08e99 100644 --- a/tools/cli/lib/installcmd.c +++ b/tools/cli/lib/installcmd.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 VMware, Inc. All Rights Reserved. + * Copyright (C) 2015-2022 VMware, Inc. All Rights Reserved. * * Licensed under the GNU General Public License v2 (the "License"); * you may not use this file except in compliance with the License. The terms @@ -212,7 +212,12 @@ TDNFCliAlterCommand( if(!nSilent) { - dwError = PrintSolvedInfo(pSolvedPkgInfo); + if (pCmdArgs->nJsonOutput) + { + dwError = PrintSolvedInfoJson(pSolvedPkgInfo); + } else { + dwError = PrintSolvedInfo(pSolvedPkgInfo); + } if (pCmdArgs->nDownloadOnly) { pr_info("tdnf will only download packages needed for the transaction\n"); @@ -256,8 +261,11 @@ TDNFCliAlterCommand( } else { - dwError = ERROR_TDNF_OPERATION_ABORTED; - BAIL_ON_CLI_ERROR(dwError); + if (!pCmdArgs->nJsonOutput) + { + dwError = ERROR_TDNF_OPERATION_ABORTED; + BAIL_ON_CLI_ERROR(dwError); + } } } @@ -275,6 +283,126 @@ TDNFCliAlterCommand( goto cleanup; } +uint32_t +JDPkgList( + PTDNF_PKG_INFO pPkgInfos, + struct json_dump **ppJDList +) +{ + uint32_t dwError = 0; + PTDNF_PKG_INFO pPkgInfo; + struct json_dump *jd_list = jd_create(0); + CHECK_JD_NULL(jd_list); + + jd_list_start(jd_list); + for(pPkgInfo = pPkgInfos; pPkgInfo; pPkgInfo = pPkgInfo->pNext) + { + struct json_dump *jd_pkg = jd_create(0); + CHECK_JD_NULL(jd_pkg); + + CHECK_JD_RC(jd_map_start(jd_pkg)); + + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Name", pPkgInfo->pszName)); + CHECK_JD_RC(jd_map_add_string(jd_pkg, "Arch", pPkgInfo->pszArch)); + CHECK_JD_RC(jd_map_add_fmt(jd_pkg, "Evr", "%s-%s", pPkgInfo->pszVersion, pPkgInfo->pszRelease)); + CHECK_JD_RC(jd_map_add_int(jd_pkg, "InstallSize", pPkgInfo->dwInstallSizeBytes)); + + CHECK_JD_RC(jd_list_add_child(jd_list, jd_pkg)); + JD_SAFE_DESTROY(jd_pkg); + } + *ppJDList = jd_list; +cleanup: + return dwError; +error: + JD_SAFE_DESTROY(jd_list); + goto cleanup; +} + +uint32_t +PrintSolvedInfoJson( + PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo + ) +{ + uint32_t dwError = 0; + struct json_dump *jd = jd_create(1024); + struct json_dump *jd_list = NULL; + + CHECK_JD_NULL(jd); + + if(!pSolvedPkgInfo) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_CLI_ERROR(dwError); + } + + CHECK_JD_RC(jd_map_start(jd)); + + if(pSolvedPkgInfo->pPkgsExisting) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsExisting, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Exist", jd_list)); + JD_SAFE_DESTROY(jd); + } + if(pSolvedPkgInfo->pPkgsNotAvailable) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsNotAvailable, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Unavailable", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if(pSolvedPkgInfo->pPkgsToInstall) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsToInstall, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Install", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if(pSolvedPkgInfo->pPkgsToUpgrade) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsToUpgrade, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Upgrade", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if(pSolvedPkgInfo->pPkgsToDowngrade) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsToDowngrade, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Downgrade", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if(pSolvedPkgInfo->pPkgsToRemove) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsToRemove, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Remove", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if(pSolvedPkgInfo->pPkgsUnNeeded) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsUnNeeded, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "UnNeeded", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if(pSolvedPkgInfo->pPkgsToReinstall) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsToReinstall, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Reinstall", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + if(pSolvedPkgInfo->pPkgsObsoleted) + { + dwError = JDPkgList(pSolvedPkgInfo->pPkgsObsoleted, &jd_list); + CHECK_JD_RC(jd_map_add_child(jd, "Obsolete", jd_list)); + JD_SAFE_DESTROY(jd_list); + } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); + +cleanup: + return dwError; + +error: + JD_SAFE_DESTROY(jd_list); + JD_SAFE_DESTROY(jd); + goto cleanup; +} + uint32_t PrintSolvedInfo( PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo diff --git a/tools/cli/lib/parseargs.c b/tools/cli/lib/parseargs.c index 50af4aa7..2b7c0e53 100644 --- a/tools/cli/lib/parseargs.c +++ b/tools/cli/lib/parseargs.c @@ -45,6 +45,7 @@ static struct option pstOptions[] = {"exclude", required_argument, 0, 0}, //--exclude {"help", no_argument, 0, 'h'}, //-h --help {"installroot", required_argument, 0, 'i'}, //--installroot + {"json", no_argument, &_opt.nJsonOutput, 1}, {"noautoremove", no_argument, &_opt.nNoAutoRemove, 1}, {"nogpgcheck", no_argument, &_opt.nNoGPGCheck, 1}, //--nogpgcheck {"noplugins", no_argument, 0, 0}, //--noplugins @@ -142,6 +143,20 @@ TDNFCliParseArgs( (void**)&pCmdArgs); BAIL_ON_CLI_ERROR(dwError); + /* + * when invoked as 'tdnfj', act as if invoked with with '-j' and '-y' + * for json output and non-interactive + */ + if (strlen(argv[0]) >= 5) + { + const char *arg0 = argv[0]; + if (strcmp(&arg0[strlen(arg0) - 5], "tdnfj") == 0) + { + _opt.nJsonOutput = 1; + _opt.nAssumeYes = 1; + } + } + opterr = 0;//tell getopt to not print errors while (1) { @@ -302,6 +317,7 @@ TDNFCopyOptions( pArgs->nDisableExcludes = pOptionArgs->nDisableExcludes; pArgs->nDownloadOnly = pOptionArgs->nDownloadOnly; pArgs->nNoAutoRemove = pOptionArgs->nNoAutoRemove; + pArgs->nJsonOutput = pOptionArgs->nJsonOutput; cleanup: return dwError; diff --git a/tools/cli/lib/prototypes.h b/tools/cli/lib/prototypes.h index 58522643..7e7539e9 100644 --- a/tools/cli/lib/prototypes.h +++ b/tools/cli/lib/prototypes.h @@ -67,8 +67,15 @@ TDNFCliUpdateInfoSummary( ); uint32_t -TDNFCliUpdateInfoInfo( - PTDNF_UPDATEINFO pInfo +TDNFCliUpdateInfoOutput( + PTDNF_UPDATEINFO pInfo, + TDNF_UPDATEINFO_OUTPUT mode + ); + +uint32_t +TDNFCliUpdateInfoOutputJson( + PTDNF_UPDATEINFO pInfo, + TDNF_UPDATEINFO_OUTPUT mode ); //help.c diff --git a/tools/cli/lib/updateinfocmd.c b/tools/cli/lib/updateinfocmd.c index 4261f5d6..2611ec2b 100644 --- a/tools/cli/lib/updateinfocmd.c +++ b/tools/cli/lib/updateinfocmd.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 VMware, Inc. All Rights Reserved. + * Copyright (C) 2015-2022 VMware, Inc. All Rights Reserved. * * Licensed under the GNU General Public License v2 (the "License"); * you may not use this file except in compliance with the License. The terms @@ -70,17 +70,16 @@ TDNFCliUpdateInfoCommand( pContext, pInfoArgs, &pUpdateInfo); - BAIL_ON_CLI_ERROR(dwError); - if(pInfoArgs->nMode == OUTPUT_LIST) - { - dwError = TDNFCliUpdateInfoList(pUpdateInfo); - BAIL_ON_CLI_ERROR(dwError); - } - else if(pInfoArgs->nMode == OUTPUT_INFO) + if (dwError == ERROR_TDNF_NO_DATA) { - dwError = TDNFCliUpdateInfoInfo(pUpdateInfo); - BAIL_ON_CLI_ERROR(dwError); + dwError = 0; } + BAIL_ON_CLI_ERROR(dwError); + + dwError = pCmdArgs->nJsonOutput ? + TDNFCliUpdateInfoOutputJson(pUpdateInfo, pInfoArgs->nMode) : + TDNFCliUpdateInfoOutput(pUpdateInfo, pInfoArgs->nMode); + BAIL_ON_CLI_ERROR(dwError); } cleanup: if(pInfoArgs) @@ -120,23 +119,36 @@ TDNFCliUpdateInfoSummary( &pSummary); BAIL_ON_CLI_ERROR(dwError); - for(i = UPDATE_UNKNOWN; i <= UPDATE_ENHANCEMENT; ++i) + if (pCmdArgs->nJsonOutput) { - if(pSummary[i].nCount > 0) + struct json_dump *jd = jd_create(0); + jd_map_start(jd); + for(i = UPDATE_UNKNOWN; i <= UPDATE_ENHANCEMENT; ++i) { - nCount++; - pr_crit( - "%d %s notice(s)\n", - pSummary[i].nCount, - TDNFGetUpdateInfoType(pSummary[i].nType)); + jd_map_add_int(jd, TDNFGetUpdateInfoType(pSummary[i].nType), pSummary[i].nCount); } + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); } - if (nCount == 0) + else { - pr_crit( - "\n%d updates.\n", nCount); - dwError = ERROR_TDNF_NO_DATA; - BAIL_ON_CLI_ERROR(dwError); + for(i = UPDATE_UNKNOWN; i <= UPDATE_ENHANCEMENT; i++) + { + if(pSummary[i].nCount > 0) + { + nCount++; + pr_crit( + "%d %s notice(s)\n", + pSummary[i].nCount, + TDNFGetUpdateInfoType(pSummary[i].nType)); + } + } + if (nCount == 0) + { + pr_crit("\n%d updates.\n", nCount); + dwError = ERROR_TDNF_NO_DATA; + BAIL_ON_CLI_ERROR(dwError); + } } cleanup: @@ -151,80 +163,93 @@ TDNFCliUpdateInfoSummary( } uint32_t -TDNFCliUpdateInfoList( - PTDNF_UPDATEINFO pInfo +TDNFCliUpdateInfoOutput( + PTDNF_UPDATEINFO pInfo, + TDNF_UPDATEINFO_OUTPUT mode ) { uint32_t dwError = 0; PTDNF_UPDATEINFO_PKG pPkg = NULL; - if(!pInfo) - { - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_CLI_ERROR(dwError); - } - - while(pInfo) + for(; pInfo; pInfo = pInfo->pNext) { - pPkg = pInfo->pPackages; - while(pPkg) + for(pPkg = pInfo->pPackages; pPkg; pPkg = pPkg->pNext) { - pr_crit("%s %s %s\n", pInfo->pszID, - TDNFGetUpdateInfoType(pInfo->nType), - pPkg->pszFileName); - - pPkg = pPkg->pNext; + if (mode == OUTPUT_INFO) + { + pr_crit(" Name : %s\n" + " Update ID : %s\n" + " Type : %s\n" + " Updated : %s\n" + "Needs Reboot: %d\n" + "Description : %s\n", + pPkg->pszFileName, + pInfo->pszID, + TDNFGetUpdateInfoType(pInfo->nType), + pInfo->pszDate, + pInfo->nRebootRequired, + pInfo->pszDescription); + } + else if (mode == OUTPUT_LIST) + { + pr_crit("%s %s %s\n", pInfo->pszID, + TDNFGetUpdateInfoType(pInfo->nType), + pPkg->pszFileName); + } } - pInfo = pInfo->pNext; } - -cleanup: return dwError; - -error: - goto cleanup; } uint32_t -TDNFCliUpdateInfoInfo( - PTDNF_UPDATEINFO pInfo +TDNFCliUpdateInfoOutputJson( + PTDNF_UPDATEINFO pInfo, + TDNF_UPDATEINFO_OUTPUT mode ) { uint32_t dwError = 0; PTDNF_UPDATEINFO_PKG pPkg = NULL; + struct json_dump *jd = jd_create(0); + struct json_dump *jd_info = NULL; + struct json_dump *jd_pkgs = NULL; - if(!pInfo) - { - dwError = ERROR_TDNF_INVALID_PARAMETER; - BAIL_ON_CLI_ERROR(dwError); - } + CHECK_JD_NULL(jd); + CHECK_JD_RC(jd_list_start(jd)); - while(pInfo) + for(; pInfo; pInfo = pInfo->pNext) { - pPkg = pInfo->pPackages; - while(pPkg) + jd_info = jd_create(0); + CHECK_JD_NULL(jd_info); + jd_pkgs = jd_create(0); + CHECK_JD_NULL(jd_pkgs); + + CHECK_JD_RC(jd_map_start(jd_info)); + + CHECK_JD_RC(jd_map_add_string(jd_info, "Type", TDNFGetUpdateInfoType(pInfo->nType))); + CHECK_JD_RC(jd_map_add_string(jd_info, "UpdateID", pInfo->pszID)); + if (mode == OUTPUT_INFO) { - pr_crit(" Name : %s\n" - " Update ID : %s\n" - " Type : %s\n" - " Updated : %s\n" - "Needs Reboot: %d\n" - "Description : %s\n", - pPkg->pszFileName, - pInfo->pszID, - TDNFGetUpdateInfoType(pInfo->nType), - pInfo->pszDate, - pInfo->nRebootRequired, - pInfo->pszDescription); + CHECK_JD_RC(jd_map_add_string(jd_info, "Updated", pInfo->pszDate)); + CHECK_JD_RC(jd_map_add_bool(jd_info, "NeedsReboot", pInfo->nRebootRequired)); + CHECK_JD_RC(jd_map_add_string(jd_info, "Description", pInfo->pszDescription)); + } + CHECK_JD_RC(jd_list_start(jd_pkgs)); - pPkg = pPkg->pNext; + for(pPkg = pInfo->pPackages; pPkg; pPkg = pPkg->pNext) + { + CHECK_JD_RC(jd_list_add_string(jd_pkgs, pPkg->pszFileName)); } - pInfo = pInfo->pNext; + CHECK_JD_RC(jd_map_add_child(jd_info, "Packages", jd_pkgs)); + JD_SAFE_DESTROY(jd_pkgs); + CHECK_JD_RC(jd_list_add_child(jd, jd_info)); + JD_SAFE_DESTROY(jd_info); } - -cleanup: - return dwError; + pr_json(jd->buf); + JD_SAFE_DESTROY(jd); error: - goto cleanup; + JD_SAFE_DESTROY(jd); + JD_SAFE_DESTROY(jd_info); + JD_SAFE_DESTROY(jd_pkgs); + return dwError; } diff --git a/tools/cli/main.c b/tools/cli/main.c index b986e7b2..20fc4106 100644 --- a/tools/cli/main.c +++ b/tools/cli/main.c @@ -65,7 +65,7 @@ int main(int argc, char **argv) if(pCmdArgs->nShowVersion) { - TDNFCliShowVersion(); + TDNFCliShowVersion(pCmdArgs); } else if(pCmdArgs->nShowHelp) { @@ -142,14 +142,20 @@ int main(int argc, char **argv) } else { - TDNFCliShowNoSuchCommand(pszCmd); + if (!pCmdArgs->nJsonOutput) + { + TDNFCliShowNoSuchCommand(pszCmd); + } dwError = ERROR_TDNF_CLI_NO_SUCH_CMD; BAIL_ON_CLI_ERROR(dwError); } } else { - TDNFCliShowUsage(); + if (!pCmdArgs->nJsonOutput) + { + TDNFCliShowUsage(); + } } cleanup: @@ -165,7 +171,7 @@ int main(int argc, char **argv) return dwError; error: - TDNFCliPrintError(dwError); + TDNFCliPrintError(dwError, pCmdArgs ? pCmdArgs->nJsonOutput : 0); if (dwError == ERROR_TDNF_CLI_NOTHING_TO_DO || dwError == ERROR_TDNF_NO_DATA) { @@ -178,11 +184,13 @@ int main(int argc, char **argv) uint32_t TDNFCliPrintError( - uint32_t dwErrorCode + uint32_t dwErrorCode, + int doJson ) { uint32_t dwError = 0; char* pszError = NULL; + struct json_dump *jd = NULL; if (!dwErrorCode) { @@ -205,16 +213,34 @@ TDNFCliPrintError( dwErrorCode = 0; } - if (dwErrorCode) + if (doJson) { - pr_err("Error(%u) : %s\n", dwErrorCode, pszError); + if (dwErrorCode) + { + jd = jd_create(0); + CHECK_JD_NULL(jd); + + CHECK_JD_RC(jd_map_start(jd)); + CHECK_JD_RC(jd_map_add_int(jd, "Error", dwErrorCode)); + CHECK_JD_RC(jd_map_add_string(jd, "ErrorMessage", pszError)); + + pr_json(jd->buf); + } } else { - pr_err("%s\n", pszError); + if (dwErrorCode) + { + pr_err("Error(%u) : %s\n", dwErrorCode, pszError); + } + else + { + pr_err("%s\n", pszError); + } } cleanup: + JD_SAFE_DESTROY(jd); TDNF_CLI_SAFE_FREE_MEMORY(pszError); return dwError; @@ -227,10 +253,22 @@ TDNFCliPrintError( void TDNFCliShowVersion( - void + PTDNF_CMD_ARGS pCmdArgs ) { - pr_info("%s: %s\n", TDNFGetPackageName(), TDNFGetVersion()); + if (pCmdArgs->nJsonOutput) + { + struct json_dump *jd = jd_create(0); + jd_map_start(jd); + jd_map_add_string(jd, "Name", TDNFGetPackageName()); + jd_map_add_string(jd, "Version", TDNFGetVersion()); + pr_json(jd->buf); + jd_destroy(jd); + } + else + { + pr_info("%s: %s\n", TDNFGetPackageName(), TDNFGetVersion()); + } } uint32_t diff --git a/tools/cli/prototypes.h b/tools/cli/prototypes.h index 501b8425..be64fca8 100644 --- a/tools/cli/prototypes.h +++ b/tools/cli/prototypes.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 VMware, Inc. All Rights Reserved. + * Copyright (C) 2015-2022 VMware, Inc. All Rights Reserved. * * Licensed under the GNU General Public License v2 (the "License"); * you may not use this file except in compliance with the License. The terms @@ -153,6 +153,11 @@ PrintSolvedInfo( PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo ); +uint32_t + PrintSolvedInfoJson( + PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo + ); + uint32_t PrintNotAvailable( char** ppszPkgsNotAvailable @@ -181,7 +186,7 @@ TDNFCliInvokeUpdateInfoSummary( //main.c void TDNFCliShowVersion( - void + PTDNF_CMD_ARGS pCmdArgs ); uint32_t