pax_global_header 0000666 0000000 0000000 00000000064 13625315071 0014515 g ustar 00root root 0000000 0000000 52 comment=495dec44f0f512478ac942091ca2a29c60ad4911
pgbackrest-release-2.24/ 0000775 0000000 0000000 00000000000 13625315071 0015207 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/.github/ 0000775 0000000 0000000 00000000000 13625315071 0016547 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/.github/ISSUE_TEMPLATE.md 0000664 0000000 0000000 00000001530 13625315071 0021253 0 ustar 00root root 0000000 0000000 Please provide the following information when submitting an issue (feature requests or general comments can skip this):
1. pgBackRest version:
2. PostgreSQL version:
3. Operating system/version - if you have more than one server (for example, a database server, a repository host server, one or more standbys), please specify each:
4. Did you install pgBackRest from source or from a package?
5. Please attach the following as applicable:
- `pgbackrest.conf` file(s)
- `postgresql.conf` settings applicable to pgBackRest (`archive_command`, `archive_mode`, `listen_addresses`, `max_wal_senders`, `wal_level`, `port`)
- errors in the postgresql log file before or during the time you experienced the issue
- log file in `/var/log/pgbackrest` for the commands run (e.g. `/var/log/pgbackrest/mystanza_backup.log`)
7. Describe the issue:
pgbackrest-release-2.24/.github/lock.yml 0000664 0000000 0000000 00000001747 13625315071 0020233 0 ustar 00root root 0000000 0000000 # Configuration for Lock Threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 90
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: false
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings just for `issues` or `pulls`
# issues:
# exemptLabels:
# - help-wanted
# lockLabel: outdated
# pulls:
# daysUntilLock: 30
# Repository to extend settings from
# _extends: repo
pgbackrest-release-2.24/.gitignore 0000664 0000000 0000000 00000000031 13625315071 0017171 0 ustar 00root root 0000000 0000000 **/*~
*~
*.swp
.DS_Store
pgbackrest-release-2.24/.travis.yml 0000664 0000000 0000000 00000001442 13625315071 0017321 0 ustar 00root root 0000000 0000000 branches:
only:
- integration
- /-ci$/
dist: trusty
sudo: required
language: c
services:
- docker
matrix:
include:
- env: PGB_CI="test --vm=u12"
- env: PGB_CI="test --vm=f30 --param=no-package --param=c-only"
- env: PGB_CI="test --vm=co6 --param=module=mock --param=module=real"
- env: PGB_CI="test --vm=u18 --param=container-only"
- env: PGB_CI=" doc --vm=u18"
- env: PGB_CI="test --vm=co7 --param=module=mock --param=module=real"
- dist: bionic
env:
- PGB_CI="test --vm=none --param=tz=America/New_York"
services:
- env: PGB_CI=" doc --vm=co6"
- env: PGB_CI=" doc --vm=co7"
install:
- umask 0022 && cd ~ && pwd && whoami && umask && groups
- df -Th && top -bn1
script:
- ${TRAVIS_BUILD_DIR?}/test/travis.pl ${PGB_CI?}
pgbackrest-release-2.24/CODING.md 0000664 0000000 0000000 00000020473 13625315071 0016502 0 ustar 00root root 0000000 0000000 # pgBackRest Coding Standards
## Standards
### Indentation
Indentation is four spaces -- no tabs. Only file types that absolutely require tabs (e.g. `Makefile`) may use them.
### Line Length
With the exception of documentation code, no line of any code or test file shall exceed 132 characters. If a line break is required, then it shall be after the first function parenthesis:
```
// CORRECT - location of line break after first function parenthesis if line length is greater than 132
StringList *removeList = infoBackupDataLabelList(
infoBackup, strNewFmt("^%s.*", strPtr(strLstGet(currentBackupList, fullIdx))));
// INCORRECT
StringList *removeList = infoBackupDataLabelList(infoBackup, strNewFmt("^%s.*", strPtr(strLstGet(currentBackupList,
fullIdx))));
```
If a conditional, then after a completed conditional, for example:
```
// CORRECT - location of line break after a completed conditional if line length is greater than 132
if (archiveInfoPgHistory.id != backupInfoPgHistory.id ||
archiveInfoPgHistory.systemId != backupInfoPgHistory.systemId ||
archiveInfoPgHistory.version != backupInfoPgHistory.version)
// INCORRECT
if (archiveInfoPgHistory.id != backupInfoPgHistory.id || archiveInfoPgHistory.systemId !=
backupInfoPgHistory.systemId || archiveInfoPgHistory.version != backupInfoPgHistory.version)
```
### Inline Comment
Inline comments shall start at character 69 and must not exceed the line length of 132. For example:
```
typedef struct InlineCommentExample
{
const String *comment; // Inline comment example
const String *longComment; // Inline comment example that exceeds 132 characters should
// then go to next line but this should be avoided
} InlineCommentExample;
```
### Naming
#### Variables
Variable names use camel case with the first letter lower-case.
- `stanzaName` - the name of the stanza
- `nameIdx` - loop variable for iterating through a list of names
Variable names should be descriptive. Avoid `i`, `j`, etc.
#### Types
Type names use camel case with the first letter upper case:
`typedef struct MemContext <...>`
`typedef enum {<...>} ErrorState;`
#### Constants
**#define Constants**
`#define` constants should be all caps with `_` separators.
```c
#define MY_CONSTANT "STRING"
```
The value should be aligned at column 69 whenever possible.
This type of constant should mostly be used for strings. Use enums whenever possible for integer constants.
**String Constants**
String constants can be declared using the `STRING_STATIC()` macro for local strings and `STRING_EXTERN()` for strings that will be extern'd for use in other modules.
Extern'd strings should be declared in the header file as:
```c
#define SAMPLE_VALUE "STRING"
STRING_DECLARE(SAMPLE_VALUE_STR);
```
And in the C file as:
```c
STRING_EXTERN(SAMPLE_VALUE_STR, SAMPLE_VALUE);
```
Static strings declared in the C file are not required to have a `#define` if the `#define` version is not used. Extern'd strings must always have the `#define` in the header file.
**Enum Constants**
Enum elements follow the same case rules as variables. They are strongly typed so this shouldn't present any confusion.
```c
typedef enum
{
cipherModeEncrypt,
cipherModeDecrypt,
} CipherMode;
```
Note the comma after the last element. This reduces diff churn when new elements are added.
#### Macros
Macro names should be upper-case with underscores between words. Macros (except simple constants) should be avoided whenever possible as they make code less clear and test coverage harder to measure.
Macros should follow the format:
```c
#define MACRO(paramName1, paramName2) \
```
If the macro defines a block it should look like:
```c
#define MACRO_2(paramName1, paramName2) \
{ \
\
}
```
Continuation characters should be aligned at column 132 (unlike the examples above that have been shortened for display purposes).
To avoid conflicts, variables in a macro will be named `[macro name]_[var name]`, e.g. `TEST_RESULT_resultExpected`. Variables that need to be accessed in wrapped code should be provided accessor macros.
[Variadic functions](#variadic-functions) are an exception to the capitalization rule.
#### Begin / End
Use `Begin` / `End` for names rather than `Start` / `Finish`, etc.
#### New / Free
Use `New` / `Free` for constructors and destructors rather than `Create` / `Destroy`, etc.
### Formatting
#### Braces
C allows braces to be excluded for a single statement. However, braces should be used when the control statement (if, while, etc.) spans more than one line or the statement to be executed spans more than one line.
No braces needed:
```c
if (condition)
return value;
```
Braces needed:
```c
if (conditionThatUsesEntireLine1 &&
conditionThatUsesEntireLine2)
{
return value;
}
```
```c
if (condition)
{
return
valueThatUsesEntireLine1 &&
valueThatUsesEntireLine2;
}
```
#### Hints, Warnings, and Errors
Hints are to be formatted with capitalized `HINT:` followed by a space and a sentence. The sentence shall only begin with a capital letter if the first word is an acronym (e.g. TLS) or a proper name (e.g. PostgreSQL). The sentence must end with a period, question mark or exclamation point as appropriate.
Warning and errors shall be lowercase with the exceptions for proper names and acronyms and end without punctuation.
## Language Elements
### Data Types
Don't get exotic - use the simplest type that will work.
Use `int` or `unsigned int` for general cases. `int` will be at least 32 bits. When not using `int` use one of the types defined in `common/type.h`.
### Macros
Don't use a macro when a function could be used instead. Macros make it hard to measure code coverage.
### Objects
Object-oriented programming is used extensively. The object pointer is always referred to as `this`.
### Variadic Functions
Variadic functions can take a variable number of parameters. While the `printf()` pattern is variadic, it is not very flexible in terms of optional parameters given in any order.
This project implements variadic functions using macros (which are exempt from the normal macro rule of being all caps). A typical variadic function definition:
```c
typedef struct StoragePathCreateParam
{
bool errorOnExists;
bool noParentCreate;
mode_t mode;
} StoragePathCreateParam;
#define storagePathCreateP(this, pathExp, ...) \
storagePathCreate(this, pathExp, (StoragePathCreateParam){__VA_ARGS__})
#define storagePathCreateP(this, pathExp) \
storagePathCreate(this, pathExp, (StoragePathCreateParam){0})
void storagePathCreate(const Storage *this, const String *pathExp, StoragePathCreateParam param);
```
Continuation characters should be aligned at column 132 (unlike the example above that has been shortened for display purposes).
This function can be called without variable parameters:
```c
storagePathCreateP(storageLocal(), "/tmp/pgbackrest");
```
Or with variable parameters:
```c
storagePathCreateP(storageLocal(), "/tmp/pgbackrest", .errorOnExists = true, .mode = 0777);
```
If the majority of functions in a module or object are variadic it is best to provide macros for all functions even if they do not have variable parameters. Do not use the base function when variadic macros exist.
## Testing
### Uncoverable/Uncovered Code
#### Uncoverable Code
The `uncoverable` keyword marks code that can never be covered. For instance, a function that never returns because it always throws an error. Uncoverable code should be rare to non-existent outside the common libraries and test code.
```c
} // {uncoverable - function throws error so never returns}
```
Subsequent code that is uncoverable for the same reason is marked with `// {+uncoverable}`.
#### Uncovered Code
Marks code that is not tested for one reason or another. This should be kept to a minimum and an excuse given for each instance.
```c
exit(EXIT_FAILURE); // {uncovered - test harness does not support non-zero exit}
```
Subsequent code that is uncovered for the same reason is marked with `// {+uncovered}`.
pgbackrest-release-2.24/CONTRIBUTING.md 0000664 0000000 0000000 00000047347 13625315071 0017457 0 ustar 00root root 0000000 0000000 # pgBackRest Contributing to pgBackRest
## Introduction
This documentation is intended to assist contributors to pgBackRest by outlining some basic steps and guidelines for contributing to the project. Coding standards to follow are defined in [CODING.md](https://github.com/pgbackrest/pgbackrest/blob/master/CODING.md). At a minimum, unit tests must be written and run and the documentation generated before submitting a Pull Request; see the [Testing](#testing) section below for details.
## Building a Development Environment
This example is based on Ubuntu 19.04, but it should work on many versions of Debian and Ubuntu.
pgbackrest-dev => Install development tools
```
sudo apt-get install rsync git devscripts build-essential valgrind autoconf \
libssl-dev zlib1g-dev libxml2-dev libpq-dev libxml-checker-perl \
libyaml-libyaml-perl libdbd-pg-perl
```
Coverage testing is an important component of pgBackRest testing and is integrated directly into the test harness. Unfortunately, the default version of lcov is often not compatible with gcc. lcov 1.14 works up to gcc 8.
pgbackrest-dev => Build lcov 2.14
```
curl -fsSL \
https://github.com/linux-test-project/lcov/releases/download/v1.14/lcov-1.14.tar.gz | \
tar zx
sudo make -C lcov-1.14 install
```
Some unit tests and all the integration test require Docker. Running in containers allows us to simulate multiple hosts, test on different distributions and versions of PostgreSQL, and use sudo without affecting the host system.
pgbackrest-dev => Install Docker
```
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker `whoami`
```
This clone of the pgBackRest repository is sufficient for experimentation. For development, create a fork and clone that instead.
pgbackrest-dev => Clone pgBackRest repository
```
git clone https://github.com/pgbackrest/pgbackrest.git
```
## Running Tests
### Without Docker
If Docker is not installed, then the available tests can be listed using `--vm-none`.
pgbackrest-dev => List tests that don't require a container
```
pgbackrest/test/test.pl --vm=none --dry-run
--- output ---
[filtered 2 lines of output]
2019-10-30 10:16:44.828 P00 INFO: check version info
2019-10-30 10:16:45.318 P00 INFO: builds required: bin
--> 2019-10-30 10:16:45.319 P00 INFO: 64 tests selected
2019-10-30 10:16:45.319 P00 INFO: P1-T01/64 - vm=none, module=common, test=error
[filtered 61 lines of output]
2019-10-30 10:16:45.334 P00 INFO: P1-T63/64 - vm=none, module=command, test=storage
2019-10-30 10:16:45.334 P00 INFO: P1-T64/64 - vm=none, module=performance, test=type
--> 2019-10-30 10:16:45.389 P00 INFO: DRY RUN COMPLETED SUCCESSFULLY (1s)
```
Once a test has been selected it can be run by specifying the module and test. The `--dev` option sets several flags that are appropriate for development rather than test. Most importantly, it reuses object files from the previous test run to speed testing. The `--vm-out` option displays the test output.
pgbackrest-dev => Run a test
```
pgbackrest/test/test.pl --vm=none --dev --vm-out --module=common --test=wait
--- output ---
2019-10-30 10:16:46.166 P00 INFO: test begin - log level info
2019-10-30 10:16:46.743 P00 INFO: check code autogenerate
2019-10-30 10:16:46.743 P00 INFO: cleanup old data
2019-10-30 10:16:47.278 P00 INFO: builds required: none
2019-10-30 10:16:47.282 P00 INFO: 1 test selected
2019-10-30 10:16:47.283 P00 INFO: P1-T1/1 - vm=none, module=common, test=wait
run 001 - waitNew(), waitMore, and waitFree()
000.004s l0018 - expect AssertError: assertion 'waitTime >= 100 && waitTime <= 999999000' failed
000.020s l0019 - expect AssertError: assertion 'waitTime >= 100 && waitTime <= 999999000' failed
000.024s l0024 - new wait = 0.2 sec
000.032s l0025 - check wait time
000.046s l0026 - check sleep time
000.049s l0027 - check sleep prev time
000.053s l0028 - check begin time
000.259s l0034 - lower range check
000.262s l0035 - upper range check
000.265s l0037 - free wait
000.270s l0042 - new wait = 1.1 sec
000.273s l0043 - check wait time
000.276s l0044 - check sleep time
000.279s l0045 - check sleep prev time
000.282s l0046 - check begin time
001.375s l0052 - lower range check
001.379s l0053 - upper range check
001.382s l0055 - free wait
TESTS COMPLETED SUCCESSFULLY
2019-10-30 10:16:51.145 P00 INFO: P1-T1/1 - vm=none, module=common, test=wait (3.87s)
2019-10-30 10:16:51.209 P00 INFO: writing C coverage report
2019-10-30 10:16:51.674 P00 INFO: TESTS COMPLETED SUCCESSFULLY (5s)
```
An entire module can be run by using only the `--module` option.
pgbackrest-dev => Run a module
```
pgbackrest/test/test.pl --vm=none --dev --module=postgres
--- output ---
2019-10-30 10:16:52.449 P00 INFO: test begin - log level info
2019-10-30 10:16:52.979 P00 INFO: check code autogenerate
2019-10-30 10:16:52.979 P00 INFO: cleanup old data
2019-10-30 10:16:53.513 P00 INFO: builds required: none
2019-10-30 10:16:53.517 P00 INFO: 3 tests selected
2019-10-30 10:16:57.501 P00 INFO: P1-T1/3 - vm=none, module=postgres, test=client (3.99s)
2019-10-30 10:17:00.957 P00 INFO: P1-T2/3 - vm=none, module=postgres, test=interface (3.46s)
2019-10-30 10:17:03.325 P00 INFO: P1-T3/3 - vm=none, module=postgres, test=page-checksum (2.37s)
2019-10-30 10:17:03.382 P00 INFO: writing C coverage report
2019-10-30 10:17:03.944 P00 INFO: TESTS COMPLETED SUCCESSFULLY (11s)
```
### With Docker
Build a container to run tests. The vm must be pre-configured but a variety are available. The vm names are all three character abbreviations, e.g. `u19` for Ubuntu 19.04.
pgbackrest-dev => Build a VM
```
pgbackrest/test/test.pl --vm-build --vm=u19
--- output ---
2019-10-30 10:17:04.779 P00 INFO: test begin - log level info
2019-10-30 10:17:04.812 P00 INFO: Using cached pgbackrest/test:u19-base-20191012A image (32d78710c7f60872ee4a8b2374d0110e015e2c24) ...
2019-10-30 10:17:05.225 P00 INFO: Building pgbackrest/test:u19-build image ...
2019-10-30 10:17:05.641 P00 INFO: Building pgbackrest/test:u19-test image ...
2019-10-30 10:17:06.013 P00 INFO: Build Complete
```
pgbackrest-dev => Run a Test
```
pgbackrest/test/test.pl --vm=u19 --dev --module=mock --test=archive --run=2
--- output ---
2019-10-30 10:17:06.768 P00 INFO: test begin - log level info
2019-10-30 10:17:07.346 P00 INFO: check code autogenerate
2019-10-30 10:17:07.346 P00 INFO: cleanup old data and containers
2019-10-30 10:17:08.403 P00 INFO: builds required: bin, libc host
2019-10-30 10:17:08.615 P00 INFO: 1 test selected
2019-10-30 10:17:41.992 P00 INFO: P1-T1/1 - vm=u19, module=mock, test=archive, run=2 (33.38s)
2019-10-30 10:17:42.163 P00 INFO: no code modules had all tests run required for coverage
2019-10-30 10:17:42.163 P00 INFO: writing C coverage report
2019-10-30 10:17:42.224 P00 INFO: TESTS COMPLETED SUCCESSFULLY (36s)
```
## Adding an Option
Options can be added to a command or multiple commands. Options can be configuration file only, command-line only or valid for both. Once an option is added, `config.auto.*`, `define.auto.*` and `parse.auto.*` files will automatically be generated by the build system.
To add an option, two files need be to be modified:
- `build/lib/pgBackRestBuild/Config/Data.pm`
- `doc/xml/reference.xml`
These files are discussed in the following sections.
### Data.pm
There is a detailed comment at the top of this file on the configuration definitions which one can refer to in determining how to define the rules for the option.
#### Command Line Only Options
Command-line only options are options where `CFGDEF_SECTION` rule is not defined. There are two sections to be updated when adding a command-line only option, each of which is marked by the comment `Command-line only options`.
- **Section 1:** Find the first section with the `Command-line only options` comment. This section defines and exports the constant for the actual option.
- **Section 2:** Find the second section with the `Command-line only options` comment. This is where the rules for the option are defined.
The steps for how to update these sections are detailed below.
**Section 1**
Copy the two lines ("use constant"/"push") of an existing option and paste them where the option would be in alphabetical order and rename it to the same name as the new option name. For example CFGOPT_DRY_RUN, defined as "dry-run".
**Section 2**
To better explain this section, `CFGOPT_ONLINE` will be used as an example:
```
&CFGOPT_ONLINE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_NEGATE => true,
&CFGDEF_DEFAULT => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_UPGRADE => {},
}
},
```
Note that `CFGDEF_SECTION` is not present thereby making this a command-line only option. Each line is explained below:
- `CFGOPT_ONLINE` - the name of the option as defined in **Section 1**
- `CFGDEF_TYPE` - the type of the option. Valid types are: `CFGDEF_TYPE_BOOLEAN`, `CFGDEF_TYPE_FLOAT`, `CFGDEF_TYPE_HASH`, `CFGDEF_TYPE_INTEGER`, `CFGDEF_TYPE_LIST`, `CFGDEF_TYPE_PATH`, `CFGDEF_TYPE_SIZE`, and `CFGDEF_TYPE_STRING`
- `CFGDEF_NEGATE` - being a command-line only boolean option, this rule would automatically default to false so it must be defined if the option is negatable. Ask yourself if negation makes sense, for example, would a --dry-run option make sense as --no-dry-run? If the answer is no, then this rule can be omitted as it would automatically default to false. Any boolean option that cannot be negatable, must be a command-line only and not a configuration file option as all configuration boolean options must be negatable.
- `CFGDEF_DEFAULT` - sets a default for the option if the option is not provided when the command is run. The default can be global or it can be specified for a specific command in the `CFGDEF_COMMAND` section. For example, if it was desirable for the default to be false for the `CFGCMD_STANZA_CREATE` then CFGDEF_NEGATE => would be set to `true` in each command listed except for `CFGCMD_STANZA_CREATE` where it would be `false` and it would not be specified (as it is here) in the global section (meaning global for all commands listed).
- `CFGDEF_COMMAND` - list each command for which the option is valid. If a command is not listed, then the option is not valid for the command and an error will be thrown if it attempted to be used for that command.
### reference.xml
All options must be documented or the system will error during the build. To add an option, find the command section identified by `command id="COMMAND"` section where `COMMAND` is the name of the command (e.g. `expire`) or, if the option is used by more than one command and the definition for the option is the same for all of the commands, the `operation-general title="General Options"` section.
To add an option, add the following to the `` section; if it does not exist, then wrap the following in `` ``. This example uses the boolean option `force` of the `restore` command. Simply replace that with your new option and the appropriate `summary`, `text` and `example`.
```
```
> **IMPORTANT:** currently a period (.) is required to end the `summary` section.
## Testing
For testing, it is recommended that Vagrant and Docker be used; instructions are provided in the `README.md` file of the pgBackRest [test](https://github.com/pgbackrest/pgbackrest/blob/master/test) directory. A list of all possible test combinations can be viewed by running:
```
/backrest/test/test.pl --dry-run
```
> **WARNING:** currently the `BACKREST_USER` in `ContainerTest.pm` must exist, or the test suite will fail with a string concatenation error.
If using a RHEL system, the CPAN XML parser is required for running `test.pl` and `doc.pl`. Instructions for installing Docker and the XML parse can be found in the `README.md` file of the pgBackRest [doc](https://github.com/pgbackrest/pgbackrest/blob/master/doc) directory in the section "The following is a sample CentOS/RHEL 7 configuration that can be used for building the documentation". NOTE that the `Install latex (for building PDF)` is not required since testing of the docs need only be run for HTML output.
While some files are automatically generated during `make`, others are generated by running the test harness as follows:
```
/backrest/test/test.pl --gen-only
```
Prior to any submission, the html version of the documentation should also be run.
```
/backrest/doc/doc.pl --out=html
```
> **NOTE:** `ERROR: [028]` regarding cache is invalid is OK; it just means there have been changes and the documentation will be built from scratch. In this case, be patient as the build could take 20 minutes or more depending on your system.
### Writing a Unit Test
The goal of unit testing is to have 100 percent coverage. Two files will usually be involved in this process:
- **define.yaml** - defines the number of tests to be run for each module and test file. There is a comment at the top of the file that provides more information about this file.
- **src/module/somefileTest.c** - where "somefile" is the path and name of the test file where the unit tests are located for the code being updated (e.g. `src/module/command/expireTest.c`).
#### define.yaml
Each module is separated by a line of asterisks (*) and each test within is separated by a line of dashes (-). In the example below, the module is `command` and the unit test is `check`. The number of calls to `testBegin()` in a unit test file will dictate the number following `total:`, in this case 2. Under `coverage:`, the list of files that will be tested must be listed followed by the coverage level, which should always be `full`.
```
# ********************************************************************************************************************************
- name: command
test:
# ----------------------------------------------------------------------------------------------------------------------------
- name: check
total: 2
coverage:
command/check/common: full
command/check/check: full
```
#### somefileTest.c
Assuming that a test file already exists, new unit tests will either go in a new `testBegin()` section or be added to an existing section.
Unit test files are organized in the test/src/module directory with the same directory structure as the source code being tested. For example, if new code is added to src/**command/expire**.c then test/src/module/**command/expire**Test.c will need to be updated.
```
// *****************************************************************************************************************************
if (testBegin("expireBackup()"))
```
**Setting up the command to be run**
If configuration options are required then a string list with the command and options must be defined and passed to the function `harnessCfgLoad()`. For example, the following will set up a test to run `pgbackrest --repo-path=test/test-0/repo info` command:
```
String *repoPath = strNewFmt("%s/repo", testPath()); // create a string defining the repo path on the test system
StringList *argList = strLstNew(); // create an empty string list
strLstAdd(argList, strNewFmt("--repo-path=%s/", strPtr(repoPath))); // add the --repo-path option as a formatted string
strLstAddZ(argList, "info"); // add the command
harnessCfgLoad(cfgCmdExpire, argList); // load the command and option list into the test harness
TEST_RESULT_STR_Z(infoRender(), "No stanzas exist in the repository.\n", "text - no stanzas"); // run the test
```
Tests are run via macros. All test macros expect the first parameter to be the function to call that is being tested. With the exception of TEST_RESULT_VOID, the second parameter is the expected result, and with the exception of TEST_ERROR, the third parameter is a short description of the test. The most common macros are:
- `TEST_RESULT_STR` - Test the actual value of the string returned by the function.
- `TEST_RESULT_UINT` / `TEST_RESULT_INT` - Test for an unsigned integer / integer.
- `TEST_RESULT_BOOL` - Test a boolean return value.
- `TEST_RESULT_PTR` / `TEST_RESULT_PTR_NE` - Test a pointer: useful for testing if the pointer is `NULL` or not equal (`NE`) to `NULL`.
- `TEST_RESULT_VOID` - The function being tested returns a `void`. This is then usually followed by tests that ensure other actions occurred (e.g. a file was written to disk).
- `TEST_ERROR` / `TEST_ERROR_FMT` - Test for that a specific error code was raised with specific wording.
**Storing a file**
Sometimes it is necessary to store a file to the test directory. The following demonstrates that. It is not necessary to wrap the storagePutNP in TEST_RESULT_VOID, but doing so allows a short description to be displayed when running the tests (in this case "store a corrupt backup.info file").
```
String *content = strNew("bad content");
TEST_RESULT_VOID(
storagePutP(storageNewWriteP(storageTest, strNewFmt("%s/backup/demo/backup.info", strPtr(repoPath))),
harnessInfoChecksum(content)), "store a corrupt backup.info file");
```
**Testing a log message**
If a function being tested logs something with `LOG_WARN`, `LOG_INFO` or other `LOG_` macro, then the logged message must be cleared before the end of the test by using the `harnessLogResult()` function.
```
harnessLogResult(
"P00 WARN: WAL segment '000000010000000100000001' was not pushed due to error [25] and was manually skipped: error");
```
### Running a Unit Test
Unit tests are run, and coverage of the code being tested is provided, by running the following. This example would run the test set from the **define.yaml** section detailed above.
```
/backrest/test/test.pl --vm-out --dev --module=command --test=check --coverage-only
```
> **NOTE:** If you have changed branches, it is recommended the above be run with `--dev-test` instead of `--dev` to rebuild the code from scratch.
Because no test run is specified and `--coverage-only` has been requested, a coverage report will be generated and written to the local file system under `backrest/test/coverage/c-coverage.html` and will highlight code that has not been tested.
Sometimes it is useful to look at files that were generated during the test. The default for running any test is that, at the start/end of the test, the test harness will clean up all files and directories created. To override this behavior, a single test run must be specified and the option `--no-cleanup` provided. Again, continuing with the check command, we see in **define.yaml** above that there are two tests. Below, test one will be run and nothing will be cleaned up so that the files and directories in test/test-0 can be inspected.
```
/backrest/test/test.pl --vm-out --dev --module=command --test=check --coverage-only --run=1 --no-cleanup
```
For more details on running tests, again, please refer to the `README.md` file of the pgBackRest [test](https://github.com/pgbackrest/pgbackrest/blob/master/test) directory.
pgbackrest-release-2.24/LICENSE 0000664 0000000 0000000 00000002220 13625315071 0016210 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Portions Copyright (c) 2015-2020, The PostgreSQL Global Development Group
Portions Copyright (c) 2013-2020, David Steele
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pgbackrest-release-2.24/README.md 0000664 0000000 0000000 00000023014 13625315071 0016466 0 ustar 00root root 0000000 0000000 # pgBackRest Reliable PostgreSQL Backup & Restore
## Introduction
pgBackRest aims to be a simple, reliable backup and restore solution that can seamlessly scale up to the largest databases and workloads by utilizing algorithms that are optimized for database-specific requirements.
pgBackRest [v2.24](https://github.com/pgbackrest/pgbackrest/releases/tag/release/2.24) is the current stable release. Release notes are on the [Releases](http://www.pgbackrest.org/release.html) page.
Documentation for v1 can be found [here](http://www.pgbackrest.org/1). No further releases are planned for v1 because v2 is backward-compatible with v1 options and repositories.
## Features
### Parallel Backup & Restore
Compression is usually the bottleneck during backup operations but, even with now ubiquitous multi-core servers, most database backup solutions are still single-process. pgBackRest solves the compression bottleneck with parallel processing.
Utilizing multiple cores for compression makes it possible to achieve 1TB/hr raw throughput even on a 1Gb/s link. More cores and a larger pipe lead to even higher throughput.
### Local or Remote Operation
A custom protocol allows pgBackRest to backup, restore, and archive locally or remotely via SSH with minimal configuration. An interface to query PostgreSQL is also provided via the protocol layer so that remote access to PostgreSQL is never required, which enhances security.
### Full, Incremental, & Differential Backups
Full, differential, and incremental backups are supported. pgBackRest is not susceptible to the time resolution issues of rsync, making differential and incremental backups completely safe.
### Backup Rotation & Archive Expiration
Retention polices can be set for full and differential backups to create coverage for any timeframe. WAL archive can be maintained for all backups or strictly for the most recent backups. In the latter case WAL required to make older backups consistent will be maintained in the archive.
### Backup Integrity
Checksums are calculated for every file in the backup and rechecked during a restore. After a backup finishes copying files, it waits until every WAL segment required to make the backup consistent reaches the repository.
Backups in the repository are stored in the same format as a standard PostgreSQL cluster (including tablespaces). If compression is disabled and hard links are enabled it is possible to snapshot a backup in the repository and bring up a PostgreSQL cluster directly on the snapshot. This is advantageous for terabyte-scale databases that are time consuming to restore in the traditional way.
All operations utilize file and directory level fsync to ensure durability.
### Page Checksums
PostgreSQL has supported page-level checksums since 9.3. If page checksums are enabled pgBackRest will validate the checksums for every file that is copied during a backup. All page checksums are validated during a full backup and checksums in files that have changed are validated during differential and incremental backups.
Validation failures do not stop the backup process, but warnings with details of exactly which pages have failed validation are output to the console and file log.
This feature allows page-level corruption to be detected early, before backups that contain valid copies of the data have expired.
### Backup Resume
An aborted backup can be resumed from the point where it was stopped. Files that were already copied are compared with the checksums in the manifest to ensure integrity. Since this operation can take place entirely on the backup server, it reduces load on the database server and saves time since checksum calculation is faster than compressing and retransmitting data.
### Streaming Compression & Checksums
Compression and checksum calculations are performed in stream while files are being copied to the repository, whether the repository is located locally or remotely.
If the repository is on a backup server, compression is performed on the database server and files are transmitted in a compressed format and simply stored on the backup server. When compression is disabled a lower level of compression is utilized to make efficient use of available bandwidth while keeping CPU cost to a minimum.
### Delta Restore
The manifest contains checksums for every file in the backup so that during a restore it is possible to use these checksums to speed processing enormously. On a delta restore any files not present in the backup are first removed and then checksums are taken for the remaining files. Files that match the backup are left in place and the rest of the files are restored as usual. Parallel processing can lead to a dramatic reduction in restore times.
### Parallel, Asynchronous WAL Push & Get
Dedicated commands are included for pushing WAL to the archive and getting WAL from the archive. Both commands support parallelism to accelerate processing and run asynchronously to provide the fastest possible response time to PostgreSQL.
WAL push automatically detects WAL segments that are pushed multiple times and de-duplicates when the segment is identical, otherwise an error is raised. Asynchronous WAL push allows transfer to be offloaded to another process which compresses WAL segments in parallel for maximum throughput. This can be a critical feature for databases with extremely high write volume.
Asynchronous WAL get maintains a local queue of WAL segments that are decompressed and ready for replay. This reduces the time needed to provide WAL to PostgreSQL which maximizes replay speed. Higher-latency connections and storage (such as S3) benefit the most.
The push and get commands both ensure that the database and repository match by comparing PostgreSQL versions and system identifiers. This virtually eliminates the possibility of misconfiguring the WAL archive location.
### Tablespace & Link Support
Tablespaces are fully supported and on restore tablespaces can be remapped to any location. It is also possible to remap all tablespaces to one location with a single command which is useful for development restores.
File and directory links are supported for any file or directory in the PostgreSQL cluster. When restoring it is possible to restore all links to their original locations, remap some or all links, or restore some or all links as normal files or directories within the cluster directory.
### S3-Compatible Object Store Support
pgBackRest repositories can be located in S3-compatible object stores to allow for virtually unlimited capacity and retention.
### Encryption
pgBackRest can encrypt the repository to secure backups wherever they are stored.
### Compatibility with PostgreSQL >= 8.3
pgBackRest includes support for versions down to 8.3, since older versions of PostgreSQL are still regularly utilized.
## Getting Started
pgBackRest strives to be easy to configure and operate:
- [User guides](http://www.pgbackrest.org/user-guide-index.html) for various operating systems and PostgreSQL versions.
- [Command reference](http://www.pgbackrest.org/command.html) for command-line operations.
- [Configuration reference](http://www.pgbackrest.org/configuration.html) for creating pgBackRest configurations.
## Contributions
Contributions to pgBackRest are always welcome!
Code fixes or new features can be submitted via pull requests. Ideas for new features and improvements to existing functionality or documentation can be [submitted as issues](https://github.com/pgbackrest/pgbackrest/issues). You may want to check the [Feature Backlog](https://github.com/pgbackrest/pgbackrest/wiki#backlog) to see if your suggestion has already been submitted.
Bug reports should be [submitted as issues](https://github.com/pgbackrest/pgbackrest/issues). Please provide as much information as possible to aid in determining the cause of the problem.
You will always receive credit in the [release notes](http://www.pgbackrest.org/release.html) for your contributions.
## Support
pgBackRest is completely free and open source under the [MIT](https://github.com/pgbackrest/pgbackrest/blob/master/LICENSE) license. You may use it for personal or commercial purposes without any restrictions whatsoever. Bug reports are taken very seriously and will be addressed as quickly as possible.
Creating a robust disaster recovery policy with proper replication and backup strategies can be a very complex and daunting task. You may find that you need help during the architecture phase and ongoing support to ensure that your enterprise continues running smoothly.
[Crunchy Data](http://www.crunchydata.com) provides packaged versions of pgBackRest for major operating systems and expert full life-cycle commercial support for pgBackRest and all things PostgreSQL. [Crunchy Data](http://www.crunchydata.com) is committed to providing open source solutions with no vendor lock-in, ensuring that cross-compatibility with the community version of pgBackRest is always strictly maintained.
Please visit [Crunchy Data](http://www.crunchydata.com) for more information.
## Recognition
Primary recognition goes to Stephen Frost for all his valuable advice and criticism during the development of pgBackRest.
[Crunchy Data](http://www.crunchydata.com) has contributed significant time and resources to pgBackRest and continues to actively support development. [Resonate](http://www.resonate.com) also contributed to the development of pgBackRest and allowed early (but well tested) versions to be installed as their primary PostgreSQL backup solution.
[Armchair](https://thenounproject.com/search/?q=lounge+chair&i=129971) graphic by [Sandor Szabo](https://thenounproject.com/sandorsz).
pgbackrest-release-2.24/build/ 0000775 0000000 0000000 00000000000 13625315071 0016306 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/build/error.yaml 0000664 0000000 0000000 00000004660 13625315071 0020331 0 ustar 00root root 0000000 0000000 # Error Definitions
#
# Master file for all errors in the C and Perl code. For C, errors are written to src/common/error.auto.c/h. For Perl, errors are
# written to lib/pgBackRest/Common/ExceptionAuto.pm.
# Errors in the code that it should not be possible for the user to encounter -- i.e., a likely bug
assert: 25
checksum: 26
config: 27
file-invalid: 28
format: 29
command-required: 30
option-invalid: 31
option-invalid-value: 32
option-invalid-range: 33
option-invalid-pair: 34
option-duplicate-key: 35
option-negate: 36
option-required: 37
postmaster-running: 38
protocol: 39
path-not-empty: 40
file-open: 41
file-read: 42
param-required: 43
archive-mismatch: 44
archive-duplicate: 45
version-not-supported: 46
path-create: 47
command-invalid: 48
host-connect: 49
lock-acquire: 50
backup-mismatch: 51
file-sync: 52
path-open: 53
path-sync: 54
file-missing: 55
db-connect: 56
db-query: 57
db-mismatch: 58
db-timeout: 59
file-remove: 60
path-remove: 61
stop: 62
term: 63
file-write: 64
protocol-timeout: 66
feature-not-supported: 67
archive-command-invalid: 68
link-expected: 69
link-destination: 70
host-invalid: 72
path-missing: 73
file-move: 74
backup-set-invalid: 75
tablespace-map: 76
path-type: 77
link-map: 78
file-close: 79
db-missing: 80
db-invalid: 81
archive-timeout: 82
file-mode: 83
option-multiple-value: 84
protocol-output-required: 85
link-open: 86
archive-disabled: 87
file-owner: 88
user-missing: 89
option-command: 90
group-missing: 91
path-exists: 92
file-exists: 93
# Memory allocation failed
memory: 94
crypto: 95
param-invalid: 96
# Unable to close a path
path-close: 97
# Unable to get info for a file
file-info: 98
# Invalid JSON format. Eventually this should be a child of format error and share the same code
json-format: 99
# An error from the kernel that there's nothing we can do about. It should always be fatal.
kernel: 100
# An error from a service that is not our fault, e.g. 5xx errors from an http server. These may be retried.
service: 101
# An error while attempting to execute a binary
execute: 102
# This error should not be thrown directly -- it serves as a parent for the C errors
runtime: 122
# Error used when an invalid error code is passed to the Perl Exception object -- this should not happen
invalid: 123
# Used when a native Perl error is not caught before exitSafe() -- this should not happen
unhandled: 124
# Used when a specific error is not provided in the Perl code. This should be rare.
unknown: 125
pgbackrest-release-2.24/build/lib/ 0000775 0000000 0000000 00000000000 13625315071 0017054 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/build/lib/pgBackRestBuild/ 0000775 0000000 0000000 00000000000 13625315071 0022061 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/build/lib/pgBackRestBuild/Build.pm 0000664 0000000 0000000 00000017105 13625315071 0023462 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Auto-Generate C Files Required for Build
####################################################################################################################################
package pgBackRestBuild::Build;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(basename);
use Storable qw(dclone);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRestBuild::Build::Common;
use pgBackRestTest::Common::Storage;
use pgBackRestTest::Common::StoragePosix;
####################################################################################################################################
# Define generator used for auto generated warning messages
####################################################################################################################################
use constant GENERATOR => 'Build.pm';
####################################################################################################################################
# buildAll - execute all build functions and generate C source code
####################################################################################################################################
sub buildAll
{
my $strBuildPath = shift;
my $rhBuild = shift;
my $strFileExt = shift;
# Storage object
my $oStorage = new pgBackRestTest::Common::Storage(
$strBuildPath, new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false}));
# List of files actually built
my @stryBuilt;
# Build and output source code
#-------------------------------------------------------------------------------------------------------------------------------
foreach my $strBuild (sort(keys(%{$rhBuild})))
{
my $strPath = $rhBuild->{$strBuild}{&BLD_PATH};
foreach my $strFile (sort(keys(%{$rhBuild->{$strBuild}{&BLD_DATA}{&BLD_FILE}})))
{
my $rhFile = $rhBuild->{$strBuild}{&BLD_DATA}{&BLD_FILE}{$strFile};
my $rhFileConstant = $rhFile->{&BLD_CONSTANT_GROUP};
my $rhFileEnum = $rhFile->{&BLD_ENUM};
my $rhFileDeclare = $rhFile->{&BLD_DECLARE};
my $rhFileData = $rhFile->{&BLD_DATA};
my $rhSource;
# Build general banner
#-------------------------------------------------------------------------------------------------------------------------------
my $strBanner = bldBanner($rhFile->{&BLD_SUMMARY}, GENERATOR);
# Build header file
#-------------------------------------------------------------------------------------------------------------------------------
if (defined($rhFileEnum) || defined($rhFileConstant) || defined($rhFileDeclare))
{
my $strHeaderDefine = uc("${strPath}/${strFile}") . '_AUTO_H';
$strHeaderDefine =~ s/\//_/g;
my $strHeader =
$strBanner .
"#ifndef ${strHeaderDefine}\n" .
"#define ${strHeaderDefine}\n";
# Iterate constant groups
foreach my $strConstantGroup (sort(keys(%{$rhFileConstant})))
{
my $rhConstantGroup = $rhFileConstant->{$strConstantGroup};
$strHeader .= "\n" . bldBanner($rhConstantGroup->{&BLD_SUMMARY} . ' constants');
# Iterate constants
foreach my $strConstant (sort(keys(%{$rhConstantGroup->{&BLD_CONSTANT}})))
{
my $rhConstant = $rhConstantGroup->{&BLD_CONSTANT}{$strConstant};
$strHeader .=
"#define ${strConstant} " . (' ' x (69 - length($strConstant) - 10)) .
$rhConstant->{&BLD_CONSTANT_VALUE} . "\n";
}
}
# Iterate enum groups
foreach my $strEnum (sort(keys(%{$rhFileEnum})))
{
my $rhEnum = $rhFileEnum->{$strEnum};
$strHeader .=
"\n" . bldBanner($rhEnum->{&BLD_SUMMARY} . ' enum');
$strHeader .=
"typedef enum\n" .
"{\n";
my $iExpectedValue = 0;
# Iterate enums
foreach my $strEnumItem (@{$rhEnum->{&BLD_LIST}})
{
$strHeader .=
" ${strEnumItem}";
if (defined($rhEnum->{&BLD_VALUE}{$strEnumItem}) && $rhEnum->{&BLD_VALUE}{$strEnumItem} != $iExpectedValue)
{
$strHeader .= ' = ' . (' ' x (61 - length($strEnumItem))) . $rhEnum->{&BLD_VALUE}{$strEnumItem};
$iExpectedValue = $rhEnum->{&BLD_VALUE}{$strEnumItem};
}
$strHeader .= ",\n";
$iExpectedValue++;
}
$strHeader .=
"} " . $rhEnum->{&BLD_NAME} . ";\n";
}
foreach my $strDeclare (sort(keys(%{$rhFileDeclare})))
{
my $rhDeclare = $rhFileDeclare->{$strDeclare};
$strHeader .= "\n" . bldBanner($rhDeclare->{&BLD_SUMMARY});
$strHeader .= $rhDeclare->{&BLD_SOURCE};
}
$strHeader .=
"\n#endif";
$rhSource->{&BLD_HEADER} = $strHeader;
}
# Build C file
#-----------------------------------------------------------------------------------------------------------------------
if (defined($rhFileData))
{
my $strCode;
foreach my $strData (sort(keys(%{$rhFileData})))
{
my $rhData = $rhFileData->{$strData};
$strCode .= "\n" . bldBanner($rhData->{&BLD_SUMMARY});
$strCode .= $rhData->{&BLD_SOURCE};
}
$rhSource->{&BLD_C} = "${strBanner}${strCode}";
}
# Output files
#-----------------------------------------------------------------------------------------------------------------------
foreach my $strFileType (sort(keys(%{$rhSource})))
{
my $strExt = $strFileType;
if (defined($strFileExt))
{
$strExt = $strFileType eq BLD_C ? $strFileExt : "${strFileExt}h";
}
# Save the file if it has not changed
my $strBuilt = "${strPath}/${strFile}.auto.${strExt}";
my $bSave = true;
my $oFile = $oStorage->openRead($strBuilt, {bIgnoreMissing => true});
if (defined($oFile) && ${$oStorage->get($oFile)} eq (trim($rhSource->{$strFileType}) . "\n"))
{
$bSave = false;
}
if ($bSave)
{
$oStorage->put($strBuilt, trim($rhSource->{$strFileType}) . "\n");
push(@stryBuilt, basename($strBuildPath) . "/${strBuilt}");
}
}
}
}
# Return list of files built
return @stryBuilt;
}
push @EXPORT, qw(buildAll);
1;
pgbackrest-release-2.24/build/lib/pgBackRestBuild/Build/ 0000775 0000000 0000000 00000000000 13625315071 0023120 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/build/lib/pgBackRestBuild/Build/Common.pm 0000664 0000000 0000000 00000012432 13625315071 0024710 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Build Constants and Functions
####################################################################################################################################
package pgBackRestBuild::Build::Common;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use Storable qw(dclone);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLD_PATH => 'path';
push @EXPORT, qw(BLD_PATH);
use constant BLD_FILE => 'file';
push @EXPORT, qw(BLD_FILE);
use constant BLD_C => 'c';
push @EXPORT, qw(BLD_C);
use constant BLD_EXT => 'ext';
push @EXPORT, qw(BLD_EXT);
use constant BLD_HEADER => 'h';
push @EXPORT, qw(BLD_HEADER);
use constant BLD_CONSTANT => 'constant';
push @EXPORT, qw(BLD_CONSTANT);
use constant BLD_CONSTANT_GROUP => 'constantGroup';
push @EXPORT, qw(BLD_CONSTANT_GROUP);
use constant BLD_CONSTANT_VALUE => 'constantValue';
push @EXPORT, qw(BLD_CONSTANT_VALUE);
use constant BLD_DATA => 'data';
push @EXPORT, qw(BLD_DATA);
use constant BLD_DECLARE => 'declare';
push @EXPORT, qw(BLD_DECLARE);
use constant BLD_ENUM => 'enum';
push @EXPORT, qw(BLD_ENUM);
use constant BLD_LIST => 'list';
push @EXPORT, qw(BLD_LIST);
use constant BLD_NAME => 'name';
push @EXPORT, qw(BLD_NAME);
use constant BLD_PATH => 'path';
push @EXPORT, qw(BLD_PATH);
use constant BLD_SOURCE => 'buildSource';
push @EXPORT, qw(BLD_SOURCE);
use constant BLD_SUMMARY => 'summary';
push @EXPORT, qw(BLD_SUMMARY);
use constant BLD_VALUE => 'value';
push @EXPORT, qw(BLD_VALUE);
####################################################################################################################################
# bldAutoWarning - warning not to modify automatically generated files directly
####################################################################################################################################
sub bldAutoWarning
{
my $strGenerator = shift;
return "Automatically generated by ${strGenerator} -- do not modify directly.";
}
push @EXPORT, qw(bldAutoWarning);
####################################################################################################################################
# bldBanner - build general banner
####################################################################################################################################
sub bldBanner
{
my $strContent = shift;
my $strGenerator = shift;
my $strBanner =
qw{/} . (qw{*} x 131) . "\n" .
trim($strContent) . "\n";
if (defined($strGenerator))
{
$strBanner .=
"\n" .
bldAutoWarning($strGenerator) . "\n";
}
$strBanner .=
(qw{*} x 131) . qw{/} . "\n";
return $strBanner;
}
push @EXPORT, qw(bldBanner);
####################################################################################################################################
# Generate an enum name from a prefix and - separated name
####################################################################################################################################
sub bldEnum
{
my $strPrefix = shift;
my $strName = shift;
my $bInitCapFirst = shift;
$bInitCapFirst = defined($bInitCapFirst) ? $bInitCapFirst : true;
my $bFirst = true;
my @stryName = split('\-', $strName);
$strName = undef;
foreach my $strPart (@stryName)
{
$strName .= ($bFirst && $bInitCapFirst) || !$bFirst ? ucfirst($strPart) : $strPart;
$bFirst = false;
}
return "${strPrefix}${strName}";
}
push @EXPORT, qw(bldEnum);
####################################################################################################################################
# Quote a list of strings
####################################################################################################################################
sub bldQuoteList
{
my $ryList = shift;
my @stryQuoteList;
foreach my $strItem (@{$ryList})
{
push(@stryQuoteList, "\"${strItem}\"");
}
return @stryQuoteList;
}
push @EXPORT, qw(bldQuoteList);
1;
pgbackrest-release-2.24/build/lib/pgBackRestBuild/Config/ 0000775 0000000 0000000 00000000000 13625315071 0023266 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/build/lib/pgBackRestBuild/Config/Build.pm 0000664 0000000 0000000 00000026535 13625315071 0024676 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Auto-Generate Command and Option Configuration Enums, Constants and Data
####################################################################################################################################
package pgBackRestBuild::Config::Build;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Version;
use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::BuildDefine;
use pgBackRestBuild::Config::Data;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLDLCL_FILE_CONFIG => 'config';
use constant BLDLCL_CONSTANT_COMMAND => '01-constantCommand';
use constant BLDLCL_CONSTANT_COMMAND_TOTAL => 'CFG_COMMAND_TOTAL';
use constant BLDLCL_CONSTANT_OPTION => '02-constantOption';
use constant BLDLCL_CONSTANT_OPTION_TOTAL => 'CFG_OPTION_TOTAL';
use constant BLDLCL_DATA_COMMAND_CONSTANT => '01-commandConstant';
use constant BLDLCL_DATA_COMMAND => '02-command';
use constant BLDLCL_DATA_OPTION_CONSTANT => '03-optionConstant';
use constant BLDLCL_DATA_OPTION => '04-option';
use constant BLDLCL_ENUM_COMMAND => '01-enumCommand';
use constant BLDLCL_ENUM_OPTION => '02-enumOption';
####################################################################################################################################
# Definitions for constants and data to build
####################################################################################################################################
my $rhBuild =
{
&BLD_FILE =>
{
#---------------------------------------------------------------------------------------------------------------------------
&BLDLCL_FILE_CONFIG =>
{
&BLD_SUMMARY => 'Command and Option Configuration',
&BLD_CONSTANT_GROUP =>
{
&BLDLCL_CONSTANT_COMMAND =>
{
&BLD_SUMMARY => 'Command',
},
&BLDLCL_CONSTANT_OPTION =>
{
&BLD_SUMMARY => 'Option',
},
},
&BLD_ENUM =>
{
&BLDLCL_ENUM_COMMAND =>
{
&BLD_SUMMARY => 'Command',
&BLD_NAME => 'ConfigCommand',
&BLD_LIST => [],
},
&BLDLCL_ENUM_OPTION =>
{
&BLD_SUMMARY => 'Option',
&BLD_NAME => 'ConfigOption',
&BLD_LIST => [],
},
},
&BLD_DATA =>
{
&BLDLCL_DATA_COMMAND_CONSTANT =>
{
&BLD_SUMMARY => 'Command constants',
},
&BLDLCL_DATA_COMMAND =>
{
&BLD_SUMMARY => 'Command data',
},
&BLDLCL_DATA_OPTION_CONSTANT =>
{
&BLD_SUMMARY => 'Option constants',
},
&BLDLCL_DATA_OPTION =>
{
&BLD_SUMMARY => 'Option data',
},
},
},
},
};
####################################################################################################################################
# Generate enum names
####################################################################################################################################
sub buildConfigCommandEnum
{
return bldEnum('cfgCmd', shift)
}
push @EXPORT, qw(buildConfigCommandEnum);
sub buildConfigOptionEnum
{
return bldEnum('cfgOpt', shift)
}
push @EXPORT, qw(buildConfigOptionEnum);
####################################################################################################################################
# Build constants and data
####################################################################################################################################
sub buildConfig
{
# Build command constants and data
#-------------------------------------------------------------------------------------------------------------------------------
my $strCommandConst;
my $rhCommandDefine = cfgDefineCommand();
my $rhEnum = $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_ENUM}{&BLDLCL_ENUM_COMMAND};
my $iCommandTotal = 0;
my $strBuildSource =
'static ConfigCommandData configCommandData[' . BLDLCL_CONSTANT_COMMAND_TOTAL . "] = CONFIG_COMMAND_LIST\n" .
"(";
my $strBuildSourceConstant = '';
foreach my $strCommand (sort(keys(%{$rhCommandDefine})))
{
my $rhCommand = $rhCommandDefine->{$strCommand};
# Build command constant name
$strCommandConst = "CFGCMD_" . uc($strCommand);
$strCommandConst =~ s/\-/_/g;
# Build C enum
my $strCommandEnum = buildConfigCommandEnum($strCommand);
push(@{$rhEnum->{&BLD_LIST}}, $strCommandEnum);
# Build command data
$strBuildSource .=
"\n" .
" CONFIG_COMMAND\n" .
" (\n" .
" CONFIG_COMMAND_NAME(${strCommandConst})\n" .
"\n" .
" CONFIG_COMMAND_INTERNAL(" . ($rhCommand->{&CFGDEF_INTERNAL} ? 'true' : 'false') . ")\n" .
" CONFIG_COMMAND_LOG_FILE(" . ($rhCommand->{&CFGDEF_LOG_FILE} ? 'true' : 'false') . ")\n" .
" CONFIG_COMMAND_LOG_LEVEL_DEFAULT(logLevel" . ucfirst(lc($rhCommand->{&CFGDEF_LOG_LEVEL_DEFAULT})) . ")\n" .
" CONFIG_COMMAND_LOCK_REQUIRED(" . ($rhCommand->{&CFGDEF_LOCK_REQUIRED} ? 'true' : 'false') . ")\n" .
" CONFIG_COMMAND_LOCK_REMOTE_REQUIRED(" .
($rhCommand->{&CFGDEF_LOCK_REMOTE_REQUIRED} ? 'true' : 'false') . ")\n" .
" CONFIG_COMMAND_LOCK_TYPE(lockType" . ucfirst(lc($rhCommand->{&CFGDEF_LOCK_TYPE})) . ")\n" .
" CONFIG_COMMAND_PARAMETER_ALLOWED(" . ($rhCommand->{&CFGDEF_PARAMETER_ALLOWED} ? 'true' : 'false') . ")\n" .
" )\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_CONSTANT_GROUP}{&BLDLCL_CONSTANT_COMMAND}{&BLD_CONSTANT}
{$strCommandConst}{&BLD_CONSTANT_VALUE} = "\"${strCommand}\"\n STRING_DECLARE(${strCommandConst}_STR);";
$strBuildSourceConstant .=
"STRING_EXTERN(${strCommandConst}_STR," . (' ' x (49 - length($strCommandConst))) . "${strCommandConst});\n";
$iCommandTotal++;
}
# Add "none" command that is used to initialize the current command before anything is parsed
push(@{$rhEnum->{&BLD_LIST}}, buildConfigCommandEnum('none'));
$iCommandTotal++;
$strBuildSource .=
")\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_DATA}{&BLDLCL_DATA_COMMAND}{&BLD_SOURCE} = $strBuildSource;
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_DATA}{&BLDLCL_DATA_COMMAND_CONSTANT}{&BLD_SOURCE} = $strBuildSourceConstant;
# Add an LF to the last command constant so there's whitespace before the total
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_CONSTANT_GROUP}{&BLDLCL_CONSTANT_COMMAND}{&BLD_CONSTANT}
{$strCommandConst}{&BLD_CONSTANT_VALUE} .= "\n";
# Set option total constant
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_CONSTANT_GROUP}{&BLDLCL_CONSTANT_COMMAND}{&BLD_CONSTANT}
{&BLDLCL_CONSTANT_COMMAND_TOTAL}{&BLD_CONSTANT_VALUE} = $iCommandTotal;
# Build option constants and data
#-------------------------------------------------------------------------------------------------------------------------------
my $strOptionConst;
my $rhConfigDefine = cfgDefine();
$rhEnum = $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_ENUM}{&BLDLCL_ENUM_OPTION};
my $iOptionTotal = 0;
$strBuildSource =
'static ConfigOptionData configOptionData[' . BLDLCL_CONSTANT_OPTION_TOTAL . "] = CONFIG_OPTION_LIST\n" .
"(";
$strBuildSourceConstant = '';
foreach my $strOption (sort(keys(%{$rhConfigDefine})))
{
my $iOptionIndexTotal = $rhConfigDefine->{$strOption}{&CFGDEF_INDEX_TOTAL};
my $strOptionPrefix = $rhConfigDefine->{$strOption}{&CFGDEF_PREFIX};
# Builds option data
for (my $iOptionIndex = 1; $iOptionIndex <= $iOptionIndexTotal; $iOptionIndex++)
{
# Build C enum
my $strOptionEnum = buildConfigOptionEnum($strOption) . ($iOptionIndex == 1 ? '' : $iOptionIndex);
push(@{$rhEnum->{&BLD_LIST}}, $strOptionEnum);
$rhEnum->{&BLD_VALUE}{$strOptionEnum} = $iOptionTotal;
# Create the indexed version of the option name
my $strOptionIndex = defined($strOptionPrefix) ?
"${strOptionPrefix}${iOptionIndex}-" . substr($strOption, length($strOptionPrefix) + 1) : $strOption;
# Build option constant name
$strOptionConst = "CFGOPT_" . uc($strOptionIndex);
$strOptionConst =~ s/\-/_/g;
# Add option data
$strBuildSource .=
"\n" .
" //" . (qw{-} x 126) . "\n" .
" CONFIG_OPTION\n" .
" (\n" .
" CONFIG_OPTION_NAME(${strOptionConst})\n" .
" CONFIG_OPTION_INDEX(" . ($iOptionIndex - 1) . ")\n" .
" CONFIG_OPTION_DEFINE_ID(" . buildConfigDefineOptionEnum($strOption) . ")\n" .
" )\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_CONSTANT_GROUP}{&BLDLCL_CONSTANT_OPTION}{&BLD_CONSTANT}
{$strOptionConst}{&BLD_CONSTANT_VALUE} = "\"${strOptionIndex}\"\n STRING_DECLARE(${strOptionConst}_STR);";
$strBuildSourceConstant .=
"STRING_EXTERN(${strOptionConst}_STR," . (' ' x (49 - length($strOptionConst))) . "${strOptionConst});\n";
$iOptionTotal += 1;
}
}
$strBuildSource .=
")\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_DATA}{&BLDLCL_DATA_OPTION}{&BLD_SOURCE} = $strBuildSource;
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_DATA}{&BLDLCL_DATA_OPTION_CONSTANT}{&BLD_SOURCE} = $strBuildSourceConstant;
# Add an LF to the last option constant so there's whitespace before the total
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_CONSTANT_GROUP}{&BLDLCL_CONSTANT_OPTION}{&BLD_CONSTANT}
{$strOptionConst}{&BLD_CONSTANT_VALUE} .= "\n";
# Set option total constant
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_CONFIG}{&BLD_CONSTANT_GROUP}{&BLDLCL_CONSTANT_OPTION}{&BLD_CONSTANT}
{&BLDLCL_CONSTANT_OPTION_TOTAL}{&BLD_CONSTANT_VALUE} = $iOptionTotal;
return $rhBuild;
}
push @EXPORT, qw(buildConfig);
1;
pgbackrest-release-2.24/build/lib/pgBackRestBuild/Config/BuildDefine.pm 0000664 0000000 0000000 00000050445 13625315071 0026006 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Auto-Generate Command and Option Configuration Definition Enums, Constants and Data
####################################################################################################################################
package pgBackRestBuild::Config::BuildDefine;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Version;
use BackRestDoc::Common::DocConfig;
use BackRestDoc::Common::DocRender;
use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::Data;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLDLCL_FILE_DEFINE => 'define';
use constant BLDLCL_DATA_COMMAND => '01-dataCommand';
use constant BLDLCL_DATA_OPTION => '02-dataOption';
use constant BLDLCL_ENUM_COMMAND => '01-enumCommand';
use constant BLDLCL_ENUM_OPTION_TYPE => '02-enumOptionType';
use constant BLDLCL_ENUM_OPTION => '03-enumOption';
####################################################################################################################################
# Definitions for constants and data to build
####################################################################################################################################
my $strSummary = 'Command and Option Definition';
my $rhBuild =
{
&BLD_FILE =>
{
&BLDLCL_FILE_DEFINE =>
{
&BLD_SUMMARY => $strSummary,
&BLD_ENUM =>
{
&BLDLCL_ENUM_COMMAND =>
{
&BLD_SUMMARY => 'Command define',
&BLD_NAME => 'ConfigDefineCommand',
},
&BLDLCL_ENUM_OPTION_TYPE =>
{
&BLD_SUMMARY => 'Option type define',
&BLD_NAME => 'ConfigDefineOptionType',
},
&BLDLCL_ENUM_OPTION =>
{
&BLD_SUMMARY => 'Option define',
&BLD_NAME => 'ConfigDefineOption',
},
},
&BLD_DATA =>
{
&BLDLCL_DATA_COMMAND =>
{
&BLD_SUMMARY => 'Command define data',
},
&BLDLCL_DATA_OPTION =>
{
&BLD_SUMMARY => 'Option define data',
},
},
},
},
};
####################################################################################################################################
# Generate enum names
####################################################################################################################################
sub buildConfigDefineCommandEnum
{
return bldEnum('cfgDefCmd', shift)
}
push @EXPORT, qw(buildConfigDefineCommandEnum);
sub buildConfigDefineOptionTypeEnum
{
return bldEnum('cfgDefOptType', shift);
}
push @EXPORT, qw(buildConfigDefineOptionTypeEnum);
sub buildConfigDefineOptionEnum
{
return bldEnum('cfgDefOpt', shift);
}
push @EXPORT, qw(buildConfigDefineOptionEnum);
####################################################################################################################################
# Helper function to format help text
####################################################################################################################################
sub helpFormatText
{
my $oManifest = shift;
my $oDocRender = shift;
my $oText = shift;
my $iIndent = shift;
my $iLength = shift;
# Split the string into lines for processing
my @stryText = split("\n", trim($oManifest->variableReplace($oDocRender->processText($oText))));
my $strText;
my $iIndex = 0;
foreach my $strLine (@stryText)
{
# Add a linefeed if this is not the first line
if (defined($strText))
{
$strText .= "\n";
}
# Escape special characters
$strLine =~ s/\"/\\"/g;
my $strPart;
my $bFirst = true;
# Split the line for output if it's too long
do
{
($strPart, $strLine) = stringSplit($strLine, ' ', defined($strPart) ? $iLength - 4 : $iLength);
$strText .= ' ' x $iIndent;
if (!$bFirst)
{
$strText .= " ";
}
$strText .= "\"${strPart}";
if (defined($strLine))
{
$strText .= "\"\n";
}
else
{
$strText .= ($iIndex + 1 < @stryText ? '\n' : '') . '"';
}
$bFirst = false;
}
while (defined($strLine));
$iIndex++;
}
return $strText;
}
####################################################################################################################################
# Helper functions for building optional option data
####################################################################################################################################
sub renderAllowList
{
my $ryAllowList = shift;
my $bCommandIndent = shift;
my $strIndent = $bCommandIndent ? ' ' : '';
return
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_ALLOW_LIST\n" .
"${strIndent} (\n" .
"${strIndent} " . join(",\n${strIndent} ", bldQuoteList($ryAllowList)) .
"\n" .
"${strIndent} )\n";
}
sub renderDepend
{
my $rhDepend = shift;
my $bCommandIndent = shift;
my $strIndent = $bCommandIndent ? ' ' : '';
my $strDependOption = $rhDepend->{&CFGDEF_DEPEND_OPTION};
my $ryDependList = $rhDepend->{&CFGDEF_DEPEND_LIST};
if (defined($ryDependList))
{
my @stryQuoteList;
foreach my $strItem (@{$ryDependList})
{
push(@stryQuoteList, "\"${strItem}\"");
}
return
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_DEPEND_LIST\n" .
"${strIndent} (\n" .
"${strIndent} " . buildConfigDefineOptionEnum($strDependOption) . ",\n" .
"${strIndent} " . join(",\n${strIndent} ", bldQuoteList($ryDependList)) .
"\n" .
"${strIndent} )\n";
}
return
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_DEPEND(" . buildConfigDefineOptionEnum($strDependOption) . ")\n";
}
sub renderOptional
{
my $rhOptional = shift;
my $bCommand = shift;
my $rhOptionHelp = shift;
my $oManifest = shift;
my $oDocRender = shift;
my $strCommand = shift;
my $strOption = shift;
my $strIndent = $bCommand ? ' ' : '';
my $strBuildSourceOptional;
my $bSingleLine = false;
if (defined($rhOptional->{&CFGDEF_ALLOW_LIST}))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
renderAllowList($rhOptional->{&CFGDEF_ALLOW_LIST}, $bCommand);
$bSingleLine = false;
}
if (defined($rhOptional->{&CFGDEF_ALLOW_RANGE}))
{
my @fyRange = @{$rhOptional->{&CFGDEF_ALLOW_RANGE}};
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_ALLOW_RANGE(" . $fyRange[0] . ', ' . $fyRange[1] . ")\n";
$bSingleLine = true;
}
if (defined($rhOptional->{&CFGDEF_DEPEND}))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
renderDepend($rhOptional->{&CFGDEF_DEPEND}, $bCommand);
$bSingleLine = defined($rhOptional->{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_LIST}) ? false : true;
}
if (defined($rhOptional->{&CFGDEF_DEFAULT}))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_DEFAULT(\"" . $rhOptional->{&CFGDEF_DEFAULT} . "\")\n";
$bSingleLine = true;
}
if (defined($rhOptional->{&CFGDEF_PREFIX}))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_PREFIX(\"" . $rhOptional->{&CFGDEF_PREFIX} . "\")\n";
$bSingleLine = true;
}
# Output alternate name
if (!$bCommand && defined($rhOptionHelp->{&CONFIG_HELP_NAME_ALT}))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_HELP_NAME_ALT(" .
join(', ', bldQuoteList($rhOptionHelp->{&CONFIG_HELP_NAME_ALT})) . ")\n";
$bSingleLine = true;
}
if ($bCommand && defined($rhOptional->{&CFGDEF_INTERNAL}))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_INTERNAL(" . ($rhOptional->{&CFGDEF_INTERNAL} ? 'true' : 'false') .
")\n";
$bSingleLine = true;
}
if ($bCommand && defined($rhOptional->{&CFGDEF_REQUIRED}))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) && !$bSingleLine ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_REQUIRED(" .
($rhOptional->{&CFGDEF_REQUIRED} ? 'true' : 'false') . ")\n";
$bSingleLine = true;
}
if ($bCommand && defined($rhOptionHelp) && defined($rhOptionHelp->{&CONFIG_HELP_SOURCE}) &&
$rhOptionHelp->{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_COMMAND)
{
my $strSummary = helpFormatText($oManifest, $oDocRender, $rhOptionHelp->{&CONFIG_HELP_SUMMARY}, 0, 72);
if (length($strSummary) > 74)
{
confess("summary for command '${strCommand}', option '${strOption}' may not be greater than 72 characters");
}
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_HELP_SUMMARY(${strSummary})\n" .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_HELP_DESCRIPTION\n" .
"${strIndent} (\n" .
helpFormatText($oManifest, $oDocRender, $rhOptionHelp->{&CONFIG_HELP_DESCRIPTION}, 20, 111) . "\n" .
"${strIndent} )\n";
}
return $strBuildSourceOptional;
}
####################################################################################################################################
# Build configuration constants and data
####################################################################################################################################
sub buildConfigDefine
{
# Load help data
#-------------------------------------------------------------------------------------------------------------------------------
require BackRestDoc::Common::Doc;
require BackRestDoc::Common::DocManifest;
my $strDocPath = abs_path(dirname($0) . '/../doc');
my $oStorageDoc = new pgBackRestTest::Common::Storage(
$strDocPath, new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false}));
my @stryEmpty = [];
my $oManifest = new BackRestDoc::Common::DocManifest(
$oStorageDoc, \@stryEmpty, \@stryEmpty, \@stryEmpty, \@stryEmpty, undef, $strDocPath, false, false);
my $oDocRender = new BackRestDoc::Common::DocRender('text', $oManifest, false);
my $oDocConfig =
new BackRestDoc::Common::DocConfig(
new BackRestDoc::Common::Doc("${strDocPath}/xml/reference.xml"), $oDocRender);
my $hConfigHelp = $oDocConfig->{oConfigHash};
# Build command constants and data
#-------------------------------------------------------------------------------------------------------------------------------
my $rhEnum = $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_ENUM}{&BLDLCL_ENUM_COMMAND};
my $strBuildSource =
"static ConfigDefineCommandData configDefineCommandData[] = CFGDEFDATA_COMMAND_LIST\n" .
"(";
foreach my $strCommand (cfgDefineCommandList())
{
# Get command help
my $hCommandHelp = $hConfigHelp->{&CONFIG_HELP_COMMAND}{$strCommand};
# Build C enum
my $strCommandEnum = buildConfigDefineCommandEnum($strCommand);
push(@{$rhEnum->{&BLD_LIST}}, $strCommandEnum);
# Build command data
$strBuildSource .=
"\n" .
" CFGDEFDATA_COMMAND\n" .
" (\n" .
" CFGDEFDATA_COMMAND_NAME(\"${strCommand}\")\n";
# Output help
if (defined($hCommandHelp))
{
$strBuildSource .=
"\n";
# Output command summary
my $strSummary = helpFormatText($oManifest, $oDocRender, $hCommandHelp->{&CONFIG_HELP_SUMMARY}, 0, 72);
if (length($strSummary) > 74)
{
confess("summary for command '${strCommand}' may not be greater than 72 characters");
}
$strBuildSource .=
" CFGDEFDATA_COMMAND_HELP_SUMMARY(${strSummary})\n";
# Output description
$strBuildSource .=
" CFGDEFDATA_COMMAND_HELP_DESCRIPTION\n" .
" (\n" .
helpFormatText($oManifest, $oDocRender, $hCommandHelp->{&CONFIG_HELP_DESCRIPTION}, 12, 119) . "\n" .
" )\n";
}
$strBuildSource .=
" )\n";
};
$strBuildSource .=
")\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_COMMAND}{&BLD_SOURCE} = $strBuildSource;
# Build option type constants
#-------------------------------------------------------------------------------------------------------------------------------
$rhEnum = $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_ENUM}{&BLDLCL_ENUM_OPTION_TYPE};
foreach my $strOptionType (cfgDefineOptionTypeList())
{
# Build C enum
my $strOptionTypeEnum = buildConfigDefineOptionTypeEnum($strOptionType);
push(@{$rhEnum->{&BLD_LIST}}, $strOptionTypeEnum);
};
# Build option constants and data
#-------------------------------------------------------------------------------------------------------------------------------
my $rhConfigDefine = cfgDefine();
$rhEnum = $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_ENUM}{&BLDLCL_ENUM_OPTION};
$strBuildSource =
"static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST\n" .
"(";
foreach my $strOption (sort(keys(%{$rhConfigDefine})))
{
# Get option help
my $hOptionHelp = $hConfigHelp->{&CONFIG_HELP_OPTION}{$strOption};
# Build C enum
my $strOptionEnum = buildConfigDefineOptionEnum($strOption);
push(@{$rhEnum->{&BLD_LIST}}, $strOptionEnum);
# Build option data
my $rhOption = $rhConfigDefine->{$strOption};
my $strOptionPrefix = $rhOption->{&CFGDEF_PREFIX};
$strBuildSource .=
"\n" .
" // " . (qw{-} x 125) . "\n" .
" CFGDEFDATA_OPTION\n" .
" (\n";
my $bRequired = $rhOption->{&CFGDEF_REQUIRED};
$strBuildSource .=
" CFGDEFDATA_OPTION_NAME(\"${strOption}\")\n" .
" CFGDEFDATA_OPTION_REQUIRED(" . ($bRequired ? 'true' : 'false') . ")\n" .
" CFGDEFDATA_OPTION_SECTION(cfgDefSection" .
(defined($rhOption->{&CFGDEF_SECTION}) ? ucfirst($rhOption->{&CFGDEF_SECTION}) : 'CommandLine') .
")\n" .
" CFGDEFDATA_OPTION_TYPE(" . buildConfigDefineOptionTypeEnum($rhOption->{&CFGDEF_TYPE}) . ")\n" .
" CFGDEFDATA_OPTION_INTERNAL(" . ($rhOption->{&CFGDEF_INTERNAL} ? 'true' : 'false') . ")\n" .
"\n" .
" CFGDEFDATA_OPTION_INDEX_TOTAL(" . $rhOption->{&CFGDEF_INDEX_TOTAL} . ")\n" .
" CFGDEFDATA_OPTION_SECURE(" . ($rhOption->{&CFGDEF_SECURE} ? 'true' : 'false') . ")\n";
if (defined($hOptionHelp))
{
$strBuildSource .=
"\n";
# Output section
my $strSection =
defined($hOptionHelp->{&CONFIG_HELP_SECTION}) ? $hOptionHelp->{&CONFIG_HELP_SECTION} : 'general';
if (length($strSection) > 72)
{
confess("section for option '${strOption}' may not be greater than 72 characters");
}
$strBuildSource .=
" CFGDEFDATA_OPTION_HELP_SECTION(\"${strSection}\")\n";
# Output summary
my $strSummary = helpFormatText($oManifest, $oDocRender, $hOptionHelp->{&CONFIG_HELP_SUMMARY}, 0, 72);
if (length($strSummary) > 74)
{
confess("summary for option '${strOption}' may not be greater than 72 characters");
}
$strBuildSource .=
" CFGDEFDATA_OPTION_HELP_SUMMARY(${strSummary})\n";
# Output description
$strBuildSource .=
" CFGDEFDATA_OPTION_HELP_DESCRIPTION\n" .
" (\n" .
helpFormatText($oManifest, $oDocRender, $hOptionHelp->{&CONFIG_HELP_DESCRIPTION}, 12, 119) . "\n" .
" )\n";
}
$strBuildSource .=
"\n" .
" CFGDEFDATA_OPTION_COMMAND_LIST\n" .
" (\n";
foreach my $strCommand (cfgDefineCommandList())
{
if (defined($rhOption->{&CFGDEF_COMMAND}{$strCommand}))
{
$strBuildSource .=
" CFGDEFDATA_OPTION_COMMAND(" . buildConfigDefineCommandEnum($strCommand) . ")\n";
}
}
$strBuildSource .=
" )\n";
# Render optional data
my $strBuildSourceOptional = renderOptional($rhOption, false, $hOptionHelp, $oManifest, $oDocRender);
# Render command overrides
foreach my $strCommand (cfgDefineCommandList())
{
my $strBuildSourceOptionalCommand;
my $rhCommand = $rhOption->{&CFGDEF_COMMAND}{$strCommand};
if (defined($rhCommand))
{
$strBuildSourceOptionalCommand = renderOptional(
$rhCommand, true, $hConfigHelp->{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption},
$oManifest, $oDocRender, $strCommand, $strOption);
if (defined($strBuildSourceOptionalCommand))
{
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) ? "\n" : '') .
" CFGDEFDATA_OPTION_OPTIONAL_COMMAND_OVERRIDE\n" .
" (\n" .
" CFGDEFDATA_OPTION_OPTIONAL_COMMAND(" . buildConfigDefineCommandEnum($strCommand) . ")\n" .
"\n" .
$strBuildSourceOptionalCommand .
" )\n";
}
}
};
if (defined($strBuildSourceOptional))
{
$strBuildSource .=
"\n" .
" CFGDEFDATA_OPTION_OPTIONAL_LIST\n" .
" (\n" .
$strBuildSourceOptional .
" )\n";
}
$strBuildSource .=
" )\n";
}
$strBuildSource .=
")\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION}{&BLD_SOURCE} = $strBuildSource;
return $rhBuild;
}
push @EXPORT, qw(buildConfigDefine);
1;
pgbackrest-release-2.24/build/lib/pgBackRestBuild/Config/BuildParse.pm 0000664 0000000 0000000 00000024640 13625315071 0025664 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Auto-Generate Option Definition for Parsing with getopt_long().
####################################################################################################################################
package pgBackRestBuild::Config::BuildParse;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Version;
use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::Build;
use pgBackRestBuild::Config::Data;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLDLCL_FILE_DEFINE => 'parse';
use constant BLDLCL_DATA_OPTION => '01-dataOption';
use constant BLDLCL_DATA_OPTION_RESOLVE => '01-dataOptionResolve';
####################################################################################################################################
# Definitions for constants and data to build
####################################################################################################################################
my $strSummary = 'Option Parse Definition';
my $rhBuild =
{
&BLD_FILE =>
{
&BLDLCL_FILE_DEFINE =>
{
&BLD_SUMMARY => $strSummary,
&BLD_DATA =>
{
&BLDLCL_DATA_OPTION =>
{
&BLD_SUMMARY => 'Option parse data',
},
&BLDLCL_DATA_OPTION_RESOLVE =>
{
&BLD_SUMMARY => 'Order for option parse resolution',
},
},
},
},
};
####################################################################################################################################
# Build configuration constants and data
####################################################################################################################################
sub buildConfigParse
{
# Build option parse list
#-------------------------------------------------------------------------------------------------------------------------------
my $rhConfigDefine = cfgDefine();
my $strBuildSource =
"static const struct option optionList[] =\n" .
"{";
foreach my $strOption (sort(keys(%{$rhConfigDefine})))
{
my $rhOption = $rhConfigDefine->{$strOption};
my $strOptionEnum = buildConfigOptionEnum($strOption);
my $strOptionArg = ($rhOption->{&CFGDEF_TYPE} ne CFGDEF_TYPE_BOOLEAN ? " .has_arg = required_argument,\n" : '');
my $strOptionPrefix = $rhConfigDefine->{$strOption}{&CFGDEF_PREFIX};
my @stryOptionName = ($strOption);
if (defined($rhOption->{&CFGDEF_NAME_ALT}))
{
foreach my $strOptionNameAlt (sort(keys(%{$rhOption->{&CFGDEF_NAME_ALT}})))
{
push(@stryOptionName, $strOptionNameAlt);
}
}
$strBuildSource .=
"\n" .
" // ${strOption} option" . (@stryOptionName > 1 ? ' and deprecations' : '') . "\n" .
" // " . (qw{-} x 125) . "\n";
for (my $iOptionIdx = 1; $iOptionIdx <= $rhOption->{&CFGDEF_INDEX_TOTAL}; $iOptionIdx++)
{
for (my $iOptionNameIdx = 0; $iOptionNameIdx < @stryOptionName; $iOptionNameIdx++)
{
my $strOptionName = $stryOptionName[$iOptionNameIdx];
my $rhNameAlt = $rhOption->{&CFGDEF_NAME_ALT}{$strOptionName};
# Skip alt name if it is not valid for this option index
if ($iOptionNameIdx > 0 && defined($rhNameAlt->{&CFGDEF_INDEX}) && $rhNameAlt->{&CFGDEF_INDEX} != $iOptionIdx)
{
next;
}
# Generate output name
my $strOptionNameOut = $strOptionName;
my $strOptionConst;
if (defined($strOptionPrefix))
{
if ($iOptionNameIdx == 0)
{
$strOptionNameOut =
"${strOptionPrefix}${iOptionIdx}-" . substr($strOptionName, length($strOptionPrefix) + 1);
}
else
{
$strOptionNameOut =~ s/\?/$iOptionIdx/g;
}
}
# Generate option value used for parsing (offset is added so options don't conflict with getopt_long return values)
my $strOptionFlag = 'PARSE_OPTION_FLAG |';
# Build option constant name if this is the current name for the option
if ($iOptionNameIdx == 0)
{
$strOptionConst = "CFGOPT_" . uc($strOptionNameOut);
$strOptionConst =~ s/\-/_/g;
}
# Else use bare string and mark as deprecated
else
{
$strOptionFlag .= ' PARSE_DEPRECATE_FLAG |';
}
my $strOptionVal =
($iOptionIdx > 1 ? "(" : '') . $strOptionEnum . ($iOptionIdx > 1 ? " + " . ($iOptionIdx - 1) . ')' : '');
# Add option
$strBuildSource .=
" {\n" .
" .name = " . (defined($strOptionConst) ? $strOptionConst : "\"${strOptionNameOut}\"") . ",\n" .
$strOptionArg .
" .val = ${strOptionFlag} ${strOptionVal},\n" .
" },\n";
# Add negation when defined
if ($rhOption->{&CFGDEF_NEGATE} &&
!($iOptionNameIdx > 0 && defined($rhNameAlt->{&CFGDEF_NEGATE}) && !$rhNameAlt->{&CFGDEF_NEGATE}))
{
$strBuildSource .=
" {\n" .
" .name = \"no-" .
(defined($strOptionConst) ? "\" ${strOptionConst}" : "${strOptionNameOut}\"") . ",\n" .
" .val = ${strOptionFlag} PARSE_NEGATE_FLAG | ${strOptionVal},\n" .
" },\n";
}
# Add reset when defined
if ($rhOption->{&CFGDEF_RESET} &&
!($iOptionNameIdx > 0 && defined($rhNameAlt->{&CFGDEF_RESET}) && !$rhNameAlt->{&CFGDEF_RESET}))
{
$strBuildSource .=
" {\n" .
" .name = \"reset-" .
(defined($strOptionConst) ? "\" ${strOptionConst}" : "${strOptionNameOut}\"") . ",\n" .
" .val = ${strOptionFlag} PARSE_RESET_FLAG | ${strOptionVal},\n" .
" },\n";
}
}
}
}
# The option list needs to be terminated or getopt_long will just keep on reading
$strBuildSource .=
" // Terminate option list\n" .
" {\n" .
" .name = NULL\n" .
" }\n" .
"};\n";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION}{&BLD_SOURCE} = $strBuildSource;
# Build option resolve order list. This allows the option validation in C to take place in a single pass.
#
# Always process the stanza option first since confusing error message are produced if it is missing.
#-------------------------------------------------------------------------------------------------------------------------------
my @stryResolveOrder = (buildConfigOptionEnum(CFGOPT_STANZA));
my $rhResolved = {&CFGOPT_STANZA => true};
my $bAllResolved;
do
{
# Assume that we will finish on this loop
$bAllResolved = true;
# Loop through all options
foreach my $strOption (sort(keys(%{$rhConfigDefine})))
{
my $bSkip = false;
# Check the default depend
my $strOptionDepend =
ref($rhConfigDefine->{$strOption}{&CFGDEF_DEPEND}) eq 'HASH' ?
$rhConfigDefine->{$strOption}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION} :
$rhConfigDefine->{$strOption}{&CFGDEF_DEPEND};
if (defined($strOptionDepend) && !$rhResolved->{$strOptionDepend})
{
# &log(WARN, "$strOptionDepend is not resolved");
$bSkip = true;
}
# Check the command depends
foreach my $strCommand (sort(keys(%{$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}})))
{
my $strOptionDepend =
ref($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND}) eq 'HASH' ?
$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION} :
$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND};
if (defined($strOptionDepend) && !$rhResolved->{$strOptionDepend})
{
$bSkip = true;
}
}
# If dependency was not found try again on the next loop
if ($bSkip)
{
$bAllResolved = false;
}
# Else add option to resolve order list
elsif (!$rhResolved->{$strOption})
{
$rhResolved->{$strOption} = true;
for (my $iIndex = 0; $iIndex < $rhConfigDefine->{$strOption}{&CFGDEF_INDEX_TOTAL}; $iIndex++)
{
push(@stryResolveOrder, buildConfigOptionEnum($strOption) . ($iIndex == 0 ? '' : " + $iIndex"));
}
}
}
}
while (!$bAllResolved);
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION_RESOLVE}{&BLD_SOURCE} =
"static const ConfigOption optionResolveOrder[] =\n" .
"{\n" .
" " . join(",\n ", @stryResolveOrder) . ",\n" .
"};\n";
return $rhBuild;
}
push @EXPORT, qw(buildConfigParse);
1;
pgbackrest-release-2.24/build/lib/pgBackRestBuild/Config/Data.pm 0000664 0000000 0000000 00000300325 13625315071 0024500 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Configuration Definition Data
#
# Contains the defines for options: which commands the option can/cannot be specified, for which commands it is required, default
# settings, types, ranges, whether the option is negatable, whether it has dependencies, etc. The initial section is the global
# section meaning the defines defined there apply to all commands listed for the option.
#
# CFGDEF_INHERIT:
# Inherit all definitions for the referenced option. Any definitions can be overridden.
#
# CFGDEF_COMMAND:
# List of commands the option can be used with this option. An empty hash signifies that the command does not deviate from the
# option defaults. Otherwise, overrides can be specified.
#
# NOTE: If the option (A) has a dependency on another option (B) then the CFGCMD_ must also be specified in the other option
# (B), else it will still error on the option (A).
#
# CFGDEF_REQUIRED:
# In global section:
# true - if the option does not have a default, then setting CFGDEF_REQUIRED in the global section means all commands
# listed in CFGDEF_COMMAND require the user to set it.
# false - no commands listed require it as an option but it can be set. This can be overridden for individual commands by
# setting CFGDEF_REQUIRED in the CFGDEF_COMMAND section.
# In CFGDEF_COMMAND section:
# true - the option must be set somehow for the command, either by default (CFGDEF_DEFAULT) or by the user.
# &CFGCMD_CHECK =>
# {
# &CFGDEF_REQUIRED => true
# },
# false - mainly used for overriding the CFGDEF_REQUIRED in the global section.
#
# CFGDEF_DEFAULT:
# Sets a default for the option for all commands if listed in the global section, or for specific commands if listed in the
# CFGDEF_COMMAND section. All boolean types require a default.
#
# CFGDEF_NEGATE:
# The option can be negated with "no" e.g. --no-compress. This applies to options that are only valid on the command line (i.e.
# no config section defined) and if not specifically defined, the default is false. All config file boolean options are
# automatically negatable.
#
# CFGDEF_RESET:
# The option can be reset to default even if the default is undefined.
#
# CFGDEF_DEPEND:
# Specify the dependencies this option has on another option. All commands listed for this option must also be listed in the
# dependent option(s).
# CFGDEF_DEPEND_LIST further defines the allowable settings for the depended option.
#
# CFGDEF_ALLOW_LIST:
# Lists the allowable settings for the option.
#
# CFGDEF_INTERNAL:
# Option is used by the command internally but is not exposed in the documentation. This is useful for commands that need to know
# where they are running by looking at other options in the config. Also good for test options.
####################################################################################################################################
package pgBackRestBuild::Config::Data;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname basename);
use Getopt::Long qw(GetOptions);
use Storable qw(dclone);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Base;
use pgBackRest::Common::Log;
use pgBackRest::Common::Wait;
use pgBackRest::Version;
####################################################################################################################################
# Command constants - commands that are allowed in the exe
####################################################################################################################################
use constant CFGCMD_ARCHIVE_GET => 'archive-get';
push @EXPORT, qw(CFGCMD_ARCHIVE_GET);
use constant CFGCMD_ARCHIVE_PUSH => 'archive-push';
push @EXPORT, qw(CFGCMD_ARCHIVE_PUSH);
use constant CFGCMD_BACKUP => 'backup';
push @EXPORT, qw(CFGCMD_BACKUP);
use constant CFGCMD_CHECK => 'check';
push @EXPORT, qw(CFGCMD_CHECK);
use constant CFGCMD_EXPIRE => 'expire';
push @EXPORT, qw(CFGCMD_EXPIRE);
use constant CFGCMD_HELP => 'help';
push @EXPORT, qw(CFGCMD_HELP);
use constant CFGCMD_INFO => 'info';
push @EXPORT, qw(CFGCMD_INFO);
use constant CFGCMD_RESTORE => 'restore';
push @EXPORT, qw(CFGCMD_RESTORE);
use constant CFGCMD_STANZA_CREATE => 'stanza-create';
push @EXPORT, qw(CFGCMD_STANZA_CREATE);
use constant CFGCMD_STANZA_DELETE => 'stanza-delete';
push @EXPORT, qw(CFGCMD_STANZA_DELETE);
use constant CFGCMD_STANZA_UPGRADE => 'stanza-upgrade';
push @EXPORT, qw(CFGCMD_STANZA_UPGRADE);
use constant CFGCMD_START => 'start';
push @EXPORT, qw(CFGCMD_START);
use constant CFGCMD_STOP => 'stop';
push @EXPORT, qw(CFGCMD_STOP);
use constant CFGCMD_STORAGE_LIST => 'ls';
push @EXPORT, qw(CFGCMD_STORAGE_LIST);
use constant CFGCMD_VERSION => 'version';
push @EXPORT, qw(CFGCMD_VERSION);
####################################################################################################################################
# Option constants - options that are allowed for commands
####################################################################################################################################
# Command-line only options
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_CONFIG => 'config';
push @EXPORT, qw(CFGOPT_CONFIG);
use constant CFGOPT_CONFIG_PATH => 'config-path';
push @EXPORT, qw(CFGOPT_CONFIG_PATH);
use constant CFGOPT_CONFIG_INCLUDE_PATH => 'config-include-path';
push @EXPORT, qw(CFGOPT_CONFIG_INCLUDE_PATH);
use constant CFGOPT_DELTA => 'delta';
push @EXPORT, qw(CFGOPT_DELTA);
use constant CFGOPT_FORCE => 'force';
push @EXPORT, qw(CFGOPT_FORCE);
use constant CFGOPT_ONLINE => 'online';
push @EXPORT, qw(CFGOPT_ONLINE);
use constant CFGOPT_SET => 'set';
push @EXPORT, qw(CFGOPT_SET);
use constant CFGOPT_STANZA => 'stanza';
push @EXPORT, qw(CFGOPT_STANZA);
use constant CFGOPT_TARGET => 'target';
push @EXPORT, qw(CFGOPT_TARGET);
use constant CFGOPT_TARGET_EXCLUSIVE => 'target-exclusive';
push @EXPORT, qw(CFGOPT_TARGET_EXCLUSIVE);
use constant CFGOPT_TARGET_ACTION => 'target-action';
push @EXPORT, qw(CFGOPT_TARGET_ACTION);
use constant CFGOPT_TARGET_TIMELINE => 'target-timeline';
push @EXPORT, qw(CFGOPT_TARGET_TIMELINE);
use constant CFGOPT_TYPE => 'type';
push @EXPORT, qw(CFGOPT_TYPE);
use constant CFGOPT_OUTPUT => 'output';
push @EXPORT, qw(CFGOPT_OUTPUT);
# Command-line only local/remote options
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_PROCESS => 'process';
push @EXPORT, qw(CFGOPT_PROCESS);
use constant CFGOPT_HOST_ID => 'host-id';
push @EXPORT, qw(CFGOPT_HOST_ID);
use constant CFGOPT_REMOTE_TYPE => 'remote-type';
push @EXPORT, qw(CFGOPT_REMOTE_TYPE);
# Command-line only storage options
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_FILTER => 'filter';
push @EXPORT, qw(CFGOPT_FILTER);
use constant CFGOPT_RECURSE => 'recurse';
push @EXPORT, qw(CFGOPT_RECURSE);
use constant CFGOPT_SORT => 'sort';
push @EXPORT, qw(CFGOPT_SORT);
# General options
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_ARCHIVE_TIMEOUT => 'archive-timeout';
push @EXPORT, qw(CFGOPT_ARCHIVE_TIMEOUT);
use constant CFGOPT_BUFFER_SIZE => 'buffer-size';
push @EXPORT, qw(CFGOPT_BUFFER_SIZE);
use constant CFGOPT_DB_TIMEOUT => 'db-timeout';
push @EXPORT, qw(CFGOPT_DB_TIMEOUT);
use constant CFGOPT_COMPRESS => 'compress';
push @EXPORT, qw(CFGOPT_COMPRESS);
use constant CFGOPT_COMPRESS_LEVEL => 'compress-level';
push @EXPORT, qw(CFGOPT_COMPRESS_LEVEL);
use constant CFGOPT_COMPRESS_LEVEL_NETWORK => 'compress-level-network';
push @EXPORT, qw(CFGOPT_COMPRESS_LEVEL_NETWORK);
use constant CFGOPT_NEUTRAL_UMASK => 'neutral-umask';
push @EXPORT, qw(CFGOPT_NEUTRAL_UMASK);
use constant CFGOPT_PROTOCOL_TIMEOUT => 'protocol-timeout';
push @EXPORT, qw(CFGOPT_PROTOCOL_TIMEOUT);
use constant CFGOPT_PROCESS_MAX => 'process-max';
push @EXPORT, qw(CFGOPT_PROCESS_MAX);
# Commands
use constant CFGOPT_CMD_SSH => 'cmd-ssh';
push @EXPORT, qw(CFGOPT_CMD_SSH);
# Paths
use constant CFGOPT_LOCK_PATH => 'lock-path';
push @EXPORT, qw(CFGOPT_LOCK_PATH);
use constant CFGOPT_LOG_PATH => 'log-path';
push @EXPORT, qw(CFGOPT_LOG_PATH);
use constant CFGOPT_SPOOL_PATH => 'spool-path';
push @EXPORT, qw(CFGOPT_SPOOL_PATH);
# Logging
use constant CFGOPT_LOG_LEVEL_CONSOLE => 'log-level-console';
push @EXPORT, qw(CFGOPT_LOG_LEVEL_CONSOLE);
use constant CFGOPT_LOG_LEVEL_FILE => 'log-level-file';
push @EXPORT, qw(CFGOPT_LOG_LEVEL_FILE);
use constant CFGOPT_LOG_LEVEL_STDERR => 'log-level-stderr';
push @EXPORT, qw(CFGOPT_LOG_LEVEL_STDERR);
use constant CFGOPT_LOG_SUBPROCESS => 'log-subprocess';
push @EXPORT, qw(CFGOPT_LOG_SUBPROCESS);
use constant CFGOPT_LOG_TIMESTAMP => 'log-timestamp';
push @EXPORT, qw(CFGOPT_LOG_TIMESTAMP);
# Repository options
#-----------------------------------------------------------------------------------------------------------------------------------
# Determines how many repositories can be configured
use constant CFGDEF_INDEX_REPO => 1;
# Prefix that must be used by all repo options that allow multiple configurations
use constant CFGDEF_PREFIX_REPO => 'repo';
push @EXPORT, qw(CFGDEF_PREFIX_REPO);
# Repository General
use constant CFGOPT_REPO_CIPHER_TYPE => CFGDEF_PREFIX_REPO . '-cipher-type';
push @EXPORT, qw(CFGOPT_REPO_CIPHER_TYPE);
use constant CFGOPT_REPO_CIPHER_PASS => CFGDEF_PREFIX_REPO . '-cipher-pass';
push @EXPORT, qw(CFGOPT_REPO_CIPHER_PASS);
use constant CFGOPT_REPO_HARDLINK => CFGDEF_PREFIX_REPO . '-hardlink';
push @EXPORT, qw(CFGOPT_REPO_HARDLINK);
use constant CFGOPT_REPO_PATH => CFGDEF_PREFIX_REPO . '-path';
push @EXPORT, qw(CFGOPT_REPO_PATH);
use constant CFGOPT_REPO_TYPE => CFGDEF_PREFIX_REPO . '-type';
push @EXPORT, qw(CFGOPT_REPO_TYPE);
# Repository Retention
use constant CFGOPT_REPO_RETENTION_ARCHIVE => CFGDEF_PREFIX_REPO . '-retention-archive';
push @EXPORT, qw(CFGOPT_REPO_RETENTION_ARCHIVE);
use constant CFGOPT_REPO_RETENTION_ARCHIVE_TYPE => CFGDEF_PREFIX_REPO . '-retention-archive-type';
push @EXPORT, qw(CFGOPT_REPO_RETENTION_ARCHIVE_TYPE);
use constant CFGOPT_REPO_RETENTION_DIFF => CFGDEF_PREFIX_REPO . '-retention-diff';
push @EXPORT, qw(CFGOPT_REPO_RETENTION_DIFF);
use constant CFGOPT_REPO_RETENTION_FULL => CFGDEF_PREFIX_REPO . '-retention-full';
push @EXPORT, qw(CFGOPT_REPO_RETENTION_FULL);
# Repository Host
use constant CFGOPT_REPO_HOST => CFGDEF_PREFIX_REPO . '-host';
push @EXPORT, qw(CFGOPT_REPO_HOST);
use constant CFGOPT_REPO_HOST_CMD => CFGOPT_REPO_HOST . '-cmd';
push @EXPORT, qw(CFGOPT_REPO_HOST_CMD);
use constant CFGOPT_REPO_HOST_CONFIG => CFGOPT_REPO_HOST . '-config';
push @EXPORT, qw(CFGOPT_REPO_HOST_CONFIG);
use constant CFGOPT_REPO_HOST_CONFIG_INCLUDE_PATH => CFGOPT_REPO_HOST_CONFIG . '-include-path';
push @EXPORT, qw(CFGOPT_REPO_HOST_CONFIG_INCLUDE_PATH);
use constant CFGOPT_REPO_HOST_CONFIG_PATH => CFGOPT_REPO_HOST_CONFIG . '-path';
push @EXPORT, qw(CFGOPT_REPO_HOST_CONFIG_PATH);
use constant CFGOPT_REPO_HOST_PORT => CFGOPT_REPO_HOST . '-port';
push @EXPORT, qw(CFGOPT_REPO_HOST_PORT);
use constant CFGOPT_REPO_HOST_USER => CFGOPT_REPO_HOST . '-user';
push @EXPORT, qw(CFGOPT_REPO_HOST_USER);
# Repository S3
use constant CFGDEF_REPO_S3 => CFGDEF_PREFIX_REPO . '-s3';
use constant CFGOPT_REPO_S3_KEY => CFGDEF_REPO_S3 . '-key';
push @EXPORT, qw(CFGOPT_REPO_S3_KEY);
use constant CFGOPT_REPO_S3_KEY_SECRET => CFGDEF_REPO_S3 . '-key-secret';
push @EXPORT, qw(CFGOPT_REPO_S3_KEY_SECRET);
use constant CFGOPT_REPO_S3_BUCKET => CFGDEF_REPO_S3 . '-bucket';
push @EXPORT, qw(CFGOPT_REPO_S3_BUCKET);
use constant CFGOPT_REPO_S3_CA_FILE => CFGDEF_REPO_S3 . '-ca-file';
push @EXPORT, qw(CFGOPT_REPO_S3_CA_FILE);
use constant CFGOPT_REPO_S3_CA_PATH => CFGDEF_REPO_S3 . '-ca-path';
push @EXPORT, qw(CFGOPT_REPO_S3_CA_PATH);
use constant CFGOPT_REPO_S3_ENDPOINT => CFGDEF_REPO_S3 . '-endpoint';
push @EXPORT, qw(CFGOPT_REPO_S3_ENDPOINT);
use constant CFGOPT_REPO_S3_HOST => CFGDEF_REPO_S3 . '-host';
push @EXPORT, qw(CFGOPT_REPO_S3_HOST);
use constant CFGOPT_REPO_S3_PORT => CFGDEF_REPO_S3 . '-port';
push @EXPORT, qw(CFGOPT_REPO_S3_PORT);
use constant CFGOPT_REPO_S3_REGION => CFGDEF_REPO_S3 . '-region';
push @EXPORT, qw(CFGOPT_REPO_S3_REGION);
use constant CFGOPT_REPO_S3_TOKEN => CFGDEF_REPO_S3 . '-token';
push @EXPORT, qw(CFGOPT_REPO_S3_TOKEN);
use constant CFGOPT_REPO_S3_URI_STYLE => CFGDEF_REPO_S3 . '-uri-style';
push @EXPORT, qw(CFGOPT_REPO_S3_URI_STYLE);
use constant CFGOPT_REPO_S3_VERIFY_TLS => CFGDEF_REPO_S3 . '-verify-tls';
push @EXPORT, qw(CFGOPT_REPO_S3_VERIFY_TLS);
# Archive options
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_ARCHIVE_ASYNC => 'archive-async';
push @EXPORT, qw(CFGOPT_ARCHIVE_ASYNC);
use constant CFGOPT_ARCHIVE_GET_QUEUE_MAX => 'archive-get-queue-max';
push @EXPORT, qw(CFGOPT_ARCHIVE_GET_QUEUE_MAX);
use constant CFGOPT_ARCHIVE_PUSH_QUEUE_MAX => 'archive-push-queue-max';
push @EXPORT, qw(CFGOPT_ARCHIVE_PUSH_QUEUE_MAX);
# Backup options
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_ARCHIVE_CHECK => 'archive-check';
push @EXPORT, qw(CFGOPT_ARCHIVE_CHECK);
use constant CFGOPT_ARCHIVE_COPY => 'archive-copy';
push @EXPORT, qw(CFGOPT_ARCHIVE_COPY);
use constant CFGOPT_BACKUP_STANDBY => 'backup-standby';
push @EXPORT, qw(CFGOPT_BACKUP_STANDBY);
use constant CFGOPT_CHECKSUM_PAGE => 'checksum-page';
push @EXPORT, qw(CFGOPT_CHECKSUM_PAGE);
use constant CFGOPT_EXCLUDE => 'exclude';
push @EXPORT, qw(CFGOPT_EXCLUDE);
use constant CFGOPT_MANIFEST_SAVE_THRESHOLD => 'manifest-save-threshold';
push @EXPORT, qw(CFGOPT_MANIFEST_SAVE_THRESHOLD);
use constant CFGOPT_RESUME => 'resume';
push @EXPORT, qw(CFGOPT_RESUME);
use constant CFGOPT_START_FAST => 'start-fast';
push @EXPORT, qw(CFGOPT_START_FAST);
use constant CFGOPT_STOP_AUTO => 'stop-auto';
push @EXPORT, qw(CFGOPT_STOP_AUTO);
# Restore options
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_DB_INCLUDE => 'db-include';
push @EXPORT, qw(CFGOPT_DB_INCLUDE);
use constant CFGOPT_LINK_ALL => 'link-all';
push @EXPORT, qw(CFGOPT_LINK_ALL);
use constant CFGOPT_LINK_MAP => 'link-map';
push @EXPORT, qw(CFGOPT_LINK_MAP);
use constant CFGOPT_TABLESPACE_MAP_ALL => 'tablespace-map-all';
push @EXPORT, qw(CFGOPT_TABLESPACE_MAP_ALL);
use constant CFGOPT_TABLESPACE_MAP => 'tablespace-map';
push @EXPORT, qw(CFGOPT_TABLESPACE_MAP);
use constant CFGOPT_RECOVERY_OPTION => 'recovery-option';
push @EXPORT, qw(CFGOPT_RECOVERY_OPTION);
# Stanza options
#-----------------------------------------------------------------------------------------------------------------------------------
# Determines how many databases can be configured
use constant CFGDEF_INDEX_PG => 8;
push @EXPORT, qw(CFGDEF_INDEX_PG);
# Prefix that must be used by all db options that allow multiple configurations
use constant CFGDEF_PREFIX_PG => 'pg';
push @EXPORT, qw(CFGDEF_PREFIX_PG);
use constant CFGOPT_PG_HOST => CFGDEF_PREFIX_PG . '-host';
push @EXPORT, qw(CFGOPT_PG_HOST);
use constant CFGOPT_PG_HOST_CMD => CFGOPT_PG_HOST . '-cmd';
push @EXPORT, qw(CFGOPT_PG_HOST_CMD);
use constant CFGOPT_PG_HOST_CONFIG => CFGOPT_PG_HOST . '-config';
push @EXPORT, qw(CFGOPT_PG_HOST_CONFIG);
use constant CFGOPT_PG_HOST_CONFIG_INCLUDE_PATH => CFGOPT_PG_HOST_CONFIG . '-include-path';
push @EXPORT, qw(CFGOPT_PG_HOST_CONFIG_INCLUDE_PATH);
use constant CFGOPT_PG_HOST_CONFIG_PATH => CFGOPT_PG_HOST_CONFIG . '-path';
push @EXPORT, qw(CFGOPT_PG_HOST_CONFIG_PATH);
use constant CFGOPT_PG_HOST_PORT => CFGOPT_PG_HOST . '-port';
push @EXPORT, qw(CFGOPT_PG_HOST_PORT);
use constant CFGOPT_PG_HOST_USER => CFGOPT_PG_HOST . '-user';
push @EXPORT, qw(CFGOPT_PG_HOST_USER);
use constant CFGOPT_PG_PATH => CFGDEF_PREFIX_PG . '-path';
push @EXPORT, qw(CFGOPT_PG_PATH);
use constant CFGOPT_PG_PORT => CFGDEF_PREFIX_PG . '-port';
push @EXPORT, qw(CFGOPT_PG_PORT);
use constant CFGOPT_PG_SOCKET_PATH => CFGDEF_PREFIX_PG . '-socket-path';
push @EXPORT, qw(CFGOPT_PG_SOCKET_PATH);
use constant CFGOPT_PG_USER => CFGDEF_PREFIX_PG . '-user';
push @EXPORT, qw(CFGOPT_PG_USER);
####################################################################################################################################
# Option values - for options that have a specific list of allowed values
####################################################################################################################################
# Storage types
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_STORAGE_TYPE_PG => 'pg';
push @EXPORT, qw(CFGOPTVAL_STORAGE_TYPE_DB);
use constant CFGOPTVAL_STORAGE_TYPE_REPO => 'repo';
push @EXPORT, qw(CFGOPTVAL_STORAGE_TYPE_BACKUP);
# Backup type
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_BACKUP_TYPE_FULL => 'full';
push @EXPORT, qw(CFGOPTVAL_BACKUP_TYPE_FULL);
use constant CFGOPTVAL_BACKUP_TYPE_DIFF => 'diff';
push @EXPORT, qw(CFGOPTVAL_BACKUP_TYPE_DIFF);
use constant CFGOPTVAL_BACKUP_TYPE_INCR => 'incr';
push @EXPORT, qw(CFGOPTVAL_BACKUP_TYPE_INCR);
# Repo type
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_REPO_TYPE_CIFS => 'cifs';
push @EXPORT, qw(CFGOPTVAL_REPO_TYPE_CIFS);
use constant CFGOPTVAL_REPO_TYPE_POSIX => 'posix';
push @EXPORT, qw(CFGOPTVAL_REPO_TYPE_POSIX);
use constant CFGOPTVAL_REPO_TYPE_S3 => 's3';
push @EXPORT, qw(CFGOPTVAL_REPO_TYPE_S3);
# Repo encryption type
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_REPO_CIPHER_TYPE_NONE => 'none';
push @EXPORT, qw(CFGOPTVAL_REPO_CIPHER_TYPE_NONE);
use constant CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC => 'aes-256-cbc';
push @EXPORT, qw(CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC);
# Repo S3 URI style
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_REPO_S3_URI_STYLE_HOST => 'host';
push @EXPORT, qw(CFGOPTVAL_REPO_S3_URI_STYLE_HOST);
use constant CFGOPTVAL_REPO_S3_URI_STYLE_PATH => 'path';
push @EXPORT, qw(CFGOPTVAL_REPO_S3_URI_STYLE_PATH);
# Info output
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_OUTPUT_TEXT => 'text';
push @EXPORT, qw(CFGOPTVAL_OUTPUT_TEXT);
use constant CFGOPTVAL_OUTPUT_JSON => 'json';
push @EXPORT, qw(CFGOPTVAL_OUTPUT_JSON);
# Restore type
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_RESTORE_TYPE_NAME => 'name';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_NAME);
use constant CFGOPTVAL_RESTORE_TYPE_TIME => 'time';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_TIME);
use constant CFGOPTVAL_RESTORE_TYPE_XID => 'xid';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_XID);
use constant CFGOPTVAL_RESTORE_TYPE_PRESERVE => 'preserve';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_PRESERVE);
use constant CFGOPTVAL_RESTORE_TYPE_NONE => 'none';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_NONE);
use constant CFGOPTVAL_RESTORE_TYPE_IMMEDIATE => 'immediate';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_IMMEDIATE);
use constant CFGOPTVAL_RESTORE_TYPE_DEFAULT => 'default';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_DEFAULT);
use constant CFGOPTVAL_RESTORE_TYPE_STANDBY => 'standby';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_STANDBY);
# Restore target action
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_RESTORE_TARGET_ACTION_PAUSE => 'pause';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TARGET_ACTION_PAUSE);
use constant CFGOPTVAL_RESTORE_TARGET_ACTION_PROMOTE => 'promote';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TARGET_ACTION_PROMOTE);
use constant CFGOPTVAL_RESTORE_TARGET_ACTION_SHUTDOWN => 'shutdown';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TARGET_ACTION_SHUTDOWN);
####################################################################################################################################
# Option defaults - only defined here when the default is used in more than one place
####################################################################################################################################
use constant CFGDEF_DEFAULT_BUFFER_SIZE_MIN => 16384;
use constant CFGDEF_DEFAULT_COMPRESS_LEVEL_MIN => 0;
use constant CFGDEF_DEFAULT_COMPRESS_LEVEL_MAX => 9;
use constant CFGDEF_DEFAULT_CONFIG_PATH => '/etc/' . PROJECT_EXE;
use constant CFGDEF_DEFAULT_CONFIG => CFGDEF_DEFAULT_CONFIG_PATH . '/' . PROJECT_CONF;
use constant CFGDEF_DEFAULT_CONFIG_INCLUDE_PATH => CFGDEF_DEFAULT_CONFIG_PATH . '/conf.d';
use constant CFGDEF_DEFAULT_DB_TIMEOUT => 1800;
use constant CFGDEF_DEFAULT_DB_TIMEOUT_MIN => WAIT_TIME_MINIMUM;
use constant CFGDEF_DEFAULT_DB_TIMEOUT_MAX => 86400 * 7;
use constant CFGDEF_DEFAULT_PROTOCOL_PORT_MIN => 0;
use constant CFGDEF_DEFAULT_PROTOCOL_PORT_MAX => 65535;
use constant CFGDEF_DEFAULT_RETENTION_MIN => 1;
use constant CFGDEF_DEFAULT_RETENTION_MAX => 9999999;
####################################################################################################################################
# Option definition constants - defines, types, sections, etc.
####################################################################################################################################
# Command defines
#-----------------------------------------------------------------------------------------------------------------------------------
# Does this command log to a file? This is the default behavior, but it can be overridden in code by calling logFileInit(). The
# default is true.
use constant CFGDEF_LOG_FILE => 'log-file';
push @EXPORT, qw(CFGDEF_LOG_FILE);
# Defines the log level to use for default messages that are output for every command. For example, the log message that lists all
# the options passed is usually output at the info level, but that might not be appropriate for some commands, such as info. Allow
# the log level to be lowered so these common messages will not be emitted where they might be distracting.
use constant CFGDEF_LOG_LEVEL_DEFAULT => 'log-level-default';
push @EXPORT, qw(CFGDEF_LOG_LEVEL_DEFAULT);
# Does the command require a lock? This doesn't mean a lock can't be taken explicitly later, just controls whether a lock will be
# acquired as soon at the command starts.
use constant CFGDEF_LOCK_REQUIRED => 'lock-required';
push @EXPORT, qw(CFGDEF_LOCK_REQUIRED);
# Does the command require a lock on the remote? The lock will only be acquired for process id 0.
use constant CFGDEF_LOCK_REMOTE_REQUIRED => 'lock-remote-required';
push @EXPORT, qw(CFGDEF_LOCK_REMOTE_REQUIRED);
# What type of lock is required?
use constant CFGDEF_LOCK_TYPE => 'lock-type';
push @EXPORT, qw(CFGDEF_LOCK_TYPE);
use constant CFGDEF_LOCK_TYPE_ARCHIVE => 'archive';
push @EXPORT, qw(CFGDEF_LOCK_TYPE_ARCHIVE);
use constant CFGDEF_LOCK_TYPE_BACKUP => 'backup';
push @EXPORT, qw(CFGDEF_LOCK_TYPE_BACKUP);
use constant CFGDEF_LOCK_TYPE_ALL => 'all';
push @EXPORT, qw(CFGDEF_LOCK_TYPE_ALL);
use constant CFGDEF_LOCK_TYPE_NONE => 'none';
push @EXPORT, qw(CFGDEF_LOCK_TYPE_NONE);
# Does the command allow parameters? If not then the config parser will automatically error out if parameters are detected. If so,
# then the command is responsible for ensuring that the parameters are valid.
use constant CFGDEF_PARAMETER_ALLOWED => 'parameter-allowed';
push @EXPORT, qw(CFGDEF_PARAMETER_ALLOWED);
# Option defines
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGDEF_ALLOW_LIST => 'allow-list';
push @EXPORT, qw(CFGDEF_ALLOW_LIST);
use constant CFGDEF_ALLOW_RANGE => 'allow-range';
push @EXPORT, qw(CFGDEF_ALLOW_RANGE);
use constant CFGDEF_DEFAULT => 'default';
push @EXPORT, qw(CFGDEF_DEFAULT);
use constant CFGDEF_DEPEND => 'depend';
push @EXPORT, qw(CFGDEF_DEPEND);
use constant CFGDEF_DEPEND_OPTION => 'depend-option';
push @EXPORT, qw(CFGDEF_DEPEND_OPTION);
use constant CFGDEF_DEPEND_LIST => 'depend-list';
push @EXPORT, qw(CFGDEF_DEPEND_LIST);
use constant CFGDEF_INDEX => 'index';
push @EXPORT, qw(CFGDEF_INDEX);
use constant CFGDEF_INDEX_TOTAL => 'indexTotal';
push @EXPORT, qw(CFGDEF_INDEX_TOTAL);
use constant CFGDEF_INHERIT => 'inherit';
push @EXPORT, qw(CFGDEF_INHERIT);
use constant CFGDEF_INTERNAL => 'internal';
push @EXPORT, qw(CFGDEF_INTERNAL);
use constant CFGDEF_NAME_ALT => 'name-alt';
push @EXPORT, qw(CFGDEF_NAME_ALT);
use constant CFGDEF_NEGATE => 'negate';
push @EXPORT, qw(CFGDEF_NEGATE);
use constant CFGDEF_PREFIX => 'prefix';
push @EXPORT, qw(CFGDEF_PREFIX);
use constant CFGDEF_COMMAND => 'command';
push @EXPORT, qw(CFGDEF_COMMAND);
use constant CFGDEF_REQUIRED => 'required';
push @EXPORT, qw(CFGDEF_REQUIRED);
use constant CFGDEF_RESET => 'reset';
push @EXPORT, qw(CFGDEF_RESET);
use constant CFGDEF_SECTION => 'section';
push @EXPORT, qw(CFGDEF_SECTION);
use constant CFGDEF_SECURE => 'secure';
push @EXPORT, qw(CFGDEF_SECURE);
use constant CFGDEF_TYPE => 'type';
push @EXPORT, qw(CFGDEF_TYPE);
# Option types
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGDEF_TYPE_BOOLEAN => 'boolean';
push @EXPORT, qw(CFGDEF_TYPE_BOOLEAN);
use constant CFGDEF_TYPE_FLOAT => 'float';
push @EXPORT, qw(CFGDEF_TYPE_FLOAT);
use constant CFGDEF_TYPE_HASH => 'hash';
push @EXPORT, qw(CFGDEF_TYPE_HASH);
use constant CFGDEF_TYPE_INTEGER => 'integer';
push @EXPORT, qw(CFGDEF_TYPE_INTEGER);
use constant CFGDEF_TYPE_LIST => 'list';
push @EXPORT, qw(CFGDEF_TYPE_LIST);
use constant CFGDEF_TYPE_PATH => 'path';
push @EXPORT, qw(CFGDEF_TYPE_PATH);
use constant CFGDEF_TYPE_STRING => 'string';
push @EXPORT, qw(CFGDEF_TYPE_STRING);
use constant CFGDEF_TYPE_SIZE => 'size';
push @EXPORT, qw(CFGDEF_TYPE_SIZE);
# Option config sections
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGDEF_SECTION_GLOBAL => 'global';
push @EXPORT, qw(CFGDEF_SECTION_GLOBAL);
use constant CFGDEF_SECTION_STANZA => 'stanza';
push @EXPORT, qw(CFGDEF_SECTION_STANZA);
####################################################################################################################################
# Command definition data
####################################################################################################################################
my $rhCommandDefine =
{
&CFGCMD_ARCHIVE_GET =>
{
&CFGDEF_LOG_FILE => false,
&CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_ARCHIVE,
&CFGDEF_PARAMETER_ALLOWED => true,
},
&CFGCMD_ARCHIVE_PUSH =>
{
&CFGDEF_LOG_FILE => false,
&CFGDEF_LOCK_REMOTE_REQUIRED => true,
&CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_ARCHIVE,
&CFGDEF_PARAMETER_ALLOWED => true,
},
&CFGCMD_BACKUP =>
{
&CFGDEF_LOCK_REQUIRED => true,
&CFGDEF_LOCK_REMOTE_REQUIRED => true,
&CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_BACKUP,
},
&CFGCMD_CHECK =>
{
&CFGDEF_LOG_FILE => false,
},
&CFGCMD_EXPIRE =>
{
&CFGDEF_LOCK_REQUIRED => true,
&CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_BACKUP,
},
&CFGCMD_HELP =>
{
&CFGDEF_LOG_FILE => false,
&CFGDEF_LOG_LEVEL_DEFAULT => DEBUG,
&CFGDEF_PARAMETER_ALLOWED => true,
},
&CFGCMD_INFO =>
{
&CFGDEF_LOG_FILE => false,
&CFGDEF_LOG_LEVEL_DEFAULT => DEBUG,
},
&CFGCMD_RESTORE =>
{
},
&CFGCMD_STANZA_CREATE =>
{
&CFGDEF_LOCK_REQUIRED => true,
&CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_ALL,
},
&CFGCMD_STANZA_DELETE =>
{
&CFGDEF_LOCK_REQUIRED => true,
&CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_ALL,
},
&CFGCMD_STANZA_UPGRADE =>
{
&CFGDEF_LOCK_REQUIRED => true,
&CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_ALL,
},
&CFGCMD_START =>
{
},
&CFGCMD_STOP =>
{
},
&CFGCMD_STORAGE_LIST =>
{
&CFGDEF_INTERNAL => true,
&CFGDEF_LOG_FILE => false,
&CFGDEF_LOG_LEVEL_DEFAULT => DEBUG,
&CFGDEF_PARAMETER_ALLOWED => true,
},
&CFGCMD_VERSION =>
{
&CFGDEF_LOG_FILE => false,
&CFGDEF_LOG_LEVEL_DEFAULT => DEBUG,
},
};
####################################################################################################################################
# Option definition data
####################################################################################################################################
my %hConfigDefine =
(
# Command-line only options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_CONFIG =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG,
&CFGDEF_NEGATE => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_CONFIG_INCLUDE_PATH =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_INHERIT => CFGOPT_CONFIG,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG_INCLUDE_PATH,
&CFGDEF_NEGATE => false,
},
&CFGOPT_CONFIG_PATH =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_INHERIT => CFGOPT_CONFIG,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG_PATH,
&CFGDEF_NEGATE => false,
},
&CFGOPT_FORCE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP =>
{
&CFGDEF_DEFAULT => false,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_ONLINE,
&CFGDEF_DEPEND_LIST => [false],
},
},
&CFGCMD_RESTORE =>
{
&CFGDEF_DEFAULT => false,
},
&CFGCMD_STANZA_CREATE =>
{
&CFGDEF_DEFAULT => false,
&CFGDEF_INTERNAL => true,
},
&CFGCMD_STANZA_DELETE =>
{
&CFGDEF_DEFAULT => false,
},
&CFGCMD_STOP =>
{
&CFGDEF_DEFAULT => false
}
}
},
&CFGOPT_ONLINE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_NEGATE => true,
&CFGDEF_DEFAULT => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_UPGRADE => {},
}
},
&CFGOPT_SET =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE =>
{
&CFGDEF_DEFAULT => 'latest',
},
&CFGCMD_INFO =>
{
&CFGDEF_REQUIRED => false,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_STANZA,
},
},
}
},
&CFGOPT_STANZA =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO =>
{
&CFGDEF_REQUIRED => false
},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START =>
{
&CFGDEF_REQUIRED => false
},
&CFGCMD_STOP =>
{
&CFGDEF_REQUIRED => false
}
}
},
&CFGOPT_TARGET =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE =>
{
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_TYPE,
&CFGDEF_DEPEND_LIST =>
[
&CFGOPTVAL_RESTORE_TYPE_NAME,
&CFGOPTVAL_RESTORE_TYPE_TIME,
&CFGOPTVAL_RESTORE_TYPE_XID,
],
},
},
},
},
&CFGOPT_TARGET_EXCLUSIVE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE =>
{
&CFGDEF_DEFAULT => false,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_TYPE,
&CFGDEF_DEPEND_LIST =>
[
&CFGOPTVAL_RESTORE_TYPE_TIME,
&CFGOPTVAL_RESTORE_TYPE_XID,
],
},
},
},
},
&CFGOPT_TARGET_ACTION =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE =>
{
&CFGDEF_DEFAULT => CFGOPTVAL_RESTORE_TARGET_ACTION_PAUSE,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_RESTORE_TARGET_ACTION_PAUSE,
&CFGOPTVAL_RESTORE_TARGET_ACTION_PROMOTE,
&CFGOPTVAL_RESTORE_TARGET_ACTION_SHUTDOWN,
],
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_TYPE,
&CFGDEF_DEPEND_LIST =>
[
&CFGOPTVAL_RESTORE_TYPE_IMMEDIATE,
&CFGOPTVAL_RESTORE_TYPE_NAME,
&CFGOPTVAL_RESTORE_TYPE_TIME,
&CFGOPTVAL_RESTORE_TYPE_XID,
],
},
},
},
},
&CFGOPT_TARGET_TIMELINE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE =>
{
&CFGDEF_REQUIRED => false,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_TYPE,
&CFGDEF_DEPEND_LIST =>
[
&CFGOPTVAL_RESTORE_TYPE_DEFAULT,
&CFGOPTVAL_RESTORE_TYPE_NAME,
&CFGOPTVAL_RESTORE_TYPE_STANDBY,
&CFGOPTVAL_RESTORE_TYPE_TIME,
&CFGOPTVAL_RESTORE_TYPE_XID,
],
},
},
},
},
&CFGOPT_TYPE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP =>
{
&CFGDEF_DEFAULT => CFGOPTVAL_BACKUP_TYPE_INCR,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_BACKUP_TYPE_FULL,
&CFGOPTVAL_BACKUP_TYPE_DIFF,
&CFGOPTVAL_BACKUP_TYPE_INCR,
]
},
&CFGCMD_RESTORE =>
{
&CFGDEF_DEFAULT => CFGOPTVAL_RESTORE_TYPE_DEFAULT,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_RESTORE_TYPE_NAME,
&CFGOPTVAL_RESTORE_TYPE_TIME,
&CFGOPTVAL_RESTORE_TYPE_XID,
&CFGOPTVAL_RESTORE_TYPE_PRESERVE,
&CFGOPTVAL_RESTORE_TYPE_NONE,
&CFGOPTVAL_RESTORE_TYPE_IMMEDIATE,
&CFGOPTVAL_RESTORE_TYPE_DEFAULT,
&CFGOPTVAL_RESTORE_TYPE_STANDBY,
]
}
}
},
&CFGOPT_OUTPUT =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_COMMAND =>
{
&CFGCMD_INFO =>
{
&CFGDEF_DEFAULT => CFGOPTVAL_OUTPUT_TEXT,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_OUTPUT_TEXT,
&CFGOPTVAL_OUTPUT_JSON,
]
},
&CFGCMD_STORAGE_LIST =>
{
&CFGDEF_DEFAULT => CFGOPTVAL_OUTPUT_TEXT,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_OUTPUT_TEXT,
&CFGOPTVAL_OUTPUT_JSON,
]
}
}
},
# Command-line only local/remote options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_HOST_ID =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_REQUIRED => false,
&CFGDEF_INTERNAL => true,
&CFGDEF_ALLOW_RANGE => [1, CFGDEF_INDEX_PG > CFGDEF_INDEX_REPO ? CFGDEF_INDEX_PG : CFGDEF_INDEX_REPO],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_RESTORE => {},
},
},
&CFGOPT_PROCESS =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_REQUIRED => false,
&CFGDEF_INTERNAL => true,
&CFGDEF_ALLOW_RANGE => [0, 1024],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_STORAGE_LIST => {},
},
},
&CFGOPT_REMOTE_TYPE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_REQUIRED => false,
&CFGDEF_INTERNAL => true,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_STORAGE_TYPE_PG,
&CFGOPTVAL_STORAGE_TYPE_REPO,
],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_STORAGE_LIST => {},
},
},
# Command-line only storage options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_FILTER =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_RECURSE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_SORT =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_DEFAULT => 'asc',
&CFGDEF_ALLOW_LIST =>
[
'none',
'asc',
'desc',
],
&CFGDEF_COMMAND =>
{
&CFGCMD_STORAGE_LIST => {},
}
},
# General options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_ARCHIVE_TIMEOUT =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_FLOAT,
&CFGDEF_DEFAULT => 60,
&CFGDEF_ALLOW_RANGE => [WAIT_TIME_MINIMUM, 86400],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
},
},
&CFGOPT_BUFFER_SIZE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_SIZE,
&CFGDEF_DEFAULT => COMMON_IO_BUFFER_MAX,
&CFGDEF_ALLOW_LIST =>
[
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 2,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 4,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 8,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 16,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 32,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 64,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 128,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 256,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 512,
&CFGDEF_DEFAULT_BUFFER_SIZE_MIN * 1024,
],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_DB_TIMEOUT =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_FLOAT,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_DB_TIMEOUT,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_DB_TIMEOUT_MIN, CFGDEF_DEFAULT_DB_TIMEOUT_MAX],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_DELTA =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_RESTORE => {},
},
},
&CFGOPT_COMPRESS =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_RESTORE => {},
}
},
&CFGOPT_COMPRESS_LEVEL =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_DEFAULT => 6,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_COMPRESS_LEVEL_MIN, CFGDEF_DEFAULT_COMPRESS_LEVEL_MAX],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
}
},
&CFGOPT_COMPRESS_LEVEL_NETWORK =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_DEFAULT => 3,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_COMPRESS_LEVEL_MIN, CFGDEF_DEFAULT_COMPRESS_LEVEL_MAX],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_NEUTRAL_UMASK =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_CMD_SSH =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_DEFAULT => 'ssh',
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
},
},
&CFGOPT_LOCK_PATH =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_DEFAULT => '/tmp/' . PROJECT_EXE,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
},
},
&CFGOPT_LOG_PATH =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_DEFAULT => '/var/log/' . PROJECT_EXE,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
},
},
&CFGOPT_PROTOCOL_TIMEOUT =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_FLOAT,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_DB_TIMEOUT + 30,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_DB_TIMEOUT_MIN, CFGDEF_DEFAULT_DB_TIMEOUT_MAX],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_STORAGE_LIST => {},
}
},
# Repository options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_REPO_CIPHER_PASS =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_SECURE => true,
&CFGDEF_REQUIRED => true,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_REPO_CIPHER_TYPE,
&CFGDEF_DEPEND_LIST => [CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC],
},
&CFGDEF_NAME_ALT =>
{
'repo-cipher-pass' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
},
&CFGOPT_REPO_CIPHER_TYPE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => CFGOPTVAL_REPO_CIPHER_TYPE_NONE,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_REPO_CIPHER_TYPE_NONE,
&CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC,
],
&CFGDEF_NAME_ALT =>
{
'repo-cipher-type' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
},
&CFGOPT_REPO_HARDLINK =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_NAME_ALT =>
{
'hardlink' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
},
},
&CFGOPT_REPO_HOST =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_REQUIRED => false,
&CFGDEF_NAME_ALT =>
{
'backup-host' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_STANZA_DELETE =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_STANZA_UPGRADE =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
},
},
&CFGOPT_REPO_HOST_CMD =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_REQUIRED => false,
&CFGDEF_NAME_ALT =>
{
'backup-cmd' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_CHECK => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
},
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_REPO_HOST
},
},
&CFGOPT_REPO_HOST_CONFIG =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG,
&CFGDEF_NAME_ALT =>
{
'backup-config' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_HOST_CMD,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_REPO_HOST
},
},
&CFGOPT_REPO_HOST_CONFIG_PATH =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_INHERIT => CFGOPT_REPO_HOST_CONFIG,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG_PATH,
},
&CFGOPT_REPO_HOST_CONFIG_INCLUDE_PATH =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_INHERIT => CFGOPT_REPO_HOST_CONFIG,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG_INCLUDE_PATH,
},
&CFGOPT_REPO_HOST_PORT =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_PROTOCOL_PORT_MIN, CFGDEF_DEFAULT_PROTOCOL_PORT_MAX],
&CFGDEF_REQUIRED => false,
&CFGDEF_NAME_ALT =>
{
'backup-ssh-port' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_HOST_CMD,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_REPO_HOST
}
},
&CFGOPT_REPO_HOST_USER =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => PROJECT_EXE,
&CFGDEF_NAME_ALT =>
{
'backup-user' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_HOST_CMD,
&CFGDEF_REQUIRED => false,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_REPO_HOST
}
},
&CFGOPT_REPO_PATH =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => '/var/lib/' . PROJECT_EXE,
&CFGDEF_NAME_ALT =>
{
'repo-path' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
},
&CFGOPT_REPO_RETENTION_ARCHIVE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_REQUIRED => false,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_RETENTION_MIN, CFGDEF_DEFAULT_RETENTION_MAX],
&CFGDEF_NAME_ALT =>
{
'retention-archive' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_EXPIRE => {},
}
},
&CFGOPT_REPO_RETENTION_ARCHIVE_TYPE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => CFGOPTVAL_BACKUP_TYPE_FULL,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_EXPIRE => {},
},
&CFGDEF_NAME_ALT =>
{
'retention-archive-type' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_BACKUP_TYPE_FULL,
&CFGOPTVAL_BACKUP_TYPE_DIFF,
&CFGOPTVAL_BACKUP_TYPE_INCR,
]
},
&CFGOPT_REPO_RETENTION_DIFF =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_REQUIRED => false,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_RETENTION_MIN, CFGDEF_DEFAULT_RETENTION_MAX],
&CFGDEF_NAME_ALT =>
{
'retention-diff' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_EXPIRE => {},
}
},
&CFGOPT_REPO_RETENTION_FULL =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_REQUIRED => false,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_RETENTION_MIN, CFGDEF_DEFAULT_RETENTION_MAX],
&CFGDEF_NAME_ALT =>
{
'retention-full' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_EXPIRE => {},
}
},
&CFGOPT_REPO_S3_BUCKET =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_REPO_TYPE,
&CFGDEF_DEPEND_LIST => [CFGOPTVAL_REPO_TYPE_S3],
},
&CFGDEF_NAME_ALT =>
{
'repo-s3-bucket' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
},
&CFGOPT_REPO_S3_CA_FILE =>
{
&CFGDEF_INHERIT => CFGOPT_REPO_S3_HOST,
&CFGDEF_NAME_ALT =>
{
'repo-s3-ca-file' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
},
&CFGOPT_REPO_S3_CA_PATH =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_INHERIT => CFGOPT_REPO_S3_HOST,
&CFGDEF_NAME_ALT =>
{
'repo-s3-ca-path' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
},
&CFGOPT_REPO_S3_KEY =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_SECURE => true,
&CFGDEF_REQUIRED => true,
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_REPO_TYPE,
&CFGDEF_DEPEND_LIST => [CFGOPTVAL_REPO_TYPE_S3],
},
&CFGDEF_NAME_ALT =>
{
'repo-s3-key' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
},
},
&CFGOPT_REPO_S3_KEY_SECRET =>
{
&CFGDEF_INHERIT => CFGOPT_REPO_S3_KEY,
&CFGDEF_NAME_ALT =>
{
'repo-s3-key-secret' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
},
&CFGOPT_REPO_S3_ENDPOINT =>
{
&CFGDEF_INHERIT => CFGOPT_REPO_S3_BUCKET,
&CFGDEF_NAME_ALT =>
{
'repo-s3-endpoint' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
},
&CFGOPT_REPO_S3_HOST =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_REQUIRED => false,
&CFGDEF_DEPEND => CFGOPT_REPO_S3_BUCKET,
&CFGDEF_NAME_ALT =>
{
'repo-s3-host' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
},
&CFGOPT_REPO_S3_PORT =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => 443,
&CFGDEF_ALLOW_RANGE => [1, 65535],
&CFGDEF_DEPEND => CFGOPT_REPO_S3_BUCKET,
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
},
&CFGOPT_REPO_S3_REGION,
{
&CFGDEF_INHERIT => CFGOPT_REPO_S3_BUCKET,
&CFGDEF_NAME_ALT =>
{
'repo-s3-region' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
},
&CFGOPT_REPO_S3_TOKEN =>
{
&CFGDEF_INHERIT => CFGOPT_REPO_S3_KEY,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
},
&CFGOPT_REPO_S3_URI_STYLE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => CFGOPTVAL_REPO_S3_URI_STYLE_HOST,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_REPO_S3_URI_STYLE_HOST,
&CFGOPTVAL_REPO_S3_URI_STYLE_PATH,
],
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
&CFGDEF_DEPEND => CFGOPT_REPO_S3_BUCKET,
},
&CFGOPT_REPO_S3_VERIFY_TLS =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => true,
&CFGDEF_NAME_ALT =>
{
'repo-s3-verify-ssl' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'repo?-s3-verify-ssl' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND => CFGOPT_REPO_TYPE,
&CFGDEF_DEPEND => CFGOPT_REPO_S3_BUCKET,
},
&CFGOPT_REPO_TYPE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_REPO,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_REPO,
&CFGDEF_DEFAULT => CFGOPTVAL_REPO_TYPE_POSIX,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_REPO_TYPE_CIFS,
&CFGOPTVAL_REPO_TYPE_POSIX,
&CFGOPTVAL_REPO_TYPE_S3,
],
&CFGDEF_NAME_ALT =>
{
'repo-type' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
},
},
&CFGOPT_SPOOL_PATH =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_DEFAULT => '/var/spool/' . PROJECT_EXE,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET =>
{
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_ARCHIVE_ASYNC,
&CFGDEF_DEPEND_LIST => [true],
},
},
&CFGCMD_ARCHIVE_PUSH =>
{
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_ARCHIVE_ASYNC,
&CFGDEF_DEPEND_LIST => [true],
},
},
},
},
&CFGOPT_PROCESS_MAX =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_DEFAULT => 1,
&CFGDEF_ALLOW_RANGE => [1, 999],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_RESTORE => {},
}
},
# Logging options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_LOG_LEVEL_CONSOLE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_DEFAULT => lc(WARN),
&CFGDEF_ALLOW_LIST =>
[
lc(OFF),
lc(ERROR),
lc(WARN),
lc(INFO),
lc(DETAIL),
lc(DEBUG),
lc(TRACE),
],
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_LOG_LEVEL_FILE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_DEFAULT => lc(INFO),
&CFGDEF_ALLOW_LIST => CFGOPT_LOG_LEVEL_CONSOLE,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_LOG_LEVEL_STDERR =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_DEFAULT => lc(WARN),
&CFGDEF_ALLOW_LIST => CFGOPT_LOG_LEVEL_CONSOLE,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_LOG_SUBPROCESS =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_LOG_TIMESTAMP =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => true,
&CFGDEF_COMMAND => CFGOPT_LOG_LEVEL_CONSOLE,
},
# Archive options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_ARCHIVE_ASYNC =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
}
},
&CFGOPT_ARCHIVE_PUSH_QUEUE_MAX =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_SIZE,
&CFGDEF_REQUIRED => false,
&CFGDEF_NAME_ALT =>
{
'archive-queue-max' => {},
},
&CFGDEF_ALLOW_RANGE => [0, 4 * 1024 * 1024 * 1024 * 1024 * 1024], # 0-4PB
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_PUSH => {},
},
},
&CFGOPT_ARCHIVE_GET_QUEUE_MAX =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_SIZE,
&CFGDEF_DEFAULT => 128 * 1024 * 1024, # 128MB
&CFGDEF_ALLOW_RANGE => [0, 4 * 1024 * 1024 * 1024 * 1024 * 1024], # 0-4PB
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
},
},
# Backup options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_ARCHIVE_CHECK =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP =>
{
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_ONLINE,
&CFGDEF_DEPEND_LIST => [true],
},
},
&CFGCMD_CHECK => {},
},
},
&CFGOPT_ARCHIVE_COPY =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP =>
{
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_ARCHIVE_CHECK,
&CFGDEF_DEPEND_LIST => [true],
}
}
}
},
&CFGOPT_BACKUP_STANDBY =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_UPGRADE => {},
},
},
&CFGOPT_CHECKSUM_PAGE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
}
},
&CFGOPT_EXCLUDE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_LIST,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
},
},
&CFGOPT_MANIFEST_SAVE_THRESHOLD =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_SIZE,
&CFGDEF_DEFAULT => 1 * 1024 * 1024 * 1024,
&CFGDEF_ALLOW_RANGE => [1, 1024 * 1024 * 1024 * 1024], # 1-1TB
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
}
},
&CFGOPT_RESUME =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
}
},
&CFGOPT_START_FAST =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
}
},
&CFGOPT_STOP_AUTO =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
}
},
# Restore options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_DB_INCLUDE =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_LIST,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE => {},
},
},
&CFGOPT_LINK_ALL =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE => {},
}
},
&CFGOPT_LINK_MAP =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_HASH,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE => {},
},
},
&CFGOPT_TABLESPACE_MAP_ALL =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE => {},
}
},
&CFGOPT_TABLESPACE_MAP =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_HASH,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE => {},
},
},
&CFGOPT_RECOVERY_OPTION =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_HASH,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_RESTORE => {},
},
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_TYPE,
&CFGDEF_DEPEND_LIST =>
[
&CFGOPTVAL_RESTORE_TYPE_DEFAULT,
&CFGOPTVAL_RESTORE_TYPE_IMMEDIATE,
&CFGOPTVAL_RESTORE_TYPE_NAME,
&CFGOPTVAL_RESTORE_TYPE_TIME,
&CFGOPTVAL_RESTORE_TYPE_STANDBY,
&CFGOPTVAL_RESTORE_TYPE_XID,
],
},
},
# Stanza options
#-------------------------------------------------------------------------------------------------------------------------------
&CFGOPT_PG_HOST =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_STANZA,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_PG,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_PG,
&CFGDEF_REQUIRED => false,
&CFGDEF_NAME_ALT =>
{
'db-host' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-host' => {&CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_ARCHIVE_PUSH =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_RESTORE =>
{
&CFGDEF_INTERNAL => true,
},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
},
},
&CFGOPT_PG_HOST_CMD =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_STANZA,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_PG,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_PG,
&CFGDEF_REQUIRED => false,
&CFGDEF_NAME_ALT =>
{
'db-cmd' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-cmd' => {&CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
},
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_PG_HOST
},
},
&CFGOPT_PG_HOST_CONFIG =>
{
&CFGDEF_INHERIT => CFGOPT_PG_HOST_CMD,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG,
&CFGDEF_REQUIRED => true,
&CFGDEF_NAME_ALT =>
{
'db-config' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-config' => {&CFGDEF_RESET => false},
},
},
&CFGOPT_PG_HOST_CONFIG_PATH =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_INHERIT => CFGOPT_PG_HOST_CMD,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG_PATH,
},
&CFGOPT_PG_HOST_CONFIG_INCLUDE_PATH =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_INHERIT => CFGOPT_PG_HOST_CMD,
&CFGDEF_DEFAULT => CFGDEF_DEFAULT_CONFIG_INCLUDE_PATH,
},
&CFGOPT_PG_HOST_PORT =>
{
&CFGDEF_INHERIT => CFGOPT_PG_HOST_CMD,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_REQUIRED => false,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_PROTOCOL_PORT_MIN, CFGDEF_DEFAULT_PROTOCOL_PORT_MAX],
&CFGDEF_NAME_ALT =>
{
'db-ssh-port' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-ssh-port' => {&CFGDEF_RESET => false},
},
},
&CFGOPT_PG_HOST_USER =>
{
&CFGDEF_INHERIT => CFGOPT_PG_HOST_CMD,
&CFGDEF_DEFAULT => 'postgres',
&CFGDEF_NAME_ALT =>
{
'db-user' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-user' => {&CFGDEF_RESET => false},
},
&CFGDEF_REQUIRED => false,
},
&CFGOPT_PG_PATH =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_STANZA,
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_PREFIX => CFGDEF_PREFIX_PG,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_PG,
&CFGDEF_REQUIRED => true,
&CFGDEF_NAME_ALT =>
{
'db-path' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-path' => {&CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET =>
{
&CFGDEF_REQUIRED => false
},
&CFGCMD_ARCHIVE_PUSH =>
{
&CFGDEF_REQUIRED => false
},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
},
},
&CFGOPT_PG_PORT =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_STANZA,
&CFGDEF_TYPE => CFGDEF_TYPE_INTEGER,
&CFGDEF_PREFIX => CFGDEF_PREFIX_PG,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_PG,
&CFGDEF_DEFAULT => 5432,
&CFGDEF_ALLOW_RANGE => [CFGDEF_DEFAULT_PROTOCOL_PORT_MIN, CFGDEF_DEFAULT_PROTOCOL_PORT_MAX],
&CFGDEF_NAME_ALT =>
{
'db-port' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-port' => {&CFGDEF_RESET => false},
},
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
},
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_PG_PATH
},
},
&CFGOPT_PG_SOCKET_PATH =>
{
&CFGDEF_INHERIT => CFGOPT_PG_PORT,
&CFGDEF_TYPE => CFGDEF_TYPE_PATH,
&CFGDEF_DEFAULT => undef,
&CFGDEF_REQUIRED => false,
&CFGDEF_NAME_ALT =>
{
'db-socket-path' => {&CFGDEF_INDEX => 1, &CFGDEF_RESET => false},
'db?-socket-path' => {&CFGDEF_RESET => false},
},
},
&CFGOPT_PG_USER =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_STANZA,
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_PREFIX => CFGDEF_PREFIX_PG,
&CFGDEF_INDEX_TOTAL => CFGDEF_INDEX_PG,
&CFGDEF_REQUIRED => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_DELETE => {},
&CFGCMD_STANZA_UPGRADE => {},
},
&CFGDEF_DEPEND =>
{
&CFGDEF_DEPEND_OPTION => CFGOPT_PG_PATH
},
},
);
####################################################################################################################################
# Process command define defaults
####################################################################################################################################
foreach my $strCommand (sort(keys(%{$rhCommandDefine})))
{
# Commands are external by default
if (!defined($rhCommandDefine->{$strCommand}{&CFGDEF_INTERNAL}))
{
$rhCommandDefine->{$strCommand}{&CFGDEF_INTERNAL} = false;
}
# Log files are created by default
if (!defined($rhCommandDefine->{$strCommand}{&CFGDEF_LOG_FILE}))
{
$rhCommandDefine->{$strCommand}{&CFGDEF_LOG_FILE} = true;
}
# Default log level is INFO
if (!defined($rhCommandDefine->{$strCommand}{&CFGDEF_LOG_LEVEL_DEFAULT}))
{
$rhCommandDefine->{$strCommand}{&CFGDEF_LOG_LEVEL_DEFAULT} = INFO;
}
# Default lock required is false
if (!defined($rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_REQUIRED}))
{
$rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_REQUIRED} = false;
}
# Default lock remote required is false
if (!defined($rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_REMOTE_REQUIRED}))
{
$rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_REMOTE_REQUIRED} = false;
}
# Lock type must be set if a lock is required
if (!defined($rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_TYPE}))
{
# Is a lock type required?
if ($rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_REQUIRED})
{
confess &log(ERROR, "lock type is required for command '${strCommand}'");
}
$rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_TYPE} = CFGDEF_LOCK_TYPE_NONE;
}
else
{
if ($rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_REQUIRED} &&
$rhCommandDefine->{$strCommand}{&CFGDEF_LOCK_TYPE} eq CFGDEF_LOCK_TYPE_NONE)
{
confess &log(ERROR, "lock type is required for command '${strCommand}' and cannot be 'none'");
}
}
# Default parameter allowed is false
if (!defined($rhCommandDefine->{$strCommand}{&CFGDEF_PARAMETER_ALLOWED}))
{
$rhCommandDefine->{$strCommand}{&CFGDEF_PARAMETER_ALLOWED} = false;
}
}
####################################################################################################################################
# Process option define defaults
####################################################################################################################################
foreach my $strKey (sort(keys(%hConfigDefine)))
{
# Error if prefix and index total are not both defined
if ((defined($hConfigDefine{$strKey}{&CFGDEF_PREFIX}) && !defined($hConfigDefine{$strKey}{&CFGDEF_INDEX_TOTAL})) ||
(!defined($hConfigDefine{$strKey}{&CFGDEF_PREFIX}) && defined($hConfigDefine{$strKey}{&CFGDEF_INDEX_TOTAL})))
{
confess &log(ASSERT, "CFGDEF_PREFIX and CFGDEF_INDEX_TOTAL must both be defined (or neither) for option '${strKey}'");
}
# If the define is a scalar then copy the entire define from the referenced option
if (defined($hConfigDefine{$strKey}{&CFGDEF_INHERIT}))
{
# Make a copy in case there are overrides that need to be applied after inheriting
my $hConfigDefineOverride = dclone($hConfigDefine{$strKey});
# Copy the option being inherited from
$hConfigDefine{$strKey} = dclone($hConfigDefine{$hConfigDefine{$strKey}{&CFGDEF_INHERIT}});
# No need to copy the inheritance key
delete($hConfigDefine{$strKey}{&CFGDEF_INHERIT});
# It makes no sense to inherit alt names - they must be specified for each option
delete($hConfigDefine{$strKey}{&CFGDEF_NAME_ALT});
# Apply overrides
foreach my $strOptionDef (sort(keys(%{$hConfigDefineOverride})))
{
$hConfigDefine{$strKey}{$strOptionDef} = $hConfigDefineOverride->{$strOptionDef};
}
}
# If the command section is a scalar then copy the section from the referenced option
if (defined($hConfigDefine{$strKey}{&CFGDEF_COMMAND}) && !ref($hConfigDefine{$strKey}{&CFGDEF_COMMAND}))
{
$hConfigDefine{$strKey}{&CFGDEF_COMMAND} =
dclone($hConfigDefine{$hConfigDefine{$strKey}{&CFGDEF_COMMAND}}{&CFGDEF_COMMAND});
}
# If the required section is a scalar then copy the section from the referenced option
if (defined($hConfigDefine{$strKey}{&CFGDEF_DEPEND}) && !ref($hConfigDefine{$strKey}{&CFGDEF_DEPEND}))
{
$hConfigDefine{$strKey}{&CFGDEF_DEPEND} =
dclone($hConfigDefine{$hConfigDefine{$strKey}{&CFGDEF_DEPEND}}{&CFGDEF_DEPEND});
}
# If the allow list is a scalar then copy the list from the referenced option
if (defined($hConfigDefine{$strKey}{&CFGDEF_ALLOW_LIST}) && !ref($hConfigDefine{$strKey}{&CFGDEF_ALLOW_LIST}))
{
$hConfigDefine{$strKey}{&CFGDEF_ALLOW_LIST} =
dclone($hConfigDefine{$hConfigDefine{$strKey}{&CFGDEF_ALLOW_LIST}}{&CFGDEF_ALLOW_LIST});
}
# Default type is string
if (!defined($hConfigDefine{$strKey}{&CFGDEF_TYPE}))
{
&log(ASSERT, "type is required for option '${strKey}'");
}
# Default required is true
if (!defined($hConfigDefine{$strKey}{&CFGDEF_REQUIRED}))
{
$hConfigDefine{$strKey}{&CFGDEF_REQUIRED} = true;
}
# Default internal is false
if (!defined($hConfigDefine{$strKey}{&CFGDEF_INTERNAL}))
{
$hConfigDefine{$strKey}{&CFGDEF_INTERNAL} = false;
}
# Set index total for any option where it has not been explicitly defined
if (!defined($hConfigDefine{$strKey}{&CFGDEF_INDEX_TOTAL}))
{
$hConfigDefine{$strKey}{&CFGDEF_INDEX_TOTAL} = 1;
}
# All boolean config options can be negated. Boolean command-line options must be marked for negation individually.
if ($hConfigDefine{$strKey}{&CFGDEF_TYPE} eq CFGDEF_TYPE_BOOLEAN && defined($hConfigDefine{$strKey}{&CFGDEF_SECTION}))
{
$hConfigDefine{$strKey}{&CFGDEF_NEGATE} = true;
}
# Default for negation is false
if (!defined($hConfigDefine{$strKey}{&CFGDEF_NEGATE}))
{
$hConfigDefine{$strKey}{&CFGDEF_NEGATE} = false;
}
# All config options can be reset
if (defined($hConfigDefine{$strKey}{&CFGDEF_SECTION}))
{
$hConfigDefine{$strKey}{&CFGDEF_RESET} = true;
}
elsif (!defined($hConfigDefine{$strKey}{&CFGDEF_RESET}))
{
$hConfigDefine{$strKey}{&CFGDEF_RESET} = false;
}
# By default options are not secure
if (!defined($hConfigDefine{$strKey}{&CFGDEF_SECURE}))
{
$hConfigDefine{$strKey}{&CFGDEF_SECURE} = false;
}
# Set all indices to 1 by default - this defines how many copies of any option there can be
if (!defined($hConfigDefine{$strKey}{&CFGDEF_INDEX_TOTAL}))
{
$hConfigDefine{$strKey}{&CFGDEF_INDEX_TOTAL} = 1;
}
# All int and float options must have an allow range
if (($hConfigDefine{$strKey}{&CFGDEF_TYPE} eq CFGDEF_TYPE_INTEGER ||
$hConfigDefine{$strKey}{&CFGDEF_TYPE} eq CFGDEF_TYPE_FLOAT ||
$hConfigDefine{$strKey}{&CFGDEF_TYPE} eq CFGDEF_TYPE_SIZE) &&
!(defined($hConfigDefine{$strKey}{&CFGDEF_ALLOW_RANGE}) || defined($hConfigDefine{$strKey}{&CFGDEF_ALLOW_LIST})))
{
confess &log(ASSERT, "int/float option '${strKey}' must have allow range or list");
}
# Ensure all commands are valid
foreach my $strCommand (sort(keys(%{$hConfigDefine{$strKey}{&CFGDEF_COMMAND}})))
{
if (!defined($rhCommandDefine->{$strCommand}))
{
confess &log(ASSERT, "invalid command '${strCommand}'");
}
}
}
####################################################################################################################################
# Get option definition
####################################################################################################################################
sub cfgDefine
{
return dclone(\%hConfigDefine);
}
push @EXPORT, qw(cfgDefine);
####################################################################################################################################
# Get command definition
####################################################################################################################################
sub cfgDefineCommand
{
return dclone($rhCommandDefine);
}
push @EXPORT, qw(cfgDefineCommand);
####################################################################################################################################
# Get list of all commands
####################################################################################################################################
sub cfgDefineCommandList
{
# Return sorted list
return (sort(keys(%{$rhCommandDefine})));
}
push @EXPORT, qw(cfgDefineCommandList);
####################################################################################################################################
# Get list of all option types
####################################################################################################################################
sub cfgDefineOptionTypeList
{
my $rhOptionTypeMap;
# Get unique list of types
foreach my $strOption (sort(keys(%hConfigDefine)))
{
my $strOptionType = $hConfigDefine{$strOption}{&CFGDEF_TYPE};
if (!defined($rhOptionTypeMap->{$strOptionType}))
{
$rhOptionTypeMap->{$strOptionType} = true;
}
};
# Return sorted list
return (sort(keys(%{$rhOptionTypeMap})));
}
push @EXPORT, qw(cfgDefineOptionTypeList);
1;
pgbackrest-release-2.24/build/lib/pgBackRestBuild/Error/ 0000775 0000000 0000000 00000000000 13625315071 0023152 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/build/lib/pgBackRestBuild/Error/Build.pm 0000664 0000000 0000000 00000010705 13625315071 0024552 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Auto-Generate Error Mappings
####################################################################################################################################
package pgBackRestBuild::Error::Build;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Error::Data;
use pgBackRest::Common::Log;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant BLDLCL_FILE_DEFINE => 'error';
use constant BLDLCL_DATA_ERROR => '01-dataError';
use constant BLDLCL_DATA_ERROR_ARRAY => '01-dataErrorArray';
####################################################################################################################################
# Definitions for constants and data to build
####################################################################################################################################
my $strSummary = 'Error Type Definition';
my $rhBuild =
{
&BLD_FILE =>
{
&BLDLCL_FILE_DEFINE =>
{
&BLD_SUMMARY => $strSummary,
&BLD_DECLARE =>
{
&BLDLCL_DATA_ERROR =>
{
&BLD_SUMMARY => 'Error type declarations',
},
},
&BLD_DATA =>
{
&BLDLCL_DATA_ERROR =>
{
&BLD_SUMMARY => 'Error type definitions',
},
&BLDLCL_DATA_ERROR_ARRAY =>
{
&BLD_SUMMARY => 'Error type array',
},
},
},
},
};
####################################################################################################################################
# Build configuration constants and data
####################################################################################################################################
sub buildError
{
# Build error list
#-------------------------------------------------------------------------------------------------------------------------------
my $rhErrorDefine = errorDefine();
# Order by id for the list that is id ordered
my $rhErrorId = {};
foreach my $strType (sort(keys(%{$rhErrorDefine})))
{
my $iCode = $rhErrorDefine->{$strType};
if (defined($rhErrorId->{$iCode}))
{
confess &log(ERROR, "error code ${iCode} is by '" . $rhErrorId->{$iCode} . "' and '${strType}'");
}
$rhErrorId->{$iCode} = $strType;
}
# Output errors
my $strBuildSource;
foreach my $iCode (sort({sprintf("%03d", $a) cmp sprintf("%03d", $b)} keys(%{$rhErrorId})))
{
my $strType = $rhErrorId->{$iCode};
$strBuildSource .=
"ERROR_DECLARE(" . bldEnum("", $strType, true) . "Error);\n";
}
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DECLARE}{&BLDLCL_DATA_ERROR}{&BLD_SOURCE} = $strBuildSource;
# Output error definition data
$strBuildSource = undef;
foreach my $iCode (sort({sprintf("%03d", $a) cmp sprintf("%03d", $b)} keys(%{$rhErrorId})))
{
my $strType = $rhErrorId->{$iCode};
$strBuildSource .=
"ERROR_DEFINE(" . (' ' x (3 - length($iCode))) . "${iCode}, " . bldEnum("", $strType, true) . "Error, RuntimeError);\n";
}
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_ERROR}{&BLD_SOURCE} = $strBuildSource;
# Output error array
$strBuildSource =
"static const ErrorType *errorTypeList[] =\n" .
"{\n";
foreach my $iCode (sort({sprintf("%03d", $a) cmp sprintf("%03d", $b)} keys(%{$rhErrorId})))
{
$strBuildSource .=
" &" . bldEnum("", $rhErrorId->{$iCode}, true) . "Error,\n";
}
$strBuildSource .=
" NULL,\n" .
"};";
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_ERROR_ARRAY}{&BLD_SOURCE} = $strBuildSource;
return $rhBuild;
}
push @EXPORT, qw(buildError);
1;
pgbackrest-release-2.24/build/lib/pgBackRestBuild/Error/Data.pm 0000664 0000000 0000000 00000004162 13625315071 0024364 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# Error Definition Data
####################################################################################################################################
package pgBackRestBuild::Error::Data;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Exporter qw(import);
our @EXPORT = qw();
use Storable qw(dclone);
####################################################################################################################################
# Error min and max values
####################################################################################################################################
use constant ERRDEF_MIN => 25;
push @EXPORT, qw(ERRDEF_MIN);
use constant ERRDEF_MAX => 125;
push @EXPORT, qw(ERRDEF_MAX);
####################################################################################################################################
# Error definition data
####################################################################################################################################
my $rhErrorDefine;
####################################################################################################################################
# Load error definition from YAML
####################################################################################################################################
sub errorDefineLoad
{
my $strErrorYaml = shift;
require YAML::XS;
YAML::XS->import(qw(Load));
$rhErrorDefine = Load($strErrorYaml);
}
push @EXPORT, qw(errorDefineLoad);
####################################################################################################################################
# Get error definition
####################################################################################################################################
sub errorDefine
{
return dclone($rhErrorDefine);
}
push @EXPORT, qw(errorDefine);
1;
pgbackrest-release-2.24/doc/ 0000775 0000000 0000000 00000000000 13625315071 0015754 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/doc/.gitignore 0000664 0000000 0000000 00000000026 13625315071 0017742 0 ustar 00root root 0000000 0000000 output/*
doc/output/*
pgbackrest-release-2.24/doc/FORMAT.md 0000664 0000000 0000000 00000001153 13625315071 0017266 0 ustar 00root root 0000000 0000000 # Thoughts on Repository Format 6
# Info Format
- Info format will be `JSON`. Something like (but without whitespace):
```
[
{"pgbackrest":{"format":6,"version":,"repo-id":,id:}},
{"content":...},
{"checksum":}
]
```
## Manifest
- Save **all** options instead of the piecemeal saves that are done now. These options are for informational purposes only so it would be fine to save them in a KeyValue or JSON blob.
- Store default sections before file/link/path sections to avoid two passes on load.
- Format backup labels using gmtime().
pgbackrest-release-2.24/doc/README.md 0000664 0000000 0000000 00000007071 13625315071 0017240 0 ustar 00root root 0000000 0000000 # pgBackRest Building Documentation
## General Builds
The pgBackRest documentation can output a variety of formats and target several platforms and PostgreSQL versions.
This will build all documentation with defaults:
```bash
./doc.pl
```
The user guide can be built for different platforms: `centos6`, `centos7`, and `debian`. This will build the HTML user guide for CentOS/RHEL 7:
```bash
./doc.pl --out=html --include=user-guide --var=os-type=centos7
```
Documentation generation will build a cache of all executed statements and use the cache to build the documentation quickly if no executed statements have changed. This makes proofing text-only edits very fast, but sometimes it is useful to do a full build without using the cache:
```bash
./doc.pl --out=html --include=user-guide --var=os-type=centos6 --no-cache
```
Each `os-type` has a default container image that will be used as a base for creating hosts. For `centos6`/`centos7` these defaults are generally fine, but for `debian` it can be useful to change the image.
```bash
./doc.pl --out=html --include=user-guide --var=os-type=debian --var=os-image=debian:9
```
The following is a sample CentOS/RHEL 7 configuration that can be used for building the documentation.
```bash
# Install docker
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce
sudo systemctl start docker
# Install tools
sudo yum install -y git wget
# Install latex (for building PDF)
sudo yum install -y texlive texlive-titlesec texlive-sectsty texlive-framed texlive-epstopdf ghostscript
# Install Perl modules that do not have CentOS packages via CPAN
sudo yum install -y yum cpanminus
sudo yum groupinstall -y "Development Tools" "Development Libraries"
sudo cpanm install --force XML::Checker::Parser
# Add documentation test user
sudo groupadd test
sudo adduser -gtest -n testdoc
sudo usermod -aG docker testdoc
```
## Building with Packages
A user-specified package can be used when building the documentation. Since the documentation exercises most pgBackRest functionality this is a great way to smoke-test packages.
The package must be located within the pgBackRest repo and the specified path should be relative to the repository base. `test/package` is a good default path to use.
Ubuntu 16.04:
```bash
./doc.pl --out=html --include=user-guide --no-cache --var=os-type=debian --var=os-image=ubuntu:16.04 --var=package=test/package/pgbackrest_2.08-0_amd64.deb
```
CentOS/RHEL 6:
```bash
./doc.pl --out=html --include=user-guide --no-cache --var=os-type=centos6 --var=package=test/package/pgbackrest-2.08-1.el6.x86_64.rpm
```
CentOS/RHEL 7:
```bash
./doc.pl --out=html --include=user-guide --no-cache --var=os-type=centos7 --var=package=test/package/pgbackrest-2.08-1.el7.x86_64.rpm
```
Packages can be built with `test.pl` using the following configuration on top of the configuration given for building the documentation.
```bash
# Install recent git
sudo yum remove -y git
sudo yum install -y https://centos7.iuscommunity.org/ius-release.rpm
sudo yum install -y git2u-all
# Install Perl modules
sudo yum install -y perl-ExtUtils-ParseXS perl-ExtUtils-Embed perl-ExtUtils-MakeMaker perl-YAML-LibYAML
# Install dev libraries
sudo yum install -y libxml2-devel openssl-devel
# Add test user with sudo privileges
sudo adduser -gtest -n test
sudo usermod -aG docker test
sudo chmod 750 /home/test
sudo echo 'test ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/pgbackrest
# Add pgbackrest user required by tests
sudo adduser -gtest -n pgbackrest
```
pgbackrest-release-2.24/doc/RELEASE.md 0000664 0000000 0000000 00000007050 13625315071 0017360 0 ustar 00root root 0000000 0000000 # Release Build Instructions
## Create a branch to test the release
```
git checkout -b release-ci
```
## Update the date, version, and release title
Edit the latest release in `doc/xml/release.xml`, e.g.:
```
```
to:
```
```
Edit version in `lib/pgBackRest/Version.pm`, e.g.:
```
use constant PROJECT_VERSION => '2.14dev';
```
to:
```
use constant PROJECT_VERSION => '2.14';
```
## Build release documentation. Be sure to install latex using the instructions from the Vagrantfile before running this step.
```
doc/release.pl --build
```
## Update code counts
```
test/test.pl --code-count
```
## Commit release branch and push to CI for testing
```
git commit -m "Release test"
git push origin release-ci
```
## Clone web documentation into `doc/site`
```
cd doc
git clone git@github.com:pgbackrest/website.git site
```
## Deploy web documentation to `doc/site`
```
doc/release.pl --deploy
```
## Final commit of release to integration
Create release notes based on the pattern in prior git commits (this should be automated at some point), e.g.
```
v2.14: Bug Fix and Improvements
Bug Fixes:
* Fix segfault when process-max > 8 for archive-push/archive-get. (Reported by Jens Wilke.)
Improvements:
* Bypass database checks when stanza-delete issued with force. (Contributed by Cynthia Shang. Suggested by hatifnatt.)
* Add configure script for improved multi-platform support.
Documentation Features:
* Add user guides for CentOS/RHEL 6/7.
```
Commit to integration with the above message and push to CI.
## Push to master
Push release commit to master once CI testing is complete.
## Create release on github
Create release notes based on pattern in prior releases (this should be automated at some point), e.g.
```
v2.14: Bug Fix and Improvements
**Bug Fixes**:
- Fix segfault when process-max > 8 for archive-push/archive-get. (Reported by Jens Wilke.)
**Improvements**:
- Bypass database checks when stanza-delete issued with force. (Contributed by Cynthia Shang. Suggested by hatifnatt.)
- Add configure script for improved multi-platform support.
**Documentation Features**:
- Add user guides for CentOS/RHEL 6/7.
```
The first line will be the release title and the rest will be the body. The tag field should be updated with the current version so a tag is created from master. **Be sure to select the release commit explicitly rather than auto-tagging the last commit in master!**
## Push web documentation to master and deploy
```
cd doc/site
git commit -m "v2.14 documentation."
git push origin master
```
Deploy the documentation on `pgbackrest.org`.
## Notify packagers of new release
## Announce release on Twitter
## Prepare for the next release
Add new release in `doc/xml/release.xml`, e.g.:
```
```
Edit version in `lib/pgBackRest/Version.pm`, e.g.:
```
use constant PROJECT_VERSION => '2.14';
```
to:
```
use constant PROJECT_VERSION => '2.15dev';
```
Run deploy to generate git history (ctrl-c as soon as the file is generated):
```
doc/release.pl --build
```
Build to generate files and test documentation:
```
test/test.pl --vm=u18 --build-only
```
Commit and push to integration:
```
git commit -m "Begin v2.15 development."
git push origin integration
```
pgbackrest-release-2.24/doc/doc.pl 0000775 0000000 0000000 00000031357 13625315071 0017072 0 ustar 00root root 0000000 0000000 #!/usr/bin/perl
####################################################################################################################################
# doc.pl - PgBackRest Doc Builder
####################################################################################################################################
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
$SIG{__DIE__} = sub { Carp::confess @_ };
use Cwd qw(abs_path);
use File::Basename qw(dirname);
use Getopt::Long qw(GetOptions);
use Pod::Usage qw(pod2usage);
use Storable;
use lib dirname(abs_path($0)) . '/lib';
use lib dirname(dirname(abs_path($0))) . '/lib';
use lib dirname(dirname(abs_path($0))) . '/build/lib';
use lib dirname(dirname(abs_path($0))) . '/test/lib';
use BackRestDoc::Common::Doc;
use BackRestDoc::Common::DocConfig;
use BackRestDoc::Common::DocManifest;
use BackRestDoc::Common::DocRender;
use BackRestDoc::Html::DocHtmlSite;
use BackRestDoc::Latex::DocLatex;
use BackRestDoc::Markdown::DocMarkdown;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Version;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::Storage;
use pgBackRestTest::Common::StoragePosix;
####################################################################################################################################
# Usage
####################################################################################################################################
=head1 NAME
doc.pl - Generate pgBackRest documentation
=head1 SYNOPSIS
doc.pl [options]
General Options:
--help Display usage and exit
--version Display pgBackRest version
--quiet Sets log level to ERROR
--log-level Log level for execution (e.g. ERROR, WARN, INFO, DEBUG)
--deploy Write exe.cache into resource for persistence
--no-exe Should commands be executed when building help? (for testing only)
--no-cache Don't use execution cache
--cache-only Only use the execution cache - don't attempt to generate it
--pre Pre-build containers for execute elements marked pre
--var Override defined variable
--key-var Override defined variable and use in cache key
--doc-path Document path to render (manifest.xml should be located here)
--out Output types (html, pdf, markdown)
--out-preserve Don't clean output directory
--require Require only certain sections of the document (to speed testing)
--include Include source in generation (links will reference website)
--exclude Exclude source from generation (links will reference website)
Variable Options:
--dev Set 'dev' variable to 'y'
--debug Set 'debug' variable to 'y'
=cut
####################################################################################################################################
# Load command line parameters and config (see usage above for details)
####################################################################################################################################
my $bHelp = false;
my $bVersion = false;
my $bQuiet = false;
my $strLogLevel = 'info';
my $bNoExe = false;
my $bNoCache = false;
my $bCacheOnly = false;
my $rhVariableOverride = {};
my $rhKeyVariableOverride = {};
my $strDocPath;
my @stryOutput;
my $bOutPreserve = false;
my @stryRequire;
my @stryInclude;
my @stryExclude;
my $bDeploy = false;
my $bDev = false;
my $bDebug = false;
my $bPre = false;
GetOptions ('help' => \$bHelp,
'version' => \$bVersion,
'quiet' => \$bQuiet,
'log-level=s' => \$strLogLevel,
'out=s@' => \@stryOutput,
'out-preserve' => \$bOutPreserve,
'require=s@' => \@stryRequire,
'include=s@' => \@stryInclude,
'exclude=s@' => \@stryExclude,
'no-exe', \$bNoExe,
'deploy', \$bDeploy,
'no-cache', \$bNoCache,
'dev', \$bDev,
'debug', \$bDebug,
'pre', \$bPre,
'cache-only', \$bCacheOnly,
'key-var=s%', $rhKeyVariableOverride,
'var=s%', $rhVariableOverride,
'doc-path=s', \$strDocPath)
or pod2usage(2);
####################################################################################################################################
# Run in eval block to catch errors
####################################################################################################################################
eval
{
# Display version and exit if requested
if ($bHelp || $bVersion)
{
print PROJECT_NAME . ' ' . PROJECT_VERSION . " Documentation Builder\n";
if ($bHelp)
{
print "\n";
pod2usage();
}
exit 0;
}
# Disable cache when no exe
if ($bNoExe)
{
$bNoCache = true;
}
# Make sure options are set correctly for deploy
if ($bDeploy)
{
my $strError = 'cannot be specified for deploy';
!$bNoExe
or confess "--no-exe ${strError}";
!@stryRequire
or confess "--require ${strError}";
}
# one --include must be specified when --required is
if (@stryRequire && @stryInclude != 1)
{
confess "one --include is required when --require is specified";
}
# Set console log level
if ($bQuiet)
{
$strLogLevel = 'error';
}
# If --dev passed then set the dev var to 'y'
if ($bDev)
{
$rhVariableOverride->{'dev'} = 'y';
}
# If --debug passed then set the debug var to 'y'
if ($bDebug)
{
$rhVariableOverride->{'debug'} = 'y';
}
# Doesn't make sense to pass include and exclude
if (@stryInclude > 0 && @stryExclude > 0)
{
confess "cannot specify both --include and --exclude";
}
logLevelSet(undef, uc($strLogLevel), OFF);
# Get the base path
my $strBasePath = abs_path(dirname($0));
my $oStorageDoc = new pgBackRestTest::Common::Storage(
$strBasePath, new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false}));
if (!defined($strDocPath))
{
$strDocPath = $strBasePath;
}
my $strOutputPath = "${strDocPath}/output";
# Create the out path if it does not exist
if (!-e $strOutputPath)
{
mkdir($strOutputPath)
or confess &log(ERROR, "unable to create path ${strOutputPath}");
}
# Merge key variables into the variable list and ensure there are no duplicates
foreach my $strKey (sort(keys(%{$rhKeyVariableOverride})))
{
if (defined($rhVariableOverride->{$strKey}))
{
confess &log(ERROR, "'${strKey}' cannot be passed as --var and --key-var");
}
$rhVariableOverride->{$strKey} = $rhKeyVariableOverride->{$strKey};
}
# Load the manifest
my $oManifest = new BackRestDoc::Common::DocManifest(
$oStorageDoc, \@stryRequire, \@stryInclude, \@stryExclude, $rhKeyVariableOverride, $rhVariableOverride,
$strDocPath, $bDeploy, $bCacheOnly, $bPre);
if (!$bNoCache)
{
$oManifest->cacheRead();
}
# If no outputs were given
if (@stryOutput == 0)
{
@stryOutput = $oManifest->renderList();
if ($oManifest->isBackRest())
{
push(@stryOutput, 'man');
}
}
# Build host containers
if (!$bCacheOnly && !$bNoExe)
{
foreach my $strSource ($oManifest->sourceList())
{
if ((@stryInclude == 0 || grep(/$strSource/, @stryInclude)) && !grep(/$strSource/, @stryExclude))
{
&log(INFO, "source $strSource");
foreach my $oHostDefine ($oManifest->sourceGet($strSource)->{doc}->nodeList('host-define', false))
{
if ($oManifest->evaluateIf($oHostDefine))
{
my $strImage = $oManifest->variableReplace($oHostDefine->paramGet('image'));
my $strFrom = $oManifest->variableReplace($oHostDefine->paramGet('from'));
my $strDockerfile = "${strOutputPath}/doc-host.dockerfile";
&log(INFO, "Build vm '${strImage}' from '${strFrom}'");
$oStorageDoc->put(
$strDockerfile,
"FROM ${strFrom}\n\n" . trim($oManifest->variableReplace($oHostDefine->valueGet())) . "\n");
executeTest("docker build -f ${strDockerfile} -t ${strImage} ${strBasePath}");
}
}
}
}
}
# Render output
for my $strOutput (@stryOutput)
{
if (!($strOutput eq 'man' && $oManifest->isBackRest()))
{
$oManifest->renderGet($strOutput);
}
&log(INFO, "render ${strOutput} output");
# Clean contents of out directory
if (!$bOutPreserve)
{
my $strOutputPath = $strOutput eq 'pdf' ? "${strOutputPath}/latex" : "${strOutputPath}/$strOutput";
# Clean the current out path if it exists
if (-e $strOutputPath)
{
executeTest("rm -rf ${strOutputPath}/*");
}
# Else create the html path
else
{
mkdir($strOutputPath)
or confess &log(ERROR, "unable to create path ${strOutputPath}");
}
}
if ($strOutput eq 'markdown')
{
my $oMarkdown =
new BackRestDoc::Markdown::DocMarkdown
(
$oManifest,
"${strBasePath}/xml",
"${strOutputPath}/markdown",
!$bNoExe
);
$oMarkdown->process();
}
elsif ($strOutput eq 'man' && $oManifest->isBackRest())
{
# Generate the command-line help
my $oRender = new BackRestDoc::Common::DocRender('text', $oManifest, !$bNoExe);
my $oDocConfig =
new BackRestDoc::Common::DocConfig(
new BackRestDoc::Common::Doc("${strBasePath}/xml/reference.xml"), $oRender);
$oStorageDoc->pathCreate(
"${strBasePath}/output/man", {strMode => '0770', bIgnoreExists => true, bCreateParent => true});
$oStorageDoc->put("${strBasePath}/output/man/" . lc(PROJECT_NAME) . '.1.txt', $oDocConfig->manGet($oManifest));
}
elsif ($strOutput eq 'html')
{
my $oHtmlSite =
new BackRestDoc::Html::DocHtmlSite
(
$oManifest,
"${strBasePath}/xml",
"${strOutputPath}/html",
"${strBasePath}/resource/html/default.css",
defined($oManifest->variableGet('project-favicon')) ?
"${strBasePath}/resource/html/" . $oManifest->variableGet('project-favicon') : undef,
defined($oManifest->variableGet('project-logo')) ?
"${strBasePath}/resource/" . $oManifest->variableGet('project-logo') : undef,
!$bNoExe
);
$oHtmlSite->process();
}
elsif ($strOutput eq 'pdf')
{
my $oLatex =
new BackRestDoc::Latex::DocLatex
(
$oManifest,
"${strBasePath}/xml",
"${strOutputPath}/latex",
"${strBasePath}/resource/latex/preamble.tex",
!$bNoExe
);
$oLatex->process();
}
}
# Cache the manifest (mostly useful for testing rendering changes in the code)
if (!$bNoCache && !$bCacheOnly)
{
$oManifest->cacheWrite();
}
# Exit with success
exit 0;
}
####################################################################################################################################
# Check for errors
####################################################################################################################################
or do
{
# If a backrest exception then return the code
exit $EVAL_ERROR->code() if (isException(\$EVAL_ERROR));
# Else output the unhandled error
print $EVAL_ERROR;
exit ERROR_UNHANDLED;
};
# It shouldn't be possible to get here
&log(ASSERT, 'execution reached invalid location in ' . __FILE__ . ', line ' . __LINE__);
exit ERROR_ASSERT;
pgbackrest-release-2.24/doc/example/ 0000775 0000000 0000000 00000000000 13625315071 0017407 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/doc/example/pgsql-pgbackrest-info.sql 0000664 0000000 0000000 00000001513 13625315071 0024332 0 ustar 00root root 0000000 0000000 -- An example of monitoring pgBackRest from within PostgreSQL
--
-- Use copy to export data from the pgBackRest info command into the jsonb
-- type so it can be queried directly by PostgreSQL.
-- Create monitor schema
create schema monitor;
-- Get pgBackRest info in JSON format
create function monitor.pgbackrest_info()
returns jsonb AS $$
declare
data jsonb;
begin
-- Create a temp table to hold the JSON data
create temp table temp_pgbackrest_data (data jsonb);
-- Copy data into the table directly from the pgBackRest info command
copy temp_pgbackrest_data (data)
from program
'pgbackrest --output=json info' (format text);
select temp_pgbackrest_data.data
into data
from temp_pgbackrest_data;
drop table temp_pgbackrest_data;
return data;
end $$ language plpgsql;
pgbackrest-release-2.24/doc/example/pgsql-pgbackrest-query.sql 0000664 0000000 0000000 00000001140 13625315071 0024540 0 ustar 00root root 0000000 0000000 -- Get last successful backup for each stanza
--
-- Requires the monitor.pgbackrest_info function.
with stanza as
(
select data->'name' as name,
data->'backup'->(
jsonb_array_length(data->'backup') - 1) as last_backup,
data->'archive'->(
jsonb_array_length(data->'archive') - 1) as current_archive
from jsonb_array_elements(monitor.pgbackrest_info()) as data
)
select name,
to_timestamp(
(last_backup->'timestamp'->>'stop')::numeric) as last_successful_backup,
current_archive->>'max' as last_archived_wal
from stanza;
pgbackrest-release-2.24/doc/lib/ 0000775 0000000 0000000 00000000000 13625315071 0016522 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/doc/lib/BackRestDoc/ 0000775 0000000 0000000 00000000000 13625315071 0020646 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/doc/lib/BackRestDoc/Common/ 0000775 0000000 0000000 00000000000 13625315071 0022076 5 ustar 00root root 0000000 0000000 pgbackrest-release-2.24/doc/lib/BackRestDoc/Common/Doc.pm 0000664 0000000 0000000 00000060645 13625315071 0023154 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# DOC MODULE
####################################################################################################################################
package BackRestDoc::Common::Doc;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use File::Basename qw(dirname);
use Scalar::Util qw(blessed);
use XML::Checker::Parser;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Create the class hash
my $self = {};
bless $self, $class;
$self->{strClass} = $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{strFileName},
my $strSgmlSearchPath,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'strFileName', required => false},
{name => 'strSgmlSearchPath', required => false},
);
# Load the doc from a file if one has been defined
if (defined($self->{strFileName}))
{
my $oParser = XML::Checker::Parser->new(ErrorContext => 2, Style => 'Tree');
$oParser->set_sgml_search_path(
defined($strSgmlSearchPath) ? $strSgmlSearchPath : dirname(dirname(abs_path($0))) . '/doc/xml/dtd');
my $oTree;
eval
{
local $XML::Checker::FAIL = sub
{
my $iCode = shift;
die XML::Checker::error_string($iCode, @_);
};
$oTree = $oParser->parsefile($self->{strFileName});
return true;
}
# Report any error that stopped parsing
or do
{
my $strException = $EVAL_ERROR;
$strException =~ s/at \/.*?$//s; # remove module line number
die "malformed xml in '$self->{strFileName}':\n" . trim($strException);
};
# Parse and build the doc
$self->{oDoc} = $self->build($self->parse(${$oTree}[0], ${$oTree}[1]));
}
# Else create a blank doc
else
{
$self->{oDoc} = {name => 'doc', children => []};
}
$self->{strName} = 'root';
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# parse
#
# Parse the xml doc into a more usable hash and array structure.
####################################################################################################################################
sub parse
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strName,
$oyNode
) =
logDebugParam
(
__PACKAGE__ . '->parse', \@_,
{name => 'strName', trace => true},
{name => 'oyNode', trace => true}
);
my %oOut;
my $iIndex = 0;
my $bText = $strName eq 'text' || $strName eq 'li' || $strName eq 'p' || $strName eq 'title' ||
$strName eq 'summary' || $strName eq 'table-cell' || $strName eq 'table-column' || $strName eq 'list-item' ||
$strName eq 'admonition';
# Store the node name
$oOut{name} = $strName;
if (keys(%{$$oyNode[$iIndex]}))
{
$oOut{param} = $$oyNode[$iIndex];
}
$iIndex++;
# Look for strings and children
while (defined($$oyNode[$iIndex]))
{
# Process string data
if (ref(\$$oyNode[$iIndex]) eq 'SCALAR' && $$oyNode[$iIndex] eq '0')
{
$iIndex++;
my $strBuffer = $$oyNode[$iIndex++];
# Strip tabs, CRs, and LFs
$strBuffer =~ s/\t|\r//g;
# If anything is left
if (length($strBuffer) > 0)
{
# If text node then create array entries for strings
if ($bText)
{
if (!defined($oOut{children}))
{
$oOut{children} = [];
}
push(@{$oOut{children}}, $strBuffer);
}
# Don't allow strings mixed with children
elsif (length(trim($strBuffer)) > 0)
{
if (defined($oOut{children}))
{
confess "text mixed with children in node ${strName} (spaces count)";
}
if (defined($oOut{value}))
{
confess "value is already defined in node ${strName} - this shouldn't happen";
}
# Don't allow text mixed with
$oOut{value} = $strBuffer;
}
}
}
# Process a child
else
{
if (defined($oOut{value}) && $bText)
{
confess "text mixed with children in node ${strName} before child " . $$oyNode[$iIndex++] . " (spaces count)";
}
if (!defined($oOut{children}))
{
$oOut{children} = [];
}
push(@{$oOut{children}}, $self->parse($$oyNode[$iIndex++], $$oyNode[$iIndex++]));
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oDoc', value => \%oOut, trace => true}
);
}
####################################################################################################################################
# build
#
# Restructure the doc to make walking it easier.
####################################################################################################################################
sub build
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oDoc
) =
logDebugParam
(
__PACKAGE__ . '->build', \@_,
{name => 'oDoc', trace => true}
);
# Initialize the node object
my $oOut = {name => $$oDoc{name}, children => [], value => $$oDoc{value}};
my $strError = "in node $$oDoc{name}";
# Get all params
if (defined($$oDoc{param}))
{
for my $strParam (keys %{$$oDoc{param}})
{
$$oOut{param}{$strParam} = $$oDoc{param}{$strParam};
}
}
if ($$oDoc{name} eq 'p' || $$oDoc{name} eq 'title' || $$oDoc{name} eq 'summary' ||
$$oDoc{name} eq 'table-cell' || $$oDoc{name} eq 'table-column' || $$oDoc{name} eq 'list-item' ||
$$oDoc{name} eq 'admonition')
{
$$oOut{field}{text} = $oDoc;
}
elsif (defined($$oDoc{children}))
{
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
{
my $oSub = $$oDoc{children}[$iIndex];
my $strName = $$oSub{name};
if ($strName eq 'text')
{
$$oOut{field}{text} = $oSub;
}
elsif ((defined($$oSub{value}) && !defined($$oSub{param})) && $strName ne 'code-block')
{
$$oOut{field}{$strName} = $$oSub{value};
}
elsif (!defined($$oSub{children}) && !defined($$oSub{value}) && !defined($$oSub{param}))
{
$$oOut{field}{$strName} = true;
}
else
{
push(@{$$oOut{children}}, $self->build($oSub));
}
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oDoc', value => $oOut, trace => true}
);
}
####################################################################################################################################
# nodeGetById
#
# Return a node by name - error if more than one exists
####################################################################################################################################
sub nodeGetById
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strName,
$strId,
$bRequired,
) =
logDebugParam
(
__PACKAGE__ . 'nodeGetById', \@_,
{name => 'strName', trace => true},
{name => 'strId', required => false, trace => true},
{name => 'bRequired', default => true, trace => true}
);
my $oDoc = $self->{oDoc};
my $oNode;
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
{
if ((defined($strName) && $$oDoc{children}[$iIndex]{name} eq $strName) &&
(!defined($strId) || $$oDoc{children}[$iIndex]{param}{id} eq $strId))
{
if (!defined($oNode))
{
$oNode = $$oDoc{children}[$iIndex];
}
else
{
confess "found more than one child ${strName} in node $$oDoc{name}";
}
}
}
if (!defined($oNode) && $bRequired)
{
confess "unable to find child ${strName}" . (defined($strId) ? " (${strId})" : '') . " in node $$oDoc{name}";
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oNodeDoc', value => $self->nodeBless($oNode), trace => true}
);
}
####################################################################################################################################
# nodeGet
#
# Return a node by name - error if more than one exists
####################################################################################################################################
sub nodeGet
{
my $self = shift;
return $self->nodeGetById(shift, undef, shift);
}
####################################################################################################################################
# nodeTest
#
# Test that a node exists
####################################################################################################################################
sub nodeTest
{
my $self = shift;
return defined($self->nodeGetById(shift, undef, false));
}
####################################################################################################################################
# nodeAdd
#
# Add a node to to the current doc's child list
####################################################################################################################################
sub nodeAdd
{
my $self = shift;
my $strName = shift;
my $strValue = shift;
my $oParam = shift;
my $oField = shift;
my $oDoc = $self->{oDoc};
my $oNode = {name => $strName, value => $strValue, param => $oParam, field => $oField};
push(@{$$oDoc{children}}, $oNode);
return $self->nodeBless($oNode);
}
####################################################################################################################################
# nodeBless
#
# Make a new Doc object from a node.
####################################################################################################################################
sub nodeBless
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oNode
) =
logDebugParam
(
__PACKAGE__ . '->nodeBless', \@_,
{name => 'oNode', required => false, trace => true}
);
my $oDoc;
if (defined($oNode))
{
$oDoc = {};
bless $oDoc, $self->{strClass};
$oDoc->{strClass} = $self->{strClass};
$oDoc->{strName} = $$oNode{name};
$oDoc->{oDoc} = $oNode;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oDoc', value => $oDoc, trace => true}
);
}
####################################################################################################################################
# nodeList
#
# Get a list of nodes.
####################################################################################################################################
sub nodeList
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strName,
$bRequired,
) =
logDebugParam
(
__PACKAGE__ . '->nodeList', \@_,
{name => 'strName', required => false, trace => true},
{name => 'bRequired', default => true, trace => true},
);
my $oDoc = $self->{oDoc};
my @oyNode;
if (defined($$oDoc{children}))
{
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
{
if (!defined($strName) || $$oDoc{children}[$iIndex]{name} eq $strName)
{
if (ref(\$$oDoc{children}[$iIndex]) eq "SCALAR")
{
push(@oyNode, $$oDoc{children}[$iIndex]);
}
else
{
push(@oyNode, $self->nodeBless($$oDoc{children}[$iIndex]));
}
}
}
}
if (@oyNode == 0 && $bRequired)
{
confess 'unable to find ' . (defined($strName) ? "children named '${strName}'" : 'any children') . " in node $$oDoc{name}";
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oyNode', value => \@oyNode, trace => true}
);
}
####################################################################################################################################
# nodeRemove
#
# Remove a child node.
####################################################################################################################################
sub nodeRemove
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oChildRemove
) =
logDebugParam
(
__PACKAGE__ . '->nodeRemove', \@_,
{name => 'oChildRemove', required => false, trace => true}
);
my $bRemove = false;
my $oDoc = $self->{oDoc};
# Error if there are no children
if (!defined($$oDoc{children}))
{
confess &log(ERROR, "node has no children");
}
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
{
if ($$oDoc{children}[$iIndex] == $oChildRemove->{oDoc})
{
splice(@{$$oDoc{children}}, $iIndex, 1);
$bRemove = true;
last;
}
}
if (!$bRemove)
{
confess &log(ERROR, "child was not found in node, could not be removed");
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# nodeReplace
#
# Replace a child node with one or more child nodes.
####################################################################################################################################
sub nodeReplace
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oChildRemove,
$oyChildReplace,
) =
logDebugParam
(
__PACKAGE__ . '->nodeReplace', \@_,
{name => 'oChildRemove', trace => true},
{name => 'oChildReplace', trace => true},
);
my $bReplace = false;
my $iReplaceIdx = undef;
my $iReplaceTotal = undef;
my $oDoc = $self->{oDoc};
# Error if there are no children
if (!defined($$oDoc{children}))
{
confess &log(ERROR, "node has no children");
}
for (my $iIndex = 0; $iIndex < @{$$oDoc{children}}; $iIndex++)
{
if ($$oDoc{children}[$iIndex] == $oChildRemove->{oDoc})
{
splice(@{$$oDoc{children}}, $iIndex, 1);
splice(@{$$oDoc{children}}, $iIndex, 0, @{$oyChildReplace});
$iReplaceIdx = $iIndex;
$iReplaceTotal = scalar(@{$oyChildReplace});
$bReplace = true;
last;
}
}
if (!$bReplace)
{
confess &log(ERROR, "child was not found in node, could not be replaced");
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'iReplaceIdx', value => $iReplaceIdx, trace => true},
{name => 'iReplaceTotal', value => $iReplaceTotal, trace => true},
);
}
####################################################################################################################################
# nameGet
####################################################################################################################################
sub nameGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->nameGet');
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strName', value => ${$self->{oDoc}}{name}, trace => true}
);
}
####################################################################################################################################
# valueGet
####################################################################################################################################
sub valueGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->valueGet');
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strValue', value => ${$self->{oDoc}}{value}, trace => true}
);
}
####################################################################################################################################
# valueSet
####################################################################################################################################
sub valueSet
{
my $self = shift;
my $strValue = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->valueSet');
# Set the value
${$self->{oDoc}}{value} = $strValue;
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# paramGet
#
# Get a parameter from a node.
####################################################################################################################################
sub paramGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strName,
$bRequired,
$strDefault,
$strType
) =
logDebugParam
(
__PACKAGE__ . '->paramGet', \@_,
{name => 'strName', trace => true},
{name => 'bRequired', default => true, trace => true},
{name => 'strDefault', required => false, trace => true},
{name => 'strType', default => 'param', trace => true}
);
my $strValue = ${$self->{oDoc}}{$strType}{$strName};
if (!defined($strValue))
{
if ($bRequired)
{
confess "${strType} '${strName}' is required in node '$self->{strName}'";
}
$strValue = $strDefault;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strValue', value => $strValue, trace => true}
);
}
####################################################################################################################################
# paramTest
#
# Test that a parameter exists or has a certain value.
####################################################################################################################################
sub paramTest
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strName,
$strExpectedValue,
$strType
) =
logDebugParam
(
__PACKAGE__ . '->paramTest', \@_,
{name => 'strName', trace => true},
{name => 'strExpectedValue', required => false, trace => true},
{name => 'strType', default => 'param', trace => true}
);
my $bResult = true;
my $strValue = $self->paramGet($strName, false, undef, $strType);
if (!defined($strValue))
{
$bResult = false;
}
elsif (defined($strExpectedValue) && $strValue ne $strExpectedValue)
{
$bResult = false;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bResult', value => $bResult, trace => true}
);
}
####################################################################################################################################
# paramSet
#
# Set a parameter in a node.
####################################################################################################################################
sub paramSet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strName,
$strValue,
$strType
) =
logDebugParam
(
__PACKAGE__ . '->paramSet', \@_,
{name => 'strName', trace => true},
{name => 'strValue', required => false, trace => true},
{name => 'strType', default => 'param', trace => true}
);
${$self->{oDoc}}{$strType}{$strName} = $strValue;
# Return from function and log return values if any
logDebugReturn($strOperation);
}
####################################################################################################################################
# fieldGet
#
# Get a field from a node.
####################################################################################################################################
sub fieldGet
{
my $self = shift;
return $self->paramGet(shift, shift, shift, 'field');
}
####################################################################################################################################
# fieldTest
#
# Test if a field exists.
####################################################################################################################################
sub fieldTest
{
my $self = shift;
return $self->paramTest(shift, shift, 'field');
}
####################################################################################################################################
# textGet
#
# Get a field from a node.
####################################################################################################################################
sub textGet
{
my $self = shift;
return $self->nodeBless($self->paramGet('text', shift, shift, 'field'));
}
####################################################################################################################################
# textSet
#
# Get a field from a node.
####################################################################################################################################
sub textSet
{
my $self = shift;
my $oText = shift;
if (blessed($oText) && $oText->isa('BackRestDoc::Common::Doc'))
{
$oText = $oText->{oDoc};
}
elsif (ref($oText) ne 'HASH')
{
$oText = {name => 'text', children => [$oText]};
}
return $self->paramSet('text', $oText, 'field');
}
####################################################################################################################################
# fieldSet
#
# Set a parameter in a node.
####################################################################################################################################
sub fieldSet
{
my $self = shift;
$self->paramSet(shift, shift, 'field');
}
1;
pgbackrest-release-2.24/doc/lib/BackRestDoc/Common/DocConfig.pm 0000664 0000000 0000000 00000102343 13625315071 0024272 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# DOC CONFIG MODULE
####################################################################################################################################
package BackRestDoc::Common::DocConfig;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRestBuild::Config::Data;
use pgBackRest::Version;
####################################################################################################################################
# Help types
####################################################################################################################################
use constant CONFIG_HELP_COMMAND => 'command';
push @EXPORT, qw(CONFIG_HELP_COMMAND);
use constant CONFIG_HELP_CURRENT => 'current';
use constant CONFIG_HELP_DEFAULT => 'default';
use constant CONFIG_HELP_DESCRIPTION => 'description';
push @EXPORT, qw(CONFIG_HELP_DESCRIPTION);
use constant CONFIG_HELP_EXAMPLE => 'example';
use constant CONFIG_HELP_NAME => 'name';
use constant CONFIG_HELP_NAME_ALT => 'name-alt';
push @EXPORT, qw(CONFIG_HELP_NAME_ALT);
use constant CONFIG_HELP_OPTION => 'option';
push @EXPORT, qw(CONFIG_HELP_OPTION);
use constant CONFIG_HELP_SECTION => 'section';
push @EXPORT, qw(CONFIG_HELP_SECTION);
use constant CONFIG_HELP_SUMMARY => 'summary';
push @EXPORT, qw(CONFIG_HELP_SUMMARY);
use constant CONFIG_HELP_SOURCE => 'source';
push @EXPORT, qw(CONFIG_HELP_SOURCE);
use constant CONFIG_HELP_SOURCE_DEFAULT => 'default';
use constant CONFIG_HELP_SOURCE_SECTION => CONFIG_HELP_SECTION;
use constant CONFIG_HELP_SOURCE_COMMAND => CONFIG_HELP_COMMAND;
push @EXPORT, qw(CONFIG_HELP_SOURCE_COMMAND);
####################################################################################################################################
# Config Section Types
####################################################################################################################################
use constant CFGDEF_COMMAND => 'command';
use constant CFGDEF_GENERAL => 'general';
use constant CFGDEF_LOG => 'log';
use constant CFGDEF_REPOSITORY => 'repository';
####################################################################################################################################
# Option define hash
####################################################################################################################################
my $rhConfigDefine = cfgDefine();
####################################################################################################################################
# Returns the option defines based on the command.
####################################################################################################################################
sub docConfigCommandDefine
{
my $strOption = shift;
my $strCommand = shift;
if (defined($strCommand))
{
return defined($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}) &&
defined($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}) &&
ref($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}) eq 'HASH' ?
$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand} : undef;
}
return;
}
####################################################################################################################################
# Does the option have a default for this command?
####################################################################################################################################
sub docConfigOptionDefault
{
my $strOption = shift;
my $strCommand = shift;
# Get the command define
my $oCommandDefine = docConfigCommandDefine($strOption, $strCommand);
# Check for default in command
my $strDefault = defined($oCommandDefine) ? $$oCommandDefine{&CFGDEF_DEFAULT} : undef;
# If defined return, else try to grab the global default
return defined($strDefault) ? $strDefault : $rhConfigDefine->{$strOption}{&CFGDEF_DEFAULT};
}
push @EXPORT, qw(docConfigOptionDefault);
####################################################################################################################################
# Get the allowed setting range for the option if it exists
####################################################################################################################################
sub docConfigOptionRange
{
my $strOption = shift;
my $strCommand = shift;
# Get the command define
my $oCommandDefine = docConfigCommandDefine($strOption, $strCommand);
# Check for default in command
if (defined($oCommandDefine) && defined($$oCommandDefine{&CFGDEF_ALLOW_RANGE}))
{
return $$oCommandDefine{&CFGDEF_ALLOW_RANGE}[0], $$oCommandDefine{&CFGDEF_ALLOW_RANGE}[1];
}
# If defined return, else try to grab the global default
return $rhConfigDefine->{$strOption}{&CFGDEF_ALLOW_RANGE}[0], $rhConfigDefine->{$strOption}{&CFGDEF_ALLOW_RANGE}[1];
}
push @EXPORT, qw(docConfigOptionRange);
####################################################################################################################################
# Get the option type
####################################################################################################################################
sub docConfigOptionType
{
my $strOption = shift;
return $rhConfigDefine->{$strOption}{&CFGDEF_TYPE};
}
push @EXPORT, qw(docConfigOptionType);
####################################################################################################################################
# Test the option type
####################################################################################################################################
sub docConfigOptionTypeTest
{
my $strOption = shift;
my $strType = shift;
return docConfigOptionType($strOption) eq $strType;
}
push @EXPORT, qw(docConfigOptionTypeTest);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{oDoc},
$self->{oDocRender}
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oDoc'},
{name => 'oDocRender', required => false}
);
$self->process();
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# process
#
# Parse the xml doc into commands and options.
####################################################################################################################################
sub process
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
# Iterate through all commands
my $oDoc = $self->{oDoc};
my $oConfigHash = {};
foreach my $strCommand (cfgDefineCommandList())
{
my $oCommandDoc = $oDoc->nodeGet('operation')->nodeGet('command-list')->nodeGetById('command', $strCommand);
$$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand} = {};
my $oCommand = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand};
$$oCommand{&CONFIG_HELP_SUMMARY} = $oCommandDoc->nodeGet('summary')->textGet();
$$oCommand{&CONFIG_HELP_DESCRIPTION} = $oCommandDoc->textGet();
}
# Iterate through all options
my $oOptionDefine = cfgDefine();
foreach my $strOption (sort(keys(%{$oOptionDefine})))
{
# Skip options that are internal only for all commands (test options)
next if $oOptionDefine->{$strOption}{&CFGDEF_INTERNAL};
# Iterate through all commands
my @stryCommandList = sort(keys(%{defined($$oOptionDefine{$strOption}{&CFGDEF_COMMAND}) ?
$$oOptionDefine{$strOption}{&CFGDEF_COMMAND} : $$oConfigHash{&CONFIG_HELP_COMMAND}}));
foreach my $strCommand (@stryCommandList)
{
if (!defined($$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand}))
{
next;
}
if (ref(\$$oOptionDefine{$strOption}{&CFGDEF_COMMAND}{$strCommand}) eq 'SCALAR' &&
$$oOptionDefine{$strOption}{&CFGDEF_COMMAND}{$strCommand} == false)
{
next;
}
# Skip options that are internal only for the current command
next if $oOptionDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_INTERNAL};
my $oCommandDoc = $oDoc->nodeGet('operation')->nodeGet('command-list')->nodeGetById('command', $strCommand);
# First check if the option is documented in the command
my $oOptionDoc;
my $strOptionSource;
my $oCommandOptionList = $oCommandDoc->nodeGet('option-list', false);
if (defined($oCommandOptionList))
{
$oOptionDoc = $oCommandOptionList->nodeGetById('option', $strOption, false);
$strOptionSource = CONFIG_HELP_SOURCE_COMMAND if (defined($oOptionDoc));
}
# If the option wasn't found keep looking
my $strSection;
if (!defined($oOptionDoc))
{
# Next see if it's documented in the section
if (defined($$oOptionDefine{$strOption}{&CFGDEF_SECTION}))
{
# &log(INFO, " trying section ${strSection}");
foreach my $oSectionNode ($oDoc->nodeGet('config')->nodeGet('config-section-list')->nodeList())
{
my $oOptionDocCheck = $oSectionNode->nodeGetById('config-key-list')
->nodeGetById('config-key', $strOption, false);
if ($oOptionDocCheck)
{
if (defined($oOptionDoc))
{
confess 'option exists in more than one section';
}
$oOptionDoc = $oOptionDocCheck;
$strOptionSource = CONFIG_HELP_SOURCE_SECTION;
$strSection = $oSectionNode->paramGet('id');
}
}
}
# If no section is defined then look in the default command option list
else
{
$oOptionDoc = $oDoc->nodeGet('operation')->nodeGet('operation-general')->nodeGet('option-list')
->nodeGetById('option', $strOption, false);
$strOptionSource = CONFIG_HELP_SOURCE_DEFAULT if (defined($oOptionDoc));
}
}
# If the option wasn't found then error
if (!defined($oOptionDoc))
{
confess &log(ERROR, "unable to find option '${strOption}' for command '${strCommand}'")
}
# if the option is documented in the command then it should be accessible from the command line only.
if (!defined($strSection))
{
if (defined($$oOptionDefine{$strOption}{&CFGDEF_SECTION}))
{
&log(ERROR,
"option ${strOption} defined in command ${strCommand} must not have " . CFGDEF_SECTION .
" defined");
}
}
# Store the option in the command
$$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_SOURCE} =
$strOptionSource;
my $oCommandOption = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption};
$$oCommandOption{&CONFIG_HELP_SUMMARY} = $oOptionDoc->nodeGet('summary')->textGet();
$$oCommandOption{&CONFIG_HELP_DESCRIPTION} = $oOptionDoc->textGet();
$$oCommandOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example');
$$oCommandOption{&CONFIG_HELP_NAME} = $oOptionDoc->paramGet('name');
# Generate a list of alternate names
if (defined($rhConfigDefine->{$strOption}{&CFGDEF_NAME_ALT}))
{
my $rhNameAlt = {};
foreach my $strNameAlt (sort(keys(%{$rhConfigDefine->{$strOption}{&CFGDEF_NAME_ALT}})))
{
$strNameAlt =~ s/\?//g;
if ($strNameAlt ne $strOption)
{
$rhNameAlt->{$strNameAlt} = true;
}
}
my @stryNameAlt = sort(keys(%{$rhNameAlt}));
if (@stryNameAlt > 0)
{
if (@stryNameAlt != 1)
{
confess &log(
ERROR, "multiple alt names are not supported for option '${strOption}': " . join(', ', @stryNameAlt));
}
$oCommandOption->{&CONFIG_HELP_NAME_ALT} = \@stryNameAlt;
}
}
# If the option did not come from the command also store in global option list. This prevents duplication of commonly
# used options.
if ($strOptionSource ne CONFIG_HELP_SOURCE_COMMAND)
{
$$oConfigHash{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_SUMMARY} = $$oCommandOption{&CONFIG_HELP_SUMMARY};
my $oOption = $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption};
if (defined($strSection))
{
$$oOption{&CONFIG_HELP_SECTION} = $strSection;
}
$$oOption{&CONFIG_HELP_NAME} = $oOptionDoc->paramGet('name');
$oOption->{&CONFIG_HELP_NAME_ALT} = $oCommandOption->{&CONFIG_HELP_NAME_ALT};
$$oOption{&CONFIG_HELP_DESCRIPTION} = $$oCommandOption{&CONFIG_HELP_DESCRIPTION};
$$oOption{&CONFIG_HELP_EXAMPLE} = $oOptionDoc->fieldGet('example');
}
}
}
# Store the config hash
$self->{oConfigHash} = $oConfigHash;
# Return from function and log return values if any
logDebugReturn($strOperation);
}
####################################################################################################################################
# manGet
#
# Generate the man page.
####################################################################################################################################
sub manGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oManifest
) =
logDebugParam
(
__PACKAGE__ . '->manGet', \@_,
{name => 'oManifest'}
);
# Get index.xml to pull various text from
my $oIndexDoc = ${$oManifest->sourceGet('index')}{doc};
# Write the header
my $strManPage =
"NAME\n" .
' ' . PROJECT_NAME . ' - ' . $oManifest->variableReplace($oIndexDoc->paramGet('subtitle')) . "\n\n" .
"SYNOPSIS\n" .
' ' . PROJECT_EXE . ' [options] [command]';
# Output the description (first two paragraphs of index.xml introduction)
my $iParaTotal = 0;
$strManPage .= "\n\n" .
"DESCRIPTION";
foreach my $oPara ($oIndexDoc->nodeGetById('section', 'introduction')->nodeList('p'))
{
$strManPage .= ($iParaTotal == 0 ? "\n" : "\n\n") . ' ' .
manGetFormatText($oManifest->variableReplace($self->{oDocRender}->processText($oPara->textGet())), 80, 2);
last;
}
# Build command and config hashes
my $hConfigDefine = cfgDefine();
my $hConfig = $self->{oConfigHash};
my $hCommandList = {};
my $iCommandMaxLen = 0;
my $hOptionList = {};
my $iOptionMaxLen = 0;
foreach my $strCommand (sort(keys(%{$$hConfig{&CONFIG_HELP_COMMAND}})))
{
my $hCommand = $$hConfig{&CONFIG_HELP_COMMAND}{$strCommand};
$iCommandMaxLen = length($strCommand) > $iCommandMaxLen ? length($strCommand) : $iCommandMaxLen;
$$hCommandList{$strCommand}{summary} = $$hCommand{&CONFIG_HELP_SUMMARY};
if (defined($$hCommand{&CONFIG_HELP_OPTION}))
{
foreach my $strOption (sort(keys(%{$$hCommand{&CONFIG_HELP_OPTION}})))
{
my $hOption = $$hCommand{&CONFIG_HELP_OPTION}{$strOption};
if ($$hOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_COMMAND)
{
$iOptionMaxLen = length($strOption) > $iOptionMaxLen ? length($strOption) : $iOptionMaxLen;
$$hOptionList{$strCommand}{$strOption}{&CONFIG_HELP_SUMMARY} = $$hOption{&CONFIG_HELP_SUMMARY};
}
}
}
}
foreach my $strOption (sort(keys(%{$$hConfig{&CONFIG_HELP_OPTION}})))
{
my $hOption = $$hConfig{&CONFIG_HELP_OPTION}{$strOption};
$iOptionMaxLen = length($strOption) > $iOptionMaxLen ? length($strOption) : $iOptionMaxLen;
my $strSection = defined($$hOption{&CONFIG_HELP_SECTION}) ? $$hOption{&CONFIG_HELP_SECTION} : CFGDEF_GENERAL;
$$hOptionList{$strSection}{$strOption}{&CONFIG_HELP_SUMMARY} = $$hOption{&CONFIG_HELP_SUMMARY};
}
# Output Commands
$strManPage .= "\n\n" .
'COMMANDS';
foreach my $strCommand (sort(keys(%{$hCommandList})))
{
# Construct the summary
my $strSummary = $oManifest->variableReplace($self->{oDocRender}->processText($$hCommandList{$strCommand}{summary}));
# $strSummary = lcfirst(substr($strSummary, 0, length($strSummary) - 1));
# Output the summary
$strManPage .=
"\n " . "${strCommand}" . (' ' x ($iCommandMaxLen - length($strCommand))) . ' ' .
manGetFormatText($strSummary, 80, $iCommandMaxLen + 4);
}
# Output options
my $bFirst = true;
$strManPage .= "\n\n" .
'OPTIONS';
foreach my $strSection (sort(keys(%{$hOptionList})))
{
$strManPage .= ($bFirst ?'' : "\n") . "\n " . ucfirst($strSection) . ' Options:';
foreach my $strOption (sort(keys(%{$$hOptionList{$strSection}})))
{
my $hOption = $$hOptionList{$strSection}{$strOption};
# Construct the default
my $strCommand = grep(/$strSection/i, cfgDefineCommandList()) ? $strSection : undef;
my $strDefault = docConfigOptionDefault($strOption, $strCommand);
if (defined($strDefault))
{
if ($strOption eq CFGOPT_REPO_HOST_CMD || $strOption eq CFGOPT_PG_HOST_CMD)
{
$strDefault = PROJECT_EXE;
}
elsif ($$hConfigDefine{$strOption}{&CFGDEF_TYPE} eq &CFGDEF_TYPE_BOOLEAN)
{
$strDefault = $strDefault ? 'y' : 'n';
}
}
#
# use Data::Dumper; confess Dumper($$hOption{&CONFIG_HELP_SUMMARY});
# Construct the summary
my $strSummary = $oManifest->variableReplace($self->{oDocRender}->processText($$hOption{&CONFIG_HELP_SUMMARY}));
$strSummary = $strSummary . (defined($strDefault) ? " [default=${strDefault}]" : '');
# Output the summary
$strManPage .=
"\n " . "--${strOption}" . (' ' x ($iOptionMaxLen - length($strOption))) . ' ' .
manGetFormatText($strSummary, 80, $iOptionMaxLen + 8);
}
$bFirst = false;
}
# Write files, examples, and references
$strManPage .= "\n\n" .
"FILES\n" .
"\n" .
' ' . docConfigOptionDefault(CFGOPT_CONFIG) . "\n" .
' ' . docConfigOptionDefault(CFGOPT_REPO_PATH) . "\n" .
' ' . docConfigOptionDefault(CFGOPT_LOG_PATH) . "\n" .
' ' . docConfigOptionDefault(CFGOPT_SPOOL_PATH) . "\n" .
' ' . docConfigOptionDefault(CFGOPT_LOCK_PATH) . "\n" .
"\n" .
"EXAMPLES\n" .
"\n" .
" * Create a backup of the PostgreSQL `main` cluster:\n" .
"\n" .
' $ ' . PROJECT_EXE . ' --' . CFGOPT_STANZA . "=main backup\n" .
"\n" .
' The `main` cluster should be configured in `' . docConfigOptionDefault(CFGOPT_CONFIG) . "`\n" .
"\n" .
" * Show all available backups:\n" .
"\n" .
' $ ' . PROJECT_EXE . ' ' . CFGCMD_INFO . "\n" .
"\n" .
" * Show all available backups for a specific cluster:\n" .
"\n" .
' $ ' . PROJECT_EXE . ' --' . CFGOPT_STANZA . '=main ' . CFGCMD_INFO . "\n" .
"\n" .
" * Show backup specific options:\n" .
"\n" .
' $ ' . PROJECT_EXE . ' ' . CFGCMD_HELP . ' ' . CFGCMD_BACKUP . "\n" .
"\n" .
"SEE ALSO\n" .
"\n" .
' /usr/share/doc/' . PROJECT_EXE . "-doc/html/index.html\n" .
' ' . $oManifest->variableReplace('{[backrest-url-base]}') . "\n";
return $strManPage;
}
# Helper function for manGet() used to format text by indenting and splitting
sub manGetFormatText
{
my $strLine = shift;
my $iLength = shift;
my $iIndentRest = shift;
my $strPart;
my $strResult;
my $bFirst = true;
do
{
my $iIndent = $bFirst ? 0 : $iIndentRest;
($strPart, $strLine) = stringSplit($strLine, ' ', $iLength - $iIndentRest);
$strResult .= ($bFirst ? '' : "\n") . (' ' x $iIndent) . trim($strPart);
$bFirst = false;
}
while (defined($strLine));
return $strResult;
}
####################################################################################################################################
# helpConfigDocGet
#
# Get the xml for configuration help.
####################################################################################################################################
sub helpConfigDocGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->helpConfigDocGet');
# Build a hash of the sections
my $oConfigHash = $self->{oConfigHash};
my $oConfigDoc = $self->{oDoc}->nodeGet('config');
my $oSectionHash = {};
foreach my $strOption (sort(keys(%{$$oConfigHash{&CONFIG_HELP_OPTION}})))
{
my $oOption = $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption};
if (defined($$oOption{&CONFIG_HELP_SECTION}))
{
$$oSectionHash{$$oOption{&CONFIG_HELP_SECTION}}{$strOption} = true;
}
}
my $oDoc = new BackRestDoc::Common::Doc();
$oDoc->paramSet('title', $oConfigDoc->paramGet('title'));
# set the description for use as a meta tag
$oDoc->fieldSet('description', $oConfigDoc->fieldGet('description'));
# Output the introduction
my $oIntroSectionDoc = $oDoc->nodeAdd('section', undef, {id => 'introduction'});
$oIntroSectionDoc->nodeAdd('title')->textSet('Introduction');
$oIntroSectionDoc->textSet($oConfigDoc->textGet());
foreach my $strSection (sort(keys(%{$oSectionHash})))
{
my $oSectionElement = $oDoc->nodeAdd('section', undef, {id => "section-${strSection}"});
my $oSectionDoc = $oConfigDoc->nodeGet('config-section-list')->nodeGetById('config-section', $strSection);
# Set the summary text for the section
$oSectionElement->textSet($oSectionDoc->textGet());
$oSectionElement->
nodeAdd('title')->textSet(
{name => 'text',
children=> [$oSectionDoc->paramGet('name') . ' Options (', {name => 'id', value => $strSection}, ')']});
foreach my $strOption (sort(keys(%{$$oSectionHash{$strSection}})))
{
$self->helpOptionGet(undef, $strOption, $oSectionElement, $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption});
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oDoc', value => $oDoc}
);
}
####################################################################################################################################
# helpCommandDocGet
#
# Get the xml for command help.
####################################################################################################################################
sub helpCommandDocGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(__PACKAGE__ . '->helpCommandDocGet');
# Working variables
my $oConfigHash = $self->{oConfigHash};
my $oOperationDoc = $self->{oDoc}->nodeGet('operation');
my $oOptionDefine = cfgDefine();
my $oDoc = new BackRestDoc::Common::Doc();
$oDoc->paramSet('title', $oOperationDoc->paramGet('title'));
# set the description for use as a meta tag
$oDoc->fieldSet('description', $oOperationDoc->fieldGet('description'));
# Output the introduction
my $oIntroSectionDoc = $oDoc->nodeAdd('section', undef, {id => 'introduction'});
$oIntroSectionDoc->nodeAdd('title')->textSet('Introduction');
$oIntroSectionDoc->textSet($oOperationDoc->textGet());
foreach my $strCommand (sort(keys(%{$$oConfigHash{&CONFIG_HELP_COMMAND}})))
{
my $oCommandHash = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand};
my $oSectionElement = $oDoc->nodeAdd('section', undef, {id => "command-${strCommand}"});
my $oCommandDoc = $oOperationDoc->nodeGet('command-list')->nodeGetById('command', $strCommand);
$oSectionElement->
nodeAdd('title')->textSet(
{name => 'text',
children=> [$oCommandDoc->paramGet('name') . ' Command (', {name => 'id', value => $strCommand}, ')']});
$oSectionElement->textSet($$oCommandHash{&CONFIG_HELP_DESCRIPTION});
# use Data::doc;
# confess Dumper($oDoc->{oDoc});
if (defined($$oCommandHash{&CONFIG_HELP_OPTION}))
{
my $oCategory = {};
foreach my $strOption (sort(keys(%{$$oCommandHash{&CONFIG_HELP_OPTION}})))
{
# Skip secure options that can't be defined on the command line
next if ($rhConfigDefine->{$strOption}{&CFGDEF_SECURE});
my ($oOption, $strCategory) = helpCommandDocGetOptionFind($oConfigHash, $oOptionDefine, $strCommand, $strOption);
$$oCategory{$strCategory}{$strOption} = $oOption;
}
# Iterate sections
foreach my $strCategory (sort(keys(%{$oCategory})))
{
my $oOptionListElement = $oSectionElement->nodeAdd(
'section', undef, {id => "category-${strCategory}", toc => 'n'});
$oOptionListElement->
nodeAdd('title')->textSet(ucfirst($strCategory) . ' Options');
# Iterate options
foreach my $strOption (sort(keys(%{$$oCategory{$strCategory}})))
{
$self->helpOptionGet($strCommand, $strOption, $oOptionListElement,
$$oCommandHash{&CONFIG_HELP_OPTION}{$strOption});
}
}
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oDoc', value => $oDoc}
);
}
# Helper function for helpCommandDocGet() to find options. The option may be stored with the command or in the option list depending
# on whether it's generic or command-specific
sub helpCommandDocGetOptionFind
{
my $oConfigHelpData = shift;
my $oOptionDefine = shift;
my $strCommand = shift;
my $strOption = shift;
my $strSection = CONFIG_HELP_COMMAND;
my $oOption = $$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption};
if ($$oOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_DEFAULT)
{
$strSection = CFGDEF_GENERAL;
}
elsif ($$oOption{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_SECTION)
{
$oOption = $$oConfigHelpData{&CONFIG_HELP_OPTION}{$strOption};
if (defined($$oOption{&CONFIG_HELP_SECTION}) && $strSection ne $strCommand)
{
$strSection = $$oOption{&CONFIG_HELP_SECTION};
}
if (($strSection ne CFGDEF_GENERAL && $strSection ne CFGDEF_LOG &&
$strSection ne CFGDEF_REPOSITORY && $strSection ne CFGDEF_SECTION_STANZA) ||
$strSection eq $strCommand)
{
$strSection = CONFIG_HELP_COMMAND;
}
}
return $oOption, $strSection;
}
####################################################################################################################################
# helpOptionGet
#
# Get the xml for an option.
####################################################################################################################################
sub helpOptionGet
{
my $self = shift;
my $strCommand = shift;
my $strOption = shift;
my $oParentElement = shift;
my $oOptionHash = shift;
# Create the option section
my $oOptionElement = $oParentElement->nodeAdd(
'section', undef, {id => "option-${strOption}", toc => defined($strCommand) ? 'n' : 'y'});
# Set the option section title
$oOptionElement->
nodeAdd('title')->textSet(
{name => 'text',
children=> [$$oOptionHash{&CONFIG_HELP_NAME} . ' Option (', {name => 'id', value => "--${strOption}"}, ')']});
# Add the option summary and description
$oOptionElement->
nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_SUMMARY});
$oOptionElement->
nodeAdd('p')->textSet($$oOptionHash{&CONFIG_HELP_DESCRIPTION});
# Get the default value (or required=n if there is no default)
my $strCodeBlock;
if (defined(docConfigOptionDefault($strOption, $strCommand)))
{
my $strDefault;
if ($strOption eq CFGOPT_REPO_HOST_CMD || $strOption eq CFGOPT_PG_HOST_CMD)
{
$strDefault = '[INSTALL-PATH]/' . PROJECT_EXE;
}
else
{
if (docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN))
{
$strDefault = docConfigOptionDefault($strOption, $strCommand) ? 'y' : 'n';
}
else
{
$strDefault = docConfigOptionDefault($strOption, $strCommand);
}
}
$strCodeBlock = "default: ${strDefault}";
}
# This won't work correctly until there is some notion of dependency
# elsif (optionRequired($strOption, $strCommand))
# {
# $strCodeBlock = 'required: y';
# }
# Get the allowed range if it exists
my ($strRangeMin, $strRangeMax) = docConfigOptionRange($strOption, $strCommand);
if (defined($strRangeMin))
{
$strCodeBlock .= (defined($strCodeBlock) ? "\n" : '') . "allowed: ${strRangeMin}-${strRangeMax}";
}
# Get the example
my $strExample;
my $strOptionPrefix = $rhConfigDefine->{$strOption}{&CFGDEF_PREFIX};
my $strOptionIndex = defined($strOptionPrefix) ?
"${strOptionPrefix}1-" . substr($strOption, length($strOptionPrefix) + 1) : $strOption;
if (defined($strCommand))
{
if (docConfigOptionTypeTest($strOption, CFGDEF_TYPE_BOOLEAN))
{
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});
}
$strExample = '--' . ($$oOptionHash{&CONFIG_HELP_EXAMPLE} eq 'n' ? 'no-' : '') . $strOptionIndex;
}
else
{
$strExample = "--${strOptionIndex}=" . $$oOptionHash{&CONFIG_HELP_EXAMPLE};
}
}
else
{
$strExample = "${strOptionIndex}=" . $$oOptionHash{&CONFIG_HELP_EXAMPLE};
}
$strCodeBlock .= (defined($strCodeBlock) ? "\n" : '') . "example: ${strExample}";
$oOptionElement->
nodeAdd('code-block')->valueSet($strCodeBlock);
# Output deprecated names
if (defined($oOptionHash->{&CONFIG_HELP_NAME_ALT}))
{
my $strCaption = 'Deprecated Name' . (@{$oOptionHash->{&CONFIG_HELP_NAME_ALT}} > 1 ? 's' : '');
$oOptionElement->
nodeAdd('p')->textSet("${strCaption}: " . join(', ', @{$oOptionHash->{&CONFIG_HELP_NAME_ALT}}));
}
}
1;
pgbackrest-release-2.24/doc/lib/BackRestDoc/Common/DocExecute.pm 0000664 0000000 0000000 00000120133 13625315071 0024464 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# DOC EXECUTE MODULE
####################################################################################################################################
package BackRestDoc::Common::DocExecute;
use parent 'BackRestDoc::Common::DocRender';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Version;
use pgBackRestBuild::Config::Data;
use BackRestDoc::Common::DocManifest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::HostTest;
use pgBackRestTest::Common::HostGroupTest;
####################################################################################################################################
# User that's building the docs
####################################################################################################################################
use constant DOC_USER => getpwuid($UID) eq 'root' ? 'ubuntu' : getpwuid($UID) . '';
####################################################################################################################################
# Generate indexed defines
####################################################################################################################################
my $rhConfigDefineIndex = cfgDefine();
foreach my $strKey (sort(keys(%{$rhConfigDefineIndex})))
{
# Build options for all possible db configurations
if (defined($rhConfigDefineIndex->{$strKey}{&CFGDEF_PREFIX}) &&
$rhConfigDefineIndex->{$strKey}{&CFGDEF_PREFIX} eq CFGDEF_PREFIX_PG)
{
my $strPrefix = $rhConfigDefineIndex->{$strKey}{&CFGDEF_PREFIX};
for (my $iIndex = 1; $iIndex <= CFGDEF_INDEX_PG; $iIndex++)
{
my $strKeyNew = "${strPrefix}${iIndex}" . substr($strKey, length($strPrefix));
$rhConfigDefineIndex->{$strKeyNew} = dclone($rhConfigDefineIndex->{$strKey});
$rhConfigDefineIndex->{$strKeyNew}{&CFGDEF_INDEX_TOTAL} = CFGDEF_INDEX_PG;
$rhConfigDefineIndex->{$strKeyNew}{&CFGDEF_INDEX} = $iIndex - 1;
# Options indexed > 1 are never required
if ($iIndex != 1)
{
$rhConfigDefineIndex->{$strKeyNew}{&CFGDEF_REQUIRED} = false;
}
if (defined($rhConfigDefineIndex->{$strKeyNew}{&CFGDEF_DEPEND}) &&
defined($rhConfigDefineIndex->{$strKeyNew}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION}))
{
$rhConfigDefineIndex->{$strKeyNew}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION} =
"${strPrefix}${iIndex}" .
substr(
$rhConfigDefineIndex->{$strKeyNew}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION},
length($strPrefix));
}
}
delete($rhConfigDefineIndex->{$strKey});
}
else
{
$rhConfigDefineIndex->{$strKey}{&CFGDEF_INDEX} = 0;
}
}
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strType,
$oManifest,
$strRenderOutKey,
$bExe
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'strType'},
{name => 'oManifest'},
{name => 'strRenderOutKey'},
{name => 'bExe'}
);
# Create the class hash
my $self = $class->SUPER::new($strType, $oManifest, $bExe, $strRenderOutKey);
bless $self, $class;
if (defined($self->{oSource}{hyCache}))
{
$self->{bCache} = true;
$self->{iCacheIdx} = 0;
}
else
{
$self->{bCache} = false;
}
$self->{bExe} = $bExe;
$self->{iCmdLineLen} = $self->{oDoc}->paramGet('cmd-line-len', false, 80);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# executeKey
#
# Get a unique key for the execution step to determine if the cache is valid.
####################################################################################################################################
sub executeKey
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strHostName,
$oCommand,
) =
logDebugParam
(
__PACKAGE__ . '->executeKey', \@_,
{name => 'strHostName', trace => true},
{name => 'oCommand', trace => true},
);
# Add user to command
my $bUserForce = $oCommand->paramTest('user-force', 'y') ? true : false;
my $strCommand = $self->{oManifest}->variableReplace(trim($oCommand->fieldGet('exe-cmd')));
my $strUser = $self->{oManifest}->variableReplace($oCommand->paramGet('user', false, DOC_USER));
$strCommand = ($strUser eq DOC_USER || $bUserForce ? '' : ('sudo ' . ($strUser eq 'root' ? '' : "-u $strUser "))) . $strCommand;
# Format and split command
$strCommand =~ s/[ ]*\n[ ]*/ \\\n /smg;
$strCommand =~ s/ \\\@ \\//smg;
my @stryCommand = split("\n", $strCommand);
my $hCacheKey =
{
host => $strHostName,
cmd => \@stryCommand,
output => JSON::PP::false,
};
$$hCacheKey{'run-as-user'} = $bUserForce ? $strUser : undef;
if (defined($oCommand->fieldGet('exe-cmd-extra', false)))
{
$$hCacheKey{'cmd-extra'} = $oCommand->fieldGet('exe-cmd-extra');
}
if (defined($oCommand->paramGet('err-expect', false)))
{
$$hCacheKey{'err-expect'} = $oCommand->paramGet('err-expect');
}
if ($oCommand->paramTest('output', 'y') || $oCommand->paramTest('show', 'y') || $oCommand->paramTest('variable-key'))
{
$$hCacheKey{'output'} = JSON::PP::true;
}
$$hCacheKey{'load-env'} = $oCommand->paramTest('load-env', 'n') ? JSON::PP::false : JSON::PP::true;
$$hCacheKey{'bash-wrap'} = $oCommand->paramTest('bash-wrap', 'n') ? JSON::PP::false : JSON::PP::true;
if (defined($oCommand->fieldGet('exe-highlight', false)))
{
$$hCacheKey{'output'} = JSON::PP::true;
$$hCacheKey{highlight}{'filter'} = $oCommand->paramTest('filter', 'n') ? JSON::PP::false : JSON::PP::true;
$$hCacheKey{highlight}{'filter-context'} = $oCommand->paramGet('filter-context', false, 2);
my @stryHighlight;
$stryHighlight[0] = $self->{oManifest}->variableReplace($oCommand->fieldGet('exe-highlight'));
$$hCacheKey{highlight}{list} = \@stryHighlight;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hExecuteKey', value => $hCacheKey, trace => true}
);
}
####################################################################################################################################
# execute
####################################################################################################################################
sub execute
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$strHostName,
$oCommand,
$iIndent,
$bCache,
$bShow,
) =
logDebugParam
(
__PACKAGE__ . '->execute', \@_,
{name => 'oSection'},
{name => 'strHostName'},
{name => 'oCommand'},
{name => 'iIndent', optional => true, default => 1},
{name => 'bCache', optional => true, default => true},
{name => 'bShow', optional => true, default => true},
);
# Working variables
my $hCacheKey = $self->executeKey($strHostName, $oCommand);
my $strCommand = join("\n", @{$$hCacheKey{cmd}});
my $strOutput;
if ($bShow && $self->{bExe} && $self->isRequired($oSection))
{
# Make sure that no lines are greater than 80 chars
foreach my $strLine (split("\n", $strCommand))
{
if (length(trim($strLine)) > $self->{iCmdLineLen})
{
confess &log(ERROR,
"command has a line > $self->{iCmdLineLen} characters:\n${strCommand}\noffending line: ${strLine}");
}
}
}
&log(DEBUG, (' ' x $iIndent) . "execute: $strCommand");
if ($self->{oManifest}->variableReplace($oCommand->paramGet('skip', false, 'n')) ne 'y')
{
if ($self->{bExe} && $self->isRequired($oSection))
{
my ($bCacheHit, $strCacheType, $hCacheKey, $hCacheValue) = $self->cachePop('exe', $hCacheKey);
if ($bCacheHit)
{
$strOutput = defined($$hCacheValue{output}) ? join("\n", @{$$hCacheValue{output}}) : undef;
}
else
{
# Check that the host is valid
my $oHost = $self->{host}{$strHostName};
if (!defined($oHost))
{
confess &log(ERROR, "cannot execute on host ${strHostName} because the host does not exist");
}
my $oExec = $oHost->execute(
$strCommand . (defined($$hCacheKey{'cmd-extra'}) ? ' ' . $$hCacheKey{'cmd-extra'} : ''),
{iExpectedExitStatus => $$hCacheKey{'err-expect'},
bSuppressError => $oCommand->paramTest('err-suppress', 'y'),
iRetrySeconds => $oCommand->paramGet('retry', false)}, $hCacheKey->{'run-as-user'},
{bLoadEnv => $hCacheKey->{'load-env'}, bBashWrap => $hCacheKey->{'bash-wrap'}});
$oExec->begin();
$oExec->end();
if (defined($oExec->{strOutLog}) && $oExec->{strOutLog} ne '')
{
$strOutput = $oExec->{strOutLog};
# Trim off extra linefeeds before and after
$strOutput =~ s/^\n+|\n$//g;
}
if (defined($$hCacheKey{'err-expect'}) && defined($oExec->{strErrorLog}) && $oExec->{strErrorLog} ne '')
{
$strOutput .= $oExec->{strErrorLog};
}
if ($$hCacheKey{output} && defined($$hCacheKey{highlight}) && $$hCacheKey{highlight}{filter} && defined($strOutput))
{
my $strHighLight = @{$$hCacheKey{highlight}{list}}[0];
if (!defined($strHighLight))
{
confess &log(ERROR, 'filter requires highlight definition: ' . $strCommand);
}
my $iFilterContext = $$hCacheKey{highlight}{'filter-context'};
my @stryOutput = split("\n", $strOutput);
undef($strOutput);
# my $iFiltered = 0;
my $iLastOutput = -1;
for (my $iIndex = 0; $iIndex < @stryOutput; $iIndex++)
{
if ($stryOutput[$iIndex] =~ /$strHighLight/)
{
# Determine the first line to output
my $iFilterFirst = $iIndex - $iFilterContext;
# Don't go past the beginning
$iFilterFirst = $iFilterFirst < 0 ? 0 : $iFilterFirst;
# Don't repeat lines that have already been output
$iFilterFirst = $iFilterFirst <= $iLastOutput ? $iLastOutput + 1 : $iFilterFirst;
# Determine the last line to output
my $iFilterLast = $iIndex + $iFilterContext;
# Don't got past the end
$iFilterLast = $iFilterLast >= @stryOutput ? @stryOutput -1 : $iFilterLast;
# Mark filtered lines if any
if ($iFilterFirst > $iLastOutput + 1)
{
my $iFiltered = $iFilterFirst - ($iLastOutput + 1);
if ($iFiltered > 1)
{
$strOutput .= (defined($strOutput) ? "\n" : '') .
" [filtered ${iFiltered} lines of output]";
}
else
{
$iFilterFirst -= 1;
}
}
# Output the lines
for (my $iOutputIndex = $iFilterFirst; $iOutputIndex <= $iFilterLast; $iOutputIndex++)
{
$strOutput .= (defined($strOutput) ? "\n" : '') . $stryOutput[$iOutputIndex];
}
$iLastOutput = $iFilterLast;
}
}
if (@stryOutput - 1 > $iLastOutput + 1)
{
my $iFiltered = (@stryOutput - 1) - ($iLastOutput + 1);
if ($iFiltered > 1)
{
$strOutput .= (defined($strOutput) ? "\n" : '') .
" [filtered ${iFiltered} lines of output]";
}
else
{
$strOutput .= (defined($strOutput) ? "\n" : '') . $stryOutput[-1];
}
}
}
if (!$$hCacheKey{output})
{
$strOutput = undef;
}
if (defined($strOutput))
{
my @stryOutput = split("\n", $strOutput);
$$hCacheValue{output} = \@stryOutput;
}
if ($bCache)
{
$self->cachePush($strCacheType, $hCacheKey, $hCacheValue);
}
}
# Output is assigned to a var
if ($oCommand->paramTest('variable-key'))
{
$self->{oManifest}->variableSet($oCommand->paramGet('variable-key'), trim($strOutput), true);
}
}
elsif ($$hCacheKey{output})
{
$strOutput = 'Output suppressed for testing';
}
}
# Default variable output when it was not set by execution
if ($oCommand->paramTest('variable-key') && !defined($self->{oManifest}->variableGet($oCommand->paramGet('variable-key'))))
{
$self->{oManifest}->variableSet($oCommand->paramGet('variable-key'), '[Test Variable]', true);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strCommand', value => $strCommand, trace => true},
{name => 'strOutput', value => $strOutput, trace => true}
);
}
####################################################################################################################################
# configKey
####################################################################################################################################
sub configKey
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oConfig,
) =
logDebugParam
(
__PACKAGE__ . '->hostKey', \@_,
{name => 'oConfig', trace => true},
);
my $hCacheKey =
{
host => $self->{oManifest}->variableReplace($oConfig->paramGet('host')),
file => $self->{oManifest}->variableReplace($oConfig->paramGet('file')),
};
if ($oConfig->paramTest('reset', 'y'))
{
$$hCacheKey{reset} = JSON::PP::true;
}
# Add all options to the key
my $strOptionTag = $oConfig->nameGet() eq 'backrest-config' ? 'backrest-config-option' : 'postgres-config-option';
foreach my $oOption ($oConfig->nodeList($strOptionTag))
{
my $hOption = {};
if ($oOption->paramTest('remove', 'y'))
{
$$hOption{remove} = JSON::PP::true;
}
if (defined($oOption->valueGet(false)))
{
$$hOption{value} = $self->{oManifest}->variableReplace($oOption->valueGet());
}
my $strKey = $self->{oManifest}->variableReplace($oOption->paramGet('key'));
if ($oConfig->nameGet() eq 'backrest-config')
{
my $strSection = $self->{oManifest}->variableReplace($oOption->paramGet('section'));
$$hCacheKey{option}{$strSection}{$strKey} = $hOption;
}
else
{
$$hCacheKey{option}{$strKey} = $hOption;
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hCacheKey', value => $hCacheKey, trace => true}
);
}
####################################################################################################################################
# backrestConfig
####################################################################################################################################
sub backrestConfig
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$oConfig,
$iDepth
) =
logDebugParam
(
__PACKAGE__ . '->backrestConfig', \@_,
{name => 'oSection'},
{name => 'oConfig'},
{name => 'iDepth'}
);
# Working variables
my $hCacheKey = $self->configKey($oConfig);
my $strFile = $$hCacheKey{file};
my $strConfig = undef;
&log(DEBUG, (' ' x $iDepth) . 'process backrest config: ' . $$hCacheKey{file});
if ($self->{bExe} && $self->isRequired($oSection))
{
# Load module dynamically
require pgBackRest::Common::Ini;
pgBackRest::Common::Ini->import();
my ($bCacheHit, $strCacheType, $hCacheKey, $hCacheValue) = $self->cachePop('cfg-' . PROJECT_EXE, $hCacheKey);
if ($bCacheHit)
{
$strConfig = defined($$hCacheValue{config}) ? join("\n", @{$$hCacheValue{config}}) : undef;
}
else
{
# Check that the host is valid
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
my $oHost = $self->{host}{$strHostName};
if (!defined($oHost))
{
confess &log(ERROR, "cannot configure backrest on host ${strHostName} because the host does not exist");
}
# Reset all options
if ($oConfig->paramTest('reset', 'y'))
{
delete(${$self->{config}}{$strHostName}{$$hCacheKey{file}})
}
foreach my $oOption ($oConfig->nodeList('backrest-config-option'))
{
my $strSection = $self->{oManifest}->variableReplace($oOption->paramGet('section'));
my $strKey = $self->{oManifest}->variableReplace($oOption->paramGet('key'));
my $strValue;
if (!$oOption->paramTest('remove', 'y'))
{
$strValue = $self->{oManifest}->variableReplace(trim($oOption->valueGet(false)));
}
if (!defined($strValue))
{
delete(${$self->{config}}{$strHostName}{$$hCacheKey{file}}{$strSection}{$strKey});
if (keys(%{${$self->{config}}{$strHostName}{$$hCacheKey{file}}{$strSection}}) == 0)
{
delete(${$self->{config}}{$strHostName}{$$hCacheKey{file}}{$strSection});
}
&log(DEBUG, (' ' x ($iDepth + 1)) . "reset ${strSection}->${strKey}");
}
else
{
# Make sure the specified option exists
# ??? This is too simplistic to handle new indexed options. The check below works for now but it would be good
# ??? to bring back more sophisticated checking in the future.
# if (!defined($rhConfigDefineIndex->{$strKey}))
# {
# confess &log(ERROR, "option ${strKey} does not exist");
# }
# If this option is a hash and the value is already set then append to the array
if (defined($rhConfigDefineIndex->{$strKey}) &&
$rhConfigDefineIndex->{$strKey}{&CFGDEF_TYPE} eq CFGDEF_TYPE_HASH &&
defined(${$self->{config}}{$strHostName}{$$hCacheKey{file}}{$strSection}{$strKey}))
{
my @oValue = ();
my $strHashValue = ${$self->{config}}{$strHostName}{$$hCacheKey{file}}{$strSection}{$strKey};
# If there is only one key/value
if (ref(\$strHashValue) eq 'SCALAR')
{
push(@oValue, $strHashValue);
}
# Else if there is an array of values
else
{
@oValue = @{$strHashValue};
}
push(@oValue, $strValue);
${$self->{config}}{$strHostName}{$$hCacheKey{file}}{$strSection}{$strKey} = \@oValue;
}
# else just set the value
else
{
${$self->{config}}{$strHostName}{$$hCacheKey{file}}{$strSection}{$strKey} = $strValue;
}
&log(DEBUG, (' ' x ($iDepth + 1)) . "set ${strSection}->${strKey} = ${strValue}");
}
}
my $strLocalFile = '/home/' . DOC_USER . '/data/pgbackrest.conf';
# Save the ini file
$self->{oManifest}->storage()->put($strLocalFile, iniRender($self->{config}{$strHostName}{$$hCacheKey{file}}, true));
$oHost->copyTo(
$strLocalFile, $$hCacheKey{file},
$self->{oManifest}->variableReplace($oConfig->paramGet('owner', false, 'postgres:postgres')), '640');
# Remove the log-console-stderr option before pushing into the cache
# ??? This is not very pretty and should be replaced with a general way to hide config options
my $oConfigClean = dclone($self->{config}{$strHostName}{$$hCacheKey{file}});
delete($$oConfigClean{&CFGDEF_SECTION_GLOBAL}{&CFGOPT_LOG_LEVEL_STDERR});
delete($$oConfigClean{&CFGDEF_SECTION_GLOBAL}{&CFGOPT_LOG_TIMESTAMP});
if (keys(%{$$oConfigClean{&CFGDEF_SECTION_GLOBAL}}) == 0)
{
delete($$oConfigClean{&CFGDEF_SECTION_GLOBAL});
}
$self->{oManifest}->storage()->put("${strLocalFile}.clean", iniRender($oConfigClean, true));
# Push config file into the cache
$strConfig = ${$self->{oManifest}->storage()->get("${strLocalFile}.clean")};
my @stryConfig = undef;
if (trim($strConfig) ne '')
{
@stryConfig = split("\n", $strConfig);
}
$$hCacheValue{config} = \@stryConfig;
$self->cachePush($strCacheType, $hCacheKey, $hCacheValue);
}
}
else
{
$strConfig = 'Config suppressed for testing';
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strFile', value => $strFile, trace => true},
{name => 'strConfig', value => $strConfig, trace => true},
{name => 'bShow', value => $oConfig->paramTest('show', 'n') ? false : true, trace => true}
);
}
####################################################################################################################################
# postgresConfig
####################################################################################################################################
sub postgresConfig
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$oConfig,
$iDepth
) =
logDebugParam
(
__PACKAGE__ . '->postgresConfig', \@_,
{name => 'oSection'},
{name => 'oConfig'},
{name => 'iDepth'}
);
# Working variables
my $hCacheKey = $self->configKey($oConfig);
my $strFile = $$hCacheKey{file};
my $strConfig;
if ($self->{bExe} && $self->isRequired($oSection))
{
my ($bCacheHit, $strCacheType, $hCacheKey, $hCacheValue) = $self->cachePop('cfg-postgresql', $hCacheKey);
if ($bCacheHit)
{
$strConfig = defined($$hCacheValue{config}) ? join("\n", @{$$hCacheValue{config}}) : undef;
}
else
{
# Check that the host is valid
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
my $oHost = $self->{host}{$strHostName};
if (!defined($oHost))
{
confess &log(ERROR, "cannot configure postgres on host ${strHostName} because the host does not exist");
}
my $strLocalFile = '/home/' . DOC_USER . '/data/postgresql.conf';
$oHost->copyFrom($$hCacheKey{file}, $strLocalFile);
if (!defined(${$self->{'pg-config'}}{$strHostName}{$$hCacheKey{file}}{base}) && $self->{bExe})
{
${$self->{'pg-config'}}{$strHostName}{$$hCacheKey{file}}{base} =
${$self->{oManifest}->storage()->get($strLocalFile)};
}
my $oConfigHash = $self->{'pg-config'}{$strHostName}{$$hCacheKey{file}};
my $oConfigHashNew;
if (!defined($$oConfigHash{old}))
{
$oConfigHashNew = {};
$$oConfigHash{old} = {}
}
else
{
$oConfigHashNew = dclone($$oConfigHash{old});
}
&log(DEBUG, (' ' x $iDepth) . 'process postgres config: ' . $$hCacheKey{file});
foreach my $oOption ($oConfig->nodeList('postgres-config-option'))
{
my $strKey = $oOption->paramGet('key');
my $strValue = $self->{oManifest}->variableReplace(trim($oOption->valueGet()));
if ($strValue eq '')
{
delete($$oConfigHashNew{$strKey});
&log(DEBUG, (' ' x ($iDepth + 1)) . "reset ${strKey}");
}
else
{
$$oConfigHashNew{$strKey} = $strValue;
&log(DEBUG, (' ' x ($iDepth + 1)) . "set ${strKey} = ${strValue}");
}
}
# Generate config text
foreach my $strKey (sort(keys(%$oConfigHashNew)))
{
if (defined($strConfig))
{
$strConfig .= "\n";
}
$strConfig .= "${strKey} = $$oConfigHashNew{$strKey}";
}
# Save the conf file
if ($self->{bExe})
{
$self->{oManifest}->storage()->put($strLocalFile, $$oConfigHash{base} .
(defined($strConfig) ? "\n# pgBackRest Configuration\n${strConfig}\n" : ''));
$oHost->copyTo($strLocalFile, $$hCacheKey{file}, 'postgres:postgres', '640');
}
$$oConfigHash{old} = $oConfigHashNew;
my @stryConfig = undef;
if (trim($strConfig) ne '')
{
@stryConfig = split("\n", $strConfig);
}
$$hCacheValue{config} = \@stryConfig;
$self->cachePush($strCacheType, $hCacheKey, $hCacheValue);
}
}
else
{
$strConfig = 'Config suppressed for testing';
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strFile', value => $strFile, trace => true},
{name => 'strConfig', value => $strConfig, trace => true},
{name => 'bShow', value => $oConfig->paramTest('show', 'n') ? false : true, trace => true}
);
}
####################################################################################################################################
# hostKey
####################################################################################################################################
sub hostKey
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oHost,
) =
logDebugParam
(
__PACKAGE__ . '->hostKey', \@_,
{name => 'oHost', trace => true},
);
my $hCacheKey =
{
name => $self->{oManifest}->variableReplace($oHost->paramGet('name')),
image => $self->{oManifest}->variableReplace($oHost->paramGet('image')),
};
if (defined($oHost->paramGet('id', false)))
{
$hCacheKey->{id} = $self->{oManifest}->variableReplace($oHost->paramGet('id'));
}
else
{
$hCacheKey->{id} = $hCacheKey->{name};
}
if (defined($oHost->paramGet('option', false)))
{
$$hCacheKey{option} = $self->{oManifest}->variableReplace($oHost->paramGet('option'));
}
if (defined($oHost->paramGet('param', false)))
{
$$hCacheKey{param} = $self->{oManifest}->variableReplace($oHost->paramGet('param'));
}
if (defined($oHost->paramGet('os', false)))
{
$$hCacheKey{os} = $self->{oManifest}->variableReplace($oHost->paramGet('os'));
}
$$hCacheKey{'update-hosts'} = $oHost->paramTest('update-hosts', 'n') ? JSON::PP::false : JSON::PP::true;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hCacheKey', value => $hCacheKey, trace => true}
);
}
####################################################################################################################################
# cachePop
####################################################################################################################################
sub cachePop
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strCacheType,
$hCacheKey,
) =
logDebugParam
(
__PACKAGE__ . '->hostKey', \@_,
{name => 'strCacheType', trace => true},
{name => 'hCacheKey', trace => true},
);
my $bCacheHit = false;
my $oCacheValue = undef;
if ($self->{bCache})
{
my $oJSON = JSON::PP->new()->canonical()->allow_nonref();
# &log(WARN, "checking cache for\ncurrent key: " . $oJSON->encode($hCacheKey));
my $hCache = ${$self->{oSource}{hyCache}}[$self->{iCacheIdx}];
if (!defined($hCache))
{
confess &log(ERROR, 'unable to get index from cache', ERROR_FILE_INVALID);
}
if (!defined($$hCache{key}))
{
confess &log(ERROR, 'unable to get key from cache', ERROR_FILE_INVALID);
}
if (!defined($$hCache{type}))
{
confess &log(ERROR, 'unable to get type from cache', ERROR_FILE_INVALID);
}
if ($$hCache{type} ne $strCacheType)
{
confess &log(ERROR, 'types do not match, cache is invalid', ERROR_FILE_INVALID);
}
if ($oJSON->encode($$hCache{key}) ne $oJSON->encode($hCacheKey))
{
confess &log(ERROR,
"keys at index $self->{iCacheIdx} do not match, cache is invalid." .
"\n cache key: " . $oJSON->encode($$hCache{key}) .
"\ncurrent key: " . $oJSON->encode($hCacheKey), ERROR_FILE_INVALID);
}
$bCacheHit = true;
$oCacheValue = $$hCache{value};
$self->{iCacheIdx}++;
}
else
{
if ($self->{oManifest}{bCacheOnly})
{
confess &log(ERROR, 'Cache only operation forced by --cache-only option');
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bCacheHit', value => $bCacheHit, trace => true},
{name => 'strCacheType', value => $strCacheType, trace => true},
{name => 'hCacheKey', value => $hCacheKey, trace => true},
{name => 'oCacheValue', value => $oCacheValue, trace => true},
);
}
####################################################################################################################################
# cachePush
####################################################################################################################################
sub cachePush
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strType,
$hCacheKey,
$oCacheValue,
) =
logDebugParam
(
__PACKAGE__ . '->hostKey', \@_,
{name => 'strType', trace => true},
{name => 'hCacheKey', trace => true},
{name => 'oCacheValue', required => false, trace => true},
);
if ($self->{bCache})
{
confess &log(ASSERT, "cachePush should not be called when cache is already present");
}
# Create the cache entry
my $hCache =
{
key => $hCacheKey,
type => $strType,
};
if (defined($oCacheValue))
{
$$hCache{value} = $oCacheValue;
}
push @{$self->{oSource}{hyCache}}, $hCache;
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# sectionChildProcesss
####################################################################################################################################
sub sectionChildProcess
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$oChild,
$iDepth
) =
logDebugParam
(
__PACKAGE__ . '->sectionChildProcess', \@_,
{name => 'oSection'},
{name => 'oChild'},
{name => 'iDepth'}
);
&log(DEBUG, (' ' x ($iDepth + 1)) . 'process child: ' . $oChild->nameGet());
# Execute a command
if ($oChild->nameGet() eq 'host-add')
{
if ($self->{bExe} && $self->isRequired($oSection))
{
my ($bCacheHit, $strCacheType, $hCacheKey, $hCacheValue) = $self->cachePop('host', $self->hostKey($oChild));
if ($bCacheHit)
{
$self->{oManifest}->variableSet('host-' . $hCacheKey->{id} . '-ip', $hCacheValue->{ip}, true);
}
else
{
if (defined($self->{host}{$$hCacheKey{name}}))
{
confess &log(ERROR, 'cannot add host ${strName} because the host already exists');
}
executeTest("rm -rf ~/data/$$hCacheKey{name}");
executeTest("mkdir -p ~/data/$$hCacheKey{name}/etc");
my $strHost = $hCacheKey->{name};
my $strImage = $hCacheKey->{image};
my $strHostUser = $self->{oManifest}->variableReplace($oChild->paramGet('user'));
# Determine if a pre-built image should be created
if (defined($self->preExecute($strHost)))
{
my $strPreImage = "${strImage}-${strHost}";
my $strFrom = $strImage;
&log(INFO, "Build vm '${strPreImage}' from '${strFrom}'");
my $strCommandList;
# Add all pre commands
foreach my $oExecute ($self->preExecute($strHost))
{
my $hExecuteKey = $self->executeKey($strHost, $oExecute);
my $strCommand =
join("\n", @{$hExecuteKey->{cmd}}) .
(defined($hExecuteKey->{'cmd-extra'}) ? ' ' . $hExecuteKey->{'cmd-extra'} : '');
$strCommand =~ s/'/'\\''/g;
$strCommand =
"sudo -u ${strHostUser}" .
($hCacheKey->{'bash-wrap'} ?
" bash" . ($hCacheKey->{'load-env'} ? ' -l' : '') . " -c '${strCommand}'" : " ${strCommand}");
if (defined($strCommandList))
{
$strCommandList .= "\n";
}
$strCommandList .= "RUN ${strCommand}";
&log(DETAIL, " Pre command $strCommand");
}
# Build container
my $strDockerfile = $self->{oManifest}{strDocPath} . "/output/doc-host.dockerfile";
$self->{oManifest}{oStorage}->put(
$strDockerfile,
"FROM ${strFrom}\n\n" . trim($self->{oManifest}->variableReplace($strCommandList)) . "\n");
executeTest("docker build -f ${strDockerfile} -t ${strPreImage} " . $self->{oManifest}{oStorage}->pathGet());
# Use the pre-built image
$strImage = $strPreImage;
}
my $strHostRepoPath = dirname(dirname(abs_path($0)));
# Replace host repo path in mounts with if present
my $strMount = undef;
if (defined($oChild->paramGet('mount', false)))
{
$strMount = $self->{oManifest}->variableReplace($oChild->paramGet('mount'));
$strMount =~ s/\{\[host\-repo\-path\]\}/${strHostRepoPath}/g;
}
# Replace host repo mount in params if present
my $strOption = $$hCacheKey{option};
if (defined($strOption))
{
$strOption =~ s/\{\[host\-repo\-path\]\}/${strHostRepoPath}/g;
}
my $oHost = new pgBackRestTest::Common::HostTest(
$$hCacheKey{name}, "doc-$$hCacheKey{name}", $strImage, $strHostUser, $$hCacheKey{os},
defined($strMount) ? [$strMount] : undef, $strOption, $$hCacheKey{param}, $$hCacheKey{'update-hosts'});
$self->{host}{$$hCacheKey{name}} = $oHost;
$self->{oManifest}->variableSet('host-' . $hCacheKey->{id} . '-ip', $oHost->{strIP}, true);
$$hCacheValue{ip} = $oHost->{strIP};
# Add to the host group
my $oHostGroup = hostGroupGet();
$oHostGroup->hostAdd($oHost);
# Execute initialize commands
foreach my $oExecute ($oChild->nodeList('execute', false))
{
$self->execute(
$oSection, $$hCacheKey{name}, $oExecute, {iIndent => $iDepth + 1, bCache => false, bShow => false});
}
$self->cachePush($strCacheType, $hCacheKey, $hCacheValue);
}
}
}
# Skip children that have already been processed and error on others
elsif ($oChild->nameGet() ne 'title')
{
confess &log(ASSERT, 'unable to process child type ' . $oChild->nameGet());
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation
);
}
1;
pgbackrest-release-2.24/doc/lib/BackRestDoc/Common/DocManifest.pm 0000664 0000000 0000000 00000063150 13625315071 0024635 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# DOC MANIFEST MODULE
####################################################################################################################################
package BackRestDoc::Common::DocManifest;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use JSON::PP;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
####################################################################################################################################
# File constants
####################################################################################################################################
use constant FILE_MANIFEST => 'manifest.xml';
####################################################################################################################################
# Render constants
####################################################################################################################################
use constant RENDER => 'render';
use constant RENDER_COMPACT => 'compact';
push @EXPORT, qw(RENDER_COMPACT);
use constant RENDER_FILE => 'file';
use constant RENDER_MENU => 'menu';
push @EXPORT, qw(RENDER_MENU);
use constant RENDER_PRETTY => 'pretty';
push @EXPORT, qw(RENDER_PRETTY);
use constant RENDER_TYPE => 'type';
use constant RENDER_TYPE_HTML => 'html';
push @EXPORT, qw(RENDER_TYPE_HTML);
use constant RENDER_TYPE_MARKDOWN => 'markdown';
push @EXPORT, qw(RENDER_TYPE_MARKDOWN);
use constant RENDER_TYPE_PDF => 'pdf';
push @EXPORT, qw(RENDER_TYPE_PDF);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{oStorage},
$self->{stryRequire},
$self->{stryInclude},
$self->{stryExclude},
$self->{rhKeyVariableOverride},
my $rhVariableOverride,
$self->{strDocPath},
$self->{bDeploy},
$self->{bCacheOnly},
$self->{bPre},
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oStorage'},
{name => 'stryRequire'},
{name => 'stryInclude'},
{name => 'stryExclude'},
{name => 'rhKeyVariableOverride', required => false},
{name => 'rhVariableOverride', required => false},
{name => 'strDocPath', required => false},
{name => 'bDeploy', required => false},
{name => 'bCacheOnly', required => false},
{name => 'bPre', required => false, default => false},
);
# Set the bin path
$self->{strBinPath} = abs_path(dirname($0));
# Set the base path if it was not passed in
if (!defined($self->{strDocPath}))
{
$self->{strDocPath} = $self->{strBinPath};
}
# Set cache file names
$self->{strExeCacheLocal} = $self->{strDocPath} . "/output/exe.cache";
$self->{strExeCacheDeploy} = $self->{strDocPath} . "/resource/exe.cache";
# Load the manifest
$self->{oManifestXml} = new BackRestDoc::Common::Doc("$self->{strDocPath}/manifest.xml");
# Iterate the sources
$self->{oManifest} = {};
foreach my $oSource ($self->{oManifestXml}->nodeGet('source-list')->nodeList('source'))
{
my $oSourceHash = {};
my $strKey = $oSource->paramGet('key');
my $strSourceType = $oSource->paramGet('type', false);
logDebugMisc
(
$strOperation, 'load source',
{name => 'strKey', value => $strKey},
{name => 'strSourceType', value => $strSourceType}
);
# Skip sources in exclude list
if (grep(/^$strKey$/, @{$self->{stryExclude}}))
{
next;
}
$$oSourceHash{doc} = new BackRestDoc::Common::Doc("$self->{strDocPath}/xml/${strKey}.xml");
# Read variables from source
$self->variableListParse($$oSourceHash{doc}->nodeGet('variable-list', false), $rhVariableOverride);
${$self->{oManifest}}{source}{$strKey} = $oSourceHash;
${$self->{oManifest}}{source}{$strKey}{strSourceType} = $strSourceType;
}
# Iterate the renderers
foreach my $oRender ($self->{oManifestXml}->nodeGet('render-list')->nodeList('render'))
{
my $oRenderHash = {};
my $strType = $oRender->paramGet(RENDER_TYPE);
# Only one instance of each render type can be defined
if (defined(${$self->{oManifest}}{&RENDER}{$strType}))
{
confess &log(ERROR, "render ${strType} has already been defined");
}
# Get the file param
$${oRenderHash}{file} = $oRender->paramGet(RENDER_FILE, false);
$${oRenderHash}{&RENDER_COMPACT} = $oRender->paramGet(RENDER_COMPACT, false, 'n') eq 'y' ? true : false;
$${oRenderHash}{&RENDER_PRETTY} = $oRender->paramGet(RENDER_PRETTY, false, 'n') eq 'y' ? true : false;
$${oRenderHash}{&RENDER_MENU} = false;
logDebugMisc
(
$strOperation, ' load render',
{name => 'strType', value => $strType},
{name => 'strFile', value => $${oRenderHash}{file}}
);
# Error if file is set and render type is not pdf
if (defined($${oRenderHash}{file}) && $strType ne RENDER_TYPE_PDF)
{
confess &log(ERROR, 'only the pdf render type can have file set')
}
# Iterate the render sources
foreach my $oRenderOut ($oRender->nodeList('render-source'))
{
my $oRenderOutHash = {};
my $strKey = $oRenderOut->paramGet('key');
my $strSource = $oRenderOut->paramGet('source', false, $strKey);
# Skip sources in exclude list
if (grep(/^$strSource$/, @{$self->{stryExclude}}))
{
next;
}
# Skip sources not in include list
if (@{$self->{stryInclude}} > 0 && !grep(/^$strSource$/, @{$self->{stryInclude}}))
{
next;
}
# Preserve natural order
push(@{$${oRenderHash}{stryOrder}}, $strKey);
$$oRenderOutHash{source} = $strSource;
# Get the filename
if (defined($oRenderOut->paramGet('file', false)))
{
if ($strType eq RENDER_TYPE_HTML || $strType eq RENDER_TYPE_MARKDOWN)
{
$$oRenderOutHash{file} = $oRenderOut->paramGet('file');
}
else
{
confess &log(ERROR, "file is only valid with html or markdown render types");
}
}
# Get the menu caption
if (defined($oRenderOut->paramGet('menu', false)) && $strType ne RENDER_TYPE_HTML)
{
confess &log(ERROR, "menu is only valid with html render type");
}
if (defined($oRenderOut->paramGet('menu', false)))
{
$${oRenderHash}{&RENDER_MENU} = true;
if ($strType eq RENDER_TYPE_HTML)
{
$$oRenderOutHash{menu} = $oRenderOut->paramGet('menu', false);
}
else
{
confess &log(ERROR, 'only the html render type can have menu set');
}
}
logDebugMisc
(
$strOperation, ' load render source',
{name => 'strKey', value => $strKey},
{name => 'strSource', value => $strSource},
{name => 'strMenu', value => $${oRenderOutHash}{menu}}
);
$${oRenderHash}{out}{$strKey} = $oRenderOutHash;
}
${$self->{oManifest}}{render}{$strType} = $oRenderHash;
}
# Set the doc path variable
$self->variableSet('doc-path', $self->{strDocPath});
# Read variables from manifest
$self->variableListParse($self->{oManifestXml}->nodeGet('variable-list', false), $rhVariableOverride);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# isBackRest
#
# Until all the backrest specific code can be abstracted, this function will identify when BackRest docs are being built.
####################################################################################################################################
sub isBackRest
{
my $self = shift;
return($self->variableTest('project-exe', 'pgbackrest'));
}
####################################################################################################################################
# Evaluate the if condition for a node
####################################################################################################################################
sub evaluateIf
{
my $self = shift;
my $oNode = shift;
my $bIf = true;
# Evaluate if condition
if (defined($oNode->paramGet('if', false)))
{
my $strIf = $self->variableReplace($oNode->paramGet('if'));
# In this case we really do want to evaluate the contents and not treat it as a literal
$bIf = eval($strIf);
# Error if the eval failed
if ($@)
{
confess &log(ERROR, "unable to evaluate '${strIf}': $@");
}
}
return $bIf;
}
####################################################################################################################################
# variableListParse
#
# Parse a variable list and store variables.
####################################################################################################################################
sub variableListParse
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oVariableList,
$rhVariableOverride
) =
logDebugParam
(
__PACKAGE__ . '->variableListParse', \@_,
{name => '$oVariableList', required => false},
{name => '$rhVariableOverride', required => false}
);
if (defined($oVariableList))
{
foreach my $oVariable ($oVariableList->nodeList('variable'))
{
if ($self->evaluateIf($oVariable))
{
my $strKey = $oVariable->paramGet('key');
my $strValue = $self->variableReplace($oVariable->valueGet());
if ($oVariable->paramTest('eval', 'y'))
{
# In this case we really do want to evaluate the contents of strValue and not treat it as a literal.
$strValue = eval($strValue);
if ($@)
{
confess &log(ERROR, "unable to evaluate ${strKey}: $@\n" . $oVariable->valueGet());
}
}
$self->variableSet($strKey, defined($rhVariableOverride->{$strKey}) ? $rhVariableOverride->{$strKey} : $strValue);
logDebugMisc
(
$strOperation, ' load variable',
{name => 'strKey', value => $strKey},
{name => 'strValue', value => $strValue}
);
}
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# variableReplace
#
# Replace variables in the string.
####################################################################################################################################
sub variableReplace
{
my $self = shift;
my $strBuffer = shift;
my $strType = shift;
if (!defined($strBuffer))
{
return;
}
foreach my $strName (sort(keys(%{$self->{oVariable}})))
{
my $strValue = $self->{oVariable}{$strName};
$strBuffer =~ s/\{\[$strName\]\}/$strValue/g;
}
if (defined($strType) && $strType eq 'latex')
{
$strBuffer =~ s/\\\_/\_/g;
$strBuffer =~ s/\_/\\\_/g;
$strBuffer =~ s/\\\#/\#/g;
$strBuffer =~ s/\#/\\\#/g;
}
return $strBuffer;
}
####################################################################################################################################
# variableSet
#
# Set a variable to be replaced later.
####################################################################################################################################
sub variableSet
{
my $self = shift;
my $strKey = shift;
my $strValue = shift;
my $bForce = shift;
if (defined(${$self->{oVariable}}{$strKey}) && (!defined($bForce) || !$bForce))
{
confess &log(ERROR, "${strKey} variable is already defined");
}
${$self->{oVariable}}{$strKey} = $self->variableReplace($strValue);
}
####################################################################################################################################
# variableGet
#
# Get the current value of a variable.
####################################################################################################################################
sub variableGet
{
my $self = shift;
my $strKey = shift;
return ${$self->{oVariable}}{$strKey};
}
####################################################################################################################################
# variableTest
#
# Test that a variable is defined or has an expected value.
####################################################################################################################################
sub variableTest
{
my $self = shift;
my $strKey = shift;
my $strExpectedValue = shift;
# Get the variable
my $strValue = ${$self->{oVariable}}{$strKey};
# Return false if it is not defined
if (!defined($strValue))
{
return false;
}
# Return false if it does not equal the expected value
if (defined($strExpectedValue) && $strValue ne $strExpectedValue)
{
return false;
}
return true;
}
####################################################################################################################################
# Get list of source documents
####################################################################################################################################
sub sourceList
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->sourceList');
# Check that sources exist
my @strySource;
if (defined(${$self->{oManifest}}{source}))
{
@strySource = sort(keys(%{${$self->{oManifest}}{source}}));
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strySource', value => \@strySource}
);
}
####################################################################################################################################
# sourceGet
####################################################################################################################################
sub sourceGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strSource
) =
logDebugParam
(
__PACKAGE__ . '->sourceGet', \@_,
{name => 'strSource', trace => true}
);
if (!defined(${$self->{oManifest}}{source}{$strSource}))
{
confess &log(ERROR, "source ${strSource} does not exist");
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oSource', value => ${$self->{oManifest}}{source}{$strSource}}
);
}
####################################################################################################################################
# renderList
####################################################################################################################################
sub renderList
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->renderList');
# Check that the render output exists
my @stryRender;
if (defined(${$self->{oManifest}}{render}))
{
@stryRender = sort(keys(%{${$self->{oManifest}}{render}}));
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'stryRender', value => \@stryRender}
);
}
####################################################################################################################################
# renderGet
####################################################################################################################################
sub renderGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strType
) =
logDebugParam
(
__PACKAGE__ . '->renderGet', \@_,
{name => 'strType', trace => true}
);
# Check that the render exists
if (!defined(${$self->{oManifest}}{render}{$strType}))
{
confess &log(ERROR, "render type ${strType} does not exist");
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oRenderOut', value => ${$self->{oManifest}}{render}{$strType}}
);
}
####################################################################################################################################
# renderOutList
####################################################################################################################################
sub renderOutList
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strType
) =
logDebugParam
(
__PACKAGE__ . '->renderOutList', \@_,
{name => 'strType'}
);
# Check that the render output exists
my @stryRenderOut;
if (defined(${$self->{oManifest}}{render}{$strType}))
{
@stryRenderOut = sort(keys(%{${$self->{oManifest}}{render}{$strType}{out}}));
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'stryRenderOut', value => \@stryRenderOut}
);
}
####################################################################################################################################
# renderOutGet
####################################################################################################################################
sub renderOutGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strType,
$strKey,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->renderOutGet', \@_,
{name => 'strType', trace => true},
{name => 'strKey', trace => true},
{name => 'bIgnoreMissing', default => false, trace => true},
);
if (!defined(${$self->{oManifest}}{render}{$strType}{out}{$strKey}) && !$bIgnoreMissing)
{
confess &log(ERROR, "render out ${strKey} does not exist");
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oRenderOut', value => ${$self->{oManifest}}{render}{$strType}{out}{$strKey}}
);
}
####################################################################################################################################
# cacheKey
####################################################################################################################################
sub cacheKey
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheKey');
# Generate a cache key from the variable override
my $strVariableKey = JSON::PP->new()->canonical()->allow_nonref()->encode($self->{rhKeyVariableOverride});
if ($strVariableKey eq '{}')
{
$strVariableKey = 'default';
}
my $strRequire = defined($self->{stryRequire}) && @{$self->{stryRequire}} > 0 ?
join("\n", @{$self->{stryRequire}}) : 'all';
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strVariableKey', value => $strVariableKey},
{name => 'strRequire', value => $strRequire},
);
}
####################################################################################################################################
# cacheRead
####################################################################################################################################
sub cacheRead
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheRead');
$self->{hCache} = undef;
my $strCacheFile = $self->{bDeploy} ? $self->{strExeCacheDeploy} : $self->{strExeCacheLocal};
if (!$self->storage()->exists($strCacheFile) && !$self->{bDeploy})
{
$strCacheFile = $self->{strExeCacheDeploy};
}
if ($self->storage()->exists($strCacheFile))
{
my ($strCacheKey, $strRequire) = $self->cacheKey();
my $oJSON = JSON::PP->new()->allow_nonref();
$self->{hCache} = $oJSON->decode(${$self->storage()->get($strCacheFile)});
foreach my $strSource (sort(keys(%{${$self->{oManifest}}{source}})))
{
my $hSource = ${$self->{oManifest}}{source}{$strSource};
if (defined(${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource}))
{
$$hSource{hyCache} = ${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource};
&log(DETAIL, "cache load $strSource (key = ${strCacheKey}, require = ${strRequire})");
}
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# cacheWrite
####################################################################################################################################
sub cacheWrite
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->cacheWrite');
my $strCacheFile = $self->{bDeploy} ? $self->{strExeCacheDeploy} : $self->{strExeCacheLocal};
my ($strCacheKey, $strRequire) = $self->cacheKey();
foreach my $strSource (sort(keys(%{${$self->{oManifest}}{source}})))
{
my $hSource = ${$self->{oManifest}}{source}{$strSource};
if (defined($$hSource{hyCache}))
{
${$self->{hCache}}{$strCacheKey}{$strRequire}{$strSource} = $$hSource{hyCache};
&log(DETAIL, "cache load $strSource (key = ${strCacheKey}, require = ${strRequire})");
}
}
if (defined($self->{hCache}))
{
my $oJSON = JSON::PP->new()->canonical()->allow_nonref()->pretty();
$self->storage()->put($strCacheFile, $oJSON->encode($self->{hCache}));
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# cacheReset
####################################################################################################################################
sub cacheReset
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strSource
) =
logDebugParam
(
__PACKAGE__ . '->cacheReset', \@_,
{name => 'strSource', trace => true}
);
if ($self->{bCacheOnly})
{
confess &log(ERROR, 'Cache reset disabled by --cache-only option');
}
&log(WARN, "Cache will be reset for source ${strSource} and rendering retried automatically");
delete(${$self->{oManifest}}{source}{$strSource}{hyCache});
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub storage {shift->{oStorage}};
1;
pgbackrest-release-2.24/doc/lib/BackRestDoc/Common/DocRender.pm 0000664 0000000 0000000 00000103656 13625315071 0024314 0 ustar 00root root 0000000 0000000 ####################################################################################################################################
# DOC RENDER MODULE
####################################################################################################################################
package BackRestDoc::Common::DocRender;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Exporter qw(import);
our @EXPORT = qw();
use JSON::PP;
use Storable qw(dclone);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use BackRestDoc::Common::DocManifest;
####################################################################################################################################
# XML tag/param constants
####################################################################################################################################
use constant XML_SECTION_PARAM_ANCHOR => 'anchor';
push @EXPORT, qw(XML_SECTION_PARAM_ANCHOR);
use constant XML_SECTION_PARAM_ANCHOR_VALUE_NOINHERIT => 'no-inherit';
push @EXPORT, qw(XML_SECTION_PARAM_ANCHOR_VALUE_NOINHERIT);
####################################################################################################################################
# Render tags for various output types
####################################################################################################################################
my $oRenderTag =
{
'markdown' =>
{
'quote' => ['"', '"'],
'b' => ['**', '**'],
'i' => ['_', '_'],
# 'bi' => ['_**', '**_'],
'ul' => ["\n", ""],
'ol' => ["\n", "\n"],
'li' => ['- ', "\n"],
'id' => ['`', '`'],
'file' => ['`', '`'],
'path' => ['`', '`'],
'cmd' => ['`', '`'],
'param' => ['`', '`'],
'setting' => ['`', '`'],
'pg-setting' => ['`', '`'],
'code' => ['`', '`'],
# 'code-block' => ['```', '```'],
# 'exe' => [undef, ''],
'backrest' => [undef, ''],
'proper' => ['', ''],
'postgres' => ['PostgreSQL', ''],
'admonition' => ["\n> **", "\n"],
},
'text' =>
{
'quote' => ['"', '"'],
'b' => ['', ''],
'i' => ['', ''],
# 'bi' => ['', ''],
'ul' => ["\n", "\n"],
'ol' => ["\n", "\n"],
'li' => ['* ', "\n"],
'id' => ['', ''],
'host' => ['', ''],
'file' => ['', ''],
'path' => ['', ''],
'cmd' => ['', ''],
'br-option' => ['', ''],
'pg-setting' => ['', ''],
'param' => ['', ''],
'setting' => ['', ''],
'code' => ['', ''],
'code-block' => ['', ''],
'exe' => [undef, ''],
'backrest' => [undef, ''],
'proper' => ['', ''],
'postgres' => ['PostgreSQL', ''],
'admonition' => ["\n", "\n\n"],
},
'latex' =>
{
'quote' => ['``', '"'],
'b' => ['\textbf{', '}'],
'i' => ['\textit{', '}'],
# 'bi' => ['', ''],
'ul' => ["\\begin{itemize}\n", "\\end{itemize}\n"],
# 'ol' => ["\n", "\n"],
'li' => ['\item ', "\n"],
'id' => ['\textnormal{\texttt{', '}}'],
'host' => ['\textnormal{\textbf{', '}}'],
'file' => ['\textnormal{\texttt{', '}}'],
'path' => ['\textnormal{\texttt{', '}}'],
'cmd' => ['\textnormal{\texttt{', "}}"],
'user' => ['\textnormal{\texttt{', '}}'],
'br-option' => ['', ''],
# 'param' => ['\texttt{', '}'],
# 'setting' => ['\texttt{', '}'],
'br-option' => ['\textnormal{\texttt{', '}}'],
'br-setting' => ['\textnormal{\texttt{', '}}'],
'pg-option' => ['\textnormal{\texttt{', '}}'],
'pg-setting' => ['\textnormal{\texttt{', '}}'],
'code' => ['\textnormal{\texttt{', '}}'],
# 'code' => ['\texttt{', '}'],
# 'code-block' => ['', ''],
# 'exe' => [undef, ''],
'backrest' => [undef, ''],
'proper' => ['\textnormal{\texttt{', '}}'],
'postgres' => ['PostgreSQL', ''],
'admonition' => ["\n\\begin{leftbar}\n\\textit{\\textbf{", "}\n\\end{leftbar}\n"],
},
'html' =>
{
'quote' => ['', ''],
'b' => ['', ''],
'i' => ['', ''],
# 'bi' => ['', ''],
'ul' => ['
'],
}
};
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{strType},
$self->{oManifest},
$self->{bExe},
$self->{strRenderOutKey},
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'strType'},
{name => 'oManifest', required => false},
{name => 'bExe', required => false},
{name => 'strRenderOutKey', required => false}
);
# Create JSON object
$self->{oJSON} = JSON::PP->new()->allow_nonref();
# Initialize project tags
$$oRenderTag{markdown}{backrest}[0] = "{[project]}";
$$oRenderTag{markdown}{exe}[0] = "{[project-exe]}";
$$oRenderTag{text}{backrest}[0] = "{[project]}";
$$oRenderTag{text}{exe}[0] = "{[project-exe]}";
$$oRenderTag{latex}{backrest}[0] = "{[project]}";
$$oRenderTag{latex}{exe}[0] = "\\textnormal\{\\texttt\{[project-exe]}}\}\}";
$$oRenderTag{html}{backrest}[0] = "{[project]}";
$$oRenderTag{html}{exe}[0] = "{[project-exe]}";
if (defined($self->{strRenderOutKey}))
{
# Copy page data to self
my $oRenderOut =
$self->{oManifest}->renderOutGet($self->{strType} eq 'latex' ? 'pdf' : $self->{strType}, $self->{strRenderOutKey});
# If these are the backrest docs then load the reference
if ($self->{oManifest}->isBackRest())
{
$self->{oReference} =
new BackRestDoc::Common::DocConfig(${$self->{oManifest}->sourceGet('reference')}{doc}, $self);
}
if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'reference' && $self->{oManifest}->isBackRest())
{
if ($self->{strRenderOutKey} eq 'configuration')
{
$self->{oDoc} = $self->{oReference}->helpConfigDocGet();
}
elsif ($self->{strRenderOutKey} eq 'command')
{
$self->{oDoc} = $self->{oReference}->helpCommandDocGet();
}
else
{
confess &log(ERROR, "cannot render $self->{strRenderOutKey} from source $$oRenderOut{source}");
}
}
elsif (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'release' && $self->{oManifest}->isBackRest())
{
require BackRestDoc::Custom::DocCustomRelease;
BackRestDoc::Custom::DocCustomRelease->import();
$self->{oDoc} =
(new BackRestDoc::Custom::DocCustomRelease(
${$self->{oManifest}->sourceGet('release')}{doc},
defined($self->{oManifest}->variableGet('dev')) && $self->{oManifest}->variableGet('dev') eq 'y'))->docGet();
}
else
{
$self->{oDoc} = ${$self->{oManifest}->sourceGet($self->{strRenderOutKey})}{doc};
}
$self->{oSource} = $self->{oManifest}->sourceGet($$oRenderOut{source});
}
if (defined($self->{strRenderOutKey}))
{
# Build the doc
$self->build($self->{oDoc});
# Get required sections
foreach my $strPath (@{$self->{oManifest}->{stryRequire}})
{
if (substr($strPath, 0, 1) ne '/')
{
confess &log(ERROR, "path ${strPath} must begin with a /");
}
if (!defined($self->{oSection}->{$strPath}))
{
confess &log(ERROR, "required section '${strPath}' does not exist");
}
if (defined(${$self->{oSection}}{$strPath}))
{
$self->required($strPath);
}
}
}
if (defined($self->{oDoc}))
{
$self->{bToc} = !defined($self->{oDoc}->paramGet('toc', false)) || $self->{oDoc}->paramGet('toc') eq 'y' ? true : false;
$self->{bTocNumber} =
$self->{bToc} &&
(!defined($self->{oDoc}->paramGet('toc-number', false)) || $self->{oDoc}->paramGet('toc-number') eq 'y') ? true : false;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# Set begin and end values for a tag
####################################################################################################################################
sub tagSet
{
my $self = shift;
my $strTag = shift;
my $strBegin = shift;
my $strEnd = shift;
$oRenderTag->{$self->{strType}}{$strTag}[0] = defined($strBegin) ? $strBegin : '';
$oRenderTag->{$self->{strType}}{$strTag}[1] = defined($strEnd) ? $strEnd : '';
}
####################################################################################################################################
# variableReplace
#
# Replace variables in the string.
####################################################################################################################################
sub variableReplace
{
my $self = shift;
return defined($self->{oManifest}) ? $self->{oManifest}->variableReplace(shift, $self->{strType}) : shift;
}
####################################################################################################################################
# variableSet
#
# Set a variable to be replaced later.
####################################################################################################################################
sub variableSet
{
my $self = shift;
return $self->{oManifest}->variableSet(shift, shift);
}
####################################################################################################################################
# variableGet
#
# Get the current value of a variable.
####################################################################################################################################
sub variableGet
{
my $self = shift;
return $self->{oManifest}->variableGet(shift);
}
####################################################################################################################################
# Get pre-execute list for a host
####################################################################################################################################
sub preExecute
{
my $self = shift;
my $strHost = shift;
if (defined($self->{preExecute}{$strHost}))
{
return @{$self->{preExecute}{$strHost}};
}
return;
}
####################################################################################################################################
# build
#
# Build the section map and perform filtering.
####################################################################################################################################
sub build
{
my $self = shift;
my $oNode = shift;
my $oParent = shift;
my $strPath = shift;
my $strPathPrefix = shift;
# &log(INFO, " node " . $oNode->nameGet());
my $strName = $oNode->nameGet();
if (defined($oParent))
{
# Evaluate if condition -- when false the node will be removed
if (!$self->{oManifest}->evaluateIf($oNode))
{
my $strDescription;
if (defined($oNode->nodeGet('title', false)))
{
$strDescription = $self->processText($oNode->nodeGet('title')->textGet());
}
&log(DEBUG, " filtered ${strName}" . (defined($strDescription) ? ": ${strDescription}" : ''));
$oParent->nodeRemove($oNode);
return;
}
}
else
{
&log(DEBUG, ' build document');
$self->{oSection} = {};
}
# Build section
if ($strName eq 'section')
{
my $strSectionId = $oNode->paramGet('id');
&log(DEBUG, "build section [${strSectionId}]");
# Set path and parent-path for this section
if (defined($strPath))
{
$oNode->paramSet('path-parent', $strPath);
}
$strPath .= '/' . $oNode->paramGet('id');
&log(DEBUG, " path ${strPath}");
${$self->{oSection}}{$strPath} = $oNode;
$oNode->paramSet('path', $strPath);
# If depend is not set then set it to the last section
my $strDepend = $oNode->paramGet('depend', false);
my $oContainerNode = defined($oParent) ? $oParent : $self->{oDoc};
my $oLastChild;
my $strDependPrev;
foreach my $oChild ($oContainerNode->nodeList('section', false))
{
if ($oChild->paramGet('id') eq $oNode->paramGet('id'))
{
if (defined($oLastChild))
{
$strDependPrev = $oLastChild->paramGet('id');
}
elsif (defined($oParent->paramGet('depend', false)))
{
$strDependPrev = $oParent->paramGet('depend');
}
last;
}
$oLastChild = $oChild;
}
if (defined($strDepend))
{
if (defined($strDependPrev) && $strDepend eq $strDependPrev && !$oNode->paramTest('depend-default'))
{
&log(WARN,
"section '${strPath}' depend is set to '${strDepend}' which is the default, best to remove" .
" because it may become obsolete if a new section is added in between");
}
}
else
{
$strDepend = $strDependPrev;
}
# If depend is defined make sure it exists
if (defined($strDepend))
{
# If this is a relative depend then prepend the parent section
if (index($strDepend, '/') != 0)
{
if (defined($oParent->paramGet('path', false)))
{
$strDepend = $oParent->paramGet('path') . '/' . $strDepend;
}
else
{
$strDepend = "/${strDepend}";
}
}
if (!defined($self->{oSection}->{$strDepend}))
{
confess &log(ERROR, "section '${strSectionId}' depend '${strDepend}' is not valid");
}
}
if (defined($strDepend))
{
$oNode->paramSet('depend', $strDepend);
}
if (defined($strDependPrev))
{
$oNode->paramSet('depend-default', $strDependPrev);
}
# Set log to true if this section has an execute list. This helps reduce the info logging by only showing sections that are
# likely to take a log time.
$oNode->paramSet('log', $self->{bExe} && $oNode->nodeList('execute-list', false) > 0 ? true : false);
# If section content is being pulled from elsewhere go get the content
if ($oNode->paramTest('source'))
{
my $oSource = ${$self->{oManifest}->sourceGet($oNode->paramGet('source'))}{doc};
# Section should not already have title defined, it should come from the source doc
if ($oNode->nodeTest('title'))
{
confess &log(ERROR, "cannot specify title in section that sources another document");
}
# Set title from source doc's title
$oNode->nodeAdd('title')->textSet($oSource->paramGet('title'));
foreach my $oSection ($oSource->nodeList('section'))
{
push(@{${$oNode->{oDoc}}{children}}, $oSection->{oDoc});
}
# Set path prefix to modify all section paths further down
$strPathPrefix = $strPath;
# Remove source so it is not included again later
$oNode->paramSet('source', undef);
}
}
# Build link
elsif ($strName eq 'link')
{
&log(DEBUG, 'build link [' . $oNode->valueGet() . ']');
# If the path prefix is set and this is a section
if (defined($strPathPrefix) && $oNode->paramTest('section'))
{
my $strNewPath = $strPathPrefix . $oNode->paramGet('section');
&log(DEBUG, "modify link section from '" . $oNode->paramGet('section') . "' to '${strNewPath}'");
$oNode->paramSet('section', $strNewPath);
}
}
# Store block defines
elsif ($strName eq 'block-define')
{
my $strBlockId = $oNode->paramGet('id');
if (defined($self->{oyBlockDefine}{$strBlockId}))
{
confess &log(ERROR, "block ${strBlockId} is already defined");
}
$self->{oyBlockDefine}{$strBlockId} = dclone($oNode->{oDoc}{children});
$oParent->nodeRemove($oNode);
}
# Copy blocks
elsif ($strName eq 'block')
{
my $strBlockId = $oNode->paramGet('id');
if (!defined($self->{oyBlockDefine}{$strBlockId}))
{
confess &log(ERROR, "block ${strBlockId} is not defined");
}
my $strNodeJSON = $self->{oJSON}->encode($self->{oyBlockDefine}{$strBlockId});
foreach my $oVariable ($oNode->nodeList('block-variable-replace', false))
{
my $strVariableKey = $oVariable->paramGet('key');
my $strVariableReplace = $oVariable->valueGet();
$strNodeJSON =~ s/\{\[$strVariableKey\]\}/$strVariableReplace/g;
}
my ($iReplaceIdx, $iReplaceTotal) = $oParent->nodeReplace($oNode, $self->{oJSON}->decode($strNodeJSON));
# Build any new children that were added
my $iChildIdx = 0;
foreach my $oChild ($oParent->nodeList(undef, false))
{
if ($iChildIdx >= $iReplaceIdx && $iChildIdx < ($iReplaceIdx + $iReplaceTotal))
{
$self->build($oChild, $oParent, $strPath, $strPathPrefix);
}
$iChildIdx++;
}
}
# Check for pre-execute statements
elsif ($strName eq 'execute')
{
if ($self->{oManifest}->{bPre} && $oNode->paramGet('pre', false, 'n') eq 'y')
{
# Add to pre-execute list
my $strHost = $self->variableReplace($oParent->paramGet('host'));
push(@{$self->{preExecute}{$strHost}}, $oNode);
# Skip this command so it doesn't get executed twice
$oNode->paramSet('skip', 'y')
}
}
# Iterate all text nodes
if (defined($oNode->textGet(false)))
{
foreach my $oChild ($oNode->textGet()->nodeList(undef, false))
{
if (ref(\$oChild) ne "SCALAR")
{
$self->build($oChild, $oNode, $strPath, $strPathPrefix);
}
}
}
# Iterate all non-text nodes
foreach my $oChild ($oNode->nodeList(undef, false))
{
if (ref(\$oChild) ne "SCALAR")
{
$self->build($oChild, $oNode, $strPath, $strPathPrefix);
# If the child should be logged then log the parent as well so the hierarchy is complete
if ($oChild->nameGet() eq 'section' && $oChild->paramGet('log', false, false))
{
$oNode->paramSet('log', true);
}
}
}
}
####################################################################################################################################
# required
#
# Build a list of required sections
####################################################################################################################################
sub required
{
my $self = shift;
my $strPath = shift;
my $bDepend = shift;
# If node is not found that means the path is invalid
my $oNode = ${$self->{oSection}}{$strPath};
if (!defined($oNode))
{
confess &log(ERROR, "invalid path ${strPath}");
}
# Only add sections that are listed dependencies
if (!defined($bDepend) || $bDepend)
{
# Match section and all child sections
foreach my $strChildPath (sort(keys(%{$self->{oSection}})))
{
if ($strChildPath =~ /^$strPath$/ || $strChildPath =~ /^$strPath\/.*$/)
{
if (!defined(${$self->{oSectionRequired}}{$strChildPath}))
{
my @stryChildPath = split('/', $strChildPath);
&log(INFO, (' ' x (scalar(@stryChildPath) - 2)) . " require section: ${strChildPath}");
${$self->{oSectionRequired}}{$strChildPath} = true;
}
}
}
}
# Get the path of the current section's parent
my $strParentPath = $oNode->paramGet('path-parent', false);
if ($oNode->paramTest('depend'))
{
foreach my $strDepend (split(',', $oNode->paramGet('depend')))
{
if ($strDepend !~ /^\//)
{
if (!defined($strParentPath))
{
$strDepend = "/${strDepend}";
}
else
{
$strDepend = "${strParentPath}/${strDepend}";
}
}
$self->required($strDepend, true);
}
}
elsif (defined($strParentPath))
{
$self->required($strParentPath, false);
}
}
####################################################################################################################################
# isRequired
#
# Is it required to execute the section statements?
####################################################################################################################################
sub isRequired
{
my $self = shift;
my $oSection = shift;
if (!defined($self->{oSectionRequired}))
{
return true;
}
my $strPath = $oSection->paramGet('path');
defined(${$self->{oSectionRequired}}{$strPath}) ? true : false;
}
####################################################################################################################################
# processTag
####################################################################################################################################
sub processTag
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oTag
) =
logDebugParam
(
__PACKAGE__ . '->processTag', \@_,
{name => 'oTag', trace => true}
);
my $strBuffer = "";
my $strType = $self->{strType};
my $strTag = $oTag->nameGet();
if (!defined($strTag))
{
require Data::Dumper;
confess Dumper($oTag);
}
if ($strTag eq 'link')
{
my $strUrl = $oTag->paramGet('url', false);
if (!defined($strUrl))
{
my $strPage = $self->variableReplace($oTag->paramGet('page', false));
# If this is a page URL
if (defined($strPage))
{
# If the page wasn't rendered then point at the website
if (!defined($self->{oManifest}->renderOutGet($strType, $strPage, true)))
{
$strUrl = '{[backrest-url-base]}/' . $oTag->paramGet('page') . '.html';
}
# Else point locally
else
{
if ($strType eq 'html' || $strType eq 'markdown')
{
$strUrl =
$oTag->paramGet('page', false) . '.' .
($strType eq 'html' ? $strType : '.md');
}
else
{
confess &log(ERROR, "page links not supported for type ${strType}, value '" . $oTag->valueGet() . "'");
}
}
}
else
{
my $strSection = $oTag->paramGet('section');
my $oSection = ${$self->{oSection}}{$strSection};
if (!defined($oSection))
{
confess &log(ERROR, "section link '${strSection}' does not exist");
}
if (!defined($strSection))
{
confess &log(ERROR, "link with value '" . $oTag->valueGet() . "' must defined url, page, or section");
}
if ($strType eq 'html')
{
$strUrl = '#' . substr($strSection, 1);
}
elsif ($strType eq 'latex')
{
$strUrl = $strSection;
}
else
{
$strUrl = lc($self->processText($oSection->nodeGet('title')->textGet()));
$strUrl =~ s/[^\w\- ]//g;
$strUrl =~ s/ /-/g;
$strUrl = '#' . $strUrl;
}
}
}
if ($strType eq 'html')
{
$strBuffer = '' . $oTag->valueGet() . '';
}
elsif ($strType eq 'markdown')
{
$strBuffer = '[' . $oTag->valueGet() . '](' . $strUrl . ')';
}
elsif ($strType eq 'latex')
{
if ($oTag->paramTest('url'))
{
$strBuffer = "\\href{$strUrl}{" . $oTag->valueGet() . "}";
}
else
{
$strBuffer = "\\hyperref[$strUrl]{" . $oTag->valueGet() . "}";
}
}
else
{
confess "'link' tag not valid for type ${strType}";
}
}
else
{
my $strStart = $$oRenderTag{$strType}{$strTag}[0];
my $strStop = $$oRenderTag{$strType}{$strTag}[1];
if (!defined($strStart) || !defined($strStop))
{
confess &log(ERROR, "invalid type ${strType} or tag ${strTag}");
}
$strBuffer .= $strStart;
# Admonitions in the reference materials are tags of the text element rather than field elements of the document so special
# handling is required
if ($strTag eq 'admonition')
{
$strBuffer .= $self->processAdmonitionStart($oTag);
}
if ($strTag eq 'p' || $strTag eq 'title' || $strTag eq 'li' || $strTag eq 'code-block' || $strTag eq 'summary'
|| $strTag eq 'admonition')
{
$strBuffer .= $self->processText($oTag);
}
elsif (defined($oTag->valueGet()))
{
$strBuffer .= $oTag->valueGet();
}
else
{
foreach my $oSubTag ($oTag->nodeList(undef, false))
{
$strBuffer .= $self->processTag($oSubTag);
}
}
if ($strTag eq 'admonition')
{
$strBuffer .= $self->processAdmonitionEnd($oTag);
}
$strBuffer .= $strStop;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strBuffer', value => $strBuffer, trace => true}
);
}
####################################################################################################################################
# processAdmonitionStart
####################################################################################################################################
sub processAdmonitionStart
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oTag
) =
logDebugParam
(
__PACKAGE__ . '->processAdmonitionStart', \@_,
{name => 'oTag', trace => true}
);
my $strType = $self->{strType};
my $strBuffer = '';
# Note that any changes to the way the HTML, markdown or latex display tags may also need to be made here
if ($strType eq 'html')
{
my $strType = $oTag->paramGet('type');
$strBuffer = '
' . uc($strType) . ':
' .
'
';
}
elsif ($strType eq 'text' || $strType eq 'markdown')
{
$strBuffer = uc($oTag->paramGet('type')) . ": ";
}
elsif ($strType eq 'latex')
{
$strBuffer = uc($oTag->paramGet('type')) . ": }";
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strBuffer', value => $strBuffer, trace => true}
);
}
####################################################################################################################################
# processAdmonitionEnd
####################################################################################################################################
sub processAdmonitionEnd
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oTag
) =
logDebugParam
(
__PACKAGE__ . '->processAdmonitionEnd', \@_,
{name => 'oTag', trace => true}
);
my $strType = $self->{strType};
my $strBuffer = '';
# Note that any changes to the way the HTML, markdown or latex display tags may also need to be made here
if ($strType eq 'html')
{
$strBuffer = '