1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-01-06 03:53:59 +02:00

Add beta feature infrastructure.

This allows options to be marked as beta, which will require that the --beta option be supplied to prevent accidental usage of a beta feature.

The online and command-line documentation also show warnings when options are beta.
This commit is contained in:
David Steele 2023-03-10 15:30:27 +07:00
parent 6b409d049e
commit 24f725212d
20 changed files with 257 additions and 31 deletions

View File

@ -19,6 +19,7 @@ use pgBackRestDoc::ProjectInfo;
####################################################################################################################################
# Help types
####################################################################################################################################
use constant CONFIG_HELP_BETA => 'beta';
use constant CONFIG_HELP_COMMAND => 'command';
push @EXPORT, qw(CONFIG_HELP_COMMAND);
use constant CONFIG_HELP_CURRENT => 'current';
@ -319,6 +320,7 @@ sub process
$$oCommandOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example');
$oCommandOption->{&CONFIG_HELP_INTERNAL} =
cfgDefineCommand()->{$strCommand}{&CFGDEF_INTERNAL} ? true : $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL};
$oCommandOption->{&CONFIG_HELP_BETA} = $oOptionDefine->{$strOption}{&CFGDEF_BETA};
# If internal is defined for the option/command it overrides everthing else
if (defined($oOptionDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_INTERNAL}))
@ -370,6 +372,7 @@ sub process
$$oOption{&CONFIG_HELP_DESCRIPTION} = $$oCommandOption{&CONFIG_HELP_DESCRIPTION};
$$oOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example');
$oOption->{&CONFIG_HELP_INTERNAL} = $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL};
$oOption->{&CONFIG_HELP_BETA} = $oOptionDefine->{$strOption}{&CFGDEF_BETA};
}
}
}
@ -819,6 +822,12 @@ sub helpOptionGet
$oOptionElement->
nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_SUMMARY});
# Add beta warning
if ($$oOptionHash{&CONFIG_HELP_BETA})
{
$oOptionElement->nodeAdd('p')->textSet({name => 'text', children => ['FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.']});
}
$oOptionElement->
nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_DESCRIPTION});
@ -862,32 +871,38 @@ sub helpOptionGet
}
# Get the example
my $strExample;
my $strExample = '';
my $strOptionPrefix = $rhConfigDefine->{$strOption}{&CFGDEF_GROUP};
my $strOptionIndex = defined($strOptionPrefix) ?
"${strOptionPrefix}1-" . substr($strOption, length($strOptionPrefix) + 1) : $strOption;
if (defined($strCommand))
if (defined($strCommand) && docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN))
{
if (docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN))
if ($$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'n' && $$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'y')
{
if ($$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'n' && $$oOptionHash{&CONFIG_HELP_EXAMPLE} ne 'y')
{
confess &log(ERROR, "option ${strOption} example should be boolean but value is: " .
$$oOptionHash{&CONFIG_HELP_EXAMPLE});
}
confess &log(ERROR, "option ${strOption} example should be boolean but value is: " .
$$oOptionHash{&CONFIG_HELP_EXAMPLE});
}
$strExample = '--' . ($$oOptionHash{&CONFIG_HELP_EXAMPLE} eq 'n' ? 'no-' : '') . $strOptionIndex;
}
else
{
$strExample = "--${strOptionIndex}=" . $$oOptionHash{&CONFIG_HELP_EXAMPLE};
}
$strExample = '--' . ($$oOptionHash{&CONFIG_HELP_EXAMPLE} eq 'n' ? 'no-' : '') . $strOptionIndex;
}
else
{
$strExample = "${strOptionIndex}=" . $$oOptionHash{&CONFIG_HELP_EXAMPLE};
foreach my $strLine (split('\\|', $$oOptionHash{&CONFIG_HELP_EXAMPLE}))
{
if ($strExample ne '')
{
$strExample .= ' ';
}
if (defined($strCommand))
{
$strExample .= '--';
}
$strExample .= "${strOptionIndex}=${strLine}";
}
}
$strCodeBlock .= (defined($strCodeBlock) ? "\n" : '') . "example: ${strExample}";

View File

