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:
parent
657c1a3e06
commit
9039d20b5b
@ -127,6 +127,19 @@
|
||||
<p>Reload GCS credentials before renewing authentication token.</p>
|
||||
</release-item>
|
||||
</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-doc-list>
|
||||
|
@ -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 \
|
||||
|
@ -361,6 +361,15 @@ option:
|
||||
command-role:
|
||||
main: {}
|
||||
|
||||
report:
|
||||
type: boolean
|
||||
internal: true
|
||||
default: false
|
||||
command:
|
||||
check: {}
|
||||
command-role:
|
||||
main: {}
|
||||
|
||||
stanza:
|
||||
type: string
|
||||
command:
|
||||
|
@ -2104,6 +2104,18 @@
|
||||
|
||||
<!-- Note, linking to the User Guide is limited since it can cause a cyclical reference -->
|
||||
</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 id="expire" name="Expire">
|
||||
|
@ -3,10 +3,14 @@ Check Command
|
||||
***********************************************************************************************************************************/
|
||||
#include "build.auto.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#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();
|
||||
|
274
src/command/check/report.c
Normal file
274
src/command/check/report.c
Normal 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);
|
||||
}
|
15
src/command/check/report.h
Normal file
15
src/command/check/report.h
Normal 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
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -10,7 +10,6 @@ Command and Option Parse
|
||||
#include <unistd.h>
|
||||
|
||||
#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",
|
||||
|
@ -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?
|
||||
|
@ -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',
|
||||
|
@ -834,11 +834,12 @@ unit:
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------------
|
||||
- name: check
|
||||
total: 3
|
||||
total: 4
|
||||
|
||||
coverage:
|
||||
- command/check/common
|
||||
- command/check/check
|
||||
- command/check/report
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------------
|
||||
- name: command
|
||||
|
@ -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()"))
|
||||
{
|
||||
|
@ -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)");
|
||||
|
Loading…
Reference in New Issue
Block a user