From 9039d20b5bd73d3732ed6dac0a2bba4558176d8f Mon Sep 17 00:00:00 2001 From: David Steele Date: Fri, 15 Sep 2023 09:30:40 -0400 Subject: [PATCH] Add report option to check command. This option is intended to eventually create a comprehensive report about the state of the pgBackRest configuration based on the results of the check command. Implement a detailed report of the configuration options in the environment and configuration files. This should be useful information when debugging configuration errors, since invalid options and configurations are automatically noted. While custom config locations will not be found automatically, it will at least be clear that the config is not in a standard location. For now keep this option internal since there is a lot of work to be done, but commit it so that it can be used when needed and tested in various environments. Note that for now when --report is specified, the check command is not being run at all. Only the config report is generated. This behavior will be improved in the future. --- doc/xml/release/2023/2.48.xml | 13 ++ src/Makefile.in | 1 + src/build/config/config.yaml | 9 + src/build/help/help.xml | 12 ++ src/command/check/check.c | 81 ++++---- src/command/check/report.c | 274 ++++++++++++++++++++++++++++ src/command/check/report.h | 15 ++ src/config/config.auto.h | 4 +- src/config/parse.auto.c.inc | 25 +++ src/config/parse.c | 29 ++- src/config/parse.h | 17 ++ src/meson.build | 1 + test/define.yaml | 3 +- test/src/module/command/checkTest.c | 176 ++++++++++++++++++ test/src/module/config/parseTest.c | 1 + 15 files changed, 608 insertions(+), 53 deletions(-) create mode 100644 src/command/check/report.c create mode 100644 src/command/check/report.h diff --git a/doc/xml/release/2023/2.48.xml b/doc/xml/release/2023/2.48.xml index f7b71befa..d58e970da 100644 --- a/doc/xml/release/2023/2.48.xml +++ b/doc/xml/release/2023/2.48.xml @@ -127,6 +127,19 @@

Reload GCS credentials before renewing authentication token.

+ + + + + + + + + + +

Add report option to check command.