@ -147,6 +147,8 @@ use constant CFGDEF_DEFAULT_LITERAL => 'default-
use constant CFGDEF_GROUP => 'group';
push @EXPORT, qw(CFGDEF_GROUP);
use constant CFGDEF_BETA => 'beta';
push @EXPORT, qw(CFGDEF_BETA);
use constant CFGDEF_INDEX => 'index';
push @EXPORT, qw(CFGDEF_INDEX);
use constant CFGDEF_INHERIT => 'inherit';

View File

@ -610,6 +610,13 @@ option:
command-role:
main: {}
beta:
section: global
type: boolean
default: false
internal: true
beta: true
buffer-size:
section: global
type: size

View File

@ -42,6 +42,7 @@ STRING_EXTERN(OPT_TYPE_TIME_STR, OPT_TYPE_TIM
/***********************************************************************************************************************************
Option constants
***********************************************************************************************************************************/
STRING_EXTERN(OPT_BETA_STR, OPT_BETA);
STRING_EXTERN(OPT_STANZA_STR, OPT_STANZA);
/***********************************************************************************************************************************
@ -297,6 +298,7 @@ typedef struct BldCfgOptionRaw
const String *type;
const String *section;
bool internal;
bool beta;
const Variant *required;
const Variant *negate;
bool reset;
@ -815,6 +817,10 @@ bldCfgParseOptionList(Yaml *const yaml, const List *const cmdList, const List *c
{
optRaw.internal = yamlBoolParse(optDefVal);
}
else if (strEqZ(optDef.value, "beta"))
{
optRaw.beta = yamlBoolParse(optDefVal);
}
else if (strEqZ(optDef.value, "negate"))
{
optRaw.negate = varNewBool(yamlBoolParse(optDefVal));
@ -904,6 +910,7 @@ bldCfgParseOptionList(Yaml *const yaml, const List *const cmdList, const List *c
.type = strDup(optRaw->type),
.section = strDup(optRaw->section),
.internal = optRaw->internal,
.beta = optRaw->beta,
.required = varBool(optRaw->required),
.negate = varBool(optRaw->negate),
.reset = optRaw->reset,

View File

@ -51,6 +51,8 @@ STRING_DECLARE(OPT_TYPE_TIME_STR);
/***********************************************************************************************************************************
Option constants
***********************************************************************************************************************************/
#define OPT_BETA "beta"
STRING_DECLARE(OPT_BETA_STR);
#define OPT_STANZA "stanza"
STRING_DECLARE(OPT_STANZA_STR);
@ -118,6 +120,7 @@ struct BldCfgOption
const String *type; // Option type, e.g. integer
const String *section; // Option section, i.e. stanza or global
bool internal; // Is the option internal?
bool beta; // Is the option beta?
bool required; // Is the option required?
bool negate; // Can the option be negated?
bool reset; // Can the option be reset?

View File

@ -711,6 +711,9 @@ bldCfgRenderParseAutoC(const Storage *const storageRepo, const BldCfg bldCfg, co
" PARSE_RULE_OPTION_TYPE(%s),\n",
strZ(opt->name), strZ(bldEnum("cfgOptType", opt->type)));
if (opt->beta)
strCatZ(configOpt, " PARSE_RULE_OPTION_BETA(true),\n");
if (opt->negate)
strCatZ(configOpt, " PARSE_RULE_OPTION_NEGATE(true),\n");

View File

@ -151,6 +151,16 @@
</text>
<config-key-list>
<config-key id="beta" name="Allow Beta Features">
<summary>Allow beta features.</summary>
<text>
<p>Allow beta features to be enabled but do not actually enable them. Beta features need to be separately enabled using related options.</p>
</text>
<example>2MiB</example>
</config-key>
<config-key id="buffer-size" name="Buffer Size">
<summary>Buffer size for I/O operations.</summary>

View File

@ -97,11 +97,14 @@ helpRenderSplitSize(const String *string, const char *delimiter, size_t size)
Helper function for helpRender() to make output look good on a console
***********************************************************************************************************************************/
static String *
helpRenderText(const String *const text, const bool internal, const size_t indent, const bool indentFirst, const size_t length)
helpRenderText(
const String *const text, const bool internal, const bool beta, const size_t indent, const bool indentFirst,
const size_t length)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, text);
FUNCTION_LOG_PARAM(BOOL, internal);
FUNCTION_LOG_PARAM(BOOL, beta);
FUNCTION_LOG_PARAM(SIZE, indent);
FUNCTION_LOG_PARAM(BOOL, indentFirst);
FUNCTION_LOG_PARAM(SIZE, length);
@ -116,7 +119,12 @@ helpRenderText(const String *const text, const bool internal, const size_t inden
{
// Split the text into paragraphs
const StringList *const lineList = strLstNewSplitZ(
strNewFmt("%s%s", strZ(text), internal ? "\n\nFOR INTERNAL USE ONLY. DO NOT USE IN PRODUCTION." : ""), "\n");
strNewFmt(
"%s%s", strZ(text),
beta ?
"\n\nFOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION." :
(internal ? "\n\nFOR INTERNAL USE ONLY. DO NOT USE IN PRODUCTION." : "")),
"\n");
// Iterate through each paragraph and split the lines according to the line length
for (unsigned int lineIdx = 0; lineIdx < strLstSize(lineList); lineIdx++)
@ -313,7 +321,7 @@ helpRender(const Buffer *const helpData)
strCatFmt(
result, " %s%*s%s\n", cfgParseCommandName(commandId),
(int)(commandSizeMax - strlen(cfgParseCommandName(commandId)) + 2), "",
strZ(helpRenderText(commandData[commandId].summary, false, commandSizeMax + 6, false, CONSOLE_WIDTH)));
strZ(helpRenderText(commandData[commandId].summary, false, false, commandSizeMax + 6, false, CONSOLE_WIDTH)));
}
// Construct message for more help
@ -405,10 +413,10 @@ helpRender(const Buffer *const helpData)
"%s\n"
"\n"
"%s\n",
strZ(helpRenderText(commandData[commandId].summary, false, 0, true, CONSOLE_WIDTH)),
strZ(helpRenderText(commandData[commandId].summary, false, false, 0, true, CONSOLE_WIDTH)),
strZ(
helpRenderText(
commandData[commandId].description, commandData[commandId].internal, 0, true, CONSOLE_WIDTH)));
commandData[commandId].description, commandData[commandId].internal, false, 0, true, CONSOLE_WIDTH)));
// Construct key/value of sections and options
KeyValue *optionKv = kvNew();
@ -484,7 +492,7 @@ helpRender(const Buffer *const helpData)
strCatFmt(
result, " --%s%*s%s\n",
cfgParseOptionName(optionId), (int)(optionSizeMax - strlen(cfgParseOptionName(optionId)) + 2), "",
strZ(helpRenderText(summary, false, optionSizeMax + 6, false, CONSOLE_WIDTH)));
strZ(helpRenderText(summary, false, false, optionSizeMax + 6, false, CONSOLE_WIDTH)));
}
}
@ -507,7 +515,7 @@ helpRender(const Buffer *const helpData)
if (!option.found || !cfgParseOptionValid(cfgCommand(), cfgCmdRoleMain, option.id))
THROW_FMT(OptionInvalidError, "option '%s' is not valid for command '%s'", strZ(optionName), commandName);
// Output option summary and description. Add a warning for internal options.
// Output option summary and description. Add a warning for internal and beta options.
CHECK(
AssertError, optionData[option.id].summary != NULL && optionData[option.id].description != NULL,
"option help missing");
@ -520,9 +528,11 @@ helpRender(const Buffer *const helpData)
"\n"
"%s\n",
cfgParseOptionName(option.id),
strZ(helpRenderText(optionData[option.id].summary, false, 0, true, CONSOLE_WIDTH)),
strZ(helpRenderText(optionData[option.id].summary, false, false, 0, true, CONSOLE_WIDTH)),
strZ(
helpRenderText(optionData[option.id].description, optionData[option.id].internal, 0, true, CONSOLE_WIDTH)));
helpRenderText(
optionData[option.id].description, optionData[option.id].internal, option.beta, 0, true,
CONSOLE_WIDTH)));
// Output current and default values if they exist
const String *defaultValue = cfgOptionDefault(option.id);

