1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-12 10:04:14 +02:00

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.
This commit is contained in:
David Steele 2023-09-15 09:30:40 -04:00 committed by GitHub
parent 657c1a3e06
commit 9039d20b5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 608 additions and 53 deletions

View File

@ -127,6 +127,19 @@
<p>Reload GCS credentials before renewing authentication token.</p> <p>Reload GCS credentials before renewing authentication token.</p>
</release-item> </release-item>
</release-improvement-list> </release-improvement-list>
<release-development-list>
<release-item>
<github-pull-request id="2176"/>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="stephen.frost"/>
</release-item-contributor-list>
<p>Add report option to <cmd>check</cmd> command.</p>
</release-item>
</release-development-list>
</release-core-list> </release-core-list>
<release-doc-list> <release-doc-list>

View File

@ -68,6 +68,7 @@ SRCS = \
command/backup/file.c \ command/backup/file.c \
command/check/check.c \ command/check/check.c \
command/check/common.c \ command/check/common.c \
command/check/report.c \
command/expire/expire.c \ command/expire/expire.c \
command/exit.c \ command/exit.c \
command/help/help.c \ command/help/help.c \

View File

@ -361,6 +361,15 @@ option:
command-role: command-role:
main: {} main: {}
report:
type: boolean
internal: true
default: false
command:
check: {}
command-role:
main: {}
stanza: stanza:
type: string type: string
command: command:

View File

@ -2104,6 +2104,18 @@
<!-- Note, linking to the User Guide is limited since it can cause a cyclical reference --> <!-- Note, linking to the User Guide is limited since it can cause a cyclical reference -->
</text> </text>
<option-list>
<option id="report" name="Report">
<summary>Generate a check report.</summary>
<text>
<p>Generate a report based on the results of the check command.</p>
</text>
<example>y</example>
</option>
</option-list>
</command> </command>
<command id="expire" name="Expire"> <command id="expire" name="Expire">

View File

@ -3,10 +3,14 @@ Check Command
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
#include "build.auto.h" #include "build.auto.h"
#include <unistd.h>
#include "command/archive/find.h" #include "command/archive/find.h"
#include "command/check/check.h" #include "command/check/check.h"
#include "command/check/common.h" #include "command/check/common.h"
#include "command/check/report.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/io/fdWrite.h"
#include "common/log.h" #include "common/log.h"
#include "common/memContext.h" #include "common/memContext.h"
#include "config/config.h" #include "config/config.h"
@ -178,53 +182,58 @@ cmdCheck(void)
MEM_CONTEXT_TEMP_BEGIN() MEM_CONTEXT_TEMP_BEGIN()
{ {
// Build stanza list based on whether a stanza was specified or not if (cfgOptionBool(cfgOptReport))
StringList *stanzaList; ioFdWriteOneStr(STDOUT_FILENO, checkReport());
bool stanzaSpecified = cfgOptionTest(cfgOptStanza);
if (stanzaSpecified)
{
stanzaList = strLstNew();
strLstAdd(stanzaList, cfgOptionStr(cfgOptStanza));
}
else 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( stanzaList = strLstNew();
"no stanzas found to check\n" strLstAdd(stanzaList, cfgOptionStr(cfgOptStanza));
"HINT: are there non-empty stanza sections in the configuration?");
} }
} else
// Iterate stanzas
for (unsigned int stanzaIdx = 0; stanzaIdx < strLstSize(stanzaList); stanzaIdx++)
{
// Switch stanza if required
if (!stanzaSpecified)
{ {
const String *const stanza = strLstGet(stanzaList, stanzaIdx); stanzaList = cfgParseStanzaList();
LOG_INFO_FMT("check stanza '%s'", strZ(stanza));
// Free storage and protocol cache if (strLstSize(stanzaList) == 0)
storageHelperFree(); {
protocolFree(); LOG_WARN(
"no stanzas found to check\n"
// Reload config with new stanza "HINT: are there non-empty stanza sections in the configuration?");
cfgLoadStanza(stanza); }
} }
// Get the primary/standby connections (standby is only required if backup from standby is enabled) // Iterate stanzas
DbGetResult dbGroup = dbGet(false, false, false); 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) // Free storage and protocol cache
THROW(ConfigError, "no database found\nHINT: check indexed pg-path/pg-host configurations"); storageHelperFree();
protocolFree();
const unsigned int pgPathDefinedTotal = checkManifest(); // Reload config with new stanza
checkStandby(dbGroup, pgPathDefinedTotal); cfgLoadStanza(stanza);
checkPrimary(dbGroup); }
// 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(); MEM_CONTEXT_TEMP_END();