+
+
diff --git a/src/Makefile.in b/src/Makefile.in index 0dca95442..259fdd189 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -68,6 +68,7 @@ SRCS = \ command/backup/file.c \ command/check/check.c \ command/check/common.c \ + command/check/report.c \ command/expire/expire.c \ command/exit.c \ command/help/help.c \ diff --git a/src/build/config/config.yaml b/src/build/config/config.yaml index ec6124e88..206b68a1f 100644 --- a/src/build/config/config.yaml +++ b/src/build/config/config.yaml @@ -361,6 +361,15 @@ option: command-role: main: {} + report: + type: boolean + internal: true + default: false + command: + check: {} + command-role: + main: {} + stanza: type: string command: diff --git a/src/build/help/help.xml b/src/build/help/help.xml index f81d38410..aa06f2c29 100644 --- a/src/build/help/help.xml +++ b/src/build/help/help.xml @@ -2104,6 +2104,18 @@ + + + + diff --git a/src/command/check/check.c b/src/command/check/check.c index 7852f708d..39740dde3 100644 --- a/src/command/check/check.c +++ b/src/command/check/check.c @@ -3,10 +3,14 @@ Check Command ***********************************************************************************************************************************/ #include "build.auto.h" +#include + #include "command/archive/find.h" #include "command/check/check.h" #include "command/check/common.h" +#include "command/check/report.h" #include "common/debug.h" +#include "common/io/fdWrite.h" #include "common/log.h" #include "common/memContext.h" #include "config/config.h" @@ -178,53 +182,58 @@ cmdCheck(void) MEM_CONTEXT_TEMP_BEGIN() { - // Build stanza list based on whether a stanza was specified or not - StringList *stanzaList; - bool stanzaSpecified = cfgOptionTest(cfgOptStanza); - - if (stanzaSpecified) - { - stanzaList = strLstNew(); - strLstAdd(stanzaList, cfgOptionStr(cfgOptStanza)); - } + if (cfgOptionBool(cfgOptReport)) + ioFdWriteOneStr(STDOUT_FILENO, checkReport()); else { - stanzaList = cfgParseStanzaList(); + // Build stanza list based on whether a stanza was specified or not + StringList *stanzaList; + bool stanzaSpecified = cfgOptionTest(cfgOptStanza); - if (strLstSize(stanzaList) == 0) + if (stanzaSpecified) { - LOG_WARN( - "no stanzas found to check\n" - "HINT: are there non-empty stanza sections in the configuration?"); + stanzaList = strLstNew(); + strLstAdd(stanzaList, cfgOptionStr(cfgOptStanza)); } - } - - // Iterate stanzas - for (unsigned int stanzaIdx = 0; stanzaIdx < strLstSize(stanzaList); stanzaIdx++) - { - // Switch stanza if required - if (!stanzaSpecified) + else { - const String *const stanza = strLstGet(stanzaList, stanzaIdx); - LOG_INFO_FMT("check stanza '%s'", strZ(stanza)); + stanzaList = cfgParseStanzaList(); - // Free storage and protocol cache - storageHelperFree(); - protocolFree(); - - // Reload config with new stanza - cfgLoadStanza(stanza); + if (strLstSize(stanzaList) == 0) + { + LOG_WARN( + "no stanzas found to check\n" + "HINT: are there non-empty stanza sections in the configuration?"); + } } - // Get the primary/standby connections (standby is only required if backup from standby is enabled) - DbGetResult dbGroup = dbGet(false, false, false); + // Iterate stanzas + for (unsigned int stanzaIdx = 0; stanzaIdx < strLstSize(stanzaList); stanzaIdx++) + { + // Switch stanza if required + if (!stanzaSpecified) + { + const String *const stanza = strLstGet(stanzaList, stanzaIdx); + LOG_INFO_FMT("check stanza '%s'", strZ(stanza)); - if (dbGroup.standby == NULL && dbGroup.primary == NULL) - THROW(ConfigError, "no database found\nHINT: check indexed pg-path/pg-host configurations"); + // Free storage and protocol cache + storageHelperFree(); + protocolFree(); - const unsigned int pgPathDefinedTotal = checkManifest(); - checkStandby(dbGroup, pgPathDefinedTotal); - checkPrimary(dbGroup); + // Reload config with new stanza + cfgLoadStanza(stanza); + } + + // Get the primary/standby connections (standby is only required if backup from standby is enabled) + DbGetResult dbGroup = dbGet(false, false, false); + + if (dbGroup.standby == NULL && dbGroup.primary == NULL) + THROW(ConfigError, "no database found\nHINT: check indexed pg-path/pg-host configurations"); + + const unsigned int pgPathDefinedTotal = checkManifest(); + checkStandby(dbGroup, pgPathDefinedTotal); + checkPrimary(dbGroup); + } } } MEM_CONTEXT_TEMP_END(); diff --git a/src/command/check/report.c b/src/command/check/report.c new file mode 100644 index 000000000..99b7fc8bc --- /dev/null +++ b/src/command/check/report.c @@ -0,0 +1,274 @@ +/*********************************************************************************************************************************** +Support Command +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include "command/check/report.h" +#include "common/debug.h" +#include "common/log.h" +#include "common/type/json.h" +#include "config/config.h" +#include "config/parse.h" + +/*********************************************************************************************************************************** +In some environments this will not be externed +***********************************************************************************************************************************/ +extern char **environ; + +/*********************************************************************************************************************************** +Render config +***********************************************************************************************************************************/ +// Helper to render an individual config value +static void +checkReportConfigVal(JsonWrite *const json, const String *const optionName, const StringList *valueList, const bool env) +{ + FUNCTION_TEST_BEGIN(); + FUNCTION_TEST_PARAM(JSON_WRITE, json); + FUNCTION_TEST_PARAM(STRING, optionName); + FUNCTION_TEST_PARAM(STRING_LIST, valueList); + FUNCTION_TEST_PARAM(BOOL, env); + FUNCTION_TEST_END(); + + ASSERT(json != NULL); + ASSERT(optionName != NULL); + ASSERT(valueList != NULL); + + MEM_CONTEXT_TEMP_BEGIN() + { + jsonWriteObjectBegin(json); + + CfgParseOptionResult option = cfgParseOptionP(optionName); + + // Render value + if (option.found) + { + jsonWriteKeyStrId(json, STRID5("val", 0x30360)); + + // Multiple values + if (option.multi) + { + ASSERT(strLstSize(valueList) >= 1); + + // Split environment values + if (env) + { + ASSERT(strLstSize(valueList) == 1); + valueList = strLstNewSplitZ(strLstGet(valueList, 0), ":"); + } + + // Render each value + jsonWriteArrayBegin(json); + + for (unsigned int valueIdx = 0; valueIdx < strLstSize(valueList); valueIdx++) + jsonWriteStr(json, strLstGet(valueList, valueIdx)); + + jsonWriteArrayEnd(json); + } + // Single value + else + { + ASSERT(strLstSize(valueList) == 1); + jsonWriteStr(json, strLstGet(valueList, 0)); + } + } + + // Warn on invalid options or option modifiers + if (!option.found || option.negate || option.reset) + { + jsonWriteKeyStrId(json, STRID5("warn", 0x748370)); + + // Warn if the option not found + if (!option.found) + { + jsonWriteZ(json, "invalid option"); + } + // Warn if negate option + else if (option.negate) + { + jsonWriteZ(json, "invalid negate option"); + } + // Warn if reset option + else + { + ASSERT(option.reset); + + jsonWriteZ(json, "invalid reset option"); + } + } + + jsonWriteObjectEnd(json); + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_TEST_RETURN_VOID(); +} + +// Helper to render the environment +static void +checkReportConfigEnv(JsonWrite *const json) +{ + FUNCTION_TEST_BEGIN(); + FUNCTION_TEST_PARAM(JSON_WRITE, json); + FUNCTION_TEST_END(); + + ASSERT(json != NULL); + + MEM_CONTEXT_TEMP_BEGIN() + { + jsonWriteKeyStrId(json, STRID5("env", 0x59c50)); + jsonWriteObjectBegin(json); + + // Loop through all environment variables and look for our env vars by matching the prefix + unsigned int environIdx = 0; + KeyValue *const keyValue = kvNew(); + + while (environ[environIdx] != NULL) + { + const char *const environKeyValue = environ[environIdx]; + environIdx++; + + if (strstr(environKeyValue, PGBACKREST_ENV) == environKeyValue) + { + // Find the first = char + const char *const equalPtr = strchr(environKeyValue, '='); + ASSERT(equalPtr != NULL); + + // Get key and value + const String *const key = strNewZN(environKeyValue, (size_t)(equalPtr - environKeyValue)); + const String *const value = STR(equalPtr + 1); + + kvPut(keyValue, VARSTR(key), VARSTR(value)); + } + } + + // Render environment variables + const StringList *const keyList = strLstSort(strLstNewVarLst(kvKeyList(keyValue)), sortOrderAsc); + + for (unsigned int keyIdx = 0; keyIdx < strLstSize(keyList); keyIdx++) + { + const String *const key = strLstGet(keyList, keyIdx); + StringList *const valueList = strLstNew(); + strLstAdd(valueList, varStr(kvGet(keyValue, VARSTR(key)))); + + jsonWriteKey(json, key); + checkReportConfigVal( + json, strReplaceChr(strLower(strNewZ(strZ(key) + PGBACKREST_ENV_SIZE)), '_', '-'), valueList, true); + } + + jsonWriteObjectEnd(json); + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_TEST_RETURN_VOID(); +} + +// Helper to render the config file +static void +checkReportConfigFile(JsonWrite *const json) +{ + FUNCTION_TEST_BEGIN(); + FUNCTION_TEST_PARAM(JSON_WRITE, json); + FUNCTION_TEST_END(); + + ASSERT(json != NULL); + + MEM_CONTEXT_TEMP_BEGIN() + { + const Ini *const ini = cfgParseIni(); + + jsonWriteKeyStrId(json, STRID5("file", 0x2b1260)); + + // If no config + if (ini == NULL) + { + jsonWriteNull(json); + } + // Else config exists + else + { + // Iterate config sections + const StringList *const sectionList = strLstSort(iniSectionList(ini), sortOrderAsc); + + jsonWriteObjectBegin(json); + + for (unsigned int sectionIdx = 0; sectionIdx < strLstSize(sectionList); sectionIdx++) + { + const String *const section = strLstGet(sectionList, sectionIdx); + + jsonWriteKey(json, section); + jsonWriteObjectBegin(json); + + // Iterate config keys + const StringList *const keyList = strLstSort(iniSectionKeyList(ini, section), sortOrderAsc); + + for (unsigned int keyIdx = 0; keyIdx < strLstSize(keyList); keyIdx++) + { + const String *const key = strLstGet(keyList, keyIdx); + + // Render value + StringList *valueList; + + if (iniSectionKeyIsList(ini, section, key)) + valueList = iniGetList(ini, section, key); + else + { + valueList = strLstNew(); + strLstAdd(valueList, iniGet(ini, section, key)); + } + + jsonWriteKey(json, key); + checkReportConfigVal(json, key, valueList, false); + } + + jsonWriteObjectEnd(json); + } + + jsonWriteObjectEnd(json); + } + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_TEST_RETURN_VOID(); +} + +static void +checkReportConfig(JsonWrite *const json) +{ + FUNCTION_TEST_BEGIN(); + FUNCTION_TEST_PARAM(JSON_WRITE, json); + FUNCTION_TEST_END(); + + ASSERT(json != NULL); + + jsonWriteKeyStrId(json, STRID5("cfg", 0x1cc30)); + jsonWriteObjectBegin(json); + + checkReportConfigEnv(json); + checkReportConfigFile(json); + + jsonWriteObjectEnd(json); + + FUNCTION_TEST_RETURN_VOID(); +} + +/**********************************************************************************************************************************/ +FN_EXTERN String * +checkReport(void) +{ + FUNCTION_TEST_VOID(); + + String *const result = strNew(); + + MEM_CONTEXT_TEMP_BEGIN() + { + JsonWrite *const json = jsonWriteNewP(.json = result); + + // Render report + jsonWriteObjectBegin(json); + checkReportConfig(json); + jsonWriteObjectEnd(json); + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_TEST_RETURN(STRING, result); +} diff --git a/src/command/check/report.h b/src/command/check/report.h new file mode 100644 index 000000000..03c896f2d --- /dev/null +++ b/src/command/check/report.h @@ -0,0 +1,15 @@ +/*********************************************************************************************************************************** +Check Command Report +***********************************************************************************************************************************/ +#ifndef COMMAND_CHECK_REPORT_H +#define COMMAND_CHECK_REPORT_H + +#include "common/type/string.h" + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +// Check report +FN_EXTERN String *checkReport(void); + +#endif diff --git a/src/config/config.auto.h b/src/config/config.auto.h index e3995a1e6..42e37f6e4 100644 --- a/src/config/config.auto.h +++ b/src/config/config.auto.h @@ -108,6 +108,7 @@ Option constants #define CFGOPT_REFERENCE "reference" #define CFGOPT_REMOTE_TYPE "remote-type" #define CFGOPT_REPO "repo" +#define CFGOPT_REPORT "report" #define CFGOPT_RESUME "resume" #define CFGOPT_SCK_BLOCK "sck-block" #define CFGOPT_SCK_KEEP_ALIVE "sck-keep-alive" @@ -135,7 +136,7 @@ Option constants #define CFGOPT_TYPE "type" #define CFGOPT_VERBOSE "verbose" -#define CFG_OPTION_TOTAL 176 +#define CFG_OPTION_TOTAL 177 /*********************************************************************************************************************************** Option value constants @@ -524,6 +525,7 @@ typedef enum cfgOptRepoStorageUploadChunkSize, cfgOptRepoStorageVerifyTls, cfgOptRepoType, + cfgOptReport, cfgOptResume, cfgOptSckBlock, cfgOptSckKeepAlive, diff --git a/src/config/parse.auto.c.inc b/src/config/parse.auto.c.inc index 079b58059..fa5af619c 100644 --- a/src/config/parse.auto.c.inc +++ b/src/config/parse.auto.c.inc @@ -9239,6 +9239,30 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] = ), // opt/repo-type ), // opt/repo-type // ----------------------------------------------------------------------------------------------------------------------------- + PARSE_RULE_OPTION // opt/report + ( // opt/report + PARSE_RULE_OPTION_NAME("report"), // opt/report + PARSE_RULE_OPTION_TYPE(cfgOptTypeBoolean), // opt/report + PARSE_RULE_OPTION_REQUIRED(true), // opt/report + PARSE_RULE_OPTION_SECTION(cfgSectionCommandLine), // opt/report + // opt/report + PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/report + ( // opt/report + PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/report + ), // opt/report + // opt/report + PARSE_RULE_OPTIONAL // opt/report + ( // opt/report + PARSE_RULE_OPTIONAL_GROUP // opt/report + ( // opt/report + PARSE_RULE_OPTIONAL_DEFAULT // opt/report + ( // opt/report + PARSE_RULE_VAL_BOOL_FALSE, // opt/report + ), // opt/report + ), // opt/report + ), // opt/report + ), // opt/report + // ----------------------------------------------------------------------------------------------------------------------------- PARSE_RULE_OPTION // opt/resume ( // opt/resume PARSE_RULE_OPTION_NAME("resume"), // opt/resume @@ -10704,6 +10728,7 @@ static const uint8_t optionResolveOrder[] = cfgOptRepoRetentionFullType, // opt-resolve-order cfgOptRepoRetentionHistory, // opt-resolve-order cfgOptRepoType, // opt-resolve-order + cfgOptReport, // opt-resolve-order cfgOptResume, // opt-resolve-order cfgOptSckBlock, // opt-resolve-order cfgOptSckKeepAlive, // opt-resolve-order diff --git a/src/config/parse.c b/src/config/parse.c index 84f87132c..d51e9b93f 100644 --- a/src/config/parse.c +++ b/src/config/parse.c @@ -10,7 +10,6 @@ Command and Option Parse #include #include "common/debug.h" -#include "common/ini.h" #include "common/io/bufferRead.h" #include "common/log.h" #include "common/macro.h" @@ -21,11 +20,6 @@ Command and Option Parse #include "config/parse.h" #include "version.h" -/*********************************************************************************************************************************** -Define global section name -***********************************************************************************************************************************/ -#define CFGDEF_SECTION_GLOBAL "global" - /*********************************************************************************************************************************** The maximum number of keys that an indexed option can have, e.g. pg256-path would be the maximum pg-path option ***********************************************************************************************************************************/ @@ -48,12 +42,8 @@ Standard config file name and old default path and name STRING_STATIC(PGBACKREST_CONFIG_ORIG_PATH_FILE_STR, PGBACKREST_CONFIG_ORIG_PATH_FILE); /*********************************************************************************************************************************** -Prefix for environment variables +In some environments this will not be externed ***********************************************************************************************************************************/ -#define PGBACKREST_ENV "PGBACKREST_" -#define PGBACKREST_ENV_SIZE (sizeof(PGBACKREST_ENV) - 1) - -// In some environments this will not be externed extern char **environ; /*********************************************************************************************************************************** @@ -490,6 +480,14 @@ cfgParseCommandRoleName(const ConfigCommand commandId, const ConfigCommandRole c FUNCTION_TEST_RETURN(STRING, result); } +/**********************************************************************************************************************************/ +FN_EXTERN const Ini * +cfgParseIni(void) +{ + FUNCTION_TEST_VOID(); + FUNCTION_TEST_RETURN(INI, configParseLocal.ini); +} + /**********************************************************************************************************************************/ FN_EXTERN StringList * cfgParseStanzaList(void) @@ -756,8 +754,9 @@ cfgParseOption(const String *const optionCandidate, const CfgParseOptionParam pa } } - // Set the beta flag + // Set flags result.beta = optionFound->beta; + result.multi = optionFound->multi; FUNCTION_TEST_RETURN_TYPE(CfgParseOptionResult, result); } @@ -1699,7 +1698,7 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha } // Add the argument - if (optionArg != NULL && parseRuleOption[option.id].multi) + if (optionArg != NULL && option.multi) { strLstAdd(optionValue->valueList, optionArg); } @@ -1867,7 +1866,7 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha THROW_FMT(OptionInvalidValueError, "environment boolean option '%s' must be 'y' or 'n'", strZ(key)); } // Else split list/hash into separate values - else if (parseRuleOption[option.id].multi) + else if (option.multi) { optionValue->valueList = strLstNewSplitZ(value, ":"); } @@ -2017,7 +2016,7 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha if (iniSectionKeyIsList(configParseLocal.ini, section, key)) { // Error if the option cannot be specified multiple times - if (!parseRuleOption[option.id].multi) + if (!option.multi) { THROW_FMT( OptionInvalidError, "option '%s' cannot be set multiple times", diff --git a/src/config/parse.h b/src/config/parse.h index b296e8bd2..b14ae5355 100644 --- a/src/config/parse.h +++ b/src/config/parse.h @@ -4,9 +4,21 @@ Parse Configuration #ifndef CONFIG_PARSE_H #define CONFIG_PARSE_H +#include "common/ini.h" #include "config/config.h" #include "storage/storage.h" +/*********************************************************************************************************************************** +Define global section name +***********************************************************************************************************************************/ +#define CFGDEF_SECTION_GLOBAL "global" + +/*********************************************************************************************************************************** +Prefix for environment variables +***********************************************************************************************************************************/ +#define PGBACKREST_ENV "PGBACKREST_" +#define PGBACKREST_ENV_SIZE (sizeof(PGBACKREST_ENV) - 1) + /*********************************************************************************************************************************** Option type enum ***********************************************************************************************************************************/ @@ -62,6 +74,9 @@ FN_EXTERN String *cfgParseCommandRoleName(const ConfigCommand commandId, const C // Convert command role enum to String FN_EXTERN const String *cfgParseCommandRoleStr(ConfigCommandRole commandRole); +// Return the parsed ini config +FN_EXTERN const Ini *cfgParseIni(void); + // Parse option name and return option info typedef struct CfgParseOptionParam { @@ -79,6 +94,7 @@ typedef struct CfgParseOptionResult bool reset; // Was the option reset? bool deprecated; // Is the option deprecated? bool beta; // Is the option in beta? + bool multi; // Can the option be specified multiple times? } CfgParseOptionResult; #define cfgParseOptionP(optionName, ...) \ @@ -107,6 +123,7 @@ FN_EXTERN ConfigOptionDataType cfgParseOptionDataType(ConfigOption optionId); // Is the option required? FN_EXTERN bool cfgParseOptionRequired(ConfigCommand commandId, ConfigOption optionId); +// Get list of stanzas in the configuration FN_EXTERN StringList *cfgParseStanzaList(void); // Is the option valid for the command? diff --git a/src/meson.build b/src/meson.build index cc6afc588..779e510a3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -132,6 +132,7 @@ src_pgbackrest = [ 'command/backup/file.c', 'command/check/check.c', 'command/check/common.c', + 'command/check/report.c', 'command/exit.c', 'command/expire/expire.c', 'command/help/help.c', diff --git a/test/define.yaml b/test/define.yaml index b0517ba50..cc91e503c 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -834,11 +834,12 @@ unit: # ---------------------------------------------------------------------------------------------------------------------------- - name: check - total: 3 + total: 4 coverage: - command/check/common - command/check/check + - command/check/report # ---------------------------------------------------------------------------------------------------------------------------- - name: command diff --git a/test/src/module/command/checkTest.c b/test/src/module/command/checkTest.c index 9846bb33e..6f34c4a17 100644 --- a/test/src/module/command/checkTest.c +++ b/test/src/module/command/checkTest.c @@ -30,6 +30,182 @@ testRun(void) StringList *argList = strLstNew(); + // ***************************************************************************************************************************** + if (testBegin("checkReport()")) + { + // Common config + HRN_STORAGE_PUT_Z( + storageTest, "pgbackrest.conf", + "[global]\n" + "repo1-path=" TEST_PATH "/repo1\n" + "repo1-block=y\n" + "no-repo1-block=bogus\n" + "bogus=y\n" + "\n" + "[global:backup]\n" + "start-fast=y\n" + "reset-start-fast=bogus\n" + "\n" + "[test2]\n" + "pg1-path=" TEST_PATH "/test2-pg1\n" + "recovery-option=key1=value1\n" + "recovery-option=key2=value2\n" + "\n" + "[test1]\n" + "pg1-path=" TEST_PATH "/test1-pg1\n"); + + StringList *const argListCommon = strLstNew(); + hrnCfgArgRawZ(argListCommon, cfgOptConfig, TEST_PATH "/pgbackrest.conf"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("no env, no config"); + { + TEST_RESULT_STR_Z( + checkReport(), + // {uncrustify_off - indentation} + "{" + "\"cfg\":{" + "\"env\":{}," + "\"file\":null" + "}" + "}", + // {uncrustify_on} + "render"); + } + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("report on stdout"); + { + argList = strLstNew(); + hrnCfgArgRawBool(argList, cfgOptReport, true); + HRN_CFG_LOAD(cfgCmdCheck, argList); + + // Redirect stdout to a file + int stdoutSave = dup(STDOUT_FILENO); + const String *stdoutFile = STRDEF(TEST_PATH "/stdout.info"); + + THROW_ON_SYS_ERROR(freopen(strZ(stdoutFile), "w", stdout) == NULL, FileWriteError, "unable to reopen stdout"); + + // Not in a test wrapper to avoid writing to stdout + cmdCheck(); + + // Restore normal stdout + dup2(stdoutSave, STDOUT_FILENO); + + // Check output of info command stored in file + TEST_STORAGE_GET( + storageTest, strZ(stdoutFile), + // {uncrustify_off - indentation} + "{" + "\"cfg\":{" + "\"env\":{}," + "\"file\":null" + "}" + "}", + // {uncrustify_on} + .remove = true); + } + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("env and config"); + { + hrnCfgEnvRawZ(cfgOptBufferSize, "64KiB"); + setenv(PGBACKREST_ENV "BOGUS", "bogus", true); + setenv(PGBACKREST_ENV "NO_ONLINE", "bogus", true); + setenv(PGBACKREST_ENV "DB_INCLUDE", "db1:db2", true); + setenv(PGBACKREST_ENV "RESET_COMPRESS_TYPE", "bogus", true); + + hrnCfgLoad(cfgCmdCheck, argListCommon, (HrnCfgLoadParam){.log = true}); + + TEST_RESULT_STR_Z( + checkReport(), + // {uncrustify_off - indentation} + "{" + "\"cfg\":{" + "\"env\":{" + "\"PGBACKREST_BOGUS\":{" + "\"warn\":\"invalid option\"" + "}," + "\"PGBACKREST_BUFFER_SIZE\":{" + "\"val\":\"64KiB\"" + "}," + "\"PGBACKREST_DB_INCLUDE\":{" + "\"val\":[" + "\"db1\"," + "\"db2\"" + "]" + "}," + "\"PGBACKREST_NO_ONLINE\":{" + "\"val\":\"bogus\"," + "\"warn\":\"invalid negate option\"" + "}," + "\"PGBACKREST_RESET_COMPRESS_TYPE\":{" + "\"val\":\"bogus\"," + "\"warn\":\"invalid reset option\"" + "}" + "}," + "\"file\":{" + "\"global\":{" + "\"bogus\":{" + "\"warn\":\"invalid option\"" + "}," + "\"no-repo1-block\":{" + "\"val\":\"bogus\"," + "\"warn\":\"invalid negate option\"" + "}," + "\"repo1-block\":{" + "\"val\":\"y\"" + "}," + "\"repo1-path\":{" + "\"val\":\"" TEST_PATH "/repo1\"" + "}" + "}," + "\"global:backup\":{" + "\"reset-start-fast\":" + "{" + "\"val\":\"bogus\"," + "\"warn\":\"invalid reset option\"" + "}," + "\"start-fast\":{" + "\"val\":\"y\"" + "}" + "}," + "\"test1\":{" + "\"pg1-path\":{" + "\"val\":\"" TEST_PATH "/test1-pg1\"" + "}" + "}," + "\"test2\":{" + "\"pg1-path\":{" + "\"val\":\"" TEST_PATH "/test2-pg1\"" + "}," + "\"recovery-option\":{" + "\"val\":[" + "\"key1=value1\"," + "\"key2=value2\"" + "]" + "}" + "}" + "}" + "}" + "}", + // {uncrustify_on} + "render"); + + TEST_RESULT_LOG( + "P00 WARN: environment contains invalid option 'bogus'\n" + "P00 WARN: environment contains invalid negate option 'no-online'\n" + "P00 WARN: environment contains invalid reset option 'reset-compress-type'\n" + "P00 WARN: configuration file contains negate option 'no-repo1-block'\n" + "P00 WARN: configuration file contains invalid option 'bogus'"); + + unsetenv(PGBACKREST_ENV "BOGUS"); + unsetenv(PGBACKREST_ENV "NO_ONLINE"); + unsetenv(PGBACKREST_ENV "DB_INCLUDE"); + unsetenv(PGBACKREST_ENV "RESET_COMPRESS_TYPE"); + } + } + // ***************************************************************************************************************************** if (testBegin("cmdCheck()")) { diff --git a/test/src/module/config/parseTest.c b/test/src/module/config/parseTest.c index 3469f2632..3821e665b 100644 --- a/test/src/module/config/parseTest.c +++ b/test/src/module/config/parseTest.c @@ -1730,6 +1730,7 @@ testRun(void) "P00 WARN: configuration file contains invalid option 'backup-standb'\n" "P00 WARN: configuration file contains stanza-only option 'pg1-path' in global section 'global'"); + TEST_RESULT_PTR(cfgParseIni(), configParseLocal.ini, "parsed ini"); TEST_RESULT_STRLST_Z(cfgParseStanzaList(), "db\n", "stanza list"); TEST_RESULT_STR_Z(jsonFromVar(varNewVarLst(cfgCommandJobRetry())), "[0,33000,33000]", "custom job retries"); TEST_RESULT_BOOL(cfgOptionIdxTest(cfgOptPgHost, 0), false, "pg1-host is not set (command line reset override)");