View File

@ -55,6 +55,7 @@ Option constants
#define CFGOPT_ARCHIVE_PUSH_QUEUE_MAX "archive-push-queue-max"
#define CFGOPT_ARCHIVE_TIMEOUT "archive-timeout"
#define CFGOPT_BACKUP_STANDBY "backup-standby"
#define CFGOPT_BETA "beta"
#define CFGOPT_BUFFER_SIZE "buffer-size"
#define CFGOPT_CHECKSUM_PAGE "checksum-page"
#define CFGOPT_CIPHER_PASS "cipher-pass"
@ -131,7 +132,7 @@ Option constants
#define CFGOPT_TYPE "type"
#define CFGOPT_VERBOSE "verbose"
#define CFG_OPTION_TOTAL 164
#define CFG_OPTION_TOTAL 165
/***********************************************************************************************************************************
Option value constants
@ -372,6 +373,7 @@ typedef enum
cfgOptArchivePushQueueMax,
cfgOptArchiveTimeout,
cfgOptBackupStandby,
cfgOptBeta,
cfgOptBufferSize,
cfgOptChecksumPage,
cfgOptCipherPass,

View File

@ -1112,6 +1112,88 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
), // opt/backup-standby
), // opt/backup-standby
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/beta
( // opt/beta
PARSE_RULE_OPTION_NAME("beta"), // opt/beta
PARSE_RULE_OPTION_TYPE(cfgOptTypeBoolean), // opt/beta
PARSE_RULE_OPTION_BETA(true), // opt/beta
PARSE_RULE_OPTION_NEGATE(true), // opt/beta
PARSE_RULE_OPTION_RESET(true), // opt/beta
PARSE_RULE_OPTION_REQUIRED(true), // opt/beta
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/beta
// opt/beta
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/beta
( // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdServer) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdServerPing) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStart) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStop) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/beta
), // opt/beta
// opt/beta
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/beta
( // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/beta
), // opt/beta
// opt/beta
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/beta
( // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/beta
), // opt/beta
// opt/beta
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/beta
( // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/beta
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/beta
), // opt/beta
// opt/beta
PARSE_RULE_OPTIONAL // opt/beta
( // opt/beta
PARSE_RULE_OPTIONAL_GROUP // opt/beta
( // opt/beta
PARSE_RULE_OPTIONAL_DEFAULT // opt/beta
( // opt/beta
PARSE_RULE_VAL_BOOL_FALSE, // opt/beta
), // opt/beta
), // opt/beta
), // opt/beta
), // opt/beta
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/buffer-size
( // opt/buffer-size
PARSE_RULE_OPTION_NAME("buffer-size"), // opt/buffer-size
@ -9575,6 +9657,7 @@ static const uint8_t optionResolveOrder[] =
cfgOptArchivePushQueueMax, // opt-resolve-order
cfgOptArchiveTimeout, // opt-resolve-order
cfgOptBackupStandby, // opt-resolve-order
cfgOptBeta, // opt-resolve-order
cfgOptBufferSize, // opt-resolve-order
cfgOptChecksumPage, // opt-resolve-order
cfgOptCipherPass, // opt-resolve-order

View File

@ -125,6 +125,7 @@ typedef struct ParseRuleOption
{
const char *name; // Name
unsigned int type : 4; // e.g. string, int, boolean
bool beta : 1; // Is the option a beta feature?
bool negate : 1; // Can the option be negated on the command line?
bool reset : 1; // Can the option be reset on the command line?
bool required : 1; // Is the option required?
@ -168,6 +169,9 @@ typedef enum
#define PARSE_RULE_OPTION_TYPE(typeParam) \
.type = typeParam
#define PARSE_RULE_OPTION_BETA(betaParam) \
.beta = betaParam
#define PARSE_RULE_OPTION_NEGATE(negateParam) \
.negate = negateParam
@ -343,6 +347,32 @@ parseOptionIdxValue(ParseOption *optionList, unsigned int optionId, unsigned int
FUNCTION_TEST_RETURN_TYPE_P(ParseOptionValue, &optionList[optionId].indexList[optionKeyIdx]);
}
/***********************************************************************************************************************************
Check that --beta is set if a beta option is used
***********************************************************************************************************************************/
static void
parseOptionBeta(
const unsigned int optionId, const unsigned int optionKeyIdx, const bool beta, const ParseOption *const parseOptionBeta)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, optionId);
FUNCTION_TEST_PARAM(UINT, optionKeyIdx);
FUNCTION_TEST_PARAM(BOOL, beta);
FUNCTION_TEST_PARAM_P(PARSE_OPTION, parseOptionBeta);
FUNCTION_TEST_END();
if (beta && optionId != cfgOptBeta && (parseOptionBeta->indexListTotal == 0 || parseOptionBeta->indexList[0].negate))
{
THROW_FMT(
OptionInvalidError,
"option '%s' is not valid without option '" CFGOPT_BETA "'\n"
"HINT: beta features require the --" CFGOPT_BETA " option to prevent accidental usage.",
cfgParseOptionKeyIdxName(optionId, optionKeyIdx));
}
FUNCTION_TEST_RETURN_VOID();
}
/***********************************************************************************************************************************
Get command id by name
***********************************************************************************************************************************/
@ -681,6 +711,9 @@ cfgParseOption(const String *const optionCandidate, const CfgParseOptionParam pa
}
}
// Set the beta flag
result.beta = optionFound->beta;
FUNCTION_TEST_RETURN_TYPE(CfgParseOptionResult, result);
}
@ -2158,6 +2191,9 @@ configParse(const Storage *storage, unsigned int argListSize, const char *argLis
configOptionValue->set = true;
configOptionValue->source = parseOptionValue->source;
// Check beta status
parseOptionBeta(optionId, optionKeyIdx, parseRuleOption[optionId].beta, &parseOptionList[cfgOptBeta]);
if (optionType == cfgOptTypeBoolean)
{
configOptionValue->value.boolean = !parseOptionValue->negate;

View File

@ -67,6 +67,7 @@ typedef struct CfgParseOptionResult
bool negate; // Was the option negated?
bool reset; // Was the option reset?
bool deprecated; // Is the option deprecated?
bool beta; // Is the option in beta?
} CfgParseOptionResult;
#define cfgParseOptionP(optionName, ...) \

View File

@ -1090,6 +1090,7 @@ sub configCreate
# General options
# ------------------------------------------------------------------------------------------------------------------------------
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'beta'} = 'y';
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'job-retry'} = 0;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'log-level-console'} = lc(DETAIL);

