mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-01-18 04:58:51 +02:00
Allow any option to be set in an environment variable.
This includes options that previously could only be specified on the command line, e.g. stanza.
This commit is contained in:
parent
4a822d3032
commit
d0b9f986a0
@ -814,7 +814,11 @@
|
||||
|
||||
Non-boolean options configured in <file>pgbackrest.conf</file> can be reset to default on the command-line by using the <id>reset-</id> prefix. This feature may be used to restore a backup directly on a repository host. Normally, <backrest/> will error because it can see that the database host is remote and restores cannot be done remotely. By adding <br-option>--reset-pg1-host</br-option> on the command-line, <backrest/> will ignore the remote database host and restore locally. It may be necessary to pass a new <br-option>--pg-path</br-option> to force the restore to happen in a specific path, i.e. not the path used on the database host.
|
||||
|
||||
The <id>no-</id> prefix may be used to set a boolean option to false on the command-line.</text>
|
||||
The <id>no-</id> prefix may be used to set a boolean option to false on the command-line.
|
||||
|
||||
Any option may be set in an environment variable using the <id>PGBACKREST_</id> prefix and the option name in all caps replacing <id>-</id> with <id>_</id>, e.g. <br-option>pg1-path</br-option> becomes <id>PGBACKREST_PG1_PATH</id> and <br-option>stanza</br-option> becomes <id>PGBACKREST_STANZA</id>. Boolean options are represented as they would be in a configuration file, e.g. <id>PGBACKREST_COMPRESS="n"</id>, and <id>reset-*</id> variants are not allowed. Options that that can be specified multiple times on the command-line or in a config file can be represented by separating the values with colons, e.g. <id>PGBACKREST_DB_INCLUDE="db1:db2"</id>.
|
||||
|
||||
Command-line options override environment options which override config file options.</text>
|
||||
|
||||
<operation-general title="General Options">
|
||||
<option-list>
|
||||
|
@ -40,6 +40,10 @@
|
||||
|
||||
<p>Exclude temporary and unlogged relation (table/index) files from backup. Implemented using the same logic as the patches adding this feature to <postgres/>, <link url="https://git.postgresql.org/pg/commitdiff/8694cc96b52a967a49725f32be7aa77fd3b6ac25">8694cc96</link> and <link url="https://git.postgresql.org/pg/commitdiff/920a5e500a119b03356fb1fb64a677eb1aa5fc6f">920a5e50</link>. Temporary relation exclusion is enabled in <postgres/> &ge; <id>9.0</id>. Unlogged relation exclusion is enabled in <postgres/> &ge; <id>9.1</id>, where the feature was introduced.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<p>Allow any option to be set in an environment variable. This includes options that previously could only be specified on the command line, e.g. <br-option>stanza</br-option>.</p>
|
||||
</release-item>
|
||||
</release-feature-list>
|
||||
|
||||
<release-improvement-list>
|
||||
|
@ -673,6 +673,19 @@
|
||||
</list>
|
||||
|
||||
<p><b>Note:</b> <br-option>--config</br-option>, <br-option>--config-include-path</br-option> and <br-option>--config-path</br-option> are command-line only options.</p>
|
||||
|
||||
<p><backrest/> can also be configured using environment variables as described in the <link url="command.html">command reference</link>.</p>
|
||||
|
||||
<execute-list host="{[host-pg1]}">
|
||||
<title>Configure <br-option>log-path</br-option> using the environment</title>
|
||||
|
||||
<execute user="postgres" output="y" filter="n">
|
||||
<exe-cmd>bash -c '
|
||||
export PGBACKREST_LOG_PATH=/path/set/by/env &&
|
||||
{[project-exe]} --log-level-console=error help backup log-path'</exe-cmd>
|
||||
<exe-highlight>current\: \/path\/set\/by\/env</exe-highlight>
|
||||
</execute>
|
||||
</execute-list>
|
||||
</section>
|
||||
|
||||
<!-- SECTION => QUICKSTART - CREATE REPOSITORY -->
|
||||
|
@ -4,6 +4,7 @@ Command and Option Parse
|
||||
#include <getopt.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
@ -22,6 +23,15 @@ Standard config file name and old default path and name
|
||||
#define PGBACKREST_CONFIG_FILE PGBACKREST_BIN ".conf"
|
||||
#define PGBACKREST_CONFIG_ORIG_PATH_FILE "/etc/" PGBACKREST_CONFIG_FILE
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Prefix for environment variables
|
||||
***********************************************************************************************************************************/
|
||||
#define PGBACKREST_ENV "PGBACKREST_"
|
||||
#define PGBACKREST_ENV_SIZE (sizeof(PGBACKREST_ENV) - 1)
|
||||
|
||||
// In some environments this will not be extern'd
|
||||
extern char **environ;
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Standard config include path name
|
||||
***********************************************************************************************************************************/
|
||||
@ -516,15 +526,100 @@ configParse(unsigned int argListSize, const char *argList[], bool resetLogLevel)
|
||||
if (cfgCommand() != cfgCmdLocal && cfgCommand() != cfgCmdRemote && resetLogLevel)
|
||||
logInit(logLevelWarn, logLevelWarn, logLevelOff, false);
|
||||
|
||||
// Phase 2: parse config file unless --no-config passed
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Only continue if command options need to be validated, i.e. a real command is running or we are getting help for a
|
||||
// specific command and would like to display actual option values in the help.
|
||||
if (cfgCommand() != cfgCmdNone &&
|
||||
cfgCommand() != cfgCmdVersion &&
|
||||
cfgCommand() != cfgCmdHelp)
|
||||
{
|
||||
// Get the command definition id
|
||||
// Phase 2: parse environment variables
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
ConfigDefineCommand commandDefId = cfgCommandDefIdFromId(cfgCommand());
|
||||
|
||||
unsigned int environIdx = 0;
|
||||
|
||||
// Loop through all environment variables and look for our env vars by matching the prefix
|
||||
while (environ[environIdx] != NULL)
|
||||
{
|
||||
const char *keyValue = environ[environIdx];
|
||||
environIdx++;
|
||||
|
||||
if (strstr(keyValue, PGBACKREST_ENV) == keyValue)
|
||||
{
|
||||
// Find the first = char
|
||||
const char *equalPtr = strchr(keyValue, '=');
|
||||
ASSERT_DEBUG(equalPtr != NULL);
|
||||
|
||||
// Get key and value
|
||||
String *key = strReplaceChr(
|
||||
strLower(strNewN(keyValue + PGBACKREST_ENV_SIZE, (size_t)(equalPtr - (keyValue + PGBACKREST_ENV_SIZE)))),
|
||||
'_', '-');
|
||||
String *value = strNew(equalPtr + 1);
|
||||
|
||||
// Find the option
|
||||
unsigned int optionIdx = optionFind(key);
|
||||
|
||||
// Warn if the option not found
|
||||
if (optionList[optionIdx].name == NULL)
|
||||
{
|
||||
LOG_WARN("environment contains invalid option '%s'", strPtr(key));
|
||||
continue;
|
||||
}
|
||||
// Warn if negate option found in env
|
||||
else if (optionList[optionIdx].val & PARSE_NEGATE_FLAG)
|
||||
{
|
||||
LOG_WARN("environment contains invalid negate option '%s'", strPtr(key));
|
||||
continue;
|
||||
}
|
||||
// Warn if reset option found in env
|
||||
else if (optionList[optionIdx].val & PARSE_RESET_FLAG)
|
||||
{
|
||||
LOG_WARN("environment contains invalid reset option '%s'", strPtr(key));
|
||||
continue;
|
||||
}
|
||||
|
||||
ConfigOption optionId = optionList[optionIdx].val & PARSE_OPTION_MASK;
|
||||
ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId);
|
||||
|
||||
// Continue if the option is not valid for this command
|
||||
if (!cfgDefOptionValid(commandDefId, optionDefId))
|
||||
continue;
|
||||
|
||||
if (strSize(value) == 0)
|
||||
THROW_FMT(OptionInvalidValueError, "environment variable '%s' must have a value", strPtr(key));
|
||||
|
||||
// Continue if the option has already been specified on the command line
|
||||
if (parseOptionList[optionId].found)
|
||||
continue;
|
||||
|
||||
parseOptionList[optionId].found = true;
|
||||
parseOptionList[optionId].source = cfgSourceParam;
|
||||
|
||||
// Convert boolean to string
|
||||
if (cfgDefOptionType(optionDefId) == cfgDefOptTypeBoolean)
|
||||
{
|
||||
if (strEqZ(value, "n"))
|
||||
parseOptionList[optionId].negate = true;
|
||||
else if (!strEqZ(value, "y"))
|
||||
THROW_FMT(OptionInvalidValueError, "environment boolean option '%s' must be 'y' or 'n'", strPtr(key));
|
||||
}
|
||||
// Else split list/hash into separate values
|
||||
else if (cfgDefOptionType(optionDefId) == cfgDefOptTypeHash ||
|
||||
cfgDefOptionType(optionDefId) == cfgDefOptTypeList)
|
||||
{
|
||||
parseOptionList[optionId].valueList = strLstNewSplitZ(value, ":");
|
||||
}
|
||||
// Else add the string value
|
||||
else
|
||||
{
|
||||
parseOptionList[optionId].valueList = strLstNew();
|
||||
strLstAdd(parseOptionList[optionId].valueList, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: parse config file unless --no-config passed
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Load the configuration file(s)
|
||||
String *configString = cfgFileLoad(parseOptionList,
|
||||
strNew(cfgDefOptionDefault(commandDefId, cfgOptionDefIdFromId(cfgOptConfig))),
|
||||
@ -672,7 +767,7 @@ configParse(unsigned int argListSize, const char *argList[], bool resetLogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: validate option definitions and load into configuration
|
||||
// Phase 4: validate option definitions and load into configuration
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
for (unsigned int optionOrderIdx = 0; optionOrderIdx < CFG_OPTION_TOTAL; optionOrderIdx++)
|
||||
{
|
||||
|
@ -697,6 +697,7 @@ testRun(void)
|
||||
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList), false), "load remote config");
|
||||
TEST_RESULT_INT(logLevelStdOut, logLevelError, "console logging is error");
|
||||
TEST_RESULT_INT(logLevelStdErr, logLevelError, "stderr logging is error");
|
||||
harnessLogLevelReset();
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
@ -737,11 +738,12 @@ testRun(void)
|
||||
strLstAdd(argList, strNew("--pg1-path=/path/to/db"));
|
||||
strLstAdd(argList, strNew("--no-config"));
|
||||
strLstAdd(argList, strNew("--stanza=db"));
|
||||
strLstAdd(argList, strNew("--repo1-s3-host=xxx"));
|
||||
setenv("PGBACKREST_REPO1_S3_HOST", "xxx", true);
|
||||
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
|
||||
TEST_ERROR(
|
||||
configParse(strLstSize(argList), strLstPtr(argList), false), OptionInvalidError,
|
||||
"option 'repo1-s3-host' not valid without option 'repo1-type' = 's3'");
|
||||
unsetenv("PGBACKREST_REPO1_S3_HOST");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
@ -864,6 +866,32 @@ testRun(void)
|
||||
configParse(strLstSize(argList), strLstPtr(argList), false), OptionInvalidValueError,
|
||||
"'bogus' is not valid for 'protocol-timeout' option");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
||||
strLstAdd(argList, strNew("--pg1-path=/path/to/db"));
|
||||
strLstAdd(argList, strNew("--stanza=db"));
|
||||
setenv("PGBACKREST_PROTOCOL_TIMEOUT", "", true);
|
||||
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
|
||||
TEST_ERROR(
|
||||
configParse(strLstSize(argList), strLstPtr(argList), false), OptionInvalidValueError,
|
||||
"environment variable 'protocol-timeout' must have a value");
|
||||
|
||||
unsetenv("PGBACKREST_PROTOCOL_TIMEOUT");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
||||
strLstAdd(argList, strNew("--pg1-path=/path/to/db"));
|
||||
strLstAdd(argList, strNew("--stanza=db"));
|
||||
setenv("PGBACKREST_DELTA", "x", true);
|
||||
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
|
||||
TEST_ERROR(
|
||||
configParse(strLstSize(argList), strLstPtr(argList), false), OptionInvalidValueError,
|
||||
"environment boolean option 'delta' must be 'y' or 'n'");
|
||||
|
||||
unsetenv("PGBACKREST_DELTA");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
||||
@ -1026,6 +1054,15 @@ testRun(void)
|
||||
strLstAdd(argList, strNew("--reset-backup-standby"));
|
||||
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
|
||||
|
||||
setenv("PGBACKRESTXXX_NOTHING", "xxx", true);
|
||||
setenv("PGBACKREST_BOGUS", "xxx", true);
|
||||
setenv("PGBACKREST_NO_COMPRESS", "xxx", true);
|
||||
setenv("PGBACKREST_RESET_REPO1_HOST", "", true);
|
||||
setenv("PGBACKREST_TARGET", "xxx", true);
|
||||
setenv("PGBACKREST_ONLINE", "y", true);
|
||||
setenv("PGBACKREST_START_FAST", "n", true);
|
||||
setenv("PGBACKREST_PG1_SOCKET_PATH", "/path/to/socket", true);
|
||||
|
||||
storagePutNP(storageNewWriteNP(storageLocalWrite(), configFile), bufNewStr(strNew(
|
||||
"[global]\n"
|
||||
"compress-level=3\n"
|
||||
@ -1037,6 +1074,7 @@ testRun(void)
|
||||
"no-compress=y\n"
|
||||
"reset-compress=y\n"
|
||||
"archive-copy=y\n"
|
||||
"start-fast=y\n"
|
||||
"online=y\n"
|
||||
"pg1-path=/not/path/to/db\n"
|
||||
"backup-standby=y\n"
|
||||
@ -1056,6 +1094,9 @@ testRun(void)
|
||||
harnessLogResult(
|
||||
strPtr(
|
||||
strNew(
|
||||
"P00 WARN: environment contains invalid option 'bogus'\n"
|
||||
"P00 WARN: environment contains invalid negate option 'no-compress'\n"
|
||||
"P00 WARN: environment contains invalid reset option 'reset-repo1-host'\n"
|
||||
"P00 WARN: configuration file contains option 'recovery-option' invalid for section 'db:backup'\n"
|
||||
"P00 WARN: configuration file contains invalid option 'bogus'\n"
|
||||
"P00 WARN: configuration file contains negate option 'no-compress'\n"
|
||||
@ -1066,6 +1107,12 @@ testRun(void)
|
||||
TEST_RESULT_BOOL(cfgOptionTest(cfgOptPgHost), false, " pg1-host is not set (command line reset override)");
|
||||
TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptPgPath)), "/path/to/db", " pg1-path is set");
|
||||
TEST_RESULT_INT(cfgOptionSource(cfgOptPgPath), cfgSourceConfig, " pg1-path is source config");
|
||||
TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptPgSocketPath)), "/path/to/socket", " pg1-socket-path is set");
|
||||
TEST_RESULT_INT(cfgOptionSource(cfgOptPgSocketPath), cfgSourceParam, " pg1-socket-path is source param");
|
||||
TEST_RESULT_BOOL(cfgOptionBool(cfgOptOnline), false, " online not is set");
|
||||
TEST_RESULT_INT(cfgOptionSource(cfgOptOnline), cfgSourceParam, " online is source param");
|
||||
TEST_RESULT_BOOL(cfgOptionBool(cfgOptStartFast), false, " start-fast not is set");
|
||||
TEST_RESULT_INT(cfgOptionSource(cfgOptStartFast), cfgSourceParam, " start-fast is source param");
|
||||
TEST_RESULT_BOOL(cfgOptionBool(cfgOptCompress), false, " compress not is set");
|
||||
TEST_RESULT_INT(cfgOptionSource(cfgOptCompress), cfgSourceConfig, " compress is source config");
|
||||
TEST_RESULT_BOOL(cfgOptionTest(cfgOptArchiveCheck), false, " archive-check is not set");
|
||||
@ -1079,6 +1126,14 @@ testRun(void)
|
||||
TEST_RESULT_BOOL(cfgOptionInt64(cfgOptBufferSize), 65536, " buffer-size is set");
|
||||
TEST_RESULT_INT(cfgOptionSource(cfgOptBufferSize), cfgSourceConfig, " backup-standby is source config");
|
||||
|
||||
unsetenv("PGBACKREST_BOGUS");
|
||||
unsetenv("PGBACKREST_NO_COMPRESS");
|
||||
unsetenv("PGBACKREST_RESET_REPO1_HOST");
|
||||
unsetenv("PGBACKREST_TARGET");
|
||||
unsetenv("PGBACKREST_ONLINE");
|
||||
unsetenv("PGBACKREST_START_FAST");
|
||||
unsetenv("PGBACKREST_PG1_SOCKET_PATH");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
||||
@ -1176,6 +1231,29 @@ testRun(void)
|
||||
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("f"))))), "g", "check recovery option");
|
||||
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("hijk"))))), "l", "check recovery option");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
||||
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
|
||||
|
||||
setenv("PGBACKREST_STANZA", "db", true);
|
||||
setenv("PGBACKREST_PG1_PATH", "/path/to/db", true);
|
||||
setenv("PGBACKREST_RECOVERY_OPTION", "f=g:hijk=l", true);
|
||||
setenv("PGBACKREST_DB_INCLUDE", "77", true);
|
||||
|
||||
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList), false), TEST_COMMAND_RESTORE " command");
|
||||
|
||||
TEST_ASSIGN(recoveryKv, cfgOptionKv(cfgOptRecoveryOption), "get recovery options");
|
||||
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("f"))))), "g", "check recovery option");
|
||||
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("hijk"))))), "l", "check recovery option");
|
||||
TEST_RESULT_STR(strPtr(varStr(varLstGet(cfgOptionLst(cfgOptDbInclude), 0))), "77", "check db include option");
|
||||
TEST_RESULT_UINT(varLstSize(cfgOptionLst(cfgOptDbInclude)), 1, "check db include option size");
|
||||
|
||||
unsetenv("PGBACKREST_STANZA");
|
||||
unsetenv("PGBACKREST_PG1_PATH");
|
||||
unsetenv("PGBACKREST_RECOVERY_OPTION");
|
||||
unsetenv("PGBACKREST_DB_INCLUDE");
|
||||
|
||||
// Stanza options should not be loaded for commands that don't take a stanza
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
|
Loading…
Reference in New Issue
Block a user