274
src/command/check/report.c Normal file
View File

@ -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);
}

View File

@ -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

View File

@ -108,6 +108,7 @@ Option constants
#define CFGOPT_REFERENCE "reference" #define CFGOPT_REFERENCE "reference"
#define CFGOPT_REMOTE_TYPE "remote-type" #define CFGOPT_REMOTE_TYPE "remote-type"
#define CFGOPT_REPO "repo" #define CFGOPT_REPO "repo"
#define CFGOPT_REPORT "report"
#define CFGOPT_RESUME "resume" #define CFGOPT_RESUME "resume"
#define CFGOPT_SCK_BLOCK "sck-block" #define CFGOPT_SCK_BLOCK "sck-block"
#define CFGOPT_SCK_KEEP_ALIVE "sck-keep-alive" #define CFGOPT_SCK_KEEP_ALIVE "sck-keep-alive"
@ -135,7 +136,7 @@ Option constants
#define CFGOPT_TYPE "type" #define CFGOPT_TYPE "type"
#define CFGOPT_VERBOSE "verbose" #define CFGOPT_VERBOSE "verbose"
#define CFG_OPTION_TOTAL 176 #define CFG_OPTION_TOTAL 177
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Option value constants Option value constants
@ -524,6 +525,7 @@ typedef enum
cfgOptRepoStorageUploadChunkSize, cfgOptRepoStorageUploadChunkSize,
cfgOptRepoStorageVerifyTls, cfgOptRepoStorageVerifyTls,
cfgOptRepoType, cfgOptRepoType,
cfgOptReport,
cfgOptResume, cfgOptResume,
cfgOptSckBlock, cfgOptSckBlock,
cfgOptSckKeepAlive, cfgOptSckKeepAlive,

View File

@ -9239,6 +9239,30 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
), // opt/repo-type ), // opt/repo-type
), // 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 PARSE_RULE_OPTION // opt/resume
( // opt/resume ( // opt/resume
PARSE_RULE_OPTION_NAME("resume"), // opt/resume PARSE_RULE_OPTION_NAME("resume"), // opt/resume
@ -10704,6 +10728,7 @@ static const uint8_t optionResolveOrder[] =
cfgOptRepoRetentionFullType, // opt-resolve-order cfgOptRepoRetentionFullType, // opt-resolve-order
cfgOptRepoRetentionHistory, // opt-resolve-order cfgOptRepoRetentionHistory, // opt-resolve-order
cfgOptRepoType, // opt-resolve-order cfgOptRepoType, // opt-resolve-order
cfgOptReport, // opt-resolve-order
cfgOptResume, // opt-resolve-order cfgOptResume, // opt-resolve-order
cfgOptSckBlock, // opt-resolve-order cfgOptSckBlock, // opt-resolve-order
cfgOptSckKeepAlive, // opt-resolve-order cfgOptSckKeepAlive, // opt-resolve-order

View File

@ -10,7 +10,6 @@ Command and Option Parse
#include <unistd.h> #include <unistd.h>
#include "common/debug.h" #include "common/debug.h"
#include "common/ini.h"
#include "common/io/bufferRead.h" #include "common/io/bufferRead.h"
#include "common/log.h" #include "common/log.h"
#include "common/macro.h" #include "common/macro.h"
@ -21,11 +20,6 @@ Command and Option Parse
#include "config/parse.h" #include "config/parse.h"
#include "version.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 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); 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; extern char **environ;
/*********************************************************************************************************************************** /***********************************************************************************************************************************
@ -490,6 +480,14 @@ cfgParseCommandRoleName(const ConfigCommand commandId, const ConfigCommandRole c
FUNCTION_TEST_RETURN(STRING, result); FUNCTION_TEST_RETURN(STRING, result);
} }
/**********************************************************************************************************************************/
FN_EXTERN const Ini *
cfgParseIni(void)
{
FUNCTION_TEST_VOID();
FUNCTION_TEST_RETURN(INI, configParseLocal.ini);
}
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
FN_EXTERN StringList * FN_EXTERN StringList *
cfgParseStanzaList(void) 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.beta = optionFound->beta;
result.multi = optionFound->multi;
FUNCTION_TEST_RETURN_TYPE(CfgParseOptionResult, result); FUNCTION_TEST_RETURN_TYPE(CfgParseOptionResult, result);
} }
@ -1699,7 +1698,7 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha
} }
// Add the argument // Add the argument
if (optionArg != NULL && parseRuleOption[option.id].multi) if (optionArg != NULL && option.multi)
{ {
strLstAdd(optionValue->valueList, optionArg); 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)); THROW_FMT(OptionInvalidValueError, "environment boolean option '%s' must be 'y' or 'n'", strZ(key));
} }
// Else split list/hash into separate values // Else split list/hash into separate values
else if (parseRuleOption[option.id].multi) else if (option.multi)
{ {
optionValue->valueList = strLstNewSplitZ(value, ":"); 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)) if (iniSectionKeyIsList(configParseLocal.ini, section, key))
{ {
// Error if the option cannot be specified multiple times // Error if the option cannot be specified multiple times
if (!parseRuleOption[option.id].multi) if (!option.multi)
{ {
THROW_FMT( THROW_FMT(
OptionInvalidError, "option '%s' cannot be set multiple times", OptionInvalidError, "option '%s' cannot be set multiple times",

View File

@ -4,9 +4,21 @@ Parse Configuration
#ifndef CONFIG_PARSE_H #ifndef CONFIG_PARSE_H
#define CONFIG_PARSE_H #define CONFIG_PARSE_H
#include "common/ini.h"
#include "config/config.h" #include "config/config.h"
#include "storage/storage.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 Option type enum
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
@ -62,6 +74,9 @@ FN_EXTERN String *cfgParseCommandRoleName(const ConfigCommand commandId, const C
// Convert command role enum to String // Convert command role enum to String
FN_EXTERN const String *cfgParseCommandRoleStr(ConfigCommandRole commandRole); 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 // Parse option name and return option info
typedef struct CfgParseOptionParam typedef struct CfgParseOptionParam
{ {
@ -79,6 +94,7 @@ typedef struct CfgParseOptionResult
bool reset; // Was the option reset? bool reset; // Was the option reset?
bool deprecated; // Is the option deprecated? bool deprecated; // Is the option deprecated?
bool beta; // Is the option in beta? bool beta; // Is the option in beta?
bool multi; // Can the option be specified multiple times?
} CfgParseOptionResult; } CfgParseOptionResult;
#define cfgParseOptionP(optionName, ...) \ #define cfgParseOptionP(optionName, ...) \
@ -107,6 +123,7 @@ FN_EXTERN ConfigOptionDataType cfgParseOptionDataType(ConfigOption optionId);
// Is the option required? // Is the option required?
FN_EXTERN bool cfgParseOptionRequired(ConfigCommand commandId, ConfigOption optionId); FN_EXTERN bool cfgParseOptionRequired(ConfigCommand commandId, ConfigOption optionId);
// Get list of stanzas in the configuration
FN_EXTERN StringList *cfgParseStanzaList(void); FN_EXTERN StringList *cfgParseStanzaList(void);
// Is the option valid for the command? // Is the option valid for the command?

View File

@ -132,6 +132,7 @@ src_pgbackrest = [
'command/backup/file.c', 'command/backup/file.c',
'command/check/check.c', 'command/check/check.c',
'command/check/common.c', 'command/check/common.c',
'command/check/report.c',
'command/exit.c', 'command/exit.c',
'command/expire/expire.c', 'command/expire/expire.c',
'command/help/help.c', 'command/help/help.c',

View File

@ -834,11 +834,12 @@ unit:
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: check - name: check
total: 3 total: 4
coverage: coverage:
- command/check/common - command/check/common
- command/check/check - command/check/check
- command/check/report
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: command - name: command

View File

@ -30,6 +30,182 @@ testRun(void)
StringList *argList = strLstNew(); 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()")) if (testBegin("cmdCheck()"))
{ {

View File

@ -1730,6 +1730,7 @@ testRun(void)
"P00 WARN: configuration file contains invalid option 'backup-standb'\n" "P00 WARN: configuration file contains invalid option 'backup-standb'\n"
"P00 WARN: configuration file contains stanza-only option 'pg1-path' in global section 'global'"); "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_STRLST_Z(cfgParseStanzaList(), "db\n", "stanza list");
TEST_RESULT_STR_Z(jsonFromVar(varNewVarLst(cfgCommandJobRetry())), "[0,33000,33000]", "custom job retries"); 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)"); TEST_RESULT_BOOL(cfgOptionIdxTest(cfgOptPgHost, 0), false, "pg1-host is not set (command line reset override)");