View File

@ -152,6 +152,7 @@ option:
# Options that are not used but must be present for modules to compile. All must have a default or not be required.
#---------------------------------------------------------------------------------------------------------------------------------
beta: {type: boolean, default: false, command: {noop: {}}}
compress-level-network: {type: string, required: false, command: {noop: {}}}
config: {
type: string, internal: true, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_FILE, default-literal: true, negate: true}

View File

@ -6,6 +6,7 @@
<operation>
<operation-general>
<option-list>
<option id="beta"><summary></summary><text><p></p></text></option>
<option id="config"><summary></summary><text><p></p></text></option>
<option id="config-path"><summary></summary><text><p></p></text></option>
<option id="config-include-path"><summary></summary><text><p></p></text></option>

View File

@ -42,6 +42,10 @@ hrnCfgLoad(ConfigCommand commandId, const StringList *argListParam, const HrnCfg
// Add standard options needed in most cases
if (!param.noStd)
{
// Enable beta features
if (cfgParseOptionValid(commandId, param.role, cfgOptBeta))
strLstInsert(argList, 0, STRDEF("--" CFGOPT_BETA));
// Set job retry to 0 if it is valid
if (cfgParseOptionValid(commandId, param.role, cfgOptJobRetry))
strLstInsert(argList, 0, strNewFmt("--" CFGOPT_JOB_RETRY "=%u", param.jobRetry));

View File

@ -401,6 +401,7 @@ testRun(void)
" backup-standby:\n"
" section: global\n"
" type: boolean\n"
" beta: true\n"
" default: false\n"
" depend:\n"
" option: online\n"
@ -759,6 +760,7 @@ testRun(void)
" (\n"
" PARSE_RULE_OPTION_NAME(\"backup-standby\"),\n"
" PARSE_RULE_OPTION_TYPE(cfgOptTypeBoolean),\n"
" PARSE_RULE_OPTION_BETA(true),\n"
" PARSE_RULE_OPTION_NEGATE(true),\n"
" PARSE_RULE_OPTION_RESET(true),\n"
" PARSE_RULE_OPTION_REQUIRED(true),\n"

View File

@ -98,17 +98,24 @@ testRun(void)
if (testBegin("helpRenderText()"))
{
TEST_RESULT_STR_Z(
helpRenderText(STRDEF("this is a short sentence"), false, 0, false, 80), "this is a short sentence", "one line");
helpRenderText(STRDEF("this is a short sentence"), false, false, 0, false, 80), "this is a short sentence", "one line");
TEST_RESULT_STR_Z(
helpRenderText(STRDEF("this is a short sentence"), false, 4, false, 14),
helpRenderText(STRDEF("this is a short sentence"), false, false, 4, false, 14),
"this is a\n"
" short\n"
" sentence",
"three lines, no indent first");
TEST_RESULT_STR_Z(
helpRenderText(STRDEF("This is a short paragraph.\n\nHere is another one."), true, 2, true, 16),
helpRenderText(STRDEF("this is a short sentence"), true, true, 4, true, 132),
" this is a short sentence\n"
"\n"
" FOR BETA TESTING ONLY. DO NOT USE IN PRODUCTION.",
"beta feature");
TEST_RESULT_STR_Z(
helpRenderText(STRDEF("This is a short paragraph.\n\nHere is another one."), true, false, 2, true, 16),
" This is a\n"
" short\n"
" paragraph.\n"

View File

@ -1634,7 +1634,7 @@ testRun(void)
RECOVERY_SETTING_HEADER
"a_setting = 'a'\n"
"b_setting = 'b'\n"
"restore_command = '" TEST_PROJECT_EXE " --lock-path=" HRN_PATH "/lock --log-path=" HRN_PATH " --pg1-path=/pg"
"restore_command = '" TEST_PROJECT_EXE " --beta --lock-path=" HRN_PATH "/lock --log-path=" HRN_PATH " --pg1-path=/pg"
" --repo1-path=/repo --stanza=test1 archive-get %f \"%p\"'\n",
"check recovery options");
@ -1648,8 +1648,8 @@ testRun(void)
TEST_RESULT_STR_Z(
restoreRecoveryConf(PG_VERSION_94, restoreLabel),
RECOVERY_SETTING_HEADER
"restore_command = '/usr/local/bin/pg_wrapper.sh --lock-path=" HRN_PATH "/lock --log-path=" HRN_PATH " --pg1-path=/pg"
" --repo1-path=/repo --stanza=test1 archive-get %f \"%p\"'\n",
"restore_command = '/usr/local/bin/pg_wrapper.sh --beta --lock-path=" HRN_PATH "/lock --log-path=" HRN_PATH
" --pg1-path=/pg --repo1-path=/repo --stanza=test1 archive-get %f \"%p\"'\n",
"restore_command invokes /usr/local/bin/pg_wrapper.sh per --cmd option");
// -------------------------------------------------------------------------------------------------------------------------

View File

@ -1163,6 +1163,37 @@ testRun(void)
"HINT: this option could expose secrets in the process list.\n"
"HINT: specify the option in a configuration file or an environment variable instead.");
// These tests cheat a bit but there may not always be a beta option available for more general testing
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("beta option dependency");
// Need to parse a command so there will be a valid index for the error message
argList = strLstNew();
strLstAddZ(argList, TEST_BACKREST_EXE);
hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/db");
hrnCfgArgRawZ(argList, cfgOptStanza, "db");
strLstAddZ(argList, TEST_COMMAND_BACKUP);
ParseOption betaOption = {0};
TEST_ERROR(
parseOptionBeta(cfgOptRepoPath, 0, true, &betaOption), OptionInvalidError,
"option 'repo1-path' is not valid without option 'beta'\n"
"HINT: beta features require the --beta option to prevent accidental usage.");
betaOption.indexListTotal = 1;
ParseOptionValue betaOptionValue = {.negate = true};
betaOption.indexList = &betaOptionValue;
TEST_ERROR(
parseOptionBeta(cfgOptRepoPath, 0, true, &betaOption), OptionInvalidError,
"option 'repo1-path' is not valid without option 'beta'\n"
"HINT: beta features require the --beta option to prevent accidental usage.");
betaOption.indexList[0].negate = false;
TEST_RESULT_VOID(parseOptionBeta(cfgOptRepoPath, 0, true, &betaOption), "beta option set");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("dependent option